@lobehub/chat 0.72.4 → 0.74.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/.github/workflows/contributor-help.yml +1 -1
- package/.github/workflows/sync.yml +43 -0
- package/.github/workflows/wiki-sync.yml +1 -0
- package/CHANGELOG.md +51 -0
- package/README.md +14 -1
- package/{README-zh_CN.md → README.zh-CN.md} +13 -0
- package/docs/Home.md +6 -0
- package/docs/Upstream-Sync.en-US.md +16 -0
- package/docs/Upstream-Sync.zh-CN.md +16 -0
- package/docs/_Sidebar.md +6 -0
- package/locales/en_US/setting.json +60 -1
- package/locales/ru_RU/setting.json +60 -1
- package/next.config.mjs +6 -2
- package/package.json +5 -10
- package/src/config/server.ts +2 -0
- package/src/const/fetch.ts +15 -2
- package/src/const/llm.ts +17 -0
- package/src/const/settings.ts +16 -8
- package/src/locales/default/setting.ts +59 -13
- package/src/pages/api/{openai.ts → createChatCompletion.ts} +7 -36
- package/src/pages/api/openai/chat.api.ts +43 -0
- package/src/pages/api/openai/createAzureOpenai.ts +26 -0
- package/src/pages/api/openai/createOpenai.ts +17 -0
- package/src/pages/api/openai/models.api.ts +17 -0
- package/src/pages/settings/features/Settings/Common.tsx +2 -26
- package/src/pages/settings/features/Settings/LLM/Checker.tsx +85 -0
- package/src/pages/settings/features/Settings/LLM/ModelList.tsx +51 -0
- package/src/pages/settings/features/Settings/LLM/getModelList.ts +9 -0
- package/src/pages/settings/features/Settings/LLM/index.tsx +152 -0
- package/src/pages/settings/features/Settings/index.tsx +13 -12
- package/src/services/_header.ts +33 -0
- package/src/services/_url.ts +17 -0
- package/src/services/chatModel.ts +4 -10
- package/src/services/modelList.ts +15 -0
- package/src/services/plugin.ts +1 -1
- package/src/services/pluginMarket.ts +1 -1
- package/src/store/global/hooks/index.ts +1 -0
- package/src/store/global/hooks/useEffectAfterHydrated.ts +22 -0
- package/src/store/global/initialState.ts +4 -0
- package/src/store/global/selectors.test.ts +264 -0
- package/src/store/global/selectors.ts +4 -5
- package/src/store/global/slices/settings.ts +16 -4
- package/src/store/global/store.ts +34 -5
- package/src/types/settings.ts +45 -8
- package/src/utils/fetch.ts +4 -12
- package/src/pages/api/openai.api.ts +0 -24
- package/src/services/url.ts +0 -8
|
@@ -1,46 +1,17 @@
|
|
|
1
1
|
import { OpenAIStream, StreamingTextResponse } from 'ai';
|
|
2
|
-
import OpenAI
|
|
2
|
+
import OpenAI from 'openai';
|
|
3
3
|
|
|
4
|
-
import { getServerConfig } from '@/config/server';
|
|
5
4
|
import { createErrorResponse } from '@/pages/api/error';
|
|
6
5
|
import { ChatErrorType } from '@/types/fetch';
|
|
7
6
|
import { OpenAIStreamPayload } from '@/types/openai';
|
|
8
7
|
|
|
9
|
-
// 创建 OpenAI 实例
|
|
10
|
-
export const createOpenAI = (userApiKey: string | null, endpoint?: string | null) => {
|
|
11
|
-
const { OPENAI_API_KEY, OPENAI_PROXY_URL } = getServerConfig();
|
|
12
|
-
|
|
13
|
-
const baseURL = endpoint ? endpoint : OPENAI_PROXY_URL ? OPENAI_PROXY_URL : undefined;
|
|
14
|
-
|
|
15
|
-
const config: ClientOptions = {
|
|
16
|
-
apiKey: !userApiKey ? OPENAI_API_KEY : userApiKey,
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
// a bug with openai: https://github.com/openai/openai-node/issues/283
|
|
20
|
-
// TODO: should refactor when openai fix the bug
|
|
21
|
-
if (baseURL) {
|
|
22
|
-
config.baseURL = baseURL;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
return new OpenAI(config);
|
|
26
|
-
};
|
|
27
|
-
|
|
28
8
|
interface CreateChatCompletionOptions {
|
|
29
|
-
|
|
30
|
-
endpoint?: string | null;
|
|
9
|
+
openai: OpenAI;
|
|
31
10
|
payload: OpenAIStreamPayload;
|
|
32
11
|
}
|
|
33
12
|
|
|
34
|
-
export const createChatCompletion = async ({
|
|
35
|
-
|
|
36
|
-
OPENAI_API_KEY,
|
|
37
|
-
endpoint,
|
|
38
|
-
}: CreateChatCompletionOptions) => {
|
|
39
|
-
// ============ 0.创建 OpenAI 实例 ============ //
|
|
40
|
-
|
|
41
|
-
const openai = createOpenAI(OPENAI_API_KEY, endpoint);
|
|
42
|
-
|
|
43
|
-
// ============ 1. 前置处理 messages ============ //
|
|
13
|
+
export const createChatCompletion = async ({ payload, openai }: CreateChatCompletionOptions) => {
|
|
14
|
+
// ============ 1. preprocess messages ============ //
|
|
44
15
|
const { messages, ...params } = payload;
|
|
45
16
|
|
|
46
17
|
const formatMessages = messages.map((m) => ({
|
|
@@ -49,7 +20,7 @@ export const createChatCompletion = async ({
|
|
|
49
20
|
role: m.role,
|
|
50
21
|
}));
|
|
51
22
|
|
|
52
|
-
// ============ 2.
|
|
23
|
+
// ============ 2. send api ============ //
|
|
53
24
|
|
|
54
25
|
try {
|
|
55
26
|
const response = await openai.chat.completions.create({
|
|
@@ -63,7 +34,7 @@ export const createChatCompletion = async ({
|
|
|
63
34
|
// Check if the error is an OpenAI APIError
|
|
64
35
|
if (error instanceof OpenAI.APIError) {
|
|
65
36
|
return createErrorResponse(ChatErrorType.OpenAIBizError, {
|
|
66
|
-
endpoint:
|
|
37
|
+
endpoint: openai.baseURL,
|
|
67
38
|
error: error.error ?? error.cause,
|
|
68
39
|
});
|
|
69
40
|
}
|
|
@@ -73,7 +44,7 @@ export const createChatCompletion = async ({
|
|
|
73
44
|
|
|
74
45
|
// return as a GatewayTimeout error
|
|
75
46
|
return createErrorResponse(ChatErrorType.InternalServerError, {
|
|
76
|
-
endpoint,
|
|
47
|
+
endpoint: openai.baseURL,
|
|
77
48
|
error: JSON.stringify(error),
|
|
78
49
|
});
|
|
79
50
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import OpenAI from 'openai';
|
|
2
|
+
|
|
3
|
+
import { getOpenAIAuthFromRequest } from '@/const/fetch';
|
|
4
|
+
import { ChatErrorType, ErrorType } from '@/types/fetch';
|
|
5
|
+
import { OpenAIStreamPayload } from '@/types/openai';
|
|
6
|
+
|
|
7
|
+
import { checkAuth } from '../auth';
|
|
8
|
+
import { createChatCompletion } from '../createChatCompletion';
|
|
9
|
+
import { createErrorResponse } from '../error';
|
|
10
|
+
import { createAzureOpenai } from './createAzureOpenai';
|
|
11
|
+
import { createOpenai } from './createOpenai';
|
|
12
|
+
|
|
13
|
+
export const runtime = 'edge';
|
|
14
|
+
|
|
15
|
+
export default async function handler(req: Request) {
|
|
16
|
+
const payload = (await req.json()) as OpenAIStreamPayload;
|
|
17
|
+
|
|
18
|
+
const { apiKey, accessCode, endpoint, useAzure, apiVersion } = getOpenAIAuthFromRequest(req);
|
|
19
|
+
|
|
20
|
+
const result = checkAuth({ accessCode, apiKey });
|
|
21
|
+
|
|
22
|
+
if (!result.auth) {
|
|
23
|
+
return createErrorResponse(result.error as ErrorType);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let openai: OpenAI;
|
|
27
|
+
if (useAzure) {
|
|
28
|
+
if (!apiVersion) return createErrorResponse(ChatErrorType.BadRequest);
|
|
29
|
+
|
|
30
|
+
// `https://test-001.openai.azure.com/openai/deployments/gpt-35-turbo`,
|
|
31
|
+
const url = `${endpoint}/openai/deployments/${payload.model.replace('.', '')}`;
|
|
32
|
+
|
|
33
|
+
openai = createAzureOpenai({
|
|
34
|
+
apiVersion,
|
|
35
|
+
endpoint: url,
|
|
36
|
+
userApiKey: apiKey,
|
|
37
|
+
});
|
|
38
|
+
} else {
|
|
39
|
+
openai = createOpenai(apiKey, endpoint);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return createChatCompletion({ openai, payload });
|
|
43
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import OpenAI, { ClientOptions } from 'openai';
|
|
2
|
+
|
|
3
|
+
import { getServerConfig } from '@/config/server';
|
|
4
|
+
|
|
5
|
+
// 创建 Azure OpenAI 实例
|
|
6
|
+
export const createAzureOpenai = (params: {
|
|
7
|
+
apiVersion: string;
|
|
8
|
+
endpoint: string;
|
|
9
|
+
userApiKey?: string | null;
|
|
10
|
+
}) => {
|
|
11
|
+
const { AZURE_API_KEY } = getServerConfig();
|
|
12
|
+
|
|
13
|
+
const baseURL = params.endpoint;
|
|
14
|
+
const apiKey = !params.userApiKey ? AZURE_API_KEY : params.userApiKey;
|
|
15
|
+
|
|
16
|
+
const config: ClientOptions = {
|
|
17
|
+
apiKey: apiKey,
|
|
18
|
+
baseURL,
|
|
19
|
+
defaultHeaders: { 'api-key': apiKey },
|
|
20
|
+
defaultQuery: {
|
|
21
|
+
'api-version': params.apiVersion,
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
return new OpenAI(config);
|
|
26
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import OpenAI, { ClientOptions } from 'openai';
|
|
2
|
+
|
|
3
|
+
import { getServerConfig } from '@/config/server';
|
|
4
|
+
|
|
5
|
+
// 创建 OpenAI 实例
|
|
6
|
+
export const createOpenai = (userApiKey: string | null, endpoint?: string | null) => {
|
|
7
|
+
const { OPENAI_API_KEY, OPENAI_PROXY_URL } = getServerConfig();
|
|
8
|
+
|
|
9
|
+
const baseURL = endpoint ? endpoint : OPENAI_PROXY_URL ? OPENAI_PROXY_URL : undefined;
|
|
10
|
+
|
|
11
|
+
const config: ClientOptions = {
|
|
12
|
+
apiKey: !userApiKey ? OPENAI_API_KEY : userApiKey,
|
|
13
|
+
baseURL,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
return new OpenAI(config);
|
|
17
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { getOpenAIAuthFromRequest } from '@/const/fetch';
|
|
2
|
+
|
|
3
|
+
import { createOpenai } from './createOpenai';
|
|
4
|
+
|
|
5
|
+
export const runtime = 'edge';
|
|
6
|
+
|
|
7
|
+
export default async function handler(req: Request) {
|
|
8
|
+
const { apiKey, endpoint } = getOpenAIAuthFromRequest(req);
|
|
9
|
+
|
|
10
|
+
const openAI = createOpenai(apiKey, endpoint);
|
|
11
|
+
|
|
12
|
+
const res = await openAI.models.list();
|
|
13
|
+
|
|
14
|
+
const modelList = res.data.map((i) => i.id);
|
|
15
|
+
|
|
16
|
+
return new Response(JSON.stringify(modelList));
|
|
17
|
+
}
|
|
@@ -3,7 +3,7 @@ import { Form as AntForm, App, Button, Input, Select } from 'antd';
|
|
|
3
3
|
import isEqual from 'fast-deep-equal';
|
|
4
4
|
import { changeLanguage } from 'i18next';
|
|
5
5
|
import { debounce } from 'lodash-es';
|
|
6
|
-
import { AppWindow, Monitor, Moon, Palette, Sun
|
|
6
|
+
import { AppWindow, Monitor, Moon, Palette, Sun } from 'lucide-react';
|
|
7
7
|
import { memo, useCallback, useMemo } from 'react';
|
|
8
8
|
import { useTranslation } from 'react-i18next';
|
|
9
9
|
|
|
@@ -144,30 +144,6 @@ const Common = memo(() => {
|
|
|
144
144
|
[settings],
|
|
145
145
|
);
|
|
146
146
|
|
|
147
|
-
const openAI: SettingItemGroup = useMemo(
|
|
148
|
-
() => ({
|
|
149
|
-
children: [
|
|
150
|
-
{
|
|
151
|
-
children: <Input.Password placeholder={t('settingOpenAI.token.placeholder')} />,
|
|
152
|
-
desc: t('settingOpenAI.token.desc'),
|
|
153
|
-
label: t('settingOpenAI.token.title'),
|
|
154
|
-
name: 'OPENAI_API_KEY',
|
|
155
|
-
},
|
|
156
|
-
{
|
|
157
|
-
children: <Input placeholder={t('settingOpenAI.endpoint.placeholder')} />,
|
|
158
|
-
desc: t('settingOpenAI.endpoint.desc'),
|
|
159
|
-
|
|
160
|
-
label: t('settingOpenAI.endpoint.title'),
|
|
161
|
-
|
|
162
|
-
name: 'endpoint',
|
|
163
|
-
},
|
|
164
|
-
],
|
|
165
|
-
icon: Webhook,
|
|
166
|
-
title: t('settingOpenAI.title'),
|
|
167
|
-
}),
|
|
168
|
-
[settings],
|
|
169
|
-
);
|
|
170
|
-
|
|
171
147
|
const system: SettingItemGroup = useMemo(
|
|
172
148
|
() => ({
|
|
173
149
|
children: [
|
|
@@ -204,7 +180,7 @@ const Common = memo(() => {
|
|
|
204
180
|
[settings],
|
|
205
181
|
);
|
|
206
182
|
|
|
207
|
-
const items = useMemo(() => [theme,
|
|
183
|
+
const items = useMemo(() => [theme, system], [settings]);
|
|
208
184
|
|
|
209
185
|
return (
|
|
210
186
|
<Form
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { CheckCircleFilled } from '@ant-design/icons';
|
|
2
|
+
import { Highlighter } from '@lobehub/ui';
|
|
3
|
+
import { Alert, Button } from 'antd';
|
|
4
|
+
import { useTheme } from 'antd-style';
|
|
5
|
+
import { memo, useState } from 'react';
|
|
6
|
+
import { useTranslation } from 'react-i18next';
|
|
7
|
+
import { Flexbox } from 'react-layout-kit';
|
|
8
|
+
|
|
9
|
+
import { ChatMessageError } from '@/types/chatMessage';
|
|
10
|
+
import { fetchPresetTaskResult } from '@/utils/fetch';
|
|
11
|
+
|
|
12
|
+
import { getModelList } from './getModelList';
|
|
13
|
+
|
|
14
|
+
interface CheckerProps {
|
|
15
|
+
checkModel?: boolean;
|
|
16
|
+
}
|
|
17
|
+
const Checker = memo<CheckerProps>(({ checkModel }) => {
|
|
18
|
+
const { t } = useTranslation('setting');
|
|
19
|
+
|
|
20
|
+
const [loading, setLoading] = useState(false);
|
|
21
|
+
const [pass, setPass] = useState(false);
|
|
22
|
+
|
|
23
|
+
const theme = useTheme();
|
|
24
|
+
const [error, setError] = useState<ChatMessageError | undefined>();
|
|
25
|
+
|
|
26
|
+
const checkConnection = async () => {
|
|
27
|
+
if (checkModel) {
|
|
28
|
+
getModelList();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const data = await fetchPresetTaskResult({
|
|
32
|
+
onError: (_, rawError) => {
|
|
33
|
+
setError(rawError);
|
|
34
|
+
},
|
|
35
|
+
onLoadingChange: (loading) => {
|
|
36
|
+
setLoading(loading);
|
|
37
|
+
},
|
|
38
|
+
params: {
|
|
39
|
+
messages: [
|
|
40
|
+
{
|
|
41
|
+
content: '你好',
|
|
42
|
+
role: 'user',
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
model: 'gpt-3.5-turbo',
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
if (data) {
|
|
50
|
+
setError(undefined);
|
|
51
|
+
setPass(true);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
return (
|
|
55
|
+
<Flexbox gap={8}>
|
|
56
|
+
<Flexbox align={'center'} gap={12} horizontal>
|
|
57
|
+
<Button loading={loading} onClick={checkConnection}>
|
|
58
|
+
{t('llm.OpenAI.check.button')}
|
|
59
|
+
</Button>
|
|
60
|
+
|
|
61
|
+
{pass && (
|
|
62
|
+
<Flexbox gap={4} horizontal>
|
|
63
|
+
<CheckCircleFilled
|
|
64
|
+
style={{
|
|
65
|
+
color: theme.colorSuccess,
|
|
66
|
+
}}
|
|
67
|
+
/>
|
|
68
|
+
{t('llm.OpenAI.check.pass')}
|
|
69
|
+
</Flexbox>
|
|
70
|
+
)}
|
|
71
|
+
</Flexbox>
|
|
72
|
+
|
|
73
|
+
{error && (
|
|
74
|
+
<Flexbox gap={8}>
|
|
75
|
+
<Alert banner message={error.message} showIcon type={'error'}></Alert>
|
|
76
|
+
<Flexbox style={{ maxWidth: 600 }}>
|
|
77
|
+
<Highlighter language={'json'}>{JSON.stringify(error.body, null, 2)}</Highlighter>
|
|
78
|
+
</Flexbox>
|
|
79
|
+
</Flexbox>
|
|
80
|
+
)}
|
|
81
|
+
</Flexbox>
|
|
82
|
+
);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
export default Checker;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { ActionIcon } from '@lobehub/ui';
|
|
2
|
+
import { Button, Dropdown, Skeleton } from 'antd';
|
|
3
|
+
import { MenuItemType } from 'antd/lib/menu/hooks/useItems';
|
|
4
|
+
import { RotateCwIcon } from 'lucide-react';
|
|
5
|
+
import { memo } from 'react';
|
|
6
|
+
import { useTranslation } from 'react-i18next';
|
|
7
|
+
import { Flexbox } from 'react-layout-kit';
|
|
8
|
+
|
|
9
|
+
import { getModelList } from './getModelList';
|
|
10
|
+
|
|
11
|
+
interface ModelListProps {
|
|
12
|
+
value?: string[];
|
|
13
|
+
}
|
|
14
|
+
const ModelList = memo<ModelListProps>(({ value }) => {
|
|
15
|
+
const { t } = useTranslation('setting');
|
|
16
|
+
|
|
17
|
+
const isLoading = !value;
|
|
18
|
+
|
|
19
|
+
if (isLoading)
|
|
20
|
+
return (
|
|
21
|
+
<Skeleton active paragraph={false} title={{ style: { marginBottom: 0 }, width: '50%' }} />
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const isEmpty = value?.length === 0;
|
|
25
|
+
|
|
26
|
+
return isEmpty ? (
|
|
27
|
+
<Button
|
|
28
|
+
onClick={() => {
|
|
29
|
+
getModelList('openAI');
|
|
30
|
+
}}
|
|
31
|
+
>
|
|
32
|
+
{t('llm.OpenAI.models.fetch')}
|
|
33
|
+
</Button>
|
|
34
|
+
) : (
|
|
35
|
+
<Dropdown
|
|
36
|
+
menu={{
|
|
37
|
+
items: (value || []).map<MenuItemType>((v) => ({
|
|
38
|
+
key: v,
|
|
39
|
+
label: v,
|
|
40
|
+
})),
|
|
41
|
+
}}
|
|
42
|
+
>
|
|
43
|
+
<Flexbox align={'center'} gap={8} horizontal>
|
|
44
|
+
{t('llm.OpenAI.models.count', { count: value.length })}
|
|
45
|
+
<ActionIcon icon={RotateCwIcon} size={'small'} title={t('llm.OpenAI.models.refetch')} />
|
|
46
|
+
</Flexbox>
|
|
47
|
+
</Dropdown>
|
|
48
|
+
);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
export default ModelList;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { fetchModelList } from '@/services/modelList';
|
|
2
|
+
import { useGlobalStore } from '@/store/global';
|
|
3
|
+
|
|
4
|
+
export const getModelList = async (brand = 'openAI') => {
|
|
5
|
+
const setSettings = useGlobalStore.getState().setSettings;
|
|
6
|
+
const models = await fetchModelList();
|
|
7
|
+
|
|
8
|
+
setSettings({ languageModel: { [brand]: { models } } });
|
|
9
|
+
};
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { Form, Markdown } from '@lobehub/ui';
|
|
2
|
+
import { Form as AntForm, AutoComplete, Input, Switch } from 'antd';
|
|
3
|
+
import { createStyles } from 'antd-style';
|
|
4
|
+
import { debounce } from 'lodash-es';
|
|
5
|
+
import { Webhook } from 'lucide-react';
|
|
6
|
+
import Link from 'next/link';
|
|
7
|
+
import { memo } from 'react';
|
|
8
|
+
import { Trans, useTranslation } from 'react-i18next';
|
|
9
|
+
import { Flexbox } from 'react-layout-kit';
|
|
10
|
+
|
|
11
|
+
import { FORM_STYLE } from '@/const/layoutTokens';
|
|
12
|
+
import { globalSelectors, useEffectAfterGlobalHydrated, useGlobalStore } from '@/store/global';
|
|
13
|
+
|
|
14
|
+
import Checker from './Checker';
|
|
15
|
+
|
|
16
|
+
const useStyles = createStyles(({ css, token }) => ({
|
|
17
|
+
markdown: css`
|
|
18
|
+
a {
|
|
19
|
+
font-size: 12px !important;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
p {
|
|
23
|
+
font-size: 12px !important;
|
|
24
|
+
color: ${token.colorTextDescription} !important;
|
|
25
|
+
}
|
|
26
|
+
`,
|
|
27
|
+
plan: css`
|
|
28
|
+
color: ${token.colorTextDescription};
|
|
29
|
+
`,
|
|
30
|
+
tip: css`
|
|
31
|
+
font-size: 12px;
|
|
32
|
+
color: ${token.colorTextDescription};
|
|
33
|
+
`,
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
const configKey = 'languageModel';
|
|
37
|
+
|
|
38
|
+
const LLM = memo(() => {
|
|
39
|
+
const { t } = useTranslation('setting');
|
|
40
|
+
const [form] = AntForm.useForm();
|
|
41
|
+
const { styles } = useStyles();
|
|
42
|
+
const [setSettings] = useGlobalStore((s) => [s.setSettings]);
|
|
43
|
+
|
|
44
|
+
useEffectAfterGlobalHydrated((store) => {
|
|
45
|
+
const settings = globalSelectors.currentSettings(store.getState());
|
|
46
|
+
|
|
47
|
+
form.setFieldsValue(settings);
|
|
48
|
+
}, []);
|
|
49
|
+
|
|
50
|
+
const useAzure = useGlobalStore((s) => s.settings.languageModel.openAI.useAzure);
|
|
51
|
+
|
|
52
|
+
const openAI = {
|
|
53
|
+
children: [
|
|
54
|
+
{
|
|
55
|
+
children: (
|
|
56
|
+
<Input.Password
|
|
57
|
+
placeholder={
|
|
58
|
+
useAzure ? t('llm.AzureOpenAI.token.placeholder') : t('llm.OpenAI.token.placeholder')
|
|
59
|
+
}
|
|
60
|
+
/>
|
|
61
|
+
),
|
|
62
|
+
desc: useAzure ? t('llm.AzureOpenAI.token.desc') : t('llm.OpenAI.token.desc'),
|
|
63
|
+
label: useAzure ? t('llm.AzureOpenAI.token.title') : t('llm.OpenAI.token.title'),
|
|
64
|
+
name: [configKey, 'openAI', 'OPENAI_API_KEY'],
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
children: (
|
|
68
|
+
<Input
|
|
69
|
+
allowClear
|
|
70
|
+
placeholder={
|
|
71
|
+
useAzure
|
|
72
|
+
? t('llm.AzureOpenAI.endpoint.placeholder')
|
|
73
|
+
: t('llm.OpenAI.endpoint.placeholder')
|
|
74
|
+
}
|
|
75
|
+
/>
|
|
76
|
+
),
|
|
77
|
+
desc: useAzure ? t('llm.AzureOpenAI.endpoint.desc') : t('llm.OpenAI.endpoint.desc'),
|
|
78
|
+
|
|
79
|
+
label: useAzure ? t('llm.AzureOpenAI.endpoint.title') : t('llm.OpenAI.endpoint.title'),
|
|
80
|
+
|
|
81
|
+
name: [configKey, 'openAI', 'endpoint'],
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
children: <Switch />,
|
|
85
|
+
desc: t('llm.OpenAI.useAzure.desc'),
|
|
86
|
+
label: t('llm.OpenAI.useAzure.title'),
|
|
87
|
+
name: [configKey, 'openAI', 'useAzure'],
|
|
88
|
+
valuePropName: 'checked',
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
children: (
|
|
92
|
+
<AutoComplete
|
|
93
|
+
options={[
|
|
94
|
+
'2023-08-01-preview',
|
|
95
|
+
'2023-07-01-preview',
|
|
96
|
+
'2023-06-01-preview',
|
|
97
|
+
'2023-05-15',
|
|
98
|
+
'2023-03-15-preview',
|
|
99
|
+
].map((i) => ({
|
|
100
|
+
label: i,
|
|
101
|
+
value: i,
|
|
102
|
+
}))}
|
|
103
|
+
placeholder={'20XX-XX-XX'}
|
|
104
|
+
/>
|
|
105
|
+
),
|
|
106
|
+
desc: (
|
|
107
|
+
<Markdown className={styles.markdown}>{t('llm.OpenAI.azureApiVersion.desc')}</Markdown>
|
|
108
|
+
),
|
|
109
|
+
hidden: !useAzure,
|
|
110
|
+
label: t('llm.OpenAI.azureApiVersion.title'),
|
|
111
|
+
name: [configKey, 'openAI', 'azureApiVersion'],
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
children: <Checker checkModel={!useAzure} />,
|
|
115
|
+
desc: t('llm.OpenAI.check.desc'),
|
|
116
|
+
label: t('llm.OpenAI.check.title'),
|
|
117
|
+
},
|
|
118
|
+
// {
|
|
119
|
+
// children: useAzure ? <Flexbox>{t('llm.OpenAI.models.notSupport')}</Flexbox> : <ModelList />,
|
|
120
|
+
// desc: useAzure ? t('llm.OpenAI.models.notSupportTip') : t('llm.OpenAI.models.desc'),
|
|
121
|
+
// label: t('llm.OpenAI.models.title'),
|
|
122
|
+
// name: [configKey, 'openAI', 'models'],
|
|
123
|
+
// },
|
|
124
|
+
],
|
|
125
|
+
icon: Webhook,
|
|
126
|
+
title: t('llm.OpenAI.title'),
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
return (
|
|
130
|
+
<Flexbox align={'center'} gap={24} width={'100%'}>
|
|
131
|
+
<Form
|
|
132
|
+
form={form}
|
|
133
|
+
items={[openAI]}
|
|
134
|
+
onValuesChange={debounce(setSettings, 100)}
|
|
135
|
+
{...FORM_STYLE}
|
|
136
|
+
/>
|
|
137
|
+
<Flexbox align={'center'} className={styles.plan}>
|
|
138
|
+
<div>
|
|
139
|
+
<Trans i18nKey="llm.waitingForMore" ns={'setting'}>
|
|
140
|
+
更多模型正在
|
|
141
|
+
<Link href="https://github.com/lobehub/lobe-chat/issues/151" target="_blank">
|
|
142
|
+
计划接入
|
|
143
|
+
</Link>
|
|
144
|
+
中 ,敬请期待 ✨
|
|
145
|
+
</Trans>
|
|
146
|
+
</div>
|
|
147
|
+
</Flexbox>
|
|
148
|
+
</Flexbox>
|
|
149
|
+
);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
export default LLM;
|
|
@@ -2,13 +2,16 @@ import { GridBackground, Icon, Logo, TabsNav } from '@lobehub/ui';
|
|
|
2
2
|
import { createStyles } from 'antd-style';
|
|
3
3
|
import { PackageCheck } from 'lucide-react';
|
|
4
4
|
import { rgba } from 'polished';
|
|
5
|
-
import { memo
|
|
5
|
+
import { memo } from 'react';
|
|
6
6
|
import { useTranslation } from 'react-i18next';
|
|
7
7
|
import { Center, Flexbox } from 'react-layout-kit';
|
|
8
8
|
|
|
9
|
+
import { useGlobalStore } from '@/store/global';
|
|
10
|
+
|
|
9
11
|
import pkg from '../../../../../package.json';
|
|
10
12
|
import Agent from './Agent';
|
|
11
13
|
import Common from './Common';
|
|
14
|
+
import LLM from './LLM';
|
|
12
15
|
|
|
13
16
|
const useStyles = createStyles(({ css, token, isDarkMode }) => ({
|
|
14
17
|
background: css`
|
|
@@ -55,16 +58,12 @@ const useStyles = createStyles(({ css, token, isDarkMode }) => ({
|
|
|
55
58
|
`,
|
|
56
59
|
}));
|
|
57
60
|
|
|
58
|
-
enum Tabs {
|
|
59
|
-
agent = 'agent',
|
|
60
|
-
common = 'common',
|
|
61
|
-
}
|
|
62
|
-
|
|
63
61
|
const Settings = memo(() => {
|
|
64
|
-
const [tab, setTab] = useState<Tabs>(Tabs.common);
|
|
65
62
|
const { styles, theme } = useStyles();
|
|
66
63
|
const { t } = useTranslation('setting');
|
|
67
64
|
|
|
65
|
+
const [tab, setTab] = useGlobalStore((s) => [s.settingsTab, s.switchSettingTabs]);
|
|
66
|
+
|
|
68
67
|
return (
|
|
69
68
|
<Center gap={16} width={'100%'}>
|
|
70
69
|
<Flexbox align={'flex-end'} className={styles.tabs} horizontal justify={'center'}>
|
|
@@ -78,10 +77,11 @@ const Settings = memo(() => {
|
|
|
78
77
|
<TabsNav
|
|
79
78
|
activeKey={tab}
|
|
80
79
|
items={[
|
|
81
|
-
{ key:
|
|
82
|
-
{ key:
|
|
80
|
+
{ key: 'common', label: t('tab.common') },
|
|
81
|
+
{ key: 'llm', label: t('tab.llm') },
|
|
82
|
+
{ key: 'agent', label: t('tab.agent') },
|
|
83
83
|
]}
|
|
84
|
-
onChange={(e) => setTab(e as
|
|
84
|
+
onChange={(e) => setTab(e as any)}
|
|
85
85
|
/>
|
|
86
86
|
<div className={styles.logo}>
|
|
87
87
|
<Logo extra={'Chat'} type={'text'} />
|
|
@@ -91,8 +91,9 @@ const Settings = memo(() => {
|
|
|
91
91
|
<div>{`${pkg.version}`}</div>
|
|
92
92
|
</Flexbox>
|
|
93
93
|
</Flexbox>
|
|
94
|
-
{tab ===
|
|
95
|
-
{tab ===
|
|
94
|
+
{tab === 'common' && <Common />}
|
|
95
|
+
{tab === 'llm' && <LLM />}
|
|
96
|
+
{tab === 'agent' && <Agent />}
|
|
96
97
|
</Center>
|
|
97
98
|
);
|
|
98
99
|
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AZURE_OPENAI_API_VERSION,
|
|
3
|
+
LOBE_CHAT_ACCESS_CODE,
|
|
4
|
+
OPENAI_API_KEY_HEADER_KEY,
|
|
5
|
+
OPENAI_END_POINT,
|
|
6
|
+
USE_AZURE_OPENAI,
|
|
7
|
+
} from '@/const/fetch';
|
|
8
|
+
import { useGlobalStore } from '@/store/global';
|
|
9
|
+
|
|
10
|
+
// eslint-disable-next-line no-undef
|
|
11
|
+
export const createHeaderWithOpenAI = (header?: HeadersInit): HeadersInit => {
|
|
12
|
+
const openai = useGlobalStore.getState().settings.languageModel.openAI;
|
|
13
|
+
|
|
14
|
+
const apiKey = openai.OPENAI_API_KEY || useGlobalStore.getState().settings.OPENAI_API_KEY || '';
|
|
15
|
+
const endpoint = openai.endpoint || useGlobalStore.getState().settings.endpoint || '';
|
|
16
|
+
|
|
17
|
+
// eslint-disable-next-line no-undef
|
|
18
|
+
const result: HeadersInit = {
|
|
19
|
+
...header,
|
|
20
|
+
[LOBE_CHAT_ACCESS_CODE]: useGlobalStore.getState().settings.password || '',
|
|
21
|
+
[OPENAI_API_KEY_HEADER_KEY]: apiKey,
|
|
22
|
+
[OPENAI_END_POINT]: endpoint,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
if (openai.useAzure) {
|
|
26
|
+
Object.assign(result, {
|
|
27
|
+
[AZURE_OPENAI_API_VERSION]: openai.azureApiVersion,
|
|
28
|
+
[USE_AZURE_OPENAI]: '1',
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const isDev = process.env.NODE_ENV === 'development';
|
|
2
|
+
|
|
3
|
+
const prefix = isDev ? '-dev' : '';
|
|
4
|
+
|
|
5
|
+
export const URLS = {
|
|
6
|
+
plugins: '/api/plugins' + prefix,
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const OPENAI_URLS = {
|
|
10
|
+
chat: '/api/openai/chat' + prefix,
|
|
11
|
+
models: '/api/openai/models' + prefix,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const AZURE_OPENAI_URLS = {
|
|
15
|
+
chat: '/api/azure-openai/chat' + prefix,
|
|
16
|
+
models: '/api/azure-openai/models' + prefix,
|
|
17
|
+
};
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { merge } from 'lodash-es';
|
|
2
2
|
|
|
3
|
-
import { LOBE_CHAT_ACCESS_CODE, OPENAI_API_KEY_HEADER_KEY, OPENAI_END_POINT } from '@/const/fetch';
|
|
4
|
-
import { useGlobalStore } from '@/store/global';
|
|
5
3
|
import { pluginSelectors, usePluginStore } from '@/store/plugin';
|
|
6
4
|
import { initialLobeAgentConfig } from '@/store/session/initialState';
|
|
7
5
|
import type { ChatCompletionFunctions, OpenAIStreamPayload } from '@/types/openai';
|
|
8
6
|
|
|
9
|
-
import {
|
|
7
|
+
import { createHeaderWithOpenAI } from './_header';
|
|
8
|
+
import { OPENAI_URLS } from './_url';
|
|
10
9
|
|
|
11
10
|
interface FetchChatModelOptions {
|
|
12
11
|
signal?: AbortSignal | undefined;
|
|
@@ -35,14 +34,9 @@ export const fetchChatModel = (
|
|
|
35
34
|
|
|
36
35
|
const functions = filterFunctions.length === 0 ? undefined : filterFunctions;
|
|
37
36
|
|
|
38
|
-
return fetch(
|
|
37
|
+
return fetch(OPENAI_URLS.chat, {
|
|
39
38
|
body: JSON.stringify({ ...payload, functions }),
|
|
40
|
-
headers: {
|
|
41
|
-
'Content-Type': 'application/json',
|
|
42
|
-
[LOBE_CHAT_ACCESS_CODE]: useGlobalStore.getState().settings.password || '',
|
|
43
|
-
[OPENAI_API_KEY_HEADER_KEY]: useGlobalStore.getState().settings.OPENAI_API_KEY || '',
|
|
44
|
-
[OPENAI_END_POINT]: useGlobalStore.getState().settings.endpoint || '',
|
|
45
|
-
},
|
|
39
|
+
headers: createHeaderWithOpenAI({ 'Content-Type': 'application/json' }),
|
|
46
40
|
method: 'POST',
|
|
47
41
|
signal: options?.signal,
|
|
48
42
|
});
|