@lobehub/chat 0.124.2 → 0.125.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 +58 -0
- package/README.md +8 -8
- package/README.zh-CN.md +8 -8
- package/docs/Deployment/Authentication.md +72 -0
- package/docs/Deployment/Authentication.zh-CN.md +72 -0
- package/docs/Deployment/Environment-Variable.md +50 -0
- package/docs/Deployment/Environment-Variable.zh-CN.md +50 -0
- package/docs/Development/Authentication.md +85 -0
- package/docs/Development/Authentication.zh-CN.md +83 -0
- package/locales/ar/common.json +1 -0
- package/locales/ar/error.json +6 -0
- package/locales/ar/setting.json +16 -0
- package/locales/de-DE/common.json +1 -0
- package/locales/de-DE/error.json +6 -0
- package/locales/de-DE/setting.json +16 -0
- package/locales/en-US/common.json +1 -0
- package/locales/en-US/error.json +6 -0
- package/locales/en-US/setting.json +16 -0
- package/locales/es-ES/common.json +1 -0
- package/locales/es-ES/error.json +6 -0
- package/locales/es-ES/setting.json +16 -0
- package/locales/fr-FR/common.json +1 -0
- package/locales/fr-FR/error.json +6 -0
- package/locales/fr-FR/setting.json +16 -0
- package/locales/it-IT/common.json +1 -0
- package/locales/it-IT/error.json +6 -0
- package/locales/it-IT/setting.json +16 -0
- package/locales/ja-JP/common.json +1 -0
- package/locales/ja-JP/error.json +6 -0
- package/locales/ja-JP/setting.json +16 -0
- package/locales/ko-KR/common.json +1 -0
- package/locales/ko-KR/error.json +6 -0
- package/locales/ko-KR/setting.json +16 -0
- package/locales/nl-NL/common.json +1 -0
- package/locales/nl-NL/error.json +6 -0
- package/locales/nl-NL/setting.json +16 -0
- package/locales/pl-PL/common.json +1 -0
- package/locales/pl-PL/error.json +6 -0
- package/locales/pl-PL/setting.json +16 -0
- package/locales/pt-BR/common.json +1 -0
- package/locales/pt-BR/error.json +6 -0
- package/locales/pt-BR/setting.json +16 -0
- package/locales/ru-RU/common.json +1 -0
- package/locales/ru-RU/error.json +6 -0
- package/locales/ru-RU/setting.json +16 -0
- package/locales/tr-TR/common.json +1 -0
- package/locales/tr-TR/error.json +6 -0
- package/locales/tr-TR/setting.json +16 -0
- package/locales/vi-VN/common.json +1 -0
- package/locales/vi-VN/error.json +6 -0
- package/locales/vi-VN/setting.json +16 -0
- package/locales/zh-CN/common.json +1 -0
- package/locales/zh-CN/error.json +6 -0
- package/locales/zh-CN/setting.json +16 -0
- package/locales/zh-TW/common.json +1 -0
- package/locales/zh-TW/error.json +6 -0
- package/locales/zh-TW/setting.json +16 -0
- package/package.json +2 -1
- package/src/app/api/auth.ts +7 -1
- package/src/app/api/chat/[provider]/agentRuntime.ts +1 -1
- package/src/app/api/chat/[provider]/route.ts +5 -3
- package/src/app/api/chat/auth.ts +11 -3
- package/src/app/api/chat/google/route.ts +5 -3
- package/src/app/api/config/route.ts +9 -2
- package/src/app/api/oauth/[...nextauth]/route.ts +3 -0
- package/src/app/api/oauth/next-auth.ts +21 -0
- package/src/app/api/openai/createBizOpenAI/index.ts +3 -2
- package/src/app/api/plugin/gateway/route.ts +7 -4
- package/src/app/settings/common/Common.tsx +39 -1
- package/src/app/settings/common/page.tsx +2 -2
- package/src/app/settings/llm/Bedrock/index.tsx +1 -1
- package/src/app/settings/llm/Google/index.tsx +1 -1
- package/src/app/settings/llm/Moonshot/index.tsx +1 -1
- package/src/app/settings/llm/Zhipu/index.tsx +1 -1
- package/src/app/settings/page.tsx +2 -2
- package/src/chains/summaryTitle.ts +1 -1
- package/src/config/server/app.ts +5 -0
- package/src/const/auth.ts +2 -0
- package/src/const/fetch.ts +5 -1
- package/src/features/ChatInput/ActionBar/Clear.tsx +6 -8
- package/src/features/Conversation/Error/InvalidAccessCode.tsx +28 -10
- package/src/features/Conversation/Error/OAuthForm.tsx +95 -0
- package/src/layout/GlobalLayout/index.tsx +10 -7
- package/src/locales/default/common.ts +1 -0
- package/src/locales/default/error.ts +6 -0
- package/src/locales/default/setting.ts +16 -0
- package/src/middleware.ts +25 -0
- package/src/store/global/slices/common/initialState.ts +0 -1
- package/src/store/global/slices/common/selectors.ts +1 -0
- package/src/types/settings/index.ts +1 -0
- package/tsconfig.json +8 -8
|
@@ -263,6 +263,22 @@
|
|
|
263
263
|
"placeholder": "请输入访问密码",
|
|
264
264
|
"title": "访问密码"
|
|
265
265
|
},
|
|
266
|
+
"oauth": {
|
|
267
|
+
"info": {
|
|
268
|
+
"desc": "已登录",
|
|
269
|
+
"title": "账户信息"
|
|
270
|
+
},
|
|
271
|
+
"signin": {
|
|
272
|
+
"action": "登录",
|
|
273
|
+
"desc": "使用 SSO 登录以解锁应用",
|
|
274
|
+
"title": "登录账号"
|
|
275
|
+
},
|
|
276
|
+
"signout": {
|
|
277
|
+
"action": "退出登录",
|
|
278
|
+
"confirm": "确认退出?",
|
|
279
|
+
"success": "退出登录成功"
|
|
280
|
+
}
|
|
281
|
+
},
|
|
266
282
|
"title": "系统设置"
|
|
267
283
|
},
|
|
268
284
|
"settingTTS": {
|
package/locales/zh-TW/error.json
CHANGED
|
@@ -94,6 +94,12 @@
|
|
|
94
94
|
},
|
|
95
95
|
"closeMessage": "關閉提示",
|
|
96
96
|
"confirm": "確認並重試",
|
|
97
|
+
"oauth": {
|
|
98
|
+
"description": "管理員已開啟統一登錄認證,點擊下方按鈕登錄,即可解鎖應用",
|
|
99
|
+
"success": "登錄成功",
|
|
100
|
+
"title": "登錄帳號",
|
|
101
|
+
"welcome": "歡迎你!"
|
|
102
|
+
},
|
|
97
103
|
"password": {
|
|
98
104
|
"description": "管理員已啟用應用程式加密,輸入應用程式密碼後即可解鎖應用程式。密碼只需填寫一次。",
|
|
99
105
|
"placeholder": "請輸入密碼",
|
|
@@ -263,6 +263,22 @@
|
|
|
263
263
|
"placeholder": "請輸入訪問密碼",
|
|
264
264
|
"title": "訪問密碼"
|
|
265
265
|
},
|
|
266
|
+
"oauth": {
|
|
267
|
+
"info": {
|
|
268
|
+
"desc": "已登錄",
|
|
269
|
+
"title": "帳戶資訊"
|
|
270
|
+
},
|
|
271
|
+
"signin": {
|
|
272
|
+
"action": "登錄",
|
|
273
|
+
"desc": "使用 SSO 登錄以解鎖應用",
|
|
274
|
+
"title": "登錄帳號"
|
|
275
|
+
},
|
|
276
|
+
"signout": {
|
|
277
|
+
"action": "退出登錄",
|
|
278
|
+
"confirm": "確認退出?",
|
|
279
|
+
"success": "退出登錄成功"
|
|
280
|
+
}
|
|
281
|
+
},
|
|
266
282
|
"title": "系統設定"
|
|
267
283
|
},
|
|
268
284
|
"settingTTS": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobehub/chat",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.125.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",
|
|
@@ -105,6 +105,7 @@
|
|
|
105
105
|
"modern-screenshot": "^4",
|
|
106
106
|
"nanoid": "^5",
|
|
107
107
|
"next": "^14.1",
|
|
108
|
+
"next-auth": "beta",
|
|
108
109
|
"numeral": "^2.0.6",
|
|
109
110
|
"nuqs": "^1.15.4",
|
|
110
111
|
"openai": "^4.22",
|
package/src/app/api/auth.ts
CHANGED
|
@@ -4,9 +4,15 @@ import { ChatErrorType } from '@/types/fetch';
|
|
|
4
4
|
interface AuthConfig {
|
|
5
5
|
accessCode?: string | null;
|
|
6
6
|
apiKey?: string | null;
|
|
7
|
+
oauthAuthorized?: boolean;
|
|
7
8
|
}
|
|
8
9
|
|
|
9
|
-
export const checkAuth = ({ apiKey, accessCode }: AuthConfig) => {
|
|
10
|
+
export const checkAuth = ({ apiKey, accessCode, oauthAuthorized }: AuthConfig) => {
|
|
11
|
+
// If authorized by oauth
|
|
12
|
+
if (oauthAuthorized) {
|
|
13
|
+
return { auth: true };
|
|
14
|
+
}
|
|
15
|
+
|
|
10
16
|
const { ACCESS_CODES } = getServerConfig();
|
|
11
17
|
|
|
12
18
|
// if apiKey exist
|
|
@@ -83,7 +83,7 @@ class AgentRuntime {
|
|
|
83
83
|
const apiVersion = azureOpenAI?.apiVersion || AZURE_API_VERSION;
|
|
84
84
|
|
|
85
85
|
return new LobeOpenAI({
|
|
86
|
-
apiKey:
|
|
86
|
+
apiKey: useAzure ? azureApiKey : apiKey,
|
|
87
87
|
azureOptions: {
|
|
88
88
|
apiVersion,
|
|
89
89
|
model: azureOpenAI?.model,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getPreferredRegion } from '@/app/api/config';
|
|
2
2
|
import { createErrorResponse } from '@/app/api/errorResponse';
|
|
3
|
-
import { LOBE_CHAT_AUTH_HEADER } from '@/const/auth';
|
|
3
|
+
import { LOBE_CHAT_AUTH_HEADER, OAUTH_AUTHORIZED } from '@/const/auth';
|
|
4
4
|
import {
|
|
5
5
|
AgentInitErrorPayload,
|
|
6
6
|
AgentRuntimeError,
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
import { ChatErrorType } from '@/types/fetch';
|
|
11
11
|
import { ChatStreamPayload } from '@/types/openai/chat';
|
|
12
12
|
|
|
13
|
-
import {
|
|
13
|
+
import { checkAuthMethod, getJWTPayload } from '../auth';
|
|
14
14
|
import AgentRuntime from './agentRuntime';
|
|
15
15
|
|
|
16
16
|
export const runtime = 'edge';
|
|
@@ -25,11 +25,13 @@ export const POST = async (req: Request, { params }: { params: { provider: strin
|
|
|
25
25
|
try {
|
|
26
26
|
// get Authorization from header
|
|
27
27
|
const authorization = req.headers.get(LOBE_CHAT_AUTH_HEADER);
|
|
28
|
+
const oauthAuthorized = !!req.headers.get(OAUTH_AUTHORIZED);
|
|
29
|
+
|
|
28
30
|
if (!authorization) throw AgentRuntimeError.createError(ChatErrorType.Unauthorized);
|
|
29
31
|
|
|
30
32
|
// check the Auth With payload
|
|
31
33
|
const payload = await getJWTPayload(authorization);
|
|
32
|
-
|
|
34
|
+
checkAuthMethod(payload.accessCode, payload.apiKey, oauthAuthorized);
|
|
33
35
|
|
|
34
36
|
const body = await req.clone().json();
|
|
35
37
|
agentRuntime = await AgentRuntime.initializeWithUserPayload(params.provider, payload, {
|
package/src/app/api/chat/auth.ts
CHANGED
|
@@ -31,14 +31,22 @@ export const getJWTPayload = async (token: string): Promise<JWTPayload> => {
|
|
|
31
31
|
};
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
|
-
* Check if the provided access code is valid
|
|
34
|
+
* Check if the provided access code is valid, a user API key should be used or the OAuth 2 header is provided.
|
|
35
35
|
*
|
|
36
36
|
* @param {string} accessCode - The access code to check.
|
|
37
37
|
* @param {string} apiKey - The user API key.
|
|
38
|
+
* @param {boolean} oauthAuthorized - Whether the OAuth 2 header is provided.
|
|
38
39
|
* @throws {AgentRuntimeError} If the access code is invalid and no user API key is provided.
|
|
39
40
|
*/
|
|
40
|
-
export const
|
|
41
|
-
|
|
41
|
+
export const checkAuthMethod = (
|
|
42
|
+
accessCode?: string,
|
|
43
|
+
apiKey?: string,
|
|
44
|
+
oauthAuthorized?: boolean,
|
|
45
|
+
) => {
|
|
46
|
+
const { ACCESS_CODES, ENABLE_OAUTH_SSO } = getServerConfig();
|
|
47
|
+
|
|
48
|
+
// if OAuth 2 header is provided
|
|
49
|
+
if (ENABLE_OAUTH_SSO && oauthAuthorized) return;
|
|
42
50
|
|
|
43
51
|
// if apiKey exist
|
|
44
52
|
if (apiKey) return;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createErrorResponse } from '@/app/api/errorResponse';
|
|
2
2
|
import { getServerConfig } from '@/config/server';
|
|
3
|
-
import { LOBE_CHAT_AUTH_HEADER } from '@/const/auth';
|
|
3
|
+
import { LOBE_CHAT_AUTH_HEADER, OAUTH_AUTHORIZED } from '@/const/auth';
|
|
4
4
|
import {
|
|
5
5
|
AgentInitErrorPayload,
|
|
6
6
|
AgentRuntimeError,
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
import { ChatErrorType } from '@/types/fetch';
|
|
12
12
|
import { ChatStreamPayload } from '@/types/openai/chat';
|
|
13
13
|
|
|
14
|
-
import {
|
|
14
|
+
import { checkAuthMethod, getJWTPayload } from '../auth';
|
|
15
15
|
|
|
16
16
|
// due to the Chinese region does not support accessing Google
|
|
17
17
|
// we need to use proxy to access it
|
|
@@ -49,11 +49,13 @@ export const POST = async (req: Request) => {
|
|
|
49
49
|
try {
|
|
50
50
|
// get Authorization from header
|
|
51
51
|
const authorization = req.headers.get(LOBE_CHAT_AUTH_HEADER);
|
|
52
|
+
const oauthAuthorized = !!req.headers.get(OAUTH_AUTHORIZED);
|
|
53
|
+
|
|
52
54
|
if (!authorization) throw AgentRuntimeError.createError(ChatErrorType.Unauthorized);
|
|
53
55
|
|
|
54
56
|
// check the Auth With payload
|
|
55
57
|
const payload = await getJWTPayload(authorization);
|
|
56
|
-
|
|
58
|
+
checkAuthMethod(payload.accessCode, payload.apiKey, oauthAuthorized);
|
|
57
59
|
|
|
58
60
|
const { GOOGLE_API_KEY } = getServerConfig();
|
|
59
61
|
const apiKey = payload?.apiKey || GOOGLE_API_KEY;
|
|
@@ -7,11 +7,18 @@ export const runtime = 'edge';
|
|
|
7
7
|
* get Server config to client
|
|
8
8
|
*/
|
|
9
9
|
export const GET = async () => {
|
|
10
|
-
const {
|
|
11
|
-
|
|
10
|
+
const {
|
|
11
|
+
CUSTOM_MODELS,
|
|
12
|
+
ENABLED_MOONSHOT,
|
|
13
|
+
ENABLED_ZHIPU,
|
|
14
|
+
ENABLED_AWS_BEDROCK,
|
|
15
|
+
ENABLED_GOOGLE,
|
|
16
|
+
ENABLE_OAUTH_SSO,
|
|
17
|
+
} = getServerConfig();
|
|
12
18
|
|
|
13
19
|
const config: GlobalServerConfig = {
|
|
14
20
|
customModelName: CUSTOM_MODELS,
|
|
21
|
+
enabledOAuthSSO: ENABLE_OAUTH_SSO,
|
|
15
22
|
languageModel: {
|
|
16
23
|
bedrock: { enabled: ENABLED_AWS_BEDROCK },
|
|
17
24
|
google: { enabled: ENABLED_GOOGLE },
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import NextAuth from 'next-auth';
|
|
2
|
+
import Auth0 from 'next-auth/providers/auth0';
|
|
3
|
+
|
|
4
|
+
import { getServerConfig } from '@/config/server';
|
|
5
|
+
|
|
6
|
+
const { AUTH0_CLIENT_ID, AUTH0_CLIENT_SECRET, AUTH0_ISSUER } = getServerConfig();
|
|
7
|
+
|
|
8
|
+
const nextAuth = NextAuth({
|
|
9
|
+
providers: [
|
|
10
|
+
Auth0({
|
|
11
|
+
clientId: AUTH0_CLIENT_ID,
|
|
12
|
+
clientSecret: AUTH0_CLIENT_SECRET,
|
|
13
|
+
issuer: AUTH0_ISSUER,
|
|
14
|
+
}),
|
|
15
|
+
],
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
export const {
|
|
19
|
+
handlers: { GET, POST },
|
|
20
|
+
auth,
|
|
21
|
+
} = nextAuth;
|
|
@@ -14,9 +14,10 @@ import { createOpenai } from './createOpenai';
|
|
|
14
14
|
* if auth not pass ,just return error response
|
|
15
15
|
*/
|
|
16
16
|
export const createBizOpenAI = (req: Request, model: string): Response | OpenAI => {
|
|
17
|
-
const { apiKey, accessCode, endpoint, useAzure, apiVersion } =
|
|
17
|
+
const { apiKey, accessCode, endpoint, useAzure, apiVersion, oauthAuthorized } =
|
|
18
|
+
getOpenAIAuthFromRequest(req);
|
|
18
19
|
|
|
19
|
-
const result = checkAuth({ accessCode, apiKey });
|
|
20
|
+
const result = checkAuth({ accessCode, apiKey, oauthAuthorized });
|
|
20
21
|
|
|
21
22
|
if (!result.auth) {
|
|
22
23
|
return createErrorResponse(result.error as ErrorType);
|
|
@@ -7,12 +7,15 @@ import { ChatErrorType, ErrorType } from '@/types/fetch';
|
|
|
7
7
|
|
|
8
8
|
import { parserPluginSettings } from './settings';
|
|
9
9
|
|
|
10
|
-
const checkAuth = (accessCode: string | null) => {
|
|
11
|
-
const { ACCESS_CODES, PLUGIN_SETTINGS } = getServerConfig();
|
|
10
|
+
const checkAuth = (accessCode: string | null, oauthAuthorized: boolean | null) => {
|
|
11
|
+
const { ACCESS_CODES, PLUGIN_SETTINGS, ENABLE_OAUTH_SSO } = getServerConfig();
|
|
12
12
|
|
|
13
13
|
// if there is no plugin settings, just skip the auth
|
|
14
14
|
if (!PLUGIN_SETTINGS) return { auth: true };
|
|
15
15
|
|
|
16
|
+
// If authorized by oauth
|
|
17
|
+
if (oauthAuthorized && ENABLE_OAUTH_SSO) return { auth: true };
|
|
18
|
+
|
|
16
19
|
// if accessCode doesn't exist
|
|
17
20
|
if (!ACCESS_CODES.length) return { auth: true };
|
|
18
21
|
|
|
@@ -30,9 +33,9 @@ const defaultPluginSettings = parserPluginSettings(PLUGIN_SETTINGS);
|
|
|
30
33
|
const handler = createGatewayOnEdgeRuntime({ defaultPluginSettings, pluginsIndexUrl });
|
|
31
34
|
|
|
32
35
|
export const POST = async (req: Request) => {
|
|
33
|
-
const { accessCode } = getOpenAIAuthFromRequest(req);
|
|
36
|
+
const { accessCode, oauthAuthorized } = getOpenAIAuthFromRequest(req);
|
|
34
37
|
|
|
35
|
-
const result = checkAuth(accessCode);
|
|
38
|
+
const result = checkAuth(accessCode, oauthAuthorized);
|
|
36
39
|
|
|
37
40
|
if (!result.auth) {
|
|
38
41
|
return createErrorResponse(result.error as ErrorType);
|
|
@@ -2,6 +2,7 @@ import { Form, type ItemGroup, SelectWithImg, SliderWithInput } from '@lobehub/u
|
|
|
2
2
|
import { Form as AntForm, App, Button, Input, Select } from 'antd';
|
|
3
3
|
import isEqual from 'fast-deep-equal';
|
|
4
4
|
import { AppWindow, Monitor, Moon, Palette, Sun } from 'lucide-react';
|
|
5
|
+
import { signIn, signOut, useSession } from 'next-auth/react';
|
|
5
6
|
import { memo, useCallback, useEffect } from 'react';
|
|
6
7
|
import { useTranslation } from 'react-i18next';
|
|
7
8
|
|
|
@@ -23,12 +24,16 @@ type SettingItemGroup = ItemGroup;
|
|
|
23
24
|
|
|
24
25
|
export interface SettingsCommonProps {
|
|
25
26
|
showAccessCodeConfig: boolean;
|
|
27
|
+
showOAuthLogin: boolean;
|
|
26
28
|
}
|
|
27
29
|
|
|
28
|
-
const Common = memo<SettingsCommonProps>(({ showAccessCodeConfig }) => {
|
|
30
|
+
const Common = memo<SettingsCommonProps>(({ showAccessCodeConfig, showOAuthLogin }) => {
|
|
29
31
|
const { t } = useTranslation('setting');
|
|
30
32
|
const [form] = AntForm.useForm();
|
|
31
33
|
|
|
34
|
+
const { data: session, status } = useSession();
|
|
35
|
+
const isOAuthLoggedIn = status === 'authenticated' && session && session.user;
|
|
36
|
+
|
|
32
37
|
const [clearSessions, clearSessionGroups] = useSessionStore((s) => [
|
|
33
38
|
s.clearSessions,
|
|
34
39
|
s.clearSessionGroups,
|
|
@@ -49,6 +54,22 @@ const Common = memo<SettingsCommonProps>(({ showAccessCodeConfig }) => {
|
|
|
49
54
|
|
|
50
55
|
const { message, modal } = App.useApp();
|
|
51
56
|
|
|
57
|
+
const handleSignOut = useCallback(() => {
|
|
58
|
+
modal.confirm({
|
|
59
|
+
centered: true,
|
|
60
|
+
okButtonProps: { danger: true },
|
|
61
|
+
onOk: () => {
|
|
62
|
+
signOut();
|
|
63
|
+
message.success(t('settingSystem.oauth.signout.success'));
|
|
64
|
+
},
|
|
65
|
+
title: t('settingSystem.oauth.signout.confirm'),
|
|
66
|
+
});
|
|
67
|
+
}, []);
|
|
68
|
+
|
|
69
|
+
const handleSignIn = useCallback(() => {
|
|
70
|
+
signIn('auth0');
|
|
71
|
+
}, []);
|
|
72
|
+
|
|
52
73
|
const handleReset = useCallback(() => {
|
|
53
74
|
modal.confirm({
|
|
54
75
|
centered: true,
|
|
@@ -196,6 +217,23 @@ const Common = memo<SettingsCommonProps>(({ showAccessCodeConfig }) => {
|
|
|
196
217
|
label: t('settingSystem.accessCode.title'),
|
|
197
218
|
name: 'password',
|
|
198
219
|
},
|
|
220
|
+
{
|
|
221
|
+
children: isOAuthLoggedIn ? (
|
|
222
|
+
<Button onClick={handleSignOut}>{t('settingSystem.oauth.signout.action')}</Button>
|
|
223
|
+
) : (
|
|
224
|
+
<Button onClick={handleSignIn} type="primary">
|
|
225
|
+
{t('settingSystem.oauth.signin.action')}
|
|
226
|
+
</Button>
|
|
227
|
+
),
|
|
228
|
+
desc: isOAuthLoggedIn
|
|
229
|
+
? `${session.user?.email} ${t('settingSystem.oauth.info.desc')}`
|
|
230
|
+
: t('settingSystem.oauth.signin.desc'),
|
|
231
|
+
hidden: !showOAuthLogin,
|
|
232
|
+
label: isOAuthLoggedIn
|
|
233
|
+
? t('settingSystem.oauth.info.title')
|
|
234
|
+
: t('settingSystem.oauth.signin.title'),
|
|
235
|
+
minWidth: undefined,
|
|
236
|
+
},
|
|
199
237
|
{
|
|
200
238
|
children: (
|
|
201
239
|
<Button danger onClick={handleReset} type="primary">
|
|
@@ -3,7 +3,7 @@ import { getServerConfig } from '@/config/server';
|
|
|
3
3
|
import Index from './index';
|
|
4
4
|
|
|
5
5
|
export default () => {
|
|
6
|
-
const { SHOW_ACCESS_CODE_CONFIG } = getServerConfig();
|
|
6
|
+
const { SHOW_ACCESS_CODE_CONFIG, ENABLE_OAUTH_SSO } = getServerConfig();
|
|
7
7
|
|
|
8
|
-
return <Index showAccessCodeConfig={SHOW_ACCESS_CODE_CONFIG} />;
|
|
8
|
+
return <Index showAccessCodeConfig={SHOW_ACCESS_CODE_CONFIG} showOAuthLogin={ENABLE_OAUTH_SSO} />;
|
|
9
9
|
};
|
|
@@ -47,7 +47,7 @@ const GoogleProvider = memo(() => {
|
|
|
47
47
|
children: <Checker model={'gemini-pro'} provider={ModelProvider.Google} />,
|
|
48
48
|
desc: t('llm.checker.desc'),
|
|
49
49
|
label: t('llm.checker.title'),
|
|
50
|
-
minWidth:
|
|
50
|
+
minWidth: '100%',
|
|
51
51
|
},
|
|
52
52
|
],
|
|
53
53
|
defaultActive: enabled,
|
|
@@ -47,7 +47,7 @@ const MoonshotProvider = memo(() => {
|
|
|
47
47
|
children: <Checker model={'moonshot-v1-8k'} provider={ModelProvider.Moonshot} />,
|
|
48
48
|
desc: t('llm.checker.desc'),
|
|
49
49
|
label: t('llm.checker.title'),
|
|
50
|
-
minWidth:
|
|
50
|
+
minWidth: '100%',
|
|
51
51
|
},
|
|
52
52
|
],
|
|
53
53
|
defaultActive: enabled,
|
|
@@ -48,7 +48,7 @@ const ZhipuProvider = memo(() => {
|
|
|
48
48
|
children: <Checker model={'glm-3-turbo'} provider={ModelProvider.ZhiPu} />,
|
|
49
49
|
desc: t('llm.checker.desc'),
|
|
50
50
|
label: t('llm.checker.title'),
|
|
51
|
-
minWidth:
|
|
51
|
+
minWidth: '100%',
|
|
52
52
|
},
|
|
53
53
|
],
|
|
54
54
|
defaultActive: enabled,
|
|
@@ -9,9 +9,9 @@ const Page = () => {
|
|
|
9
9
|
|
|
10
10
|
const Page = mobile ? MobilePage : DesktopPage;
|
|
11
11
|
|
|
12
|
-
const { SHOW_ACCESS_CODE_CONFIG } = getServerConfig();
|
|
12
|
+
const { SHOW_ACCESS_CODE_CONFIG, ENABLE_OAUTH_SSO } = getServerConfig();
|
|
13
13
|
|
|
14
|
-
return <Page showAccessCodeConfig={SHOW_ACCESS_CODE_CONFIG} />;
|
|
14
|
+
return <Page showAccessCodeConfig={SHOW_ACCESS_CODE_CONFIG} showOAuthLogin={ENABLE_OAUTH_SSO} />;
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
export default Page;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { chatHelpers } from '@/store/chat/helpers';
|
|
2
2
|
import { globalHelpers } from '@/store/global/helpers';
|
|
3
3
|
import { LanguageModel } from '@/types/llm';
|
|
4
|
-
import {
|
|
4
|
+
import { ChatStreamPayload, OpenAIChatMessage } from '@/types/openai/chat';
|
|
5
5
|
|
|
6
6
|
export const chainSummaryTitle = async (
|
|
7
7
|
messages: OpenAIChatMessage[],
|
package/src/config/server/app.ts
CHANGED
|
@@ -47,5 +47,10 @@ export const getAppConfig = () => {
|
|
|
47
47
|
: 'https://chat-plugins.lobehub.com',
|
|
48
48
|
|
|
49
49
|
PLUGIN_SETTINGS: process.env.PLUGIN_SETTINGS,
|
|
50
|
+
|
|
51
|
+
ENABLE_OAUTH_SSO: !!process.env.ENABLE_OAUTH_SSO,
|
|
52
|
+
AUTH0_CLIENT_ID: process.env.AUTH0_CLIENT_ID || '',
|
|
53
|
+
AUTH0_CLIENT_SECRET: process.env.AUTH0_CLIENT_SECRET || '',
|
|
54
|
+
AUTH0_ISSUER: process.env.AUTH0_ISSUER || '',
|
|
50
55
|
};
|
|
51
56
|
};
|
package/src/const/auth.ts
CHANGED
package/src/const/fetch.ts
CHANGED
|
@@ -7,6 +7,8 @@ export const AZURE_OPENAI_API_VERSION = 'X-azure-openai-api-version';
|
|
|
7
7
|
|
|
8
8
|
export const LOBE_CHAT_ACCESS_CODE = 'X-lobe-chat-access-code';
|
|
9
9
|
|
|
10
|
+
export const OAUTH_AUTHORIZED = 'X-oauth-authorized';
|
|
11
|
+
|
|
10
12
|
/**
|
|
11
13
|
* @deprecated
|
|
12
14
|
*/
|
|
@@ -16,8 +18,10 @@ export const getOpenAIAuthFromRequest = (req: Request) => {
|
|
|
16
18
|
const accessCode = req.headers.get(LOBE_CHAT_ACCESS_CODE);
|
|
17
19
|
const useAzureStr = req.headers.get(USE_AZURE_OPENAI);
|
|
18
20
|
const apiVersion = req.headers.get(AZURE_OPENAI_API_VERSION);
|
|
21
|
+
const oauthAuthorizedStr = req.headers.get(OAUTH_AUTHORIZED);
|
|
19
22
|
|
|
23
|
+
const oauthAuthorized = !!oauthAuthorizedStr;
|
|
20
24
|
const useAzure = !!useAzureStr;
|
|
21
25
|
|
|
22
|
-
return { accessCode, apiKey, apiVersion, endpoint, useAzure };
|
|
26
|
+
return { accessCode, apiKey, apiVersion, endpoint, oauthAuthorized, useAzure };
|
|
23
27
|
};
|
|
@@ -21,9 +21,11 @@ const Clear = memo(() => {
|
|
|
21
21
|
clearImageList();
|
|
22
22
|
}, []);
|
|
23
23
|
|
|
24
|
-
const actionTitle: any = confirmOpened
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
const actionTitle: any = confirmOpened ? (
|
|
25
|
+
void 0
|
|
26
|
+
) : (
|
|
27
|
+
<HotKeys desc={t('clearCurrentMessages', { ns: 'chat' })} keys={hotkeys} />
|
|
28
|
+
);
|
|
27
29
|
|
|
28
30
|
return (
|
|
29
31
|
<Popconfirm
|
|
@@ -35,11 +37,7 @@ const Clear = memo(() => {
|
|
|
35
37
|
placement={'topRight'}
|
|
36
38
|
title={t('confirmClearCurrentMessages', { ns: 'chat' })}
|
|
37
39
|
>
|
|
38
|
-
<ActionIcon
|
|
39
|
-
icon={Eraser}
|
|
40
|
-
placement={'bottom'}
|
|
41
|
-
title={actionTitle}
|
|
42
|
-
/>
|
|
40
|
+
<ActionIcon icon={Eraser} placement={'bottom'} title={actionTitle} />
|
|
43
41
|
</Popconfirm>
|
|
44
42
|
);
|
|
45
43
|
});
|
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
import { Icon } from '@lobehub/ui';
|
|
2
2
|
import { Segmented } from 'antd';
|
|
3
|
-
import {
|
|
3
|
+
import { SegmentedLabeledOption } from 'antd/es/segmented';
|
|
4
|
+
import { AsteriskSquare, KeySquare, ScanFace } from 'lucide-react';
|
|
4
5
|
import { memo, useState } from 'react';
|
|
5
6
|
import { useTranslation } from 'react-i18next';
|
|
6
7
|
import { Flexbox } from 'react-layout-kit';
|
|
7
8
|
|
|
9
|
+
import { useGlobalStore } from '@/store/global';
|
|
10
|
+
import { commonSelectors } from '@/store/global/selectors';
|
|
11
|
+
|
|
8
12
|
import APIKeyForm from './APIKeyForm';
|
|
9
13
|
import AccessCodeForm from './AccessCodeForm';
|
|
14
|
+
import OAuthForm from './OAuthForm';
|
|
10
15
|
import { ErrorActionContainer } from './style';
|
|
11
16
|
|
|
12
17
|
enum Tab {
|
|
13
18
|
Api = 'api',
|
|
19
|
+
Oauth = 'oauth',
|
|
14
20
|
Password = 'password',
|
|
15
21
|
}
|
|
16
22
|
|
|
@@ -21,27 +27,39 @@ interface InvalidAccessCodeProps {
|
|
|
21
27
|
|
|
22
28
|
const InvalidAccessCode = memo<InvalidAccessCodeProps>(({ id, provider }) => {
|
|
23
29
|
const { t } = useTranslation('error');
|
|
24
|
-
const
|
|
30
|
+
const isEnabledOAuth = useGlobalStore(commonSelectors.enabledOAuthSSO);
|
|
31
|
+
const defaultTab = isEnabledOAuth ? Tab.Oauth : Tab.Password;
|
|
32
|
+
const [mode, setMode] = useState<Tab>(defaultTab);
|
|
25
33
|
|
|
26
34
|
return (
|
|
27
35
|
<ErrorActionContainer>
|
|
28
36
|
<Segmented
|
|
29
37
|
block
|
|
30
38
|
onChange={(value) => setMode(value as Tab)}
|
|
31
|
-
options={
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
+
options={
|
|
40
|
+
[
|
|
41
|
+
isEnabledOAuth
|
|
42
|
+
? {
|
|
43
|
+
icon: <Icon icon={ScanFace} />,
|
|
44
|
+
label: t('oauth', { ns: 'common' }),
|
|
45
|
+
value: Tab.Oauth,
|
|
46
|
+
}
|
|
47
|
+
: undefined,
|
|
48
|
+
{
|
|
49
|
+
icon: <Icon icon={AsteriskSquare} />,
|
|
50
|
+
label: t('unlock.tabs.password'),
|
|
51
|
+
value: Tab.Password,
|
|
52
|
+
},
|
|
53
|
+
{ icon: <Icon icon={KeySquare} />, label: t('unlock.tabs.apiKey'), value: Tab.Api },
|
|
54
|
+
].filter(Boolean) as SegmentedLabeledOption[]
|
|
55
|
+
}
|
|
39
56
|
style={{ width: '100%' }}
|
|
40
57
|
value={mode}
|
|
41
58
|
/>
|
|
42
59
|
<Flexbox gap={24}>
|
|
43
60
|
{mode === Tab.Password && <AccessCodeForm id={id} />}
|
|
44
61
|
{mode === Tab.Api && <APIKeyForm id={id} provider={provider} />}
|
|
62
|
+
{isEnabledOAuth && mode === Tab.Oauth && <OAuthForm id={id} />}
|
|
45
63
|
</Flexbox>
|
|
46
64
|
</ErrorActionContainer>
|
|
47
65
|
);
|