@lobehub/chat 1.40.4 → 1.41.0
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.
- package/CHANGELOG.md +27 -0
- package/changelog/v1.json +9 -0
- package/docs/self-hosting/advanced/auth/next-auth/wechat.mdx +46 -0
- package/docs/self-hosting/advanced/auth/next-auth/wechat.zh-CN.mdx +43 -0
- package/package.json +1 -1
- package/src/app/(backend)/webapi/assistant/store/route.ts +2 -11
- package/src/app/(main)/discover/(detail)/provider/[slug]/features/ProviderConfig.tsx +7 -4
- package/src/config/app.ts +4 -0
- package/src/features/MobileTabBar/index.tsx +3 -2
- package/src/features/User/UserAvatar.tsx +2 -2
- package/src/features/User/UserPanel/useMenu.tsx +5 -20
- package/src/hooks/useInterceptingRoutes.test.ts +2 -16
- package/src/hooks/useInterceptingRoutes.ts +2 -18
- package/src/libs/next-auth/sso-providers/index.ts +2 -0
- package/src/libs/next-auth/sso-providers/wechat.ts +24 -0
- package/src/server/modules/AssistantStore/index.test.ts +5 -5
- package/src/server/modules/AssistantStore/index.ts +39 -1
- package/src/server/modules/EdgeConfig/index.ts +23 -0
- package/src/server/services/discover/index.ts +2 -13
- package/src/types/discover.ts +20 -0
- package/src/app/@modal/(.)settings/modal/index.tsx +0 -45
- package/src/app/@modal/(.)settings/modal/layout.tsx +0 -47
- package/src/app/@modal/(.)settings/modal/loading.tsx +0 -5
- package/src/app/@modal/(.)settings/modal/page.tsx +0 -19
package/CHANGELOG.md
CHANGED
@@ -2,6 +2,33 @@
|
|
2
2
|
|
3
3
|
# Changelog
|
4
4
|
|
5
|
+
## [Version 1.41.0](https://github.com/lobehub/lobe-chat/compare/v1.40.4...v1.41.0)
|
6
|
+
|
7
|
+
<sup>Released on **2024-12-28**</sup>
|
8
|
+
|
9
|
+
#### ✨ Features
|
10
|
+
|
11
|
+
- **auth**: Add WeChat authentication support.
|
12
|
+
- **misc**: Support white list for discover assistant.
|
13
|
+
|
14
|
+
<br/>
|
15
|
+
|
16
|
+
<details>
|
17
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
18
|
+
|
19
|
+
#### What's improved
|
20
|
+
|
21
|
+
- **auth**: Add WeChat authentication support, closes [#5195](https://github.com/lobehub/lobe-chat/issues/5195) ([95153a4](https://github.com/lobehub/lobe-chat/commit/95153a4))
|
22
|
+
- **misc**: Support white list for discover assistant, closes [#5216](https://github.com/lobehub/lobe-chat/issues/5216) ([90bb20d](https://github.com/lobehub/lobe-chat/commit/90bb20d))
|
23
|
+
|
24
|
+
</details>
|
25
|
+
|
26
|
+
<div align="right">
|
27
|
+
|
28
|
+
[](#readme-top)
|
29
|
+
|
30
|
+
</div>
|
31
|
+
|
5
32
|
### [Version 1.40.4](https://github.com/lobehub/lobe-chat/compare/v1.40.3...v1.40.4)
|
6
33
|
|
7
34
|
<sup>Released on **2024-12-28**</sup>
|
package/changelog/v1.json
CHANGED
@@ -0,0 +1,46 @@
|
|
1
|
+
---
|
2
|
+
title: Configure Wechat Authentication Service in LobeChat
|
3
|
+
description: Learn how to configure Wechat authentication service in LobeChat, including creating a new Wechat App, setting permissions, and environment variables.
|
4
|
+
tags:
|
5
|
+
- Wechat Authentication
|
6
|
+
- Wechat App
|
7
|
+
- Environment Variable Configuration
|
8
|
+
- Single Sign-On
|
9
|
+
- LobeChat
|
10
|
+
---
|
11
|
+
|
12
|
+
# Configure Wechat Authentication Service
|
13
|
+
|
14
|
+
## Wechat Configuration Process
|
15
|
+
|
16
|
+
<Steps>
|
17
|
+
### Create a Wechat Application
|
18
|
+
|
19
|
+
Click [here](https://open.weixin.qq.com/cgi-bin/index) and then click "Management Center", "Website Application", and "Create Website Application" in sequence.
|
20
|
+
|
21
|
+
Fill in the information as required by the official website prompts and submit for review.
|
22
|
+
|
23
|
+
After successful creation, click "Application Details" to obtain the AppID and AppSecret.
|
24
|
+
|
25
|
+
### Configure Environment Variables
|
26
|
+
|
27
|
+
When deploying LobeChat, you need to configure the following environment variables:
|
28
|
+
|
29
|
+
| Environment Variable | Type | Description |
|
30
|
+
| --- | --- | --- |
|
31
|
+
| `NEXT_AUTH_SECRET` | Required | Key used to encrypt Auth.js session tokens. You can generate the key using the command: `openssl rand -base64 32` |
|
32
|
+
| `NEXT_AUTH_SSO_PROVIDERS` | Required | Select the Single Sign-On provider for LobeChat. Use `github` for Github. |
|
33
|
+
| `WECHAT_CLIENT_ID` | Required | Client ID from the Wechat website application details page |
|
34
|
+
| `WECHAT_CLIENT_SECRET` | Required | Client Secret from the Wechat website application details page |
|
35
|
+
| `NEXTAUTH_URL` | Required | This URL is used to specify the callback address for Auth.js when performing OAuth authentication. Only set it if the default generated redirect address is incorrect. `https://example.com/api/auth` |
|
36
|
+
|
37
|
+
<Callout type={'tip'}>
|
38
|
+
Go to [📘 Environment Variables](/en/docs/self-hosting/environment-variables/auth#wechat) for more details about related variables.
|
39
|
+
|
40
|
+
</Callout>
|
41
|
+
</Steps>
|
42
|
+
|
43
|
+
<Callout type={'info'}>
|
44
|
+
After successful deployment, users will be able to authenticate through the WeChat Open Platform
|
45
|
+
and use LobeChat.
|
46
|
+
</Callout>
|
@@ -0,0 +1,43 @@
|
|
1
|
+
---
|
2
|
+
title: 在 LobeChat 中配置微信身份验证服务
|
3
|
+
description: 学习如何在 LobeChat 中配置微信身份验证服务,包括创建新的微信网站应用、设置权限和环境变量。
|
4
|
+
tags:
|
5
|
+
-微信身份验证
|
6
|
+
-微信网站应用
|
7
|
+
- 环境变量配置
|
8
|
+
- 单点登录
|
9
|
+
- LobeChat
|
10
|
+
---
|
11
|
+
|
12
|
+
# 配置微信身份验证服务
|
13
|
+
|
14
|
+
##微信配置流程
|
15
|
+
|
16
|
+
<Steps>
|
17
|
+
### 创建微信网站应用
|
18
|
+
|
19
|
+
点击 [这里](https://open.weixin.qq.com/cgi-bin/index) 依次点击“管理中心”、“网站应用”、“创建网站应用”
|
20
|
+
|
21
|
+
按照管网提示要求填写信息并提交审核。
|
22
|
+
|
23
|
+
创建成功后,点击“应用详情”,可获知AppID和AppSecret。
|
24
|
+
|
25
|
+
### 配置环境变量
|
26
|
+
|
27
|
+
在部署 LobeChat 时,你需要配置以下环境变量:
|
28
|
+
|
29
|
+
| 环境变量 | 类型 | 描述 |
|
30
|
+
| --- | --- | --- |
|
31
|
+
| `NEXT_AUTH_SECRET` | 必选 | 用于加密 Auth.js 会话令牌的密钥。您可以使用以下命令生成秘钥: `openssl rand -base64 32` |
|
32
|
+
| `NEXT_AUTH_SSO_PROVIDERS` | 必选 | 选择 LoboChat 的单点登录提供商。使用 Github 请填写 `github`。 |
|
33
|
+
| `WECHAT_CLIENT_ID` | 必选 |微信网站应用详情页的 客户端 ID |
|
34
|
+
| `WECHAT_CLIENT_SECRET` | 必选 |微信网站应用详情页的 客户端 Secret |
|
35
|
+
| `NEXTAUTH_URL` | 必选 | 该 URL 用于指定 Auth.js 在执行 OAuth 验证时的回调地址,当默认生成的重定向地址发生不正确时才需要设置。`https://example.com/api/auth` |
|
36
|
+
|
37
|
+
<Callout type={'tip'}>
|
38
|
+
前往 [📘 环境变量](/zh/docs/self-hosting/environment-variables/auth#wechat) 可查阅相关变量详情。
|
39
|
+
|
40
|
+
</Callout>
|
41
|
+
</Steps>
|
42
|
+
|
43
|
+
<Callout type={'info'}>部署成功后,用户将可以通过微信开放平台身份认证并使用 LobeChat。</Callout>
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@lobehub/chat",
|
3
|
-
"version": "1.
|
3
|
+
"version": "1.41.0",
|
4
4
|
"description": "Lobe Chat - an open-source, high-performance chatbot 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",
|
@@ -1,6 +1,5 @@
|
|
1
1
|
import { NextResponse } from 'next/server';
|
2
2
|
|
3
|
-
import { DEFAULT_LANG } from '@/const/locale';
|
4
3
|
import { AssistantStore } from '@/server/modules/AssistantStore';
|
5
4
|
|
6
5
|
export const runtime = 'edge';
|
@@ -11,18 +10,10 @@ export const GET = async (req: Request) => {
|
|
11
10
|
|
12
11
|
const market = new AssistantStore();
|
13
12
|
|
14
|
-
|
13
|
+
const data = await market.getAgentIndex(locale as any);
|
15
14
|
|
16
|
-
res = await fetch(market.getAgentIndexUrl(locale as any));
|
17
|
-
|
18
|
-
if (res.status === 404) {
|
19
|
-
res = await fetch(market.getAgentIndexUrl(DEFAULT_LANG));
|
20
|
-
}
|
21
|
-
|
22
|
-
const data = await res.json();
|
23
15
|
return NextResponse.json(data);
|
24
|
-
} catch
|
25
|
-
console.error(e);
|
16
|
+
} catch {
|
26
17
|
return new Response(`failed to fetch agent market index`, {
|
27
18
|
headers: {
|
28
19
|
'Access-Control-Allow-Origin': '*',
|
@@ -5,12 +5,11 @@ import { Button, Dropdown } from 'antd';
|
|
5
5
|
import { createStyles } from 'antd-style';
|
6
6
|
import { ChevronDownIcon, SquareArrowOutUpRight } from 'lucide-react';
|
7
7
|
import Link from 'next/link';
|
8
|
+
import { useRouter } from 'next/navigation';
|
8
9
|
import { memo } from 'react';
|
9
10
|
import { useTranslation } from 'react-i18next';
|
10
11
|
import { FlexboxProps } from 'react-layout-kit';
|
11
12
|
|
12
|
-
import { useOpenSettings } from '@/hooks/useInterceptingRoutes';
|
13
|
-
import { SettingsTabs } from '@/store/global/initialState';
|
14
13
|
import { DiscoverProviderItem } from '@/types/discover';
|
15
14
|
|
16
15
|
const useStyles = createStyles(({ css }) => ({
|
@@ -29,7 +28,11 @@ interface ProviderConfigProps extends FlexboxProps {
|
|
29
28
|
const ProviderConfig = memo<ProviderConfigProps>(({ data }) => {
|
30
29
|
const { styles } = useStyles();
|
31
30
|
const { t } = useTranslation('discover');
|
32
|
-
|
31
|
+
|
32
|
+
const router = useRouter();
|
33
|
+
const openSettings = () => {
|
34
|
+
router.push('/settings/llm');
|
35
|
+
};
|
33
36
|
|
34
37
|
const icon = <Icon icon={SquareArrowOutUpRight} size={{ fontSize: 16 }} />;
|
35
38
|
|
@@ -56,7 +59,7 @@ const ProviderConfig = memo<ProviderConfigProps>(({ data }) => {
|
|
56
59
|
|
57
60
|
if (!items || items?.length === 0)
|
58
61
|
return (
|
59
|
-
<Button
|
62
|
+
<Button size={'large'} style={{ flex: 1 }} type={'primary'}>
|
60
63
|
{t('providers.config')}
|
61
64
|
</Button>
|
62
65
|
);
|
package/src/config/app.ts
CHANGED
@@ -47,6 +47,8 @@ export const getAppConfig = () => {
|
|
47
47
|
PLUGIN_SETTINGS: z.string().optional(),
|
48
48
|
|
49
49
|
APP_URL: z.string().optional(),
|
50
|
+
VERCEL_EDGE_CONFIG: z.string().optional(),
|
51
|
+
|
50
52
|
CDN_USE_GLOBAL: z.boolean().optional(),
|
51
53
|
CUSTOM_FONT_FAMILY: z.string().optional(),
|
52
54
|
CUSTOM_FONT_URL: z.string().optional(),
|
@@ -75,6 +77,8 @@ export const getAppConfig = () => {
|
|
75
77
|
|
76
78
|
PLUGIN_SETTINGS: process.env.PLUGIN_SETTINGS,
|
77
79
|
|
80
|
+
VERCEL_EDGE_CONFIG: process.env.VERCEL_EDGE_CONFIG,
|
81
|
+
|
78
82
|
APP_URL,
|
79
83
|
CUSTOM_FONT_FAMILY: process.env.CUSTOM_FONT_FAMILY,
|
80
84
|
CUSTOM_FONT_URL: process.env.CUSTOM_FONT_URL,
|
@@ -6,7 +6,6 @@ import { rgba } from 'polished';
|
|
6
6
|
import { memo, useMemo } from 'react';
|
7
7
|
import { useTranslation } from 'react-i18next';
|
8
8
|
|
9
|
-
import { useOpenSettings } from '@/hooks/useInterceptingRoutes';
|
10
9
|
import { SidebarTabKey } from '@/store/global/initialState';
|
11
10
|
import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
|
12
11
|
|
@@ -26,8 +25,10 @@ interface Props {
|
|
26
25
|
export default memo<Props>(({ className, tabBarKey }) => {
|
27
26
|
const { t } = useTranslation('common');
|
28
27
|
const { styles } = useStyles();
|
29
|
-
const openSettings = useOpenSettings();
|
30
28
|
const router = useRouter();
|
29
|
+
const openSettings = () => {
|
30
|
+
router.push('/settings/llm');
|
31
|
+
};
|
31
32
|
const { showMarket } = useServerConfigStore(featureFlagsSelectors);
|
32
33
|
|
33
34
|
const items: MobileTabBarProps['items'] = useMemo(
|
@@ -57,8 +57,8 @@ const UserAvatar = forwardRef<HTMLDivElement, UserAvatarProps>(
|
|
57
57
|
|
58
58
|
return (
|
59
59
|
<Avatar
|
60
|
-
alt={isSignedIn
|
61
|
-
avatar={isSignedIn
|
60
|
+
alt={isSignedIn && !!username ? username : BRANDING_NAME}
|
61
|
+
avatar={isSignedIn && !!avatar ? avatar : DEFAULT_USER_AVATAR_URL}
|
62
62
|
background={isSignedIn && avatar ? background : undefined}
|
63
63
|
className={cx(clickable && styles.clickable, className)}
|
64
64
|
ref={ref}
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import {
|
1
|
+
import { DiscordIcon, Icon } from '@lobehub/ui';
|
2
2
|
import { Badge } from 'antd';
|
3
3
|
import { ItemType } from 'antd/es/menu/interface';
|
4
4
|
import {
|
@@ -13,14 +13,12 @@ import {
|
|
13
13
|
LifeBuoy,
|
14
14
|
LogOut,
|
15
15
|
Mail,
|
16
|
-
Maximize,
|
17
16
|
Settings2,
|
18
17
|
} from 'lucide-react';
|
19
18
|
import Link from 'next/link';
|
20
19
|
import { PropsWithChildren, memo } from 'react';
|
21
20
|
import { useTranslation } from 'react-i18next';
|
22
21
|
import { Flexbox } from 'react-layout-kit';
|
23
|
-
import urlJoin from 'url-join';
|
24
22
|
|
25
23
|
import type { MenuProps } from '@/components/Menu';
|
26
24
|
import { LOBE_CHAT_CLOUD } from '@/const/branding';
|
@@ -35,11 +33,8 @@ import {
|
|
35
33
|
} from '@/const/url';
|
36
34
|
import { isServerMode } from '@/const/version';
|
37
35
|
import DataImporter from '@/features/DataImporter';
|
38
|
-
import { useOpenSettings } from '@/hooks/useInterceptingRoutes';
|
39
36
|
import { usePWAInstall } from '@/hooks/usePWAInstall';
|
40
|
-
import { useQueryRoute } from '@/hooks/useQueryRoute';
|
41
37
|
import { configService } from '@/services/config';
|
42
|
-
import { SettingsTabs } from '@/store/global/initialState';
|
43
38
|
import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
|
44
39
|
import { useUserStore } from '@/store/user';
|
45
40
|
import { authSelectors } from '@/store/user/selectors';
|
@@ -51,7 +46,7 @@ const NewVersionBadge = memo(
|
|
51
46
|
children,
|
52
47
|
showBadge,
|
53
48
|
onClick,
|
54
|
-
}: PropsWithChildren & { onClick
|
49
|
+
}: PropsWithChildren & { onClick?: () => void; showBadge?: boolean }) => {
|
55
50
|
const { t } = useTranslation('common');
|
56
51
|
if (!showBadge)
|
57
52
|
return (
|
@@ -69,10 +64,8 @@ const NewVersionBadge = memo(
|
|
69
64
|
);
|
70
65
|
|
71
66
|
export const useMenu = () => {
|
72
|
-
const router = useQueryRoute();
|
73
67
|
const { canInstall, install } = usePWAInstall();
|
74
68
|
const hasNewVersion = useNewVersion();
|
75
|
-
const openSettings = useOpenSettings();
|
76
69
|
const { t } = useTranslation(['common', 'setting', 'auth']);
|
77
70
|
const { showCloudPromotion, hideDocs } = useServerConfigStore(featureFlagsSelectors);
|
78
71
|
const [isLogin, isLoginWithAuth, isLoginWithClerk, openUserProfile] = useUserStore((s) => [
|
@@ -93,20 +86,12 @@ export const useMenu = () => {
|
|
93
86
|
|
94
87
|
const settings: MenuProps['items'] = [
|
95
88
|
{
|
96
|
-
extra: (
|
97
|
-
<ActionIcon
|
98
|
-
icon={Maximize}
|
99
|
-
onClick={() => router.push(urlJoin('/settings', SettingsTabs.Common))}
|
100
|
-
size={'small'}
|
101
|
-
title={t('fullscreen')}
|
102
|
-
/>
|
103
|
-
),
|
104
89
|
icon: <Icon icon={Settings2} />,
|
105
90
|
key: 'setting',
|
106
91
|
label: (
|
107
|
-
<
|
108
|
-
{t('userPanel.setting')}
|
109
|
-
</
|
92
|
+
<Link href={'/settings/common'}>
|
93
|
+
<NewVersionBadge showBadge={hasNewVersion}>{t('userPanel.setting')}</NewVersionBadge>
|
94
|
+
</Link>
|
110
95
|
),
|
111
96
|
},
|
112
97
|
{
|
@@ -8,7 +8,7 @@ import { useGlobalStore } from '@/store/global';
|
|
8
8
|
import { ChatSettingsTabs, SettingsTabs, SidebarTabKey } from '@/store/global/initialState';
|
9
9
|
import { useSessionStore } from '@/store/session';
|
10
10
|
|
11
|
-
import { useOpenChatSettings
|
11
|
+
import { useOpenChatSettings } from './useInterceptingRoutes';
|
12
12
|
|
13
13
|
// Mocks
|
14
14
|
vi.mock('next/navigation', () => ({
|
@@ -32,26 +32,12 @@ vi.mock('@/store/global', () => ({
|
|
32
32
|
},
|
33
33
|
}));
|
34
34
|
|
35
|
-
describe('useOpenSettings', () => {
|
36
|
-
it('should handle mobile route correctly', () => {
|
37
|
-
vi.mocked(useIsMobile).mockReturnValue(true);
|
38
|
-
const { result } = renderHook(() => useOpenSettings(SettingsTabs.Common));
|
39
|
-
expect(result.current()).toBe('/settings/common');
|
40
|
-
});
|
41
|
-
|
42
|
-
it('should handle desktop route correctly', () => {
|
43
|
-
vi.mocked(useIsMobile).mockReturnValue(false);
|
44
|
-
const { result } = renderHook(() => useOpenSettings(SettingsTabs.Agent));
|
45
|
-
expect(result.current()).toBe('/settings/modal?tab=agent');
|
46
|
-
});
|
47
|
-
});
|
48
|
-
|
49
35
|
describe('useOpenChatSettings', () => {
|
50
36
|
it('should handle inbox session id correctly', () => {
|
51
37
|
vi.mocked(useSessionStore).mockReturnValue(INBOX_SESSION_ID);
|
52
38
|
const { result } = renderHook(() => useOpenChatSettings());
|
53
39
|
|
54
|
-
expect(result.current()).toBe('/settings/
|
40
|
+
expect(result.current()).toBe('/settings/agent'); // Assuming openSettings returns a function
|
55
41
|
});
|
56
42
|
|
57
43
|
it('should handle mobile route for chat settings', () => {
|
@@ -8,24 +8,8 @@ import { useGlobalStore } from '@/store/global';
|
|
8
8
|
import { ChatSettingsTabs, SettingsTabs, SidebarTabKey } from '@/store/global/initialState';
|
9
9
|
import { useSessionStore } from '@/store/session';
|
10
10
|
|
11
|
-
export const useOpenSettings = (tab: SettingsTabs = SettingsTabs.Common) => {
|
12
|
-
const activeId = useSessionStore((s) => s.activeId);
|
13
|
-
const router = useQueryRoute();
|
14
|
-
const mobile = useIsMobile();
|
15
|
-
|
16
|
-
return useMemo(() => {
|
17
|
-
if (mobile) {
|
18
|
-
return () => router.push(urlJoin('/settings', tab));
|
19
|
-
} else {
|
20
|
-
// use Intercepting Routes on Desktop
|
21
|
-
return () => router.push('/settings/modal', { query: { session: activeId, tab } });
|
22
|
-
}
|
23
|
-
}, [mobile, tab, activeId, router]);
|
24
|
-
};
|
25
|
-
|
26
11
|
export const useOpenChatSettings = (tab: ChatSettingsTabs = ChatSettingsTabs.Meta) => {
|
27
12
|
const activeId = useSessionStore((s) => s.activeId);
|
28
|
-
const openSettings = useOpenSettings(SettingsTabs.Agent);
|
29
13
|
const router = useQueryRoute();
|
30
14
|
const mobile = useIsMobile();
|
31
15
|
|
@@ -34,7 +18,7 @@ export const useOpenChatSettings = (tab: ChatSettingsTabs = ChatSettingsTabs.Met
|
|
34
18
|
useGlobalStore.setState({
|
35
19
|
sidebarKey: SidebarTabKey.Setting,
|
36
20
|
});
|
37
|
-
return
|
21
|
+
return () => router.push(urlJoin('/settings', SettingsTabs.Agent));
|
38
22
|
}
|
39
23
|
if (mobile) {
|
40
24
|
return () => router.push('/chat/settings');
|
@@ -42,5 +26,5 @@ export const useOpenChatSettings = (tab: ChatSettingsTabs = ChatSettingsTabs.Met
|
|
42
26
|
// use Intercepting Routes on Desktop
|
43
27
|
return () => router.push('/chat/settings/modal', { query: { session: activeId, tab } });
|
44
28
|
}
|
45
|
-
}, [
|
29
|
+
}, [mobile, activeId, router, tab]);
|
46
30
|
};
|
@@ -8,6 +8,7 @@ import GenericOIDC from './generic-oidc';
|
|
8
8
|
import Github from './github';
|
9
9
|
import Logto from './logto';
|
10
10
|
import MicrosoftEntraID from './microsoft-entra-id';
|
11
|
+
import WeChat from './wechat';
|
11
12
|
import Zitadel from './zitadel';
|
12
13
|
|
13
14
|
export const ssoProviders = [
|
@@ -22,4 +23,5 @@ export const ssoProviders = [
|
|
22
23
|
CloudflareZeroTrust,
|
23
24
|
Casdoor,
|
24
25
|
MicrosoftEntraID,
|
26
|
+
WeChat,
|
25
27
|
];
|
@@ -0,0 +1,24 @@
|
|
1
|
+
import WeChat from '@auth/core/providers/wechat';
|
2
|
+
|
3
|
+
import { CommonProviderConfig } from './sso.config';
|
4
|
+
|
5
|
+
const provider = {
|
6
|
+
id: 'wechat',
|
7
|
+
provider: WeChat({
|
8
|
+
...CommonProviderConfig,
|
9
|
+
clientId: process.env.AUTH_WECHAT_ID,
|
10
|
+
clientSecret: process.env.AUTH_WECHAT_SECRET,
|
11
|
+
platformType: 'WebsiteApp',
|
12
|
+
profile: (profile) => {
|
13
|
+
return {
|
14
|
+
email: null,
|
15
|
+
id: profile.unionid,
|
16
|
+
image: profile.headimgurl,
|
17
|
+
name: profile.nickname,
|
18
|
+
providerAccountId: profile.unionid,
|
19
|
+
};
|
20
|
+
},
|
21
|
+
}),
|
22
|
+
};
|
23
|
+
|
24
|
+
export default provider;
|
@@ -7,19 +7,19 @@ const baseURL = 'https://registry.npmmirror.com/@lobehub/agents-index/v1/files/p
|
|
7
7
|
describe('AssistantStore', () => {
|
8
8
|
it('should return the default index URL when no language is provided', () => {
|
9
9
|
const agentMarket = new AssistantStore();
|
10
|
-
const url = agentMarket
|
10
|
+
const url = agentMarket['getAgentIndexUrl']();
|
11
11
|
expect(url).toBe(`${baseURL}/index.en-US.json`);
|
12
12
|
});
|
13
13
|
|
14
14
|
it('should return the index URL for a not supported language', () => {
|
15
15
|
const agentMarket = new AssistantStore();
|
16
|
-
const url = agentMarket
|
16
|
+
const url = agentMarket['getAgentIndexUrl']('xxx' as any);
|
17
17
|
expect(url).toBe('https://registry.npmmirror.com/@lobehub/agents-index/v1/files/public');
|
18
18
|
});
|
19
19
|
|
20
20
|
it('should return the zh-CN URL for zh locale', () => {
|
21
21
|
const agentMarket = new AssistantStore();
|
22
|
-
const url = agentMarket
|
22
|
+
const url = agentMarket['getAgentIndexUrl']('zh' as any);
|
23
23
|
expect(url).toBe(
|
24
24
|
'https://registry.npmmirror.com/@lobehub/agents-index/v1/files/public/index.zh-CN.json',
|
25
25
|
);
|
@@ -27,7 +27,7 @@ describe('AssistantStore', () => {
|
|
27
27
|
|
28
28
|
it('should return the default URL for en locale', () => {
|
29
29
|
const agentMarket = new AssistantStore();
|
30
|
-
const url = agentMarket
|
30
|
+
const url = agentMarket['getAgentIndexUrl']('en' as any);
|
31
31
|
expect(url).toBe(
|
32
32
|
'https://registry.npmmirror.com/@lobehub/agents-index/v1/files/public/index.en-US.json',
|
33
33
|
);
|
@@ -35,7 +35,7 @@ describe('AssistantStore', () => {
|
|
35
35
|
|
36
36
|
it('should return the base URL if the provided language is not supported', () => {
|
37
37
|
const agentMarket = new AssistantStore();
|
38
|
-
const url = agentMarket
|
38
|
+
const url = agentMarket['getAgentIndexUrl']('fr' as any);
|
39
39
|
expect(url).toBe(baseURL);
|
40
40
|
});
|
41
41
|
|
@@ -3,6 +3,8 @@ import urlJoin from 'url-join';
|
|
3
3
|
import { appEnv } from '@/config/app';
|
4
4
|
import { DEFAULT_LANG, isLocaleNotSupport } from '@/const/locale';
|
5
5
|
import { Locales, normalizeLocale } from '@/locales/resources';
|
6
|
+
import { EdgeConfig } from '@/server/modules/EdgeConfig';
|
7
|
+
import { AgentStoreIndex } from '@/types/discover';
|
6
8
|
|
7
9
|
export class AssistantStore {
|
8
10
|
private readonly baseUrl: string;
|
@@ -11,7 +13,7 @@ export class AssistantStore {
|
|
11
13
|
this.baseUrl = baseUrl || appEnv.AGENTS_INDEX_URL;
|
12
14
|
}
|
13
15
|
|
14
|
-
getAgentIndexUrl = (lang: Locales = DEFAULT_LANG) => {
|
16
|
+
private getAgentIndexUrl = (lang: Locales = DEFAULT_LANG) => {
|
15
17
|
if (isLocaleNotSupport(lang)) return this.baseUrl;
|
16
18
|
|
17
19
|
return urlJoin(this.baseUrl, `index.${normalizeLocale(lang)}.json`);
|
@@ -22,4 +24,40 @@ export class AssistantStore {
|
|
22
24
|
|
23
25
|
return urlJoin(this.baseUrl, `${identifier}.${normalizeLocale(lang)}.json`);
|
24
26
|
};
|
27
|
+
|
28
|
+
getAgentIndex = async (locale: Locales = DEFAULT_LANG, revalidate?: number) => {
|
29
|
+
try {
|
30
|
+
let res: Response;
|
31
|
+
|
32
|
+
res = await fetch(this.getAgentIndexUrl(locale as any), { next: { revalidate } });
|
33
|
+
|
34
|
+
if (res.status === 404) {
|
35
|
+
res = await fetch(this.getAgentIndexUrl(DEFAULT_LANG), { next: { revalidate } });
|
36
|
+
}
|
37
|
+
|
38
|
+
if (!res.ok) {
|
39
|
+
console.error('fetch agent index error:', await res.text());
|
40
|
+
return [];
|
41
|
+
}
|
42
|
+
|
43
|
+
const data: AgentStoreIndex = await res.json();
|
44
|
+
|
45
|
+
// Get the assistant whitelist from Edge Config
|
46
|
+
const edgeConfig = new EdgeConfig();
|
47
|
+
|
48
|
+
if (!!appEnv.VERCEL_EDGE_CONFIG) {
|
49
|
+
const assistantWhitelist = await edgeConfig.getAgentWhitelist();
|
50
|
+
|
51
|
+
if (assistantWhitelist && assistantWhitelist?.length > 0) {
|
52
|
+
data.agents = data.agents.filter((item) => assistantWhitelist.includes(item.identifier));
|
53
|
+
}
|
54
|
+
}
|
55
|
+
|
56
|
+
return data;
|
57
|
+
} catch (e) {
|
58
|
+
console.error('fetch agent index error:', e);
|
59
|
+
|
60
|
+
throw e;
|
61
|
+
}
|
62
|
+
};
|
25
63
|
}
|
@@ -0,0 +1,23 @@
|
|
1
|
+
import { EdgeConfigClient, createClient } from '@vercel/edge-config';
|
2
|
+
|
3
|
+
import { appEnv } from '@/config/app';
|
4
|
+
|
5
|
+
enum EdgeConfigKeys {
|
6
|
+
/**
|
7
|
+
* Assistant whitelist
|
8
|
+
*/
|
9
|
+
AssistantWhitelist = 'assistant_whitelist',
|
10
|
+
}
|
11
|
+
|
12
|
+
export class EdgeConfig {
|
13
|
+
get client(): EdgeConfigClient {
|
14
|
+
if (!appEnv.VERCEL_EDGE_CONFIG) {
|
15
|
+
throw new Error('VERCEL_EDGE_CONFIG is not set');
|
16
|
+
}
|
17
|
+
return createClient(appEnv.VERCEL_EDGE_CONFIG);
|
18
|
+
}
|
19
|
+
|
20
|
+
getAgentWhitelist = async (): Promise<string[] | undefined> => {
|
21
|
+
return this.client.get<string[]>(EdgeConfigKeys.AssistantWhitelist);
|
22
|
+
};
|
23
|
+
}
|
@@ -50,20 +50,9 @@ export class DiscoverService {
|
|
50
50
|
};
|
51
51
|
|
52
52
|
getAssistantList = async (locale: Locales): Promise<DiscoverAssistantItem[]> => {
|
53
|
-
|
54
|
-
next: { revalidate },
|
55
|
-
});
|
56
|
-
|
57
|
-
if (!res.ok) {
|
58
|
-
res = await fetch(this.assistantStore.getAgentIndexUrl(DEFAULT_LANG), {
|
59
|
-
next: { revalidate },
|
60
|
-
});
|
61
|
-
}
|
62
|
-
|
63
|
-
if (!res.ok) return [];
|
64
|
-
|
65
|
-
const json = await res.json();
|
53
|
+
const json = await this.assistantStore.getAgentIndex(locale, revalidate);
|
66
54
|
|
55
|
+
// @ts-expect-error 目前类型不一致,未来要统一
|
67
56
|
return json.agents;
|
68
57
|
};
|
69
58
|
|
package/src/types/discover.ts
CHANGED
@@ -154,3 +154,23 @@ export interface FilterBy {
|
|
154
154
|
token?: number;
|
155
155
|
vision?: boolean;
|
156
156
|
}
|
157
|
+
|
158
|
+
interface AgentIndexItem {
|
159
|
+
author: string;
|
160
|
+
createAt: string;
|
161
|
+
createdAt: string;
|
162
|
+
homepage: string;
|
163
|
+
identifier: string;
|
164
|
+
meta: {
|
165
|
+
avatar: string;
|
166
|
+
category: string;
|
167
|
+
description: string;
|
168
|
+
tags: string[];
|
169
|
+
title: string;
|
170
|
+
};
|
171
|
+
}
|
172
|
+
|
173
|
+
export interface AgentStoreIndex {
|
174
|
+
agents: AgentIndexItem[];
|
175
|
+
schemaVersion: number;
|
176
|
+
}
|
@@ -1,45 +0,0 @@
|
|
1
|
-
'use client';
|
2
|
-
|
3
|
-
import dynamic from 'next/dynamic';
|
4
|
-
import { memo } from 'react';
|
5
|
-
|
6
|
-
import { useQuery } from '@/hooks/useQuery';
|
7
|
-
import { SettingsTabs } from '@/store/global/initialState';
|
8
|
-
|
9
|
-
import Skeleton from './loading';
|
10
|
-
|
11
|
-
const loading = () => <Skeleton />;
|
12
|
-
|
13
|
-
const Common = dynamic(() => import('@/app/(main)/settings/common'), { loading, ssr: false });
|
14
|
-
const SystemAgent = dynamic(() => import('@/app/(main)/settings/system-agent'), {
|
15
|
-
loading,
|
16
|
-
ssr: false,
|
17
|
-
});
|
18
|
-
const About = dynamic(() => import('@/app/(main)/settings/about'), { loading, ssr: false });
|
19
|
-
const LLM = dynamic(() => import('@/app/(main)/settings/llm'), { loading, ssr: false });
|
20
|
-
const TTS = dynamic(() => import('@/app/(main)/settings/tts'), { loading, ssr: false });
|
21
|
-
const Agent = dynamic(() => import('@/app/(main)/settings/agent'), { loading, ssr: false });
|
22
|
-
const Sync = dynamic(() => import('@/app/(main)/settings/sync'), { loading, ssr: false });
|
23
|
-
|
24
|
-
interface SettingsModalProps {
|
25
|
-
browser?: string;
|
26
|
-
mobile?: boolean;
|
27
|
-
os?: string;
|
28
|
-
}
|
29
|
-
|
30
|
-
const SettingsModal = memo<SettingsModalProps>(({ browser, os, mobile }) => {
|
31
|
-
const { tab = SettingsTabs.Common } = useQuery();
|
32
|
-
return (
|
33
|
-
<>
|
34
|
-
{tab === SettingsTabs.Common && <Common />}
|
35
|
-
{tab === SettingsTabs.SystemAgent && <SystemAgent />}
|
36
|
-
{tab === SettingsTabs.Sync && <Sync browser={browser} mobile={mobile} os={os} />}
|
37
|
-
{tab === SettingsTabs.LLM && <LLM />}
|
38
|
-
{tab === SettingsTabs.TTS && <TTS />}
|
39
|
-
{tab === SettingsTabs.Agent && <Agent />}
|
40
|
-
{tab === SettingsTabs.About && <About mobile={mobile} />}
|
41
|
-
</>
|
42
|
-
);
|
43
|
-
});
|
44
|
-
|
45
|
-
export default SettingsModal;
|
@@ -1,47 +0,0 @@
|
|
1
|
-
'use client';
|
2
|
-
|
3
|
-
import { Skeleton, Tag } from 'antd';
|
4
|
-
import dynamic from 'next/dynamic';
|
5
|
-
import { PropsWithChildren, memo } from 'react';
|
6
|
-
import { useTranslation } from 'react-i18next';
|
7
|
-
|
8
|
-
import { useActiveSettingsKey } from '@/hooks/useActiveSettingsKey';
|
9
|
-
import { SettingsTabs } from '@/store/global/initialState';
|
10
|
-
|
11
|
-
import ModalLayout from '../../_layout/ModalLayout';
|
12
|
-
import SettingModalLayout from '../../_layout/SettingModalLayout';
|
13
|
-
|
14
|
-
const CategoryContent = dynamic(
|
15
|
-
() => import('@/app/(main)/settings/@category/features/CategoryContent'),
|
16
|
-
{ loading: () => <Skeleton paragraph={{ rows: 6 }} title={false} />, ssr: false },
|
17
|
-
);
|
18
|
-
const UpgradeAlert = dynamic(() => import('@/app/(main)/settings/features/UpgradeAlert'), {
|
19
|
-
ssr: false,
|
20
|
-
});
|
21
|
-
|
22
|
-
const Layout = memo<PropsWithChildren>(({ children }) => {
|
23
|
-
const { t } = useTranslation('setting');
|
24
|
-
const activeKey = useActiveSettingsKey();
|
25
|
-
return (
|
26
|
-
<ModalLayout>
|
27
|
-
<SettingModalLayout
|
28
|
-
activeTitle={
|
29
|
-
<>
|
30
|
-
{t(`tab.${activeKey}`)}
|
31
|
-
{activeKey === SettingsTabs.Sync && <Tag color={'gold'}>{t('tab.experiment')}</Tag>}
|
32
|
-
</>
|
33
|
-
}
|
34
|
-
category={
|
35
|
-
<>
|
36
|
-
<CategoryContent modal />
|
37
|
-
<UpgradeAlert />
|
38
|
-
</>
|
39
|
-
}
|
40
|
-
>
|
41
|
-
{children}
|
42
|
-
</SettingModalLayout>
|
43
|
-
</ModalLayout>
|
44
|
-
);
|
45
|
-
});
|
46
|
-
|
47
|
-
export default Layout;
|
@@ -1,19 +0,0 @@
|
|
1
|
-
import { gerServerDeviceInfo, isMobileDevice } from '@/utils/server/responsive';
|
2
|
-
|
3
|
-
import SettingsModal from './index';
|
4
|
-
|
5
|
-
/**
|
6
|
-
* @description: Settings Modal (intercepting route: /settings/modal )
|
7
|
-
* @refs: https://github.com/lobehub/lobe-chat/discussions/2295#discussioncomment-9290942
|
8
|
-
*/
|
9
|
-
|
10
|
-
const Page = async () => {
|
11
|
-
const isMobile = await isMobileDevice();
|
12
|
-
const { os, browser } = await gerServerDeviceInfo();
|
13
|
-
|
14
|
-
return <SettingsModal browser={browser} mobile={isMobile} os={os} />;
|
15
|
-
};
|
16
|
-
|
17
|
-
Page.displayName = 'SettingModal';
|
18
|
-
|
19
|
-
export default Page;
|