@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.
Files changed (60) hide show
  1. package/.eslintrc.js +2 -1
  2. package/CHANGELOG.md +113 -6
  3. package/package.json +2 -5
  4. package/src/config/server.ts +22 -0
  5. package/src/const/fetch.ts +2 -2
  6. package/src/const/message.ts +2 -0
  7. package/src/locales/create.ts +3 -14
  8. package/src/locales/default/common.ts +4 -0
  9. package/src/locales/default/error.ts +27 -0
  10. package/src/locales/default/plugin.ts +19 -1
  11. package/src/locales/resources/zh_CN.ts +2 -0
  12. package/src/pages/api/auth.ts +25 -0
  13. package/src/pages/api/error.ts +21 -0
  14. package/src/pages/api/openai.api.ts +11 -5
  15. package/src/pages/api/openai.ts +24 -9
  16. package/src/pages/api/plugins.api.ts +11 -36
  17. package/src/pages/chat/[id]/Conversation/ChatList/Error/ApiKeyForm.tsx +40 -0
  18. package/src/pages/chat/[id]/Conversation/ChatList/Error/ErrorActionContainer.tsx +23 -0
  19. package/src/pages/chat/[id]/Conversation/ChatList/Error/InvalidAccess.tsx +76 -0
  20. package/src/pages/chat/[id]/Conversation/ChatList/Error/OpenAiBizError.tsx +52 -0
  21. package/src/pages/chat/[id]/Conversation/ChatList/Error/style.tsx +44 -0
  22. package/src/pages/chat/[id]/Conversation/{MessageExtra.tsx → ChatList/MessageExtra.tsx} +28 -26
  23. package/src/pages/chat/[id]/Conversation/ChatList/OTPInput.tsx +142 -0
  24. package/src/pages/chat/[id]/Conversation/ChatList/Plugins/FunctionCall.tsx +60 -0
  25. package/src/pages/chat/[id]/Conversation/ChatList/Plugins/PluginMessage.tsx +45 -0
  26. package/src/pages/chat/[id]/Conversation/ChatList/Plugins/PluginResultRender.tsx +51 -0
  27. package/src/pages/chat/[id]/Conversation/ChatList/Plugins/style.ts +25 -0
  28. package/src/pages/chat/[id]/Conversation/ChatList/index.tsx +112 -0
  29. package/src/pages/chat/[id]/Conversation/Input/Token.tsx +1 -1
  30. package/src/pages/chat/[id]/edit/AgentPlugin/index.tsx +2 -2
  31. package/src/pages/setting/Header.tsx +2 -2
  32. package/src/pages/setting/index.page.tsx +11 -28
  33. package/src/pages/setting/layout.tsx +35 -0
  34. package/src/plugins/Render.ts +8 -0
  35. package/src/plugins/index.ts +7 -3
  36. package/src/plugins/searchEngine/index.ts +4 -1
  37. package/src/plugins/searchEngine/runner.ts +5 -73
  38. package/src/plugins/searchEngine/type.ts +80 -0
  39. package/src/plugins/type.ts +17 -0
  40. package/src/plugins/weather/Render.tsx +53 -0
  41. package/src/plugins/weather/index.ts +9 -1
  42. package/src/plugins/weather/runner.ts +12 -7
  43. package/src/plugins/weather/type.ts +36 -0
  44. package/src/plugins/webCrawler/index.ts +4 -1
  45. package/src/plugins/webCrawler/runner.ts +3 -27
  46. package/src/plugins/webCrawler/type.ts +32 -0
  47. package/src/services/chatModel.ts +3 -3
  48. package/src/services/plugin.ts +30 -0
  49. package/src/store/session/slices/chat/actions/message.ts +61 -42
  50. package/src/store/session/slices/chat/initialState.ts +2 -4
  51. package/src/store/session/slices/chat/selectors/utils.test.ts +3 -2
  52. package/src/store/session/slices/chat/selectors/utils.ts +4 -3
  53. package/src/types/chatMessage.ts +9 -6
  54. package/src/types/fetch.ts +26 -0
  55. package/src/types/openai.ts +3 -0
  56. package/src/types/plugin.ts +4 -0
  57. package/src/utils/fetch.ts +21 -25
  58. package/src/utils/message.ts +16 -0
  59. package/src/pages/chat/[id]/Conversation/ChatList.tsx +0 -69
  60. 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
- # [0.25.0](https://github.com/lobehub/lobe-chat/compare/v0.24.0...v0.25.0) (2023-07-26)
5
+ ### [Version 0.26.1](https://github.com/lobehub/lobe-chat/compare/v0.26.0...v0.26.1)
6
6
 
7
- ### ✨ Features
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
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#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
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#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
- # [0.24.0](https://github.com/lobehub/lobe-chat/compare/v0.23.0...v0.24.0) (2023-07-26)
72
+ </details>
12
73
 
13
- ### ✨ Features
74
+ <div align="right">
75
+
76
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#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
- # [0.23.0](https://github.com/lobehub/lobe-chat/compare/v0.22.2...v0.23.0) (2023-07-26)
97
+ </details>
18
98
 
19
- ### ✨ Features
99
+ <div align="right">
100
+
101
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#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
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#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.25.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
+ };
@@ -1,3 +1,3 @@
1
- export const OPENAI_SERVICE_ERROR_CODE = 555;
2
-
3
1
  export const OPENAI_API_KEY_HEADER_KEY = 'X-OPENAI-API-KEY';
2
+
3
+ export const LOBE_CHAT_ACCESS_CODE = 'X-LOBE_CHAT_ACCESS_CODE';
@@ -1,2 +1,4 @@
1
1
  // 只要 start with 这个,就可以判断为 function message
2
2
  export const FUNCTION_MESSAGE_FLAG = '{"function';
3
+
4
+ export const FUNCTION_LOADING = 'FUNCTION_LOADING';
@@ -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
@@ -30,6 +30,10 @@ export default {
30
30
  },
31
31
  feedback: '反馈与建议',
32
32
  import: '导入配置',
33
+ message: {
34
+ function_loading: '插件请求中...',
35
+ },
36
+ moreSetting: '更多设置...',
33
37
  newAgent: '新建助手',
34
38
  noDescription: '暂无描述',
35
39
  ok: '确定',
@@ -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
  };
@@ -1,9 +1,11 @@
1
1
  import common from '../default/common';
2
+ import error from '../default/error';
2
3
  import plugin from '../default/plugin';
3
4
  import setting from '../default/setting';
4
5
 
5
6
  const resources = {
6
7
  common,
8
+ error,
7
9
  plugin,
8
10
  setting,
9
11
  } as const;
@@ -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 { StreamingTextResponse } from 'ai';
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
- const stream = await createChatCompletion({ OPENAI_API_KEY: apiKey, payload });
18
+ if (!result.auth) {
19
+ return createErrorResponse(result.error as ErrorType);
20
+ }
15
21
 
16
- return new StreamingTextResponse(stream);
22
+ return createChatCompletion({ OPENAI_API_KEY: apiKey, payload });
17
23
  }
@@ -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 = (OPENAI_API_KEY: string | null) => {
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: !OPENAI_API_KEY ? process.env.OPENAI_API_KEY : OPENAI_API_KEY,
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[] = pluginList
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) => ({ content: m.content, role: m.role }));
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
- return OpenAIStream(response, callbacks?.(requestParams));
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 { StreamingTextResponse } from 'ai';
2
- import { ChatCompletionRequestMessage } from 'openai-edge';
1
+ import { OpenAIPluginPayload } from '@/types/plugin';
3
2
 
4
- import { OPENAI_API_KEY_HEADER_KEY } from '@/const/fetch';
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 payload = (await req.json()) as OpenAIStreamPayload;
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
- if (func) {
30
- const result = await func.runner(args as any);
10
+ console.log(`检测到 functionCall: ${name}`);
31
11
 
32
- console.log(`[${name}]`, args, `result:`, JSON.stringify(result, null, 2));
12
+ const func = PluginsMap[name];
33
13
 
34
- const newMessages = createFunctionCallMessages(result) as ChatCompletionRequestMessage[];
14
+ if (func) {
15
+ const data = JSON.parse(args);
16
+ const result = await func.runner(data);
35
17
 
36
- return openai.createChatCompletion({
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
- return new StreamingTextResponse(stream);
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;