@lobehub/chat 0.72.4 → 0.73.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.
Files changed (36) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/next.config.mjs +6 -2
  3. package/package.json +5 -10
  4. package/src/config/server.ts +2 -0
  5. package/src/const/fetch.ts +15 -2
  6. package/src/const/llm.ts +17 -0
  7. package/src/const/settings.ts +16 -8
  8. package/src/locales/default/setting.ts +59 -13
  9. package/src/pages/api/{openai.ts → createChatCompletion.ts} +7 -36
  10. package/src/pages/api/openai/chat.api.ts +43 -0
  11. package/src/pages/api/openai/createAzureOpenai.ts +26 -0
  12. package/src/pages/api/openai/createOpenai.ts +17 -0
  13. package/src/pages/api/openai/models.api.ts +17 -0
  14. package/src/pages/settings/features/Settings/Common.tsx +2 -26
  15. package/src/pages/settings/features/Settings/LLM/Checker.tsx +85 -0
  16. package/src/pages/settings/features/Settings/LLM/ModelList.tsx +51 -0
  17. package/src/pages/settings/features/Settings/LLM/getModelList.ts +9 -0
  18. package/src/pages/settings/features/Settings/LLM/index.tsx +152 -0
  19. package/src/pages/settings/features/Settings/index.tsx +13 -12
  20. package/src/services/_header.ts +33 -0
  21. package/src/services/_url.ts +17 -0
  22. package/src/services/chatModel.ts +4 -10
  23. package/src/services/modelList.ts +15 -0
  24. package/src/services/plugin.ts +1 -1
  25. package/src/services/pluginMarket.ts +1 -1
  26. package/src/store/global/hooks/index.ts +1 -0
  27. package/src/store/global/hooks/useEffectAfterHydrated.ts +22 -0
  28. package/src/store/global/initialState.ts +4 -0
  29. package/src/store/global/selectors.test.ts +264 -0
  30. package/src/store/global/selectors.ts +4 -5
  31. package/src/store/global/slices/settings.ts +16 -4
  32. package/src/store/global/store.ts +34 -5
  33. package/src/types/settings.ts +45 -8
  34. package/src/utils/fetch.ts +4 -12
  35. package/src/pages/api/openai.api.ts +0 -24
  36. package/src/services/url.ts +0 -8
package/CHANGELOG.md CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 0.73.0](https://github.com/lobehub/lobe-chat/compare/v0.72.4...v0.73.0)
6
+
7
+ <sup>Released on **2023-09-10**</sup>
8
+
9
+ #### ✨ Features
10
+
11
+ - **misc**: Support Azure OpenAI.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's improved
19
+
20
+ - **misc**: Support Azure OpenAI, closes [#177](https://github.com/lobehub/lobe-chat/issues/177) ([f0c9532](https://github.com/lobehub/lobe-chat/commit/f0c9532))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
5
30
  ### [Version 0.72.4](https://github.com/lobehub/lobe-chat/compare/v0.72.3...v0.72.4)
6
31
 
7
32
  <sup>Released on **2023-09-10**</sup>
package/next.config.mjs CHANGED
@@ -27,8 +27,12 @@ const nextConfig = {
27
27
  async rewrites() {
28
28
  return [
29
29
  {
30
- source: '/api/openai-dev',
31
- destination: `${API_END_PORT_URL}/api/openai`,
30
+ source: '/api/openai/chat-dev',
31
+ destination: `${API_END_PORT_URL}/api/openai/chat`,
32
+ },
33
+ {
34
+ source: '/api/openai/models-dev',
35
+ destination: `${API_END_PORT_URL}/api/openai/models`,
32
36
  },
33
37
  {
34
38
  source: '/api/plugins-dev',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "0.72.4",
3
+ "version": "0.73.0",
4
4
  "description": "Lobe Chat is an open-source chatbot client using Vercel AI SDK, Typescript and Next.js",
5
5
  "keywords": [
6
6
  "chatbot",
@@ -65,11 +65,12 @@
65
65
  "dependencies": {
66
66
  "@ant-design/colors": "^7",
67
67
  "@ant-design/icons": "^5",
68
+ "@azure/openai": "latest",
68
69
  "@emoji-mart/data": "^1",
69
70
  "@emoji-mart/react": "^1",
70
71
  "@icons-pack/react-simple-icons": "^9",
71
- "@lobehub/chat-plugin-sdk": "^1.17.0",
72
- "@lobehub/chat-plugins-gateway": "^1.5.0",
72
+ "@lobehub/chat-plugin-sdk": "^1.17.7",
73
+ "@lobehub/chat-plugins-gateway": "^1.5.1",
73
74
  "@lobehub/ui": "latest",
74
75
  "@vercel/analytics": "^1",
75
76
  "ahooks": "^3",
@@ -96,11 +97,11 @@
96
97
  "react-i18next": "^13",
97
98
  "react-intersection-observer": "^9",
98
99
  "react-layout-kit": "^1.7.1",
99
- "serpapi": "^2",
100
100
  "swr": "^2",
101
101
  "systemjs": "^6.14.2",
102
102
  "ts-md5": "^1",
103
103
  "use-merge-value": "^1",
104
+ "utility-types": "^3",
104
105
  "uuid": "^9",
105
106
  "zustand": "^4.4",
106
107
  "zustand-utils": "^1"
@@ -142,12 +143,6 @@
142
143
  "typescript": "^5",
143
144
  "vitest": "latest"
144
145
  },
145
- "peerDependencies": {
146
- "antd": ">=5",
147
- "antd-style": ">=3",
148
- "react": ">=18",
149
- "react-dom": ">=18"
150
- },
151
146
  "publishConfig": {
152
147
  "access": "public",
153
148
  "registry": "https://registry.npmjs.org"
@@ -3,6 +3,7 @@ declare global {
3
3
  namespace NodeJS {
4
4
  interface ProcessEnv {
5
5
  ACCESS_CODE?: string;
6
+ AZURE_API_KEY?: string;
6
7
  OPENAI_API_KEY?: string;
7
8
  OPENAI_PROXY_URL?: string;
8
9
  }
@@ -16,6 +17,7 @@ export const getServerConfig = () => {
16
17
 
17
18
  return {
18
19
  ACCESS_CODE: process.env.ACCESS_CODE,
20
+ AZURE_API_KEY: process.env.AZURE_API_KEY,
19
21
  OPENAI_API_KEY: process.env.OPENAI_API_KEY,
20
22
  OPENAI_PROXY_URL: process.env.OPENAI_PROXY_URL,
21
23
  };
@@ -1,7 +1,20 @@
1
1
  export const OPENAI_END_POINT = 'X-OPENAI-END_POINT';
2
-
3
2
  export const OPENAI_API_KEY_HEADER_KEY = 'X-OPENAI-API-KEY';
4
3
 
4
+ export const USE_AZURE_OPENAI = 'X-USE_AZURE_OPENAI';
5
+
6
+ export const AZURE_OPENAI_API_VERSION = 'X-AZURE_OPENAI_API_VERSION';
7
+
5
8
  export const LOBE_CHAT_ACCESS_CODE = 'X-LOBE_CHAT_ACCESS_CODE';
6
9
 
7
- export const LOBE_PLUGIN_SETTINGS = 'X-LOBE_PLUGIN_SETTINGS';
10
+ export const getOpenAIAuthFromRequest = (req: Request) => {
11
+ const apiKey = req.headers.get(OPENAI_API_KEY_HEADER_KEY);
12
+ const endpoint = req.headers.get(OPENAI_END_POINT);
13
+ const accessCode = req.headers.get(LOBE_CHAT_ACCESS_CODE);
14
+ const useAzureStr = req.headers.get(USE_AZURE_OPENAI);
15
+ const apiVersion = req.headers.get(AZURE_OPENAI_API_VERSION);
16
+
17
+ const useAzure = !!useAzureStr;
18
+
19
+ return { accessCode, apiKey, apiVersion, endpoint, useAzure };
20
+ };
@@ -0,0 +1,17 @@
1
+ /**
2
+ * A white list of language models that are allowed to display and be used in the app.
3
+ */
4
+ export const LanguageModelWhiteList = [
5
+ // OpenAI
6
+ 'gpt-3.5-turbo',
7
+ 'gpt-3.5-turbo-16k',
8
+ 'gpt-4',
9
+ 'gpt-4-32k',
10
+ ];
11
+
12
+ export const DEFAULT_OPENAI_MODEL_LIST = [
13
+ 'gpt-3.5-turbo',
14
+ 'gpt-3.5-turbo-16k',
15
+ 'gpt-4',
16
+ 'gpt-4-32k',
17
+ ];
@@ -1,18 +1,17 @@
1
+ import { DEFAULT_OPENAI_MODEL_LIST } from '@/const/llm';
1
2
  import { DEFAULT_AGENT_META } from '@/const/meta';
2
3
  import { LanguageModel } from '@/types/llm';
3
4
  import { LobeAgentConfig } from '@/types/session';
4
- import { GlobalBaseSettings, GlobalDefaultAgent, GlobalSettings } from '@/types/settings';
5
+ import {
6
+ GlobalBaseSettings,
7
+ GlobalDefaultAgent,
8
+ GlobalLLMConfig,
9
+ GlobalSettings,
10
+ } from '@/types/settings';
5
11
 
6
12
  export const DEFAULT_BASE_SETTINGS: GlobalBaseSettings = {
7
- OPENAI_API_KEY: '',
8
13
  avatar: '',
9
- compressThreshold: 24,
10
- enableCompressThreshold: false,
11
- enableHistoryCount: false,
12
- enableMaxTokens: true,
13
- endpoint: '',
14
14
  fontSize: 14,
15
- historyCount: 24,
16
15
  language: 'zh-CN',
17
16
  neutralColor: '',
18
17
  password: '',
@@ -34,6 +33,14 @@ export const DEFAULT_AGENT_CONFIG: LobeAgentConfig = {
34
33
  systemRole: '',
35
34
  };
36
35
 
36
+ export const DEFAULT_LLM_CONFIG: GlobalLLMConfig = {
37
+ openAI: {
38
+ OPENAI_API_KEY: '',
39
+ azureApiVersion: '2023-08-01-preview',
40
+ models: DEFAULT_OPENAI_MODEL_LIST,
41
+ },
42
+ };
43
+
37
44
  export const DEFAULT_AGENT: GlobalDefaultAgent = {
38
45
  config: DEFAULT_AGENT_CONFIG,
39
46
  meta: DEFAULT_AGENT_META,
@@ -41,5 +48,6 @@ export const DEFAULT_AGENT: GlobalDefaultAgent = {
41
48
 
42
49
  export const DEFAULT_SETTINGS: GlobalSettings = {
43
50
  defaultAgent: DEFAULT_AGENT,
51
+ languageModel: DEFAULT_LLM_CONFIG,
44
52
  ...DEFAULT_BASE_SETTINGS,
45
53
  };
@@ -20,6 +20,64 @@ export default {
20
20
  session: '会话设置',
21
21
  sessionWithName: '会话设置 · {{name}}',
22
22
  },
23
+ llm: {
24
+ AzureOpenAI: {
25
+ endpoint: {
26
+ desc: '从 Azure 门户检查资源时,可在“密钥和终结点”部分中找到此值',
27
+ placeholder: 'https://docs-test-001.openai.azure.com',
28
+ title: 'Azure API 地址',
29
+ },
30
+ models: {
31
+ desc: '支持的模型',
32
+ title: '模型列表',
33
+ },
34
+ title: 'Azure OpenAI 设置',
35
+ token: {
36
+ desc: '从 Azure 门户检查资源时,可在“密钥和终结点”部分中找到此值。 可以使用 KEY1 或 KEY2',
37
+ placeholder: 'Azure API Key',
38
+ title: 'API Key',
39
+ },
40
+ },
41
+ OpenAI: {
42
+ azureApiVersion: {
43
+ desc: 'Azure 的 API 版本,遵循 YYYY-MM-DD 格式,查阅[最新版本](https://learn.microsoft.com/zh-cn/azure/ai-services/openai/reference#chat-completions)',
44
+ fetch: '获取列表',
45
+ title: 'Azure Api Version',
46
+ },
47
+ check: {
48
+ button: '检查',
49
+ desc: '测试 Api Key 与代理地址是否正确填写',
50
+ pass: '检查通过',
51
+ title: '连通性检查',
52
+ },
53
+ endpoint: {
54
+ desc: '除默认地址外,必须包含 http(s)://',
55
+ placeholder: 'https://api.openai.com/v1',
56
+ title: '接口代理地址',
57
+ },
58
+ models: {
59
+ count: '共支持 {{count}} 个模型',
60
+ desc: '支持的模型',
61
+ fetch: '获取模型列表',
62
+ notSupport: 'Azure OpenAI 暂不支持查看模型列表',
63
+ notSupportTip: '你需要自行确保部署名称与模型名称一致',
64
+ refetch: '重新获取模型列表',
65
+ title: '模型列表',
66
+ },
67
+ title: 'OpenAI 设置',
68
+ token: {
69
+ desc: '使用自己的 OpenAI Key',
70
+ placeholder: 'OpenAI API Key',
71
+ title: 'API Key',
72
+ },
73
+ useAzure: {
74
+ desc: '使用 Azure 提供的 OpenAI 服务',
75
+ fetch: '获取列表',
76
+ title: 'Azure OpenAI',
77
+ },
78
+ },
79
+ waitingForMore: '更多模型正在 <1>计划接入</1> 中,敬请期待 ✨',
80
+ },
23
81
  settingAgent: {
24
82
  avatar: {
25
83
  title: '头像',
@@ -114,19 +172,6 @@ export default {
114
172
  title: '核采样',
115
173
  },
116
174
  },
117
- settingOpenAI: {
118
- endpoint: {
119
- desc: '除默认地址外,必须包含 http(s)://',
120
- placeholder: 'https://api.openai.com/v1',
121
- title: '接口代理地址',
122
- },
123
- title: 'OpenAI 设置',
124
- token: {
125
- desc: '使用自己的 OpenAI Key',
126
- placeholder: 'OpenAI API Key',
127
- title: 'API Key',
128
- },
129
- },
130
175
  settingPlugin: {
131
176
  add: '添加',
132
177
  addTooltip: '添加自定义插件',
@@ -173,5 +218,6 @@ export default {
173
218
  tab: {
174
219
  agent: '默认助手',
175
220
  common: '通用设置',
221
+ llm: '语言模型',
176
222
  },
177
223
  };
@@ -1,46 +1,17 @@
1
1
  import { OpenAIStream, StreamingTextResponse } from 'ai';
2
- import OpenAI, { ClientOptions } from '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
- OPENAI_API_KEY: string | null;
30
- endpoint?: string | null;
9
+ openai: OpenAI;
31
10
  payload: OpenAIStreamPayload;
32
11
  }
33
12
 
34
- export const createChatCompletion = async ({
35
- payload,
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: !!endpoint ? endpoint : undefined,
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, Webhook } from 'lucide-react';
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, openAI, system], [settings]);
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
+ };