@lobehub/chat 0.25.0 → 0.26.1
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/.eslintrc.js +2 -1
- package/CHANGELOG.md +113 -6
- package/package.json +2 -5
- package/src/config/server.ts +22 -0
- package/src/const/fetch.ts +2 -2
- package/src/const/message.ts +2 -0
- package/src/locales/create.ts +3 -14
- package/src/locales/default/common.ts +4 -0
- package/src/locales/default/error.ts +27 -0
- package/src/locales/default/plugin.ts +19 -1
- package/src/locales/resources/zh_CN.ts +2 -0
- package/src/pages/api/auth.ts +25 -0
- package/src/pages/api/error.ts +21 -0
- package/src/pages/api/openai.api.ts +11 -5
- package/src/pages/api/openai.ts +24 -9
- package/src/pages/api/plugins.api.ts +11 -36
- package/src/pages/chat/[id]/Conversation/ChatList/Error/ApiKeyForm.tsx +40 -0
- package/src/pages/chat/[id]/Conversation/ChatList/Error/ErrorActionContainer.tsx +23 -0
- package/src/pages/chat/[id]/Conversation/ChatList/Error/InvalidAccess.tsx +76 -0
- package/src/pages/chat/[id]/Conversation/ChatList/Error/OpenAiBizError.tsx +52 -0
- package/src/pages/chat/[id]/Conversation/ChatList/Error/style.tsx +44 -0
- package/src/pages/chat/[id]/Conversation/{MessageExtra.tsx → ChatList/MessageExtra.tsx} +28 -26
- package/src/pages/chat/[id]/Conversation/ChatList/OTPInput.tsx +142 -0
- package/src/pages/chat/[id]/Conversation/ChatList/Plugins/FunctionCall.tsx +60 -0
- package/src/pages/chat/[id]/Conversation/ChatList/Plugins/PluginMessage.tsx +45 -0
- package/src/pages/chat/[id]/Conversation/ChatList/Plugins/PluginResultRender.tsx +51 -0
- package/src/pages/chat/[id]/Conversation/ChatList/Plugins/style.ts +25 -0
- package/src/pages/chat/[id]/Conversation/ChatList/index.tsx +112 -0
- package/src/pages/chat/[id]/Conversation/Input/Token.tsx +1 -1
- package/src/pages/chat/[id]/edit/AgentPlugin/index.tsx +2 -2
- package/src/pages/setting/Header.tsx +2 -2
- package/src/pages/setting/index.page.tsx +11 -28
- package/src/pages/setting/layout.tsx +35 -0
- package/src/plugins/Render.ts +8 -0
- package/src/plugins/index.ts +7 -3
- package/src/plugins/searchEngine/index.ts +4 -1
- package/src/plugins/searchEngine/runner.ts +5 -73
- package/src/plugins/searchEngine/type.ts +80 -0
- package/src/plugins/type.ts +17 -0
- package/src/plugins/weather/Render.tsx +53 -0
- package/src/plugins/weather/index.ts +9 -1
- package/src/plugins/weather/runner.ts +12 -7
- package/src/plugins/weather/type.ts +36 -0
- package/src/plugins/webCrawler/index.ts +4 -1
- package/src/plugins/webCrawler/runner.ts +3 -27
- package/src/plugins/webCrawler/type.ts +32 -0
- package/src/services/chatModel.ts +3 -3
- package/src/services/plugin.ts +30 -0
- package/src/store/session/slices/chat/actions/message.ts +61 -42
- package/src/store/session/slices/chat/initialState.ts +2 -4
- package/src/store/session/slices/chat/selectors/utils.test.ts +3 -2
- package/src/store/session/slices/chat/selectors/utils.ts +4 -3
- package/src/types/chatMessage.ts +9 -6
- package/src/types/fetch.ts +26 -0
- package/src/types/openai.ts +3 -0
- package/src/types/plugin.ts +4 -0
- package/src/utils/fetch.ts +21 -25
- package/src/utils/message.ts +16 -0
- package/src/pages/chat/[id]/Conversation/ChatList.tsx +0 -69
- package/src/pages/chat/[id]/Conversation/FunctionMessage.tsx +0 -31
package/.eslintrc.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
const config = require('@lobehub/lint').eslint;
|
|
2
2
|
|
|
3
3
|
config.extends.push('plugin:@next/next/recommended');
|
|
4
|
-
//config.extends.push('plugin:@next/next/core-web-vitals');
|
|
5
4
|
|
|
6
5
|
config.rules['unicorn/no-negated-condition'] = 0;
|
|
6
|
+
config.rules['unicorn/prefer-type-error'] = 0;
|
|
7
|
+
|
|
7
8
|
module.exports = config;
|
package/CHANGELOG.md
CHANGED
|
@@ -2,24 +2,131 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
### [Version 0.26.1](https://github.com/lobehub/lobe-chat/compare/v0.26.0...v0.26.1)
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
<sup>Released on **2023-07-29**</sup>
|
|
8
|
+
|
|
9
|
+
#### ♻ Code Refactoring
|
|
10
|
+
|
|
11
|
+
- **misc**: 优化 setting Layout 实现.
|
|
12
|
+
|
|
13
|
+
<br/>
|
|
14
|
+
|
|
15
|
+
<details>
|
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
17
|
+
|
|
18
|
+
#### Code refactoring
|
|
19
|
+
|
|
20
|
+
- **misc**: 优化 setting Layout 实现 ([f789935](https://github.com/lobehub/lobe-chat/commit/f789935))
|
|
21
|
+
|
|
22
|
+
</details>
|
|
23
|
+
|
|
24
|
+
<div align="right">
|
|
25
|
+
|
|
26
|
+
[](#readme-top)
|
|
27
|
+
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
## [Version 0.26.0](https://github.com/lobehub/lobe-chat/compare/v0.25.0...v0.26.0)
|
|
31
|
+
|
|
32
|
+
<sup>Released on **2023-07-28**</sup>
|
|
33
|
+
|
|
34
|
+
#### ✨ Features
|
|
35
|
+
|
|
36
|
+
- **misc**: support password auth and error.
|
|
37
|
+
|
|
38
|
+
<br/>
|
|
39
|
+
|
|
40
|
+
<details>
|
|
41
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
42
|
+
|
|
43
|
+
#### What's improved
|
|
44
|
+
|
|
45
|
+
- **misc**: support password auth and error, closes [#22](https://github.com/lobehub/lobe-chat/issues/22) ([67f1f4d](https://github.com/lobehub/lobe-chat/commit/67f1f4d))
|
|
46
|
+
|
|
47
|
+
</details>
|
|
48
|
+
|
|
49
|
+
<div align="right">
|
|
50
|
+
|
|
51
|
+
[](#readme-top)
|
|
52
|
+
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
## [Version 0.25.0](https://github.com/lobehub/lobe-chat/compare/v0.24.0...v0.25.0)
|
|
56
|
+
|
|
57
|
+
<sup>Released on **2023-07-26**</sup>
|
|
58
|
+
|
|
59
|
+
#### ✨ Features
|
|
60
|
+
|
|
61
|
+
- **sidebar**: Add import functionality and set labels and onClick functions
|
|
62
|
+
|
|
63
|
+
<br/>
|
|
64
|
+
|
|
65
|
+
<details>
|
|
66
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
67
|
+
|
|
68
|
+
#### ✨ Features
|
|
8
69
|
|
|
9
70
|
- **sidebar**: Add import functionality and set labels and onClick functions ([03ea9bd](https://github.com/lobehub/lobe-chat/commit/03ea9bd))
|
|
10
71
|
|
|
11
|
-
|
|
72
|
+
</details>
|
|
12
73
|
|
|
13
|
-
|
|
74
|
+
<div align="right">
|
|
75
|
+
|
|
76
|
+
[](#readme-top)
|
|
77
|
+
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
## [Version 0.24.0](https://github.com/lobehub/lobe-chat/compare/v0.23.0...v0.24.0)
|
|
81
|
+
|
|
82
|
+
<sup>Released on **2023-07-26**</sup>
|
|
83
|
+
|
|
84
|
+
#### ✨ Features
|
|
85
|
+
|
|
86
|
+
- **misc**: Add new translations, update existing translations, add functionality to components, modify styling, and adjust placeholder text
|
|
87
|
+
|
|
88
|
+
<br/>
|
|
89
|
+
|
|
90
|
+
<details>
|
|
91
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
92
|
+
|
|
93
|
+
#### ✨ Features
|
|
14
94
|
|
|
15
95
|
- Add new translations, update existing translations, add functionality to components, modify styling, and adjust placeholder text ([da4ae72](https://github.com/lobehub/lobe-chat/commit/da4ae72))
|
|
16
96
|
|
|
17
|
-
|
|
97
|
+
</details>
|
|
18
98
|
|
|
19
|
-
|
|
99
|
+
<div align="right">
|
|
100
|
+
|
|
101
|
+
[](#readme-top)
|
|
102
|
+
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
## [Version 0.23.0](https://github.com/lobehub/lobe-chat/compare/v0.22.2...v0.23.0)
|
|
106
|
+
|
|
107
|
+
<sup>Released on **2023-07-26**</sup>
|
|
108
|
+
|
|
109
|
+
#### ✨ Features
|
|
110
|
+
|
|
111
|
+
- **misc**: Add new features, update URLs, customize appearance, and implement components
|
|
112
|
+
|
|
113
|
+
<br/>
|
|
114
|
+
|
|
115
|
+
<details>
|
|
116
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
117
|
+
|
|
118
|
+
#### ✨ Features
|
|
20
119
|
|
|
21
120
|
- Add new features, update URLs, customize appearance, and implement components ([4b61bf4](https://github.com/lobehub/lobe-chat/commit/4b61bf4))
|
|
22
121
|
|
|
122
|
+
</details>
|
|
123
|
+
|
|
124
|
+
<div align="right">
|
|
125
|
+
|
|
126
|
+
[](#readme-top)
|
|
127
|
+
|
|
128
|
+
</div>
|
|
129
|
+
|
|
23
130
|
### [Version 0.22.2](https://github.com/lobehub/lobe-chat/compare/v0.22.1...v0.22.2)
|
|
24
131
|
|
|
25
132
|
<sup>Released on **2023-07-26**</sup>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobehub/chat",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.1",
|
|
4
4
|
"description": "Lobe Chat is an open-source chatbot client using LangChain, Typescript and Next.js",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"chatbot",
|
|
@@ -122,12 +122,10 @@
|
|
|
122
122
|
"lint-staged": "^13",
|
|
123
123
|
"next-pwa": "^5",
|
|
124
124
|
"node-fetch": "^3",
|
|
125
|
-
"postcss-styled-syntax": "^0.4",
|
|
126
125
|
"prettier": "^2",
|
|
127
126
|
"remark": "^14",
|
|
128
127
|
"remark-cli": "^11",
|
|
129
128
|
"semantic-release": "^21",
|
|
130
|
-
"semantic-release-config-gitmoji": "^1",
|
|
131
129
|
"stylelint": "^15",
|
|
132
130
|
"ts-node": "^10",
|
|
133
131
|
"typescript": "^5",
|
|
@@ -142,6 +140,5 @@
|
|
|
142
140
|
"publishConfig": {
|
|
143
141
|
"access": "public",
|
|
144
142
|
"registry": "https://registry.npmjs.org"
|
|
145
|
-
}
|
|
146
|
-
"changelog": "https://github.com/lobehub/lobe-chat"
|
|
143
|
+
}
|
|
147
144
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
declare global {
|
|
2
|
+
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
3
|
+
namespace NodeJS {
|
|
4
|
+
interface ProcessEnv {
|
|
5
|
+
ACCESS_CODE?: string;
|
|
6
|
+
OPENAI_API_KEY?: string;
|
|
7
|
+
OPENAI_PROXY_URL?: string;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const getServerConfig = () => {
|
|
13
|
+
if (typeof process === 'undefined') {
|
|
14
|
+
throw new Error('[Server Config] you are importing a nodejs-only module outside of nodejs');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
ACCESS_CODE: process.env.ACCESS_CODE,
|
|
19
|
+
OPENAI_API_KEY: process.env.OPENAI_API_KEY,
|
|
20
|
+
OPENAI_PROXY_URL: process.env.OPENAI_PROXY_URL,
|
|
21
|
+
};
|
|
22
|
+
};
|
package/src/const/fetch.ts
CHANGED
package/src/const/message.ts
CHANGED
package/src/locales/create.ts
CHANGED
|
@@ -7,23 +7,12 @@ import type { Namespaces } from '@/types/locale';
|
|
|
7
7
|
|
|
8
8
|
import resources from './resources';
|
|
9
9
|
|
|
10
|
-
// const getRes = (res: Resources, namespace: Namespaces[]) => {
|
|
11
|
-
// const newRes: any = {};
|
|
12
|
-
// for (const [locale, value] of Object.entries(res)) {
|
|
13
|
-
// newRes[locale] = {};
|
|
14
|
-
// for (const ns of namespace) {
|
|
15
|
-
// newRes[locale][ns] = value[ns];
|
|
16
|
-
// }
|
|
17
|
-
// }
|
|
18
|
-
// return newRes;
|
|
19
|
-
// };
|
|
20
|
-
|
|
21
10
|
export const createI18nNext = (namespace?: Namespaces[] | Namespaces) => {
|
|
22
11
|
const ns: Namespaces[] = namespace
|
|
23
12
|
? isArray(namespace)
|
|
24
|
-
? ['common', ...namespace]
|
|
25
|
-
: ['common', namespace]
|
|
26
|
-
: ['common'];
|
|
13
|
+
? ['error', 'common', ...namespace]
|
|
14
|
+
: ['error', 'common', namespace]
|
|
15
|
+
: ['error', 'common'];
|
|
27
16
|
return (
|
|
28
17
|
i18n
|
|
29
18
|
// detect user language
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
response: {
|
|
3
|
+
400: '很抱歉,服务器不明白您的请求,请确认您的请求参数是否正确',
|
|
4
|
+
401: '很抱歉,服务器拒绝了您的请求,可能是因为您的权限不足或未提供有效的身份验证',
|
|
5
|
+
403: '很抱歉,服务器拒绝了您的请求,您没有访问此内容的权限 ',
|
|
6
|
+
404: '很抱歉,服务器找不到您请求的页面或资源,请确认您的 URL 是否正确',
|
|
7
|
+
429: '很抱歉,您的请求太多,服务器有点累了,请稍后再试',
|
|
8
|
+
500: '很抱歉,服务器似乎遇到了一些困难,暂时无法完成您的请求,请稍后再试',
|
|
9
|
+
502: '很抱歉,服务器似乎迷失了方向,暂时无法提供服务,请稍后再试',
|
|
10
|
+
503: '很抱歉,服务器当前无法处理您的请求,可能是由于过载或正在进行维护,请稍后再试',
|
|
11
|
+
504: '很抱歉,服务器没有等到上游服务器的回应,请稍后再试',
|
|
12
|
+
|
|
13
|
+
InvalidAccessCode: '密码不正确或为空,请输入正确的访问密码,或者添加自定义 OpenAI API Key',
|
|
14
|
+
OpenAIBizError: '请求 OpenAI 服务出错,请根据以下信息排查或重试',
|
|
15
|
+
},
|
|
16
|
+
unlock: {
|
|
17
|
+
apikey: {
|
|
18
|
+
description: '输入你的 OpenAI API Key 即可绕过密码验证。应用不会记录你的 API Key',
|
|
19
|
+
title: '使用自定义 API Key',
|
|
20
|
+
},
|
|
21
|
+
confirm: '确认并重试',
|
|
22
|
+
password: {
|
|
23
|
+
description: '管理员已开启应用加密,输入应用密码后即可解锁应用。密码只需填写一次',
|
|
24
|
+
title: '输入密码解锁应用',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
};
|
|
@@ -1,10 +1,28 @@
|
|
|
1
1
|
export default {
|
|
2
|
+
loading: {
|
|
3
|
+
content: '数据获取中...',
|
|
4
|
+
plugin: '插件运行中...',
|
|
5
|
+
},
|
|
2
6
|
pluginList: '插件列表',
|
|
3
|
-
pluginLoading: '插件运行中...',
|
|
4
7
|
plugins: {
|
|
5
8
|
realtimeWeather: '实时天气预报',
|
|
6
9
|
searchEngine: '搜索引擎',
|
|
7
10
|
undefined: '插件检测中...',
|
|
8
11
|
websiteCrawler: '网页内容提取',
|
|
9
12
|
},
|
|
13
|
+
realtimeWeather: {
|
|
14
|
+
data: {
|
|
15
|
+
date: '日期',
|
|
16
|
+
daytemp_float: '日间温度',
|
|
17
|
+
dayweather: '日间天气',
|
|
18
|
+
daywind: '日间风速',
|
|
19
|
+
nighttemp_float: '夜间温度',
|
|
20
|
+
nightweather: '夜间天气',
|
|
21
|
+
nightwind: '夜间风速',
|
|
22
|
+
week: '星期',
|
|
23
|
+
},
|
|
24
|
+
title: '近7日天气数据({{city}})',
|
|
25
|
+
updateAt: '更新时间',
|
|
26
|
+
},
|
|
27
|
+
responseData: '返回数据',
|
|
10
28
|
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { getServerConfig } from '@/config/server';
|
|
2
|
+
import { ErrorType } from '@/types/fetch';
|
|
3
|
+
|
|
4
|
+
interface AuthConfig {
|
|
5
|
+
accessCode?: string | null;
|
|
6
|
+
apiKey?: string | null;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const checkAuth = ({ apiKey, accessCode }: AuthConfig) => {
|
|
10
|
+
const { ACCESS_CODE } = getServerConfig();
|
|
11
|
+
|
|
12
|
+
// 如果存在 apiKey
|
|
13
|
+
if (apiKey) {
|
|
14
|
+
return { auth: true };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// 如果不存在,则检查 accessCode
|
|
18
|
+
if (!ACCESS_CODE) return { auth: true };
|
|
19
|
+
|
|
20
|
+
if (accessCode !== ACCESS_CODE) {
|
|
21
|
+
return { auth: false, error: ErrorType.InvalidAccessCode };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return { auth: true };
|
|
25
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ErrorResponse, ErrorType } from '@/types/fetch';
|
|
2
|
+
|
|
3
|
+
const getStatus = (errorType: ErrorType) => {
|
|
4
|
+
switch (errorType) {
|
|
5
|
+
case ErrorType.InvalidAccessCode: {
|
|
6
|
+
return 401;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
case ErrorType.OpenAIBizError: {
|
|
10
|
+
return 577;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const createErrorResponse = (errorType: ErrorType, body?: any) => {
|
|
16
|
+
const statusCode = getStatus(errorType);
|
|
17
|
+
|
|
18
|
+
const data: ErrorResponse = { body, errorType };
|
|
19
|
+
|
|
20
|
+
return new Response(JSON.stringify(data), { status: statusCode });
|
|
21
|
+
};
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
import { OPENAI_API_KEY_HEADER_KEY } from '@/const/fetch';
|
|
1
|
+
import { LOBE_CHAT_ACCESS_CODE, OPENAI_API_KEY_HEADER_KEY } from '@/const/fetch';
|
|
2
|
+
import { ErrorType } from '@/types/fetch';
|
|
4
3
|
import { OpenAIStreamPayload } from '@/types/openai';
|
|
5
4
|
|
|
5
|
+
import { checkAuth } from './auth';
|
|
6
|
+
import { createErrorResponse } from './error';
|
|
6
7
|
import { createChatCompletion } from './openai';
|
|
7
8
|
|
|
8
9
|
export const runtime = 'edge';
|
|
@@ -10,8 +11,13 @@ export const runtime = 'edge';
|
|
|
10
11
|
export default async function handler(req: Request) {
|
|
11
12
|
const payload = (await req.json()) as OpenAIStreamPayload;
|
|
12
13
|
const apiKey = req.headers.get(OPENAI_API_KEY_HEADER_KEY);
|
|
14
|
+
const accessCode = req.headers.get(LOBE_CHAT_ACCESS_CODE);
|
|
15
|
+
|
|
16
|
+
const result = checkAuth({ accessCode, apiKey });
|
|
13
17
|
|
|
14
|
-
|
|
18
|
+
if (!result.auth) {
|
|
19
|
+
return createErrorResponse(result.error as ErrorType);
|
|
20
|
+
}
|
|
15
21
|
|
|
16
|
-
return
|
|
22
|
+
return createChatCompletion({ OPENAI_API_KEY: apiKey, payload });
|
|
17
23
|
}
|
package/src/pages/api/openai.ts
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
|
-
import { OpenAIStream, OpenAIStreamCallbacks } from 'ai';
|
|
1
|
+
import { OpenAIStream, OpenAIStreamCallbacks, StreamingTextResponse } from 'ai';
|
|
2
2
|
import { Configuration, OpenAIApi } from 'openai-edge';
|
|
3
3
|
import { ChatCompletionFunctions } from 'openai-edge/types/api';
|
|
4
4
|
|
|
5
|
+
import { getServerConfig } from '@/config/server';
|
|
6
|
+
import { createErrorResponse } from '@/pages/api/error';
|
|
7
|
+
import { PluginsMap } from '@/plugins';
|
|
8
|
+
import { ErrorType } from '@/types/fetch';
|
|
5
9
|
import { OpenAIStreamPayload } from '@/types/openai';
|
|
6
10
|
|
|
7
|
-
import pluginList from '../../plugins';
|
|
8
|
-
|
|
9
11
|
const isDev = process.env.NODE_ENV === 'development';
|
|
10
|
-
const OPENAI_PROXY_URL = process.env.OPENAI_PROXY_URL;
|
|
11
12
|
|
|
12
13
|
// 创建 OpenAI 实例
|
|
13
|
-
export const createOpenAI = (
|
|
14
|
+
export const createOpenAI = (userApiKey: string | null) => {
|
|
15
|
+
const { OPENAI_API_KEY, OPENAI_PROXY_URL } = getServerConfig();
|
|
16
|
+
|
|
14
17
|
const config = new Configuration({
|
|
15
|
-
apiKey: !
|
|
18
|
+
apiKey: !userApiKey ? OPENAI_API_KEY : userApiKey,
|
|
16
19
|
});
|
|
17
20
|
|
|
18
21
|
return new OpenAIApi(config, isDev && OPENAI_PROXY_URL ? OPENAI_PROXY_URL : undefined);
|
|
@@ -37,7 +40,7 @@ export const createChatCompletion = async ({
|
|
|
37
40
|
|
|
38
41
|
// ============ 1. 前置处理 functions ============ //
|
|
39
42
|
|
|
40
|
-
const filterFunctions: ChatCompletionFunctions[] =
|
|
43
|
+
const filterFunctions: ChatCompletionFunctions[] = Object.values(PluginsMap)
|
|
41
44
|
.filter((p) => {
|
|
42
45
|
// 如果不存在 enabledPlugins,那么全部不启用
|
|
43
46
|
if (!enabledPlugins) return false;
|
|
@@ -50,7 +53,11 @@ export const createChatCompletion = async ({
|
|
|
50
53
|
const functions = filterFunctions.length === 0 ? undefined : filterFunctions;
|
|
51
54
|
|
|
52
55
|
// ============ 2. 前置处理 messages ============ //
|
|
53
|
-
const formatMessages = messages.map((m) => ({
|
|
56
|
+
const formatMessages = messages.map((m) => ({
|
|
57
|
+
content: m.content,
|
|
58
|
+
name: m.name,
|
|
59
|
+
role: m.role,
|
|
60
|
+
}));
|
|
54
61
|
|
|
55
62
|
// ============ 3. 发送请求 ============ //
|
|
56
63
|
|
|
@@ -58,5 +65,13 @@ export const createChatCompletion = async ({
|
|
|
58
65
|
|
|
59
66
|
const response = await openai.createChatCompletion(requestParams);
|
|
60
67
|
|
|
61
|
-
|
|
68
|
+
if (!response.ok) {
|
|
69
|
+
const error = await response.json();
|
|
70
|
+
|
|
71
|
+
return createErrorResponse(ErrorType.OpenAIBizError, error);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const stream = OpenAIStream(response, callbacks?.(requestParams));
|
|
75
|
+
|
|
76
|
+
return new StreamingTextResponse(stream);
|
|
62
77
|
};
|
|
@@ -1,47 +1,22 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { ChatCompletionRequestMessage } from 'openai-edge';
|
|
1
|
+
import { OpenAIPluginPayload } from '@/types/plugin';
|
|
3
2
|
|
|
4
|
-
import {
|
|
5
|
-
import { OpenAIStreamPayload } from '@/types/openai';
|
|
6
|
-
|
|
7
|
-
import pluginList from '../../plugins';
|
|
8
|
-
import { createChatCompletion, createOpenAI } from './openai';
|
|
3
|
+
import { PluginsMap } from '../../plugins';
|
|
9
4
|
|
|
10
5
|
export const runtime = 'edge';
|
|
11
6
|
|
|
12
7
|
export default async function handler(req: Request) {
|
|
13
|
-
const
|
|
14
|
-
const apiKey = req.headers.get(OPENAI_API_KEY_HEADER_KEY);
|
|
15
|
-
|
|
16
|
-
const openai = createOpenAI(apiKey);
|
|
17
|
-
|
|
18
|
-
const stream = await createChatCompletion({
|
|
19
|
-
OPENAI_API_KEY: apiKey,
|
|
20
|
-
callbacks: (payload) => ({
|
|
21
|
-
experimental_onFunctionCall: async (
|
|
22
|
-
{ name, arguments: args },
|
|
23
|
-
createFunctionCallMessages,
|
|
24
|
-
) => {
|
|
25
|
-
console.log(`检测到 functionCall: ${name}`);
|
|
26
|
-
|
|
27
|
-
const func = pluginList.find((f) => f.name === name);
|
|
8
|
+
const { name, arguments: args } = (await req.json()) as OpenAIPluginPayload;
|
|
28
9
|
|
|
29
|
-
|
|
30
|
-
const result = await func.runner(args as any);
|
|
10
|
+
console.log(`检测到 functionCall: ${name}`);
|
|
31
11
|
|
|
32
|
-
|
|
12
|
+
const func = PluginsMap[name];
|
|
33
13
|
|
|
34
|
-
|
|
14
|
+
if (func) {
|
|
15
|
+
const data = JSON.parse(args);
|
|
16
|
+
const result = await func.runner(data);
|
|
35
17
|
|
|
36
|
-
|
|
37
|
-
...payload,
|
|
38
|
-
messages: [...payload.messages, ...newMessages],
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
},
|
|
42
|
-
}),
|
|
43
|
-
payload,
|
|
44
|
-
});
|
|
18
|
+
console.log(`[${name}]`, args, `result:`, JSON.stringify(result, null, 2));
|
|
45
19
|
|
|
46
|
-
|
|
20
|
+
return new Response(JSON.stringify(result));
|
|
21
|
+
}
|
|
47
22
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Avatar } from '@lobehub/ui';
|
|
2
|
+
import { Button, Input } from 'antd';
|
|
3
|
+
import { memo } from 'react';
|
|
4
|
+
import { useTranslation } from 'react-i18next';
|
|
5
|
+
import { Center, Flexbox } from 'react-layout-kit';
|
|
6
|
+
import { shallow } from 'zustand/shallow';
|
|
7
|
+
|
|
8
|
+
import { useSettings } from '@/store/settings';
|
|
9
|
+
|
|
10
|
+
import { useStyles } from './style';
|
|
11
|
+
|
|
12
|
+
const APIKeyForm = memo<{ onConfirm?: () => void }>(({ onConfirm }) => {
|
|
13
|
+
const { t } = useTranslation('error');
|
|
14
|
+
const { styles, theme } = useStyles();
|
|
15
|
+
const [apiKey, setSettings] = useSettings(
|
|
16
|
+
(s) => [s.settings.OPENAI_API_KEY, s.setSettings],
|
|
17
|
+
shallow,
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<Center gap={16} style={{ maxWidth: 300 }}>
|
|
22
|
+
<Avatar avatar={'🔑'} background={theme.colorText} gap={12} size={80} />
|
|
23
|
+
<Flexbox style={{ fontSize: 20 }}>{t('unlock.apikey.title')}</Flexbox>
|
|
24
|
+
<Flexbox className={styles.desc}>{t('unlock.apikey.description')}</Flexbox>
|
|
25
|
+
<Input
|
|
26
|
+
onChange={(e) => {
|
|
27
|
+
setSettings({ OPENAI_API_KEY: e.target.value });
|
|
28
|
+
}}
|
|
29
|
+
placeholder={'sk-*****************************************'}
|
|
30
|
+
type={'block'}
|
|
31
|
+
value={apiKey}
|
|
32
|
+
/>
|
|
33
|
+
<Button block onClick={onConfirm} style={{ marginTop: 8 }} type={'primary'}>
|
|
34
|
+
{t('unlock.confirm')}
|
|
35
|
+
</Button>
|
|
36
|
+
</Center>
|
|
37
|
+
);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
export default APIKeyForm;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { createStyles } from 'antd-style';
|
|
2
|
+
import { ReactNode, memo } from 'react';
|
|
3
|
+
import { Center } from 'react-layout-kit';
|
|
4
|
+
|
|
5
|
+
const useStyles = createStyles(({ css, token }) => ({
|
|
6
|
+
container: css`
|
|
7
|
+
background: ${token.colorBgContainer};
|
|
8
|
+
border: 1px solid ${token.colorSplit};
|
|
9
|
+
border-radius: 8px;
|
|
10
|
+
`,
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
const ErrorActionContainer = memo<{ children: ReactNode }>(({ children }) => {
|
|
14
|
+
const { styles } = useStyles();
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<Center className={styles.container} gap={24} padding={24}>
|
|
18
|
+
{children}
|
|
19
|
+
</Center>
|
|
20
|
+
);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export default ErrorActionContainer;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Icon } from '@lobehub/ui';
|
|
2
|
+
import { Button, Segmented } from 'antd';
|
|
3
|
+
import { KeySquare, SquareAsterisk } from 'lucide-react';
|
|
4
|
+
import { memo, useState } from 'react';
|
|
5
|
+
import { useTranslation } from 'react-i18next';
|
|
6
|
+
import { Flexbox } from 'react-layout-kit';
|
|
7
|
+
import { shallow } from 'zustand/shallow';
|
|
8
|
+
|
|
9
|
+
import { useSessionStore } from '@/store/session';
|
|
10
|
+
import { useSettings } from '@/store/settings';
|
|
11
|
+
|
|
12
|
+
import OtpInput from '../OTPInput';
|
|
13
|
+
import APIKeyForm from './ApiKeyForm';
|
|
14
|
+
import { ErrorActionContainer, FormAction } from './style';
|
|
15
|
+
|
|
16
|
+
const InvalidAccess = memo<{ id: string }>(({ id }) => {
|
|
17
|
+
const { t } = useTranslation('error');
|
|
18
|
+
const [mode, setMode] = useState('password');
|
|
19
|
+
const [password, setSettings] = useSettings((s) => [s.settings.password, s.setSettings], shallow);
|
|
20
|
+
const [resend, deleteMessage] = useSessionStore(
|
|
21
|
+
(s) => [s.resendMessage, s.deleteMessage],
|
|
22
|
+
shallow,
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<ErrorActionContainer>
|
|
27
|
+
<Segmented
|
|
28
|
+
block
|
|
29
|
+
onChange={(value) => setMode(value as string)}
|
|
30
|
+
options={[
|
|
31
|
+
{ icon: <Icon icon={SquareAsterisk} />, label: '密码', value: 'password' },
|
|
32
|
+
{ icon: <Icon icon={KeySquare} />, label: 'OpenAI API Key', value: 'api' },
|
|
33
|
+
]}
|
|
34
|
+
style={{ width: '100%' }}
|
|
35
|
+
value={mode}
|
|
36
|
+
/>
|
|
37
|
+
<Flexbox gap={24}>
|
|
38
|
+
{mode === 'password' ? (
|
|
39
|
+
<>
|
|
40
|
+
<FormAction
|
|
41
|
+
avatar={'🗳'}
|
|
42
|
+
description={t('unlock.password.description')}
|
|
43
|
+
title={t('unlock.password.title')}
|
|
44
|
+
>
|
|
45
|
+
<OtpInput
|
|
46
|
+
onChange={(e) => {
|
|
47
|
+
setSettings({ password: e });
|
|
48
|
+
}}
|
|
49
|
+
validationPattern={/[\dA-Za-z]/}
|
|
50
|
+
value={password}
|
|
51
|
+
/>
|
|
52
|
+
</FormAction>
|
|
53
|
+
<Button
|
|
54
|
+
onClick={() => {
|
|
55
|
+
resend(id);
|
|
56
|
+
deleteMessage(id);
|
|
57
|
+
}}
|
|
58
|
+
type={'primary'}
|
|
59
|
+
>
|
|
60
|
+
{t('unlock.confirm')}
|
|
61
|
+
</Button>
|
|
62
|
+
</>
|
|
63
|
+
) : (
|
|
64
|
+
<APIKeyForm
|
|
65
|
+
onConfirm={() => {
|
|
66
|
+
resend(id);
|
|
67
|
+
deleteMessage(id);
|
|
68
|
+
}}
|
|
69
|
+
/>
|
|
70
|
+
)}
|
|
71
|
+
</Flexbox>
|
|
72
|
+
</ErrorActionContainer>
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
export default InvalidAccess;
|