@lobehub/lobehub 2.0.0-next.153 → 2.0.0-next.155

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 (32) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/changelog/v1.json +18 -0
  3. package/locales/ar/discover.json +2 -0
  4. package/locales/bg-BG/discover.json +2 -0
  5. package/locales/de-DE/discover.json +2 -0
  6. package/locales/en-US/discover.json +2 -0
  7. package/locales/es-ES/discover.json +2 -0
  8. package/locales/fa-IR/discover.json +2 -0
  9. package/locales/fr-FR/discover.json +2 -0
  10. package/locales/it-IT/discover.json +2 -0
  11. package/locales/ja-JP/discover.json +2 -0
  12. package/locales/ko-KR/discover.json +2 -0
  13. package/locales/nl-NL/discover.json +2 -0
  14. package/locales/pl-PL/discover.json +2 -0
  15. package/locales/pt-BR/discover.json +2 -0
  16. package/locales/ru-RU/discover.json +2 -0
  17. package/locales/tr-TR/discover.json +2 -0
  18. package/locales/vi-VN/discover.json +2 -0
  19. package/locales/zh-CN/discover.json +2 -0
  20. package/locales/zh-TW/discover.json +2 -0
  21. package/package.json +1 -1
  22. package/packages/python-interpreter/src/types.ts +2 -2
  23. package/packages/types/src/discover/plugins.ts +12 -0
  24. package/src/app/[variants]/(main)/discover/(detail)/assistant/features/Details/Capabilities/PluginItem.tsx +109 -13
  25. package/src/app/[variants]/(main)/discover/(detail)/assistant/features/Details/Capabilities/Plugins.tsx +1 -7
  26. package/src/app/[variants]/(main)/discover/(list)/(home)/Client.tsx +2 -2
  27. package/src/app/[variants]/(main)/discover/(list)/(home)/index.tsx +2 -3
  28. package/src/app/[variants]/(main)/discover/features/Title.tsx +2 -1
  29. package/src/auth.ts +21 -0
  30. package/src/locales/default/discover.ts +2 -0
  31. package/src/server/services/discover/index.ts +95 -73
  32. package/src/server/services/user/index.ts +35 -22
package/CHANGELOG.md CHANGED
@@ -2,6 +2,56 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 2.0.0-next.155](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.154...v2.0.0-next.155)
6
+
7
+ <sup>Released on **2025-12-03**</sup>
8
+
9
+ #### 🐛 Bug Fixes
10
+
11
+ - **misc**: Missing init user after user creation.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's fixed
19
+
20
+ - **misc**: Missing init user after user creation, closes [#10587](https://github.com/lobehub/lobe-chat/issues/10587) ([0e97a42](https://github.com/lobehub/lobe-chat/commit/0e97a42))
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
+
30
+ ## [Version 2.0.0-next.154](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.153...v2.0.0-next.154)
31
+
32
+ <sup>Released on **2025-12-03**</sup>
33
+
34
+ #### 🐛 Bug Fixes
35
+
36
+ - **misc**: Udpate discover detail tools get & more link.
37
+
38
+ <br/>
39
+
40
+ <details>
41
+ <summary><kbd>Improvements and Fixes</kbd></summary>
42
+
43
+ #### What's fixed
44
+
45
+ - **misc**: Udpate discover detail tools get & more link, closes [#10586](https://github.com/lobehub/lobe-chat/issues/10586) ([8ace3f0](https://github.com/lobehub/lobe-chat/commit/8ace3f0))
46
+
47
+ </details>
48
+
49
+ <div align="right">
50
+
51
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
52
+
53
+ </div>
54
+
5
55
  ## [Version 2.0.0-next.153](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.152...v2.0.0-next.153)
6
56
 
7
57
  <sup>Released on **2025-12-03**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,22 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "fixes": [
5
+ "Missing init user after user creation."
6
+ ]
7
+ },
8
+ "date": "2025-12-03",
9
+ "version": "2.0.0-next.155"
10
+ },
11
+ {
12
+ "children": {
13
+ "fixes": [
14
+ "Udpate discover detail tools get & more link."
15
+ ]
16
+ },
17
+ "date": "2025-12-03",
18
+ "version": "2.0.0-next.154"
19
+ },
2
20
  {
3
21
  "children": {},
4
22
  "date": "2025-12-03",
@@ -616,6 +616,7 @@
616
616
  "supportedProviders": "مزودو الخدمة المدعومون لهذا النموذج"
617
617
  },
618
618
  "plugins": {
619
+ "builtinTag": "الملحقات المدمجة",
619
620
  "community": "إضافات المجتمع",
620
621
  "details": {
621
622
  "settings": {
@@ -630,6 +631,7 @@
630
631
  },
631
632
  "install": "تثبيت الإضافة",
632
633
  "installed": "تم التثبيت",
634
+ "legacyTag": "الملحقات القديمة",
633
635
  "list": "قائمة الإضافات",
634
636
  "meta": {
635
637
  "description": "وصف",
@@ -616,6 +616,7 @@
616
616
  "supportedProviders": "Доставчици, поддържащи този модел"
617
617
  },
618
618
  "plugins": {
619
+ "builtinTag": "Вграден плъгин",
619
620
  "community": "Обществени плъгини",
620
621
  "details": {
621
622
  "settings": {
@@ -630,6 +631,7 @@
630
631
  },
631
632
  "install": "Инсталирай плъгин",
632
633
  "installed": "Инсталиран",
634
+ "legacyTag": "Остарял плъгин",
633
635
  "list": "Списък с плъгини",
634
636
  "meta": {
635
637
  "description": "Описание",
@@ -616,6 +616,7 @@
616
616
  "supportedProviders": "Anbieter, die dieses Modell unterstützen"
617
617
  },
618
618
  "plugins": {
619
+ "builtinTag": "Integriertes Plugin",
619
620
  "community": "Community-Plugins",
620
621
  "details": {
621
622
  "settings": {
@@ -630,6 +631,7 @@
630
631
  },
631
632
  "install": "Plugin installieren",
632
633
  "installed": "Installiert",
634
+ "legacyTag": "Veraltetes Plugin",
633
635
  "list": "Plugin-Liste",
634
636
  "meta": {
635
637
  "description": "Beschreibung",
@@ -616,6 +616,7 @@
616
616
  "supportedProviders": "Providers Supporting This Model"
617
617
  },
618
618
  "plugins": {
619
+ "builtinTag": "Built-in",
619
620
  "community": "Community Plugins",
620
621
  "details": {
621
622
  "settings": {
@@ -630,6 +631,7 @@
630
631
  },
631
632
  "install": "Install Plugin",
632
633
  "installed": "Installed",
634
+ "legacyTag": "Legacy",
633
635
  "list": "Plugin List",
634
636
  "meta": {
635
637
  "description": "Description",
@@ -616,6 +616,7 @@
616
616
  "supportedProviders": "Proveedores que admiten este modelo"
617
617
  },
618
618
  "plugins": {
619
+ "builtinTag": "Complemento incorporado",
619
620
  "community": "Complementos de la comunidad",
620
621
  "details": {
621
622
  "settings": {
@@ -630,6 +631,7 @@
630
631
  },
631
632
  "install": "Instalar complemento",
632
633
  "installed": "Instalado",
634
+ "legacyTag": "Complemento heredado",
633
635
  "list": "Lista de complementos",
634
636
  "meta": {
635
637
  "description": "Descripción",
@@ -616,6 +616,7 @@
616
616
  "supportedProviders": "ارائه‌دهندگان پشتیبانی شده برای این مدل"
617
617
  },
618
618
  "plugins": {
619
+ "builtinTag": "افزونه داخلی",
619
620
  "community": "پلاگین‌های انجمن",
620
621
  "details": {
621
622
  "settings": {
@@ -630,6 +631,7 @@
630
631
  },
631
632
  "install": "نصب پلاگین",
632
633
  "installed": "نصب شده",
634
+ "legacyTag": "افزونه قدیمی",
633
635
  "list": "فهرست پلاگین‌ها",
634
636
  "meta": {
635
637
  "description": "توضیحات",
@@ -616,6 +616,7 @@
616
616
  "supportedProviders": "Fournisseurs prenant en charge ce modèle"
617
617
  },
618
618
  "plugins": {
619
+ "builtinTag": "Plugin intégré",
619
620
  "community": "Plugins communautaires",
620
621
  "details": {
621
622
  "settings": {
@@ -630,6 +631,7 @@
630
631
  },
631
632
  "install": "Installer le plugin",
632
633
  "installed": "Installé",
634
+ "legacyTag": "Ancien plugin",
633
635
  "list": "Liste des plugins",
634
636
  "meta": {
635
637
  "description": "Description",
@@ -616,6 +616,7 @@
616
616
  "supportedProviders": "Fornitori supportati da questo modello"
617
617
  },
618
618
  "plugins": {
619
+ "builtinTag": "Plugin integrato",
619
620
  "community": "Plugin della comunità",
620
621
  "details": {
621
622
  "settings": {
@@ -630,6 +631,7 @@
630
631
  },
631
632
  "install": "Installa plugin",
632
633
  "installed": "Installato",
634
+ "legacyTag": "Plugin legacy",
633
635
  "list": "Elenco plugin",
634
636
  "meta": {
635
637
  "description": "Descrizione",
@@ -616,6 +616,7 @@
616
616
  "supportedProviders": "このモデルをサポートするプロバイダー"
617
617
  },
618
618
  "plugins": {
619
+ "builtinTag": "内蔵プラグイン",
619
620
  "community": "コミュニティプラグイン",
620
621
  "details": {
621
622
  "settings": {
@@ -630,6 +631,7 @@
630
631
  },
631
632
  "install": "プラグインをインストール",
632
633
  "installed": "インストール済み",
634
+ "legacyTag": "旧バージョンプラグイン",
633
635
  "list": "プラグインリスト",
634
636
  "meta": {
635
637
  "description": "説明",
@@ -616,6 +616,7 @@
616
616
  "supportedProviders": "이 모델을 지원하는 서비스 제공자"
617
617
  },
618
618
  "plugins": {
619
+ "builtinTag": "내장 플러그인",
619
620
  "community": "커뮤니티 플러그인",
620
621
  "details": {
621
622
  "settings": {
@@ -630,6 +631,7 @@
630
631
  },
631
632
  "install": "플러그인 설치",
632
633
  "installed": "설치됨",
634
+ "legacyTag": "구버전 플러그인",
633
635
  "list": "플러그인 목록",
634
636
  "meta": {
635
637
  "description": "설명",
@@ -616,6 +616,7 @@
616
616
  "supportedProviders": "Providers die dit model ondersteunen"
617
617
  },
618
618
  "plugins": {
619
+ "builtinTag": "Ingebouwde plug-in",
619
620
  "community": "Gemeenschapsplugins",
620
621
  "details": {
621
622
  "settings": {
@@ -630,6 +631,7 @@
630
631
  },
631
632
  "install": "Plugin installeren",
632
633
  "installed": "Geïnstalleerd",
634
+ "legacyTag": "Verouderde plug-in",
633
635
  "list": "Pluginlijst",
634
636
  "meta": {
635
637
  "description": "Beschrijving",
@@ -616,6 +616,7 @@
616
616
  "supportedProviders": "Dostawcy obsługujący ten model"
617
617
  },
618
618
  "plugins": {
619
+ "builtinTag": "Wbudowana wtyczka",
619
620
  "community": "Wtyczki społecznościowe",
620
621
  "details": {
621
622
  "settings": {
@@ -630,6 +631,7 @@
630
631
  },
631
632
  "install": "Zainstaluj wtyczkę",
632
633
  "installed": "Zainstalowane",
634
+ "legacyTag": "Starsza wersja wtyczki",
633
635
  "list": "Lista wtyczek",
634
636
  "meta": {
635
637
  "description": "Opis",
@@ -616,6 +616,7 @@
616
616
  "supportedProviders": "Provedores que suportam este modelo"
617
617
  },
618
618
  "plugins": {
619
+ "builtinTag": "Plugin Integrado",
619
620
  "community": "Plugins da Comunidade",
620
621
  "details": {
621
622
  "settings": {
@@ -630,6 +631,7 @@
630
631
  },
631
632
  "install": "Instalar Plugin",
632
633
  "installed": "Instalado",
634
+ "legacyTag": "Plugin Legado",
633
635
  "list": "Lista de Plugins",
634
636
  "meta": {
635
637
  "description": "Descrição",
@@ -616,6 +616,7 @@
616
616
  "supportedProviders": "Поставщики, поддерживающие эту модель"
617
617
  },
618
618
  "plugins": {
619
+ "builtinTag": "Встроенный плагин",
619
620
  "community": "Сообщество плагинов",
620
621
  "details": {
621
622
  "settings": {
@@ -630,6 +631,7 @@
630
631
  },
631
632
  "install": "Установить плагин",
632
633
  "installed": "Установлено",
634
+ "legacyTag": "Устаревший плагин",
633
635
  "list": "Список плагинов",
634
636
  "meta": {
635
637
  "description": "Описание",
@@ -616,6 +616,7 @@
616
616
  "supportedProviders": "Bu modeli destekleyen sağlayıcılar"
617
617
  },
618
618
  "plugins": {
619
+ "builtinTag": "Yerleşik Eklenti",
619
620
  "community": "Topluluk Eklentisi",
620
621
  "details": {
621
622
  "settings": {
@@ -630,6 +631,7 @@
630
631
  },
631
632
  "install": "Eklenti Yükle",
632
633
  "installed": "Yüklü",
634
+ "legacyTag": "Eski Sürüm Eklenti",
633
635
  "list": "Eklenti Listesi",
634
636
  "meta": {
635
637
  "description": "Açıklama",
@@ -616,6 +616,7 @@
616
616
  "supportedProviders": "Nhà cung cấp hỗ trợ mô hình này"
617
617
  },
618
618
  "plugins": {
619
+ "builtinTag": "Plugin tích hợp sẵn",
619
620
  "community": "Plugin cộng đồng",
620
621
  "details": {
621
622
  "settings": {
@@ -630,6 +631,7 @@
630
631
  },
631
632
  "install": "Cài đặt plugin",
632
633
  "installed": "Đã cài đặt",
634
+ "legacyTag": "Plugin phiên bản cũ",
633
635
  "list": "Danh sách plugin",
634
636
  "meta": {
635
637
  "description": "Mô tả",
@@ -616,6 +616,7 @@
616
616
  "supportedProviders": "支持该模型的服务商"
617
617
  },
618
618
  "plugins": {
619
+ "builtinTag": "内置插件",
619
620
  "community": "社区插件",
620
621
  "details": {
621
622
  "settings": {
@@ -630,6 +631,7 @@
630
631
  },
631
632
  "install": "安装插件",
632
633
  "installed": "已安装",
634
+ "legacyTag": "旧版插件",
633
635
  "list": "插件列表",
634
636
  "meta": {
635
637
  "description": "描述",
@@ -616,6 +616,7 @@
616
616
  "supportedProviders": "支持該模型的服務商"
617
617
  },
618
618
  "plugins": {
619
+ "builtinTag": "內建外掛",
619
620
  "community": "社區插件",
620
621
  "details": {
621
622
  "settings": {
@@ -630,6 +631,7 @@
630
631
  },
631
632
  "install": "安裝插件",
632
633
  "installed": "已安裝",
634
+ "legacyTag": "舊版外掛",
633
635
  "list": "插件列表",
634
636
  "meta": {
635
637
  "description": "描述",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/lobehub",
3
- "version": "2.0.0-next.153",
3
+ "version": "2.0.0-next.155",
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",
@@ -4,9 +4,9 @@ export interface PythonOptions {
4
4
  */
5
5
  pyodideIndexUrl?: string;
6
6
  /**
7
- * PyPI 索引 URL,要求支持 [JSON API](https://warehouse.pypa.io/api-reference/json.html)
7
+ * PyPI index URL, must support [JSON API](https://warehouse.pypa.io/api-reference/json.html)
8
8
  *
9
- * 默认值:`https://pypi.org/pypi/{package_name}/json`
9
+ * Default value: `https://pypi.org/pypi/{package_name}/json`
10
10
  */
11
11
  pypiIndexUrl?: string;
12
12
  }
@@ -46,7 +46,19 @@ export interface PluginListResponse {
46
46
  totalPages: number;
47
47
  }
48
48
 
49
+ /**
50
+ * Plugin source types
51
+ * - legacy: From old plugin list (_getPluginList)
52
+ * - market: From Market SDK (getMcpDetail)
53
+ * - builtin: From LobeHub builtin tools
54
+ */
55
+ export type PluginSource = 'legacy' | 'market' | 'builtin';
56
+
49
57
  export interface DiscoverPluginDetail extends Omit<DiscoverPluginItem, 'manifest'> {
50
58
  manifest?: LobeChatPluginManifest | string;
51
59
  related: DiscoverPluginItem[];
60
+ /**
61
+ * Plugin source type
62
+ */
63
+ source?: PluginSource;
52
64
  }
@@ -1,35 +1,96 @@
1
- import { Avatar, Block, Text } from '@lobehub/ui';
1
+ import { PluginSource } from '@lobechat/types';
2
+ import { Avatar, Block, Tag, Text } from '@lobehub/ui';
2
3
  import { Skeleton } from 'antd';
3
4
  import { createStyles } from 'antd-style';
4
- import { memo } from 'react';
5
+ import { memo, useMemo } from 'react';
6
+ import { useTranslation } from 'react-i18next';
5
7
  import { Flexbox } from 'react-layout-kit';
8
+ import { Link } from 'react-router-dom';
9
+ import urlJoin from 'url-join';
6
10
 
7
11
  import { useDiscoverStore } from '@/store/discover';
8
12
 
9
13
  const useStyles = createStyles(({ css, token }) => {
10
14
  return {
15
+ clickable: css`
16
+ cursor: pointer;
17
+
18
+ &:hover {
19
+ .plugin-title {
20
+ color: ${token.colorLink};
21
+ }
22
+ }
23
+ `,
11
24
  desc: css`
12
25
  flex: 1;
13
26
  margin: 0 !important;
14
27
  font-size: 14px !important;
15
28
  color: ${token.colorTextSecondary};
16
29
  `,
30
+ noLink: css`
31
+ cursor: default;
32
+ `,
33
+ tag: css`
34
+ flex-shrink: 0;
35
+ `,
17
36
  title: css`
18
37
  margin: 0 !important;
19
38
  font-size: 14px !important;
20
39
  font-weight: 500 !important;
21
-
22
- &:hover {
23
- color: ${token.colorLink};
24
- }
40
+ `,
41
+ titleRow: css`
42
+ display: flex;
43
+ gap: 8px;
44
+ align-items: center;
25
45
  `,
26
46
  };
27
47
  });
28
48
 
29
- const PluginItem = memo<{ identifier: string }>(({ identifier }) => {
49
+ interface PluginItemProps {
50
+ identifier: string;
51
+ }
52
+
53
+ const PluginItem = memo<PluginItemProps>(({ identifier }) => {
54
+ const { t } = useTranslation('discover');
30
55
  const usePluginDetail = useDiscoverStore((s) => s.usePluginDetail);
31
56
  const { data, isLoading } = usePluginDetail({ identifier, withManifest: false });
32
- const { styles } = useStyles();
57
+ const { styles, cx } = useStyles();
58
+
59
+ const sourceConfig = useMemo(() => {
60
+ const source: PluginSource = data?.source || 'market';
61
+
62
+ switch (source) {
63
+ case 'builtin': {
64
+ return {
65
+ clickable: false,
66
+ href: undefined,
67
+ isExternal: false,
68
+ tagColor: 'geekblue' as const,
69
+ tagText: t('plugins.builtinTag'),
70
+ };
71
+ }
72
+ case 'legacy': {
73
+ return {
74
+ clickable: true,
75
+ href: data?.homepage,
76
+ isExternal: true,
77
+ tagColor: 'orange' as const,
78
+ tagText: t('plugins.legacyTag'),
79
+ };
80
+ }
81
+ // eslint-disable-next-line unicorn/no-useless-switch-case
82
+ case 'market':
83
+ default: {
84
+ return {
85
+ clickable: true,
86
+ href: urlJoin('/discover/plugin', identifier),
87
+ isExternal: false,
88
+ tagColor: undefined,
89
+ tagText: undefined,
90
+ };
91
+ }
92
+ }
93
+ }, [data?.source, data?.homepage, identifier, t]);
33
94
 
34
95
  if (isLoading || !data)
35
96
  return (
@@ -38,8 +99,15 @@ const PluginItem = memo<{ identifier: string }>(({ identifier }) => {
38
99
  </Block>
39
100
  );
40
101
 
41
- return (
42
- <Block gap={12} horizontal key={identifier} padding={12} variant={'outlined'}>
102
+ const content = (
103
+ <Block
104
+ className={cx(sourceConfig.clickable ? styles.clickable : styles.noLink)}
105
+ gap={12}
106
+ horizontal
107
+ key={identifier}
108
+ padding={12}
109
+ variant={'outlined'}
110
+ >
43
111
  <Avatar avatar={data.avatar} size={40} style={{ flex: 'none' }} />
44
112
  <Flexbox
45
113
  flex={1}
@@ -48,9 +116,16 @@ const PluginItem = memo<{ identifier: string }>(({ identifier }) => {
48
116
  overflow: 'hidden',
49
117
  }}
50
118
  >
51
- <Text as={'h2'} className={styles.title} ellipsis>
52
- {data.title}
53
- </Text>
119
+ <div className={styles.titleRow}>
120
+ <Text as={'h2'} className={cx(styles.title, 'plugin-title')} ellipsis>
121
+ {data.title}
122
+ </Text>
123
+ {sourceConfig.tagText && (
124
+ <Tag className={styles.tag} color={sourceConfig.tagColor} size={'small'}>
125
+ {sourceConfig.tagText}
126
+ </Tag>
127
+ )}
128
+ </div>
54
129
  <Text
55
130
  as={'p'}
56
131
  className={styles.desc}
@@ -63,6 +138,27 @@ const PluginItem = memo<{ identifier: string }>(({ identifier }) => {
63
138
  </Flexbox>
64
139
  </Block>
65
140
  );
141
+
142
+ // For builtin plugins, no link wrapper
143
+ if (!sourceConfig.clickable) {
144
+ return content;
145
+ }
146
+
147
+ // For external links (legacy plugins), use <a> tag
148
+ if (sourceConfig.isExternal && sourceConfig.href) {
149
+ return (
150
+ <a href={sourceConfig.href} rel="noopener noreferrer" target="_blank">
151
+ {content}
152
+ </a>
153
+ );
154
+ }
155
+
156
+ // For internal links (market plugins), use Link component
157
+ if (sourceConfig.href) {
158
+ return <Link to={sourceConfig.href}>{content}</Link>;
159
+ }
160
+
161
+ return content;
66
162
  });
67
163
 
68
164
  export default PluginItem;
@@ -1,9 +1,7 @@
1
1
  import { Block } from '@lobehub/ui';
2
2
  import { Empty } from 'antd';
3
- import { Link } from 'react-router-dom';
4
3
  import { memo } from 'react';
5
4
  import { Flexbox } from 'react-layout-kit';
6
- import urlJoin from 'url-join';
7
5
 
8
6
  import { useDetailContext } from '../../DetailProvider';
9
7
  import PluginItem from './PluginItem';
@@ -24,11 +22,7 @@ const Plugin = memo(() => {
24
22
  const identifier =
25
23
  typeof item === 'string' ? item : (item as { identifier: string }).identifier;
26
24
 
27
- return (
28
- <Link key={identifier} to={urlJoin('/discover/plugin', identifier)}>
29
- <PluginItem identifier={identifier} />
30
- </Link>
31
- );
25
+ return <PluginItem identifier={identifier} key={identifier} />;
32
26
  })}
33
27
  </Flexbox>
34
28
  );
@@ -29,12 +29,12 @@ const Client = memo<{ mobile?: boolean }>(() => {
29
29
 
30
30
  return (
31
31
  <>
32
- <Title more={t('home.more')} moreLink={'/assistant'}>
32
+ <Title more={t('home.more')} moreLink={'/discover/assistant'}>
33
33
  {t('home.featuredAssistants')}
34
34
  </Title>
35
35
  <AssistantList data={assistantList.items} rows={4} />
36
36
  <div />
37
- <Title more={t('home.more')} moreLink={'/mcp'}>
37
+ <Title more={t('home.more')} moreLink={'/discover/mcp'}>
38
38
  {t('home.featuredTools')}
39
39
  </Title>
40
40
  <McpList data={mcpList.items} rows={4} />
@@ -29,12 +29,12 @@ const HomePage = memo<{ mobile?: boolean }>(() => {
29
29
 
30
30
  return (
31
31
  <>
32
- <Title more={t('home.more')} moreLink={'/assistant'}>
32
+ <Title more={t('home.more')} moreLink={'/discover/assistant'}>
33
33
  {t('home.featuredAssistants')}
34
34
  </Title>
35
35
  <AssistantList data={assistantList.items} rows={4} />
36
36
  <div />
37
- <Title more={t('home.more')} moreLink={'/mcp'}>
37
+ <Title more={t('home.more')} moreLink={'/discover/mcp'}>
38
38
  {t('home.featuredTools')}
39
39
  </Title>
40
40
  <McpList data={mcpList.items} rows={4} />
@@ -56,4 +56,3 @@ DesktopHomePage.displayName = 'DesktopHomePage';
56
56
 
57
57
  export { DesktopHomePage, MobileHomePage };
58
58
  export default HomePage;
59
-
@@ -48,6 +48,7 @@ const Title = memo<TitleProps>(
48
48
  // Check if it's an external link or internal discover route
49
49
  const isExternalLink = moreLink?.startsWith('http') ?? false;
50
50
  const isDiscoverRoute = moreLink?.startsWith('/discover') ?? false;
51
+ console.log('moreLink', moreLink);
51
52
 
52
53
  let moreLinkElement = null;
53
54
  if (moreLink) {
@@ -60,7 +61,7 @@ const Title = memo<TitleProps>(
60
61
  );
61
62
  } else if (isDiscoverRoute) {
62
63
  moreLinkElement = (
63
- <RouterLink className={styles.more} to={moreLink.replace('/discover', '')}>
64
+ <RouterLink className={styles.more} to={moreLink}>
64
65
  <span style={{ marginRight: 4 }}>{more}</span>
65
66
  <Icon icon={ChevronRight} />
66
67
  </RouterLink>
package/src/auth.ts CHANGED
@@ -13,6 +13,7 @@ import {
13
13
  import { initBetterAuthSSOProviders } from '@/libs/better-auth/sso';
14
14
  import { parseSSOProviders } from '@/libs/better-auth/utils/server';
15
15
  import { EmailService } from '@/server/services/email';
16
+ import { UserService } from '@/server/services/user';
16
17
 
17
18
  // Email verification link expiration time (in seconds)
18
19
  // Default is 1 hour (3600 seconds) as per Better Auth documentation
@@ -120,6 +121,26 @@ export const auth = betterAuth({
120
121
  database: drizzleAdapter(serverDB, {
121
122
  provider: 'pg',
122
123
  }),
124
+ /**
125
+ * Run user bootstrap for every newly created account (email, magic link, OAuth/social, etc.).
126
+ * Using Better Auth database hooks ensures we catch social flows that bypass /sign-up/* routes.
127
+ * Ref: https://www.better-auth.com/docs/reference/options#databasehooks
128
+ */
129
+ databaseHooks: {
130
+ user: {
131
+ create: {
132
+ after: async (user) => {
133
+ const userService = new UserService(serverDB);
134
+ await userService.initUser({
135
+ email: user.email,
136
+ id: user.id,
137
+ username: user.username as string | null,
138
+ // TODO: if add phone plugin, we should fill phone here
139
+ });
140
+ },
141
+ },
142
+ },
143
+ },
123
144
  user: {
124
145
  additionalFields: {
125
146
  username: {
@@ -623,6 +623,7 @@ export default {
623
623
  supportedProviders: '支持该模型的服务商',
624
624
  },
625
625
  plugins: {
626
+ builtinTag: '内置插件',
626
627
  community: '社区插件',
627
628
  details: {
628
629
  settings: {
@@ -637,6 +638,7 @@ export default {
637
638
  },
638
639
  install: '安装插件',
639
640
  installed: '已安装',
641
+ legacyTag: '旧版插件',
640
642
  list: '插件列表',
641
643
  meta: {
642
644
  description: '描述',
@@ -921,88 +921,110 @@ export class DiscoverService {
921
921
  }): Promise<DiscoverPluginDetail | undefined> => {
922
922
  log('getPluginDetail: params=%O', params);
923
923
  const { locale, identifier, withManifest } = params;
924
+
925
+ // Step 1: Try to find in legacy plugin list
924
926
  const all = await this._getPluginList(locale);
925
- let raw = all.find((item) => item.identifier === identifier);
926
- if (!raw) {
927
- log(
928
- 'getPluginDetail: plugin not found in default store for identifier=%s, trying MCP plugin',
929
- identifier,
930
- );
931
- try {
932
- const mcpDetail = await this.getMcpDetail({ identifier, locale });
933
- const convertedMcp: Partial<DiscoverPluginDetail> = {
927
+ const raw = all.find((item) => item.identifier === identifier);
928
+ if (raw) {
929
+ log('getPluginDetail: found plugin in legacy list for identifier=%s', identifier);
930
+ const mergedRaw = merge(cloneDeep(DEFAULT_DISCOVER_PLUGIN_ITEM), raw);
931
+ const list = await this.getPluginList({
932
+ category: mergedRaw.category,
933
+ locale,
934
+ page: 1,
935
+ pageSize: 7,
936
+ });
937
+
938
+ const plugin: DiscoverPluginDetail = {
939
+ ...mergedRaw,
940
+ related: list.items.filter((item) => item.identifier !== mergedRaw.identifier).slice(0, 6),
941
+ source: 'legacy',
942
+ };
943
+
944
+ if (!withManifest || !plugin?.manifest || !isString(plugin?.manifest)) {
945
+ log('getPluginDetail: returning legacy plugin without manifest processing');
946
+ return plugin;
947
+ }
948
+
949
+ return plugin;
950
+ }
951
+
952
+ // Step 2: Try to find in Market MCP plugins
953
+ log(
954
+ 'getPluginDetail: plugin not found in legacy store for identifier=%s, trying MCP plugin',
955
+ identifier,
956
+ );
957
+ try {
958
+ const mcpDetail = await this.getMcpDetail({ identifier, locale });
959
+ const convertedMcp: Partial<DiscoverPluginDetail> = {
960
+ author:
961
+ typeof (mcpDetail as any).author === 'object'
962
+ ? (mcpDetail as any).author?.name || ''
963
+ : (mcpDetail as any).author || '',
964
+ avatar: (mcpDetail as any).icon || (mcpDetail as any).avatar || '',
965
+ category: (mcpDetail as any).category as any,
966
+ createdAt: (mcpDetail as any).createdAt || '',
967
+ description: mcpDetail.description || '',
968
+ homepage: mcpDetail.homepage || '',
969
+ identifier: mcpDetail.identifier,
970
+ manifest: undefined,
971
+ related: mcpDetail.related.map((item) => ({
934
972
  author:
935
- typeof (mcpDetail as any).author === 'object'
936
- ? (mcpDetail as any).author?.name || ''
937
- : (mcpDetail as any).author || '',
938
- avatar: (mcpDetail as any).icon || (mcpDetail as any).avatar || '',
939
- category: (mcpDetail as any).category as any,
940
- createdAt: (mcpDetail as any).createdAt || '',
941
- description: mcpDetail.description || '',
942
- homepage: mcpDetail.homepage || '',
943
- identifier: mcpDetail.identifier,
973
+ typeof (item as any).author === 'object'
974
+ ? (item as any).author?.name || ''
975
+ : (item as any).author || '',
976
+ avatar: (item as any).icon || (item as any).avatar || '',
977
+ category: (item as any).category as any,
978
+ createdAt: (item as any).createdAt || '',
979
+ description: (item as any).description || '',
980
+ homepage: (item as any).homepage || '',
981
+ identifier: item.identifier,
944
982
  manifest: undefined,
945
- related: mcpDetail.related.map((item) => ({
946
- author:
947
- typeof (item as any).author === 'object'
948
- ? (item as any).author?.name || ''
949
- : (item as any).author || '',
950
- avatar: (item as any).icon || (item as any).avatar || '',
951
- category: (item as any).category as any,
952
- createdAt: (item as any).createdAt || '',
953
- description: (item as any).description || '',
954
- homepage: (item as any).homepage || '',
955
- identifier: item.identifier,
956
- manifest: undefined,
957
- schemaVersion: 1,
958
- tags: (item as any).tags || [],
959
- title: (item as any).name || item.identifier,
960
- })) as unknown as DiscoverPluginItem[],
961
983
  schemaVersion: 1,
962
- tags: (mcpDetail as any).tags || [],
963
- title: (mcpDetail as any).name || mcpDetail.identifier,
964
- };
965
- const plugin = merge(cloneDeep(DEFAULT_DISCOVER_PLUGIN_ITEM), convertedMcp);
966
- log('getPluginDetail: returning converted MCP plugin');
967
- return plugin as DiscoverPluginDetail;
968
- } catch (error) {
969
- log('getPluginDetail: MCP plugin not found for identifier=%s, error=%O', identifier, error);
970
- return;
971
- }
984
+ tags: (item as any).tags || [],
985
+ title: (item as any).name || item.identifier,
986
+ })) as unknown as DiscoverPluginItem[],
987
+ schemaVersion: 1,
988
+ source: 'market',
989
+ tags: (mcpDetail as any).tags || [],
990
+ title: (mcpDetail as any).name || mcpDetail.identifier,
991
+ };
992
+ const plugin = merge(cloneDeep(DEFAULT_DISCOVER_PLUGIN_ITEM), convertedMcp);
993
+ log('getPluginDetail: returning converted MCP plugin');
994
+ return plugin as DiscoverPluginDetail;
995
+ } catch {
996
+ log(
997
+ 'getPluginDetail: MCP plugin not found for identifier=%s, trying builtin tools',
998
+ identifier,
999
+ );
972
1000
  }
973
1001
 
974
- raw = merge(cloneDeep(DEFAULT_DISCOVER_PLUGIN_ITEM), raw);
975
- const list = await this.getPluginList({
976
- category: raw.category,
977
- locale,
978
- page: 1,
979
- pageSize: 7,
980
- });
981
-
982
- let plugin = {
983
- ...raw,
984
- related: list.items.filter((item) => item.identifier !== raw.identifier).slice(0, 6),
985
- };
986
-
987
- if (!withManifest || !plugin?.manifest || !isString(plugin?.manifest)) {
988
- log('getPluginDetail: returning plugin without manifest processing');
1002
+ // Step 3: Try to find in builtin tools
1003
+ const { builtinTools } = await import('@/tools/index');
1004
+ const builtinTool = builtinTools.find((tool) => tool.identifier === identifier);
1005
+ if (builtinTool) {
1006
+ log('getPluginDetail: found builtin tool for identifier=%s', identifier);
1007
+ const plugin: DiscoverPluginDetail = {
1008
+ author: 'LobeHub',
1009
+ avatar: builtinTool.manifest.meta.avatar || '',
1010
+ category: undefined,
1011
+ createdAt: '',
1012
+ description: builtinTool.manifest.meta.description || '',
1013
+ homepage: 'https://lobehub.com',
1014
+ identifier: builtinTool.identifier,
1015
+ manifest: undefined,
1016
+ related: [],
1017
+ schemaVersion: 1,
1018
+ source: 'builtin',
1019
+ tags: builtinTool.manifest.meta.tags || [],
1020
+ title: builtinTool.manifest.meta.title,
1021
+ };
1022
+ log('getPluginDetail: returning builtin tool plugin');
989
1023
  return plugin;
990
1024
  }
991
1025
 
992
- // Edge Runtime 环境中使用了 Node.js path 模块,但 Edge Runtime 不支持所有 Node.js API
993
- // 这个函数使用了 @lobehub/chat-plugin-sdk/openapi,该包最终依赖了 @apidevtools/swagger-parser,而这个包在 Edge Runtime 环境中使用了不被支持的 Node.js path 模块。
994
- // try {
995
- // const manifest = await getToolManifest(plugin.manifest);
996
- //
997
- // return {
998
- // ...plugin,
999
- // manifest,
1000
- // };
1001
- // } catch {
1002
- // return plugin;
1003
- // }
1004
-
1005
- return plugin;
1026
+ log('getPluginDetail: plugin not found anywhere for identifier=%s', identifier);
1027
+ return;
1006
1028
  };
1007
1029
 
1008
1030
  getPluginIdentifiers = async (): Promise<IdentifiersResponse> => {
@@ -8,6 +8,15 @@ import { KeyVaultsGateKeeper } from '@/server/modules/KeyVaultsEncrypt';
8
8
  import { S3 } from '@/server/modules/S3';
9
9
  import { AgentService } from '@/server/services/agent';
10
10
 
11
+ type CreatedUser = {
12
+ email?: string | null;
13
+ firstName?: string | null;
14
+ id: string;
15
+ lastName?: string | null;
16
+ phone?: string | null;
17
+ username?: string | null;
18
+ };
19
+
11
20
  export class UserService {
12
21
  private db: LobeChatDatabase;
13
22
 
@@ -15,6 +24,30 @@ export class UserService {
15
24
  this.db = db;
16
25
  }
17
26
 
27
+ async initUser(user: CreatedUser) {
28
+ const agentService = new AgentService(this.db, user.id);
29
+ await agentService.createInbox();
30
+
31
+ /* ↓ cloud slot ↓ */
32
+ /* ↑ cloud slot ↑ */
33
+
34
+ const analytics = await initializeServerAnalytics();
35
+ analytics?.identify(user.id, {
36
+ email: user.email ?? undefined,
37
+ firstName: user.firstName ?? undefined,
38
+ lastName: user.lastName ?? undefined,
39
+ phone: user.phone ?? undefined,
40
+ username: user.username ?? undefined,
41
+ });
42
+ analytics?.track({
43
+ name: 'user_register_completed',
44
+ properties: {
45
+ spm: 'user_service.init_user.user_created',
46
+ },
47
+ userId: user.id,
48
+ });
49
+ }
50
+
18
51
  createUser = async (id: string, params: UserJSON) => {
19
52
  // Check if user already exists
20
53
  const res = await UserModel.findById(this.db, id);
@@ -34,10 +67,6 @@ export class UserService {
34
67
  return index === 0;
35
68
  });
36
69
 
37
- /* ↓ cloud slot ↓ */
38
-
39
- /* ↑ cloud slot ↑ */
40
-
41
70
  // 2. create user in database
42
71
  await UserModel.createUser(this.db, {
43
72
  avatar: params.image_url,
@@ -50,30 +79,14 @@ export class UserService {
50
79
  username: params.username,
51
80
  });
52
81
 
53
- // 3. Create an inbox session for the user
54
- const agentService = new AgentService(this.db, id);
55
- await agentService.createInbox();
56
-
57
- /* ↓ cloud slot ↓ */
58
-
59
- /* ↑ cloud slot ↑ */
60
-
61
- //analytics
62
- const analytics = await initializeServerAnalytics();
63
- analytics?.identify(id, {
82
+ await this.initUser({
64
83
  email: email?.email_address,
65
84
  firstName: params.first_name,
85
+ id,
66
86
  lastName: params.last_name,
67
87
  phone: phone?.phone_number,
68
88
  username: params.username,
69
89
  });
70
- analytics?.track({
71
- name: 'user_register_completed',
72
- properties: {
73
- spm: 'user_service.create_user.user_created',
74
- },
75
- userId: id,
76
- });
77
90
 
78
91
  return { message: 'user created', success: true };
79
92
  };