@lobehub/chat 0.56.0 → 0.57.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +46 -0
- package/package.json +2 -2
- package/src/const/fetch.ts +2 -0
- package/src/features/AgentSetting/AgentPlugin/index.tsx +107 -60
- package/src/features/PluginSettings/PluginSettingRender.tsx +65 -0
- package/src/features/PluginSettings/index.tsx +65 -0
- package/src/locales/default/error.ts +30 -2
- package/src/locales/default/setting.ts +1 -0
- package/src/pages/api/auth.ts +2 -2
- package/src/pages/api/error.ts +3 -3
- package/src/pages/api/openai.ts +3 -3
- package/src/pages/chat/SessionList/List/Item/index.tsx +2 -2
- package/src/pages/chat/features/Conversation/ChatList/Error/Plugin/PluginError.tsx +28 -0
- package/src/pages/chat/features/Conversation/ChatList/Error/Plugin/PluginSettings.tsx +61 -0
- package/src/pages/chat/features/Conversation/ChatList/Error/index.tsx +42 -0
- package/src/pages/chat/features/Conversation/ChatList/index.tsx +2 -15
- package/src/services/plugin.ts +11 -14
- package/src/services/pluginMarket.ts +10 -0
- package/src/store/global/selectors.ts +2 -2
- package/src/store/global/store.ts +7 -0
- package/src/store/plugin/action.ts +13 -2
- package/src/store/plugin/initialState.ts +3 -0
- package/src/store/plugin/selectors.ts +4 -0
- package/src/store/plugin/store.ts +2 -1
- package/src/store/session/slices/chat/actions/message.ts +11 -2
- package/src/types/fetch.ts +18 -14
- package/src/utils/fetch.ts +24 -18
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,52 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
+
## [Version 0.57.0](https://github.com/lobehub/lobe-chat/compare/v0.56.0...v0.57.0)
|
|
6
|
+
|
|
7
|
+
<sup>Released on **2023-08-26**</sup>
|
|
8
|
+
|
|
9
|
+
#### ♻ Code Refactoring
|
|
10
|
+
|
|
11
|
+
- **misc**: Refactor to ChatErrorType.
|
|
12
|
+
|
|
13
|
+
#### ✨ Features
|
|
14
|
+
|
|
15
|
+
- **misc**: 完善插件请求的错误处理, 支持修改与记录插件的配置, 支持发送插件配置信息, 支持渲染 manifest 中的 settings, 支持设置不正确时进行插件的配置, 新增插件请求状态的错误处理.
|
|
16
|
+
|
|
17
|
+
#### 🐛 Bug Fixes
|
|
18
|
+
|
|
19
|
+
- **misc**: 修正缓存旧数据的报错问题.
|
|
20
|
+
|
|
21
|
+
<br/>
|
|
22
|
+
|
|
23
|
+
<details>
|
|
24
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
25
|
+
|
|
26
|
+
#### Code refactoring
|
|
27
|
+
|
|
28
|
+
- **misc**: Refactor to ChatErrorType ([cd1a033](https://github.com/lobehub/lobe-chat/commit/cd1a033))
|
|
29
|
+
|
|
30
|
+
#### What's improved
|
|
31
|
+
|
|
32
|
+
- **misc**: 完善插件请求的错误处理 ([0698d89](https://github.com/lobehub/lobe-chat/commit/0698d89))
|
|
33
|
+
- **misc**: 支持修改与记录插件的配置 ([76e8237](https://github.com/lobehub/lobe-chat/commit/76e8237))
|
|
34
|
+
- **misc**: 支持发送插件配置信息 ([2cedc85](https://github.com/lobehub/lobe-chat/commit/2cedc85))
|
|
35
|
+
- **misc**: 支持渲染 manifest 中的 settings ([1185300](https://github.com/lobehub/lobe-chat/commit/1185300))
|
|
36
|
+
- **misc**: 支持设置不正确时进行插件的配置 ([f972481](https://github.com/lobehub/lobe-chat/commit/f972481))
|
|
37
|
+
- **misc**: 新增插件请求状态的错误处理 ([228002a](https://github.com/lobehub/lobe-chat/commit/228002a))
|
|
38
|
+
|
|
39
|
+
#### What's fixed
|
|
40
|
+
|
|
41
|
+
- **misc**: 修正缓存旧数据的报错问题 ([5d8008f](https://github.com/lobehub/lobe-chat/commit/5d8008f))
|
|
42
|
+
|
|
43
|
+
</details>
|
|
44
|
+
|
|
45
|
+
<div align="right">
|
|
46
|
+
|
|
47
|
+
[](#readme-top)
|
|
48
|
+
|
|
49
|
+
</div>
|
|
50
|
+
|
|
5
51
|
## [Version 0.56.0](https://github.com/lobehub/lobe-chat/compare/v0.55.1...v0.56.0)
|
|
6
52
|
|
|
7
53
|
<sup>Released on **2023-08-24**</sup>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobehub/chat",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.57.0",
|
|
4
4
|
"description": "Lobe Chat is an open-source chatbot client using LangChain, Typescript and Next.js",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"chatbot",
|
|
@@ -130,7 +130,7 @@
|
|
|
130
130
|
"lint-staged": "^14",
|
|
131
131
|
"next-pwa": "^5",
|
|
132
132
|
"node-fetch": "^3",
|
|
133
|
-
"prettier": "^
|
|
133
|
+
"prettier": "^3",
|
|
134
134
|
"remark": "^14",
|
|
135
135
|
"remark-cli": "^11",
|
|
136
136
|
"semantic-release": "^21",
|
package/src/const/fetch.ts
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
|
-
import { Avatar, Form, ItemGroup } from '@lobehub/ui';
|
|
1
|
+
import { Avatar, Form, ItemGroup, Markdown } from '@lobehub/ui';
|
|
2
2
|
import { Skeleton, Switch } from 'antd';
|
|
3
3
|
import { createStyles } from 'antd-style';
|
|
4
4
|
import isEqual from 'fast-deep-equal';
|
|
5
|
-
import { ToyBrick } from 'lucide-react';
|
|
6
|
-
import { memo
|
|
5
|
+
import { LucideStore, ToyBrick } from 'lucide-react';
|
|
6
|
+
import { memo } from 'react';
|
|
7
7
|
import { useTranslation } from 'react-i18next';
|
|
8
8
|
|
|
9
9
|
import { FORM_STYLE } from '@/const/layoutTokens';
|
|
10
|
+
import { transformPluginSettings } from '@/features/PluginSettings';
|
|
11
|
+
import PluginSettingRender from '@/features/PluginSettings/PluginSettingRender';
|
|
10
12
|
import { usePluginStore } from '@/store/plugin';
|
|
11
|
-
import {
|
|
13
|
+
import { useSessionStore } from '@/store/session';
|
|
14
|
+
import { AgentAction, agentSelectors } from '@/store/session/slices/agentConfig';
|
|
12
15
|
import { LobeAgentConfig } from '@/types/session';
|
|
13
16
|
|
|
14
|
-
const useStyles = createStyles(({ css }) => ({
|
|
17
|
+
const useStyles = createStyles(({ css, token }) => ({
|
|
15
18
|
avatar: css`
|
|
16
19
|
.ant-skeleton-header {
|
|
17
20
|
padding-right: 0;
|
|
@@ -26,6 +29,11 @@ const useStyles = createStyles(({ css }) => ({
|
|
|
26
29
|
margin-block-start: 12px !important;
|
|
27
30
|
}
|
|
28
31
|
`,
|
|
32
|
+
md: css`
|
|
33
|
+
p {
|
|
34
|
+
color: ${token.colorTextDescription};
|
|
35
|
+
}
|
|
36
|
+
`,
|
|
29
37
|
}));
|
|
30
38
|
|
|
31
39
|
export interface AgentPluginProps {
|
|
@@ -36,72 +44,111 @@ export interface AgentPluginProps {
|
|
|
36
44
|
const AgentPlugin = memo<AgentPluginProps>(({ config, updateConfig }) => {
|
|
37
45
|
const { t } = useTranslation('setting');
|
|
38
46
|
const { styles } = useStyles();
|
|
39
|
-
const [useFetchPluginList, fetchPluginManifest] = usePluginStore((s) => [
|
|
47
|
+
const [useFetchPluginList, fetchPluginManifest, updatePluginSettings] = usePluginStore((s) => [
|
|
40
48
|
s.useFetchPluginList,
|
|
41
49
|
s.fetchPluginManifest,
|
|
50
|
+
s.updatePluginSettings,
|
|
42
51
|
]);
|
|
43
52
|
const pluginManifestLoading = usePluginStore((s) => s.pluginManifestLoading, isEqual);
|
|
53
|
+
const pluginManifestMap = usePluginStore((s) => s.pluginManifestMap, isEqual);
|
|
54
|
+
const pluginsSettings = usePluginStore((s) => s.pluginsSettings, isEqual);
|
|
44
55
|
const { data } = useFetchPluginList();
|
|
45
56
|
|
|
46
|
-
const
|
|
47
|
-
let children;
|
|
57
|
+
const plugins = useSessionStore(agentSelectors.currentAgentPlugins);
|
|
48
58
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
59
|
+
const loadingItem = {
|
|
60
|
+
avatar: (
|
|
61
|
+
<Skeleton
|
|
62
|
+
active
|
|
63
|
+
avatar={{ size: 40 }}
|
|
64
|
+
className={styles.avatar}
|
|
65
|
+
paragraph={false}
|
|
66
|
+
title={false}
|
|
67
|
+
/>
|
|
68
|
+
),
|
|
69
|
+
label: (
|
|
70
|
+
<Skeleton
|
|
71
|
+
active
|
|
72
|
+
avatar={false}
|
|
73
|
+
paragraph={{ className: styles.label, style: { marginBottom: 0 } }}
|
|
74
|
+
style={{ width: 300 }}
|
|
75
|
+
title={false}
|
|
76
|
+
/>
|
|
77
|
+
),
|
|
78
|
+
};
|
|
69
79
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
80
|
+
const children = !data
|
|
81
|
+
? [loadingItem, loadingItem, loadingItem]
|
|
82
|
+
: data.plugins.map(({ identifier, meta }) => ({
|
|
83
|
+
avatar: <Avatar avatar={meta.avatar} />,
|
|
84
|
+
children: (
|
|
85
|
+
<Switch
|
|
86
|
+
checked={
|
|
87
|
+
// 如果在加载中,说明激活了
|
|
88
|
+
pluginManifestLoading[identifier] || !config.plugins
|
|
89
|
+
? false
|
|
90
|
+
: config.plugins.includes(identifier)
|
|
91
|
+
}
|
|
92
|
+
loading={pluginManifestLoading[identifier]}
|
|
93
|
+
onChange={(checked) => {
|
|
94
|
+
updateConfig(identifier, checked);
|
|
95
|
+
if (checked) {
|
|
96
|
+
fetchPluginManifest(identifier);
|
|
81
97
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
98
|
+
}}
|
|
99
|
+
/>
|
|
100
|
+
),
|
|
101
|
+
desc: meta?.description,
|
|
102
|
+
label: meta.title,
|
|
103
|
+
minWidth: undefined,
|
|
104
|
+
tag: identifier,
|
|
105
|
+
}));
|
|
106
|
+
|
|
107
|
+
const pluginsConfig = !data
|
|
108
|
+
? []
|
|
109
|
+
: (plugins
|
|
110
|
+
.map((identifier) => {
|
|
111
|
+
const item = data.plugins.find((i) => i.identifier === identifier);
|
|
112
|
+
|
|
113
|
+
if (!item) return null;
|
|
114
|
+
const manifest = pluginManifestMap[identifier];
|
|
115
|
+
|
|
116
|
+
if (!manifest.settings) return null;
|
|
96
117
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
118
|
+
return {
|
|
119
|
+
children: transformPluginSettings(manifest.settings).map((item) => ({
|
|
120
|
+
...item,
|
|
121
|
+
children: (
|
|
122
|
+
<PluginSettingRender
|
|
123
|
+
defaultValue={pluginsSettings[identifier]?.[item.name]}
|
|
124
|
+
format={item.format}
|
|
125
|
+
onChange={(value) => {
|
|
126
|
+
updatePluginSettings(identifier, { [item.name]: value });
|
|
127
|
+
}}
|
|
128
|
+
type={item.type as any}
|
|
129
|
+
/>
|
|
130
|
+
),
|
|
131
|
+
desc: item.desc && <Markdown className={styles.md}>{item.desc}</Markdown>,
|
|
132
|
+
})),
|
|
133
|
+
icon: ToyBrick,
|
|
134
|
+
title: t('settingPlugin.config', { id: item.meta.title || item.identifier }),
|
|
135
|
+
};
|
|
136
|
+
})
|
|
137
|
+
.filter(Boolean) as unknown as ItemGroup[]);
|
|
103
138
|
|
|
104
|
-
return
|
|
139
|
+
return (
|
|
140
|
+
<Form
|
|
141
|
+
items={[
|
|
142
|
+
{
|
|
143
|
+
children,
|
|
144
|
+
icon: LucideStore,
|
|
145
|
+
title: t('settingPlugin.title'),
|
|
146
|
+
},
|
|
147
|
+
...pluginsConfig,
|
|
148
|
+
]}
|
|
149
|
+
{...FORM_STYLE}
|
|
150
|
+
/>
|
|
151
|
+
);
|
|
105
152
|
});
|
|
106
153
|
|
|
107
154
|
export default AgentPlugin;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Input, InputNumber, Switch } from 'antd';
|
|
2
|
+
import { memo } from 'react';
|
|
3
|
+
|
|
4
|
+
interface PluginSettingsProps {
|
|
5
|
+
defaultValue?: any;
|
|
6
|
+
format?: string;
|
|
7
|
+
onChange: (value: any) => void;
|
|
8
|
+
props?: any;
|
|
9
|
+
type?: 'string' | 'number' | 'boolean';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const PluginSettingRender = memo<PluginSettingsProps>(
|
|
13
|
+
({ type, defaultValue, onChange, format, props }) => {
|
|
14
|
+
switch (type) {
|
|
15
|
+
case 'string': {
|
|
16
|
+
switch (format) {
|
|
17
|
+
case 'password': {
|
|
18
|
+
return (
|
|
19
|
+
<Input.Password
|
|
20
|
+
{...props}
|
|
21
|
+
defaultValue={defaultValue}
|
|
22
|
+
onChange={(v) => {
|
|
23
|
+
onChange(v.target.value);
|
|
24
|
+
}}
|
|
25
|
+
/>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return (
|
|
30
|
+
<Input
|
|
31
|
+
{...props}
|
|
32
|
+
defaultValue={defaultValue}
|
|
33
|
+
onChange={(v) => {
|
|
34
|
+
onChange(v.target.value);
|
|
35
|
+
}}
|
|
36
|
+
/>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
case 'number': {
|
|
40
|
+
return (
|
|
41
|
+
<InputNumber
|
|
42
|
+
{...props}
|
|
43
|
+
defaultValue={defaultValue}
|
|
44
|
+
onChange={(v) => {
|
|
45
|
+
onChange(v);
|
|
46
|
+
}}
|
|
47
|
+
/>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
case 'boolean': {
|
|
51
|
+
return (
|
|
52
|
+
<Switch
|
|
53
|
+
{...props}
|
|
54
|
+
defaultValue={defaultValue}
|
|
55
|
+
onChange={(e) => {
|
|
56
|
+
onChange(e);
|
|
57
|
+
}}
|
|
58
|
+
/>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
export default PluginSettingRender;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { PluginSchema } from '@lobehub/chat-plugin-sdk';
|
|
2
|
+
import { Form, Markdown } from '@lobehub/ui';
|
|
3
|
+
import { createStyles } from 'antd-style';
|
|
4
|
+
import isEqual from 'fast-deep-equal';
|
|
5
|
+
import { memo } from 'react';
|
|
6
|
+
|
|
7
|
+
import { usePluginStore } from '@/store/plugin';
|
|
8
|
+
|
|
9
|
+
import PluginSettingRender from './PluginSettingRender';
|
|
10
|
+
|
|
11
|
+
export const transformPluginSettings = (pluginSettings: PluginSchema) => {
|
|
12
|
+
return Object.entries(pluginSettings.properties).map(([name, i]) => ({
|
|
13
|
+
desc: i.description,
|
|
14
|
+
format: i.format,
|
|
15
|
+
label: i.title,
|
|
16
|
+
name,
|
|
17
|
+
tag: name,
|
|
18
|
+
type: i.type,
|
|
19
|
+
}));
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
interface PluginSettingsConfigProps {
|
|
23
|
+
id: string;
|
|
24
|
+
settings: PluginSchema;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const useStyles = createStyles(({ css, token }) => ({
|
|
28
|
+
md: css`
|
|
29
|
+
p {
|
|
30
|
+
color: ${token.colorTextDescription};
|
|
31
|
+
}
|
|
32
|
+
`,
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
const PluginSettingsConfig = memo<PluginSettingsConfigProps>(({ settings, id }) => {
|
|
36
|
+
const items = transformPluginSettings(settings);
|
|
37
|
+
|
|
38
|
+
const { styles } = useStyles();
|
|
39
|
+
const [updatePluginSettings] = usePluginStore((s) => [s.updatePluginSettings]);
|
|
40
|
+
const pluginSetting = usePluginStore((s) => s.pluginsSettings[id] || {}, isEqual);
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<Form layout={'vertical'}>
|
|
44
|
+
{items.map((item) => (
|
|
45
|
+
<Form.Item
|
|
46
|
+
desc={item.desc && <Markdown className={styles.md}>{item.desc as string}</Markdown>}
|
|
47
|
+
key={item.label}
|
|
48
|
+
label={item.label}
|
|
49
|
+
tag={item.tag}
|
|
50
|
+
>
|
|
51
|
+
<PluginSettingRender
|
|
52
|
+
defaultValue={pluginSetting[item.name]}
|
|
53
|
+
format={item.format}
|
|
54
|
+
onChange={(value) => {
|
|
55
|
+
updatePluginSettings(id, { [item.name]: value });
|
|
56
|
+
}}
|
|
57
|
+
type={item.type as any}
|
|
58
|
+
/>
|
|
59
|
+
</Form.Item>
|
|
60
|
+
))}
|
|
61
|
+
</Form>
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
export default PluginSettingsConfig;
|
|
@@ -1,4 +1,12 @@
|
|
|
1
|
+
import { PluginErrorType } from '@lobehub/chat-plugin-sdk';
|
|
2
|
+
|
|
3
|
+
import { ChatErrorType } from '@/types/fetch';
|
|
4
|
+
|
|
1
5
|
export default {
|
|
6
|
+
pluginSettings: {
|
|
7
|
+
desc: '完成以下配置,即可开始使用该插件',
|
|
8
|
+
title: '{{name}}插件配置',
|
|
9
|
+
},
|
|
2
10
|
response: {
|
|
3
11
|
400: '很抱歉,服务器不明白您的请求,请确认您的请求参数是否正确',
|
|
4
12
|
401: '很抱歉,服务器拒绝了您的请求,可能是因为您的权限不足或未提供有效的身份验证',
|
|
@@ -11,8 +19,28 @@ export default {
|
|
|
11
19
|
503: '很抱歉,服务器当前无法处理您的请求,可能是由于过载或正在进行维护,请稍后再试',
|
|
12
20
|
504: '很抱歉,服务器没有等到上游服务器的回应,请稍后再试',
|
|
13
21
|
|
|
14
|
-
|
|
15
|
-
|
|
22
|
+
[PluginErrorType.PluginMarketIndexNotFound]:
|
|
23
|
+
'很抱歉,服务器没有找到插件索引,请检查索引地址是否正确',
|
|
24
|
+
[PluginErrorType.PluginMarketIndexInvalid]:
|
|
25
|
+
'很抱歉,插件索引校验未通过,请检查索引文件格式是否规范',
|
|
26
|
+
[PluginErrorType.PluginMetaNotFound]:
|
|
27
|
+
'很抱歉,没有在索引中发现该插件,请插件在索引中的配置信息',
|
|
28
|
+
[PluginErrorType.PluginMetaInvalid]:
|
|
29
|
+
'很抱歉,该插件的元信息校验未通过,请检查插件元信息格式是否规范',
|
|
30
|
+
[PluginErrorType.PluginManifestNotFound]:
|
|
31
|
+
'很抱歉,服务器没有找到该插件的描述清单 (manifest.json),请检查插件描述文件地址是否正确',
|
|
32
|
+
[PluginErrorType.PluginManifestInvalid]:
|
|
33
|
+
'很抱歉,该插件的描述清单校验未通过,请检查描述清单格式是否规范',
|
|
34
|
+
[PluginErrorType.PluginApiNotFound]:
|
|
35
|
+
'很抱歉,插件描述清单中不存在该 API ,请检查你的请求方法与插件清单 API 是否匹配',
|
|
36
|
+
[PluginErrorType.PluginApiParamsError]:
|
|
37
|
+
'很抱歉,该插件请求的入参校验未通过,请检查入参与 Api 描述信息是否匹配',
|
|
38
|
+
[PluginErrorType.PluginSettingsInvalid]:
|
|
39
|
+
'很抱歉,该插件需要正确配置后才可以使用,请检查你的配置是否正确',
|
|
40
|
+
|
|
41
|
+
[ChatErrorType.InvalidAccessCode]:
|
|
42
|
+
'密码不正确或为空,请输入正确的访问密码,或者添加自定义 OpenAI API Key',
|
|
43
|
+
[ChatErrorType.OpenAIBizError]: '请求 OpenAI 服务出错,请根据以下信息排查或重试',
|
|
16
44
|
},
|
|
17
45
|
unlock: {
|
|
18
46
|
apikey: {
|
package/src/pages/api/auth.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getServerConfig } from '@/config/server';
|
|
2
|
-
import {
|
|
2
|
+
import { ChatErrorType } from '@/types/fetch';
|
|
3
3
|
|
|
4
4
|
interface AuthConfig {
|
|
5
5
|
accessCode?: string | null;
|
|
@@ -18,7 +18,7 @@ export const checkAuth = ({ apiKey, accessCode }: AuthConfig) => {
|
|
|
18
18
|
if (!ACCESS_CODE) return { auth: true };
|
|
19
19
|
|
|
20
20
|
if (accessCode !== ACCESS_CODE) {
|
|
21
|
-
return { auth: false, error:
|
|
21
|
+
return { auth: false, error: ChatErrorType.InvalidAccessCode };
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
return { auth: true };
|
package/src/pages/api/error.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { ErrorResponse, ErrorType } from '@/types/fetch';
|
|
1
|
+
import { ChatErrorType, ErrorResponse, ErrorType } from '@/types/fetch';
|
|
2
2
|
|
|
3
3
|
const getStatus = (errorType: ErrorType) => {
|
|
4
4
|
switch (errorType) {
|
|
5
|
-
case
|
|
5
|
+
case ChatErrorType.InvalidAccessCode: {
|
|
6
6
|
return 401;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
case
|
|
9
|
+
case ChatErrorType.OpenAIBizError: {
|
|
10
10
|
return 577;
|
|
11
11
|
}
|
|
12
12
|
}
|
package/src/pages/api/openai.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { Configuration, OpenAIApi } from 'openai-edge';
|
|
|
3
3
|
|
|
4
4
|
import { getServerConfig } from '@/config/server';
|
|
5
5
|
import { createErrorResponse } from '@/pages/api/error';
|
|
6
|
-
import {
|
|
6
|
+
import { ChatErrorType } from '@/types/fetch';
|
|
7
7
|
import { OpenAIStreamPayload } from '@/types/openai';
|
|
8
8
|
|
|
9
9
|
// 创建 OpenAI 实例
|
|
@@ -55,7 +55,7 @@ export const createChatCompletion = async ({
|
|
|
55
55
|
response = await openai.createChatCompletion(requestParams);
|
|
56
56
|
} catch (error) {
|
|
57
57
|
// 如果 await 超时报错,说明是 OpenAI 服务端的问题
|
|
58
|
-
return createErrorResponse(
|
|
58
|
+
return createErrorResponse(ChatErrorType.GatewayTimeout, { message: error });
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
// ============ 4. 处理异常响应 ============ //
|
|
@@ -72,7 +72,7 @@ export const createChatCompletion = async ({
|
|
|
72
72
|
error = { message: result };
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
return createErrorResponse(
|
|
75
|
+
return createErrorResponse(ChatErrorType.OpenAIBizError, { ...error, endpoint });
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
// ============ 5. 发送正常相应 ============ //
|
|
@@ -6,7 +6,7 @@ import { memo, useMemo, useRef, useState } from 'react';
|
|
|
6
6
|
import { Flexbox } from 'react-layout-kit';
|
|
7
7
|
import { shallow } from 'zustand/shallow';
|
|
8
8
|
|
|
9
|
-
import { useGlobalStore } from '@/store/global';
|
|
9
|
+
import { settingsSelectors, useGlobalStore } from '@/store/global';
|
|
10
10
|
import { agentSelectors, sessionSelectors, useSessionStore } from '@/store/session';
|
|
11
11
|
|
|
12
12
|
import Actions from './Actions';
|
|
@@ -35,7 +35,7 @@ const SessionItem = memo<SessionItemProps>(({ id }) => {
|
|
|
35
35
|
const isHovering = useHover(ref);
|
|
36
36
|
|
|
37
37
|
const { styles } = useStyles();
|
|
38
|
-
const [defaultModel] = useGlobalStore((s) => [s.
|
|
38
|
+
const [defaultModel] = useGlobalStore((s) => [settingsSelectors.currentAgentConfig(s).model]);
|
|
39
39
|
|
|
40
40
|
const [
|
|
41
41
|
active,
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Highlighter } from '@lobehub/ui';
|
|
2
|
+
import { memo } from 'react';
|
|
3
|
+
import { Flexbox } from 'react-layout-kit';
|
|
4
|
+
|
|
5
|
+
import { useStyles } from '../style';
|
|
6
|
+
|
|
7
|
+
interface OpenAIError {
|
|
8
|
+
code: 'invalid_api_key' | string;
|
|
9
|
+
message: string;
|
|
10
|
+
param?: any;
|
|
11
|
+
type: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface OpenAIErrorResponse {
|
|
15
|
+
error: OpenAIError;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const PluginError = memo<{ content: OpenAIErrorResponse; id: string }>(({ content }) => {
|
|
19
|
+
const { styles } = useStyles();
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<Flexbox className={styles.container} style={{ maxWidth: 600 }}>
|
|
23
|
+
<Highlighter language={'json'}>{JSON.stringify(content, null, 2)}</Highlighter>
|
|
24
|
+
</Flexbox>
|
|
25
|
+
);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
export default PluginError;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Avatar } from '@lobehub/ui';
|
|
2
|
+
import { Button, Divider } from 'antd';
|
|
3
|
+
import { useTheme } from 'antd-style';
|
|
4
|
+
import { memo } from 'react';
|
|
5
|
+
import { useTranslation } from 'react-i18next';
|
|
6
|
+
import { Center, Flexbox } from 'react-layout-kit';
|
|
7
|
+
|
|
8
|
+
import PluginSettingsConfig from '@/features/PluginSettings';
|
|
9
|
+
import { pluginSelectors, usePluginStore } from '@/store/plugin';
|
|
10
|
+
import { useSessionStore } from '@/store/session';
|
|
11
|
+
|
|
12
|
+
import { ErrorActionContainer, useStyles } from '../style';
|
|
13
|
+
|
|
14
|
+
interface PluginSettingsProps {
|
|
15
|
+
id: string;
|
|
16
|
+
pluginIdentifier: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const PluginSettings = memo<PluginSettingsProps>(({ id, pluginIdentifier }) => {
|
|
20
|
+
const { styles } = useStyles();
|
|
21
|
+
|
|
22
|
+
const { t } = useTranslation('error');
|
|
23
|
+
const theme = useTheme();
|
|
24
|
+
const [resend, deleteMessage] = useSessionStore((s) => [s.resendMessage, s.deleteMessage]);
|
|
25
|
+
const plugin = usePluginStore(pluginSelectors.getPluginMetaById(pluginIdentifier));
|
|
26
|
+
const manifest = usePluginStore(pluginSelectors.getPluginManifestSettingsById(pluginIdentifier));
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<ErrorActionContainer>
|
|
30
|
+
<Center gap={16} style={{ maxWidth: 400 }}>
|
|
31
|
+
<Avatar
|
|
32
|
+
avatar={plugin?.meta.avatar || '⚙️'}
|
|
33
|
+
background={theme.colorFillContent}
|
|
34
|
+
gap={12}
|
|
35
|
+
size={80}
|
|
36
|
+
/>
|
|
37
|
+
<Flexbox style={{ fontSize: 20 }}>
|
|
38
|
+
{t('pluginSettings.title', { name: plugin?.meta.title })}
|
|
39
|
+
</Flexbox>
|
|
40
|
+
<Flexbox className={styles.desc}>{t('pluginSettings.desc')}</Flexbox>
|
|
41
|
+
<Divider style={{ margin: '0 16px' }} />
|
|
42
|
+
{manifest.settings && (
|
|
43
|
+
<PluginSettingsConfig id={manifest.identifier} settings={manifest.settings} />
|
|
44
|
+
)}
|
|
45
|
+
<Button
|
|
46
|
+
block
|
|
47
|
+
onClick={() => {
|
|
48
|
+
resend(id);
|
|
49
|
+
deleteMessage(id);
|
|
50
|
+
}}
|
|
51
|
+
style={{ marginTop: 8 }}
|
|
52
|
+
type={'primary'}
|
|
53
|
+
>
|
|
54
|
+
{t('unlock.confirm')}
|
|
55
|
+
</Button>
|
|
56
|
+
</Center>
|
|
57
|
+
</ErrorActionContainer>
|
|
58
|
+
);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
export default PluginSettings;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { IPluginErrorType, PluginErrorType } from '@lobehub/chat-plugin-sdk';
|
|
2
|
+
import { RenderErrorMessage } from '@lobehub/ui';
|
|
3
|
+
|
|
4
|
+
import { ChatMessage } from '@/types/chatMessage';
|
|
5
|
+
import { ChatErrorType, ErrorType } from '@/types/fetch';
|
|
6
|
+
|
|
7
|
+
import InvalidAccess from './InvalidAccess';
|
|
8
|
+
import OpenAiBizError from './OpenAiBizError';
|
|
9
|
+
import PluginError from './Plugin/PluginError';
|
|
10
|
+
import PluginSettings from './Plugin/PluginSettings';
|
|
11
|
+
|
|
12
|
+
export const renderErrorMessage: RenderErrorMessage = (error, message: ChatMessage) => {
|
|
13
|
+
switch (error.type as IPluginErrorType) {
|
|
14
|
+
case PluginErrorType.PluginMarketIndexNotFound:
|
|
15
|
+
case PluginErrorType.PluginMarketIndexInvalid:
|
|
16
|
+
case PluginErrorType.PluginMetaInvalid:
|
|
17
|
+
case PluginErrorType.PluginMetaNotFound:
|
|
18
|
+
case PluginErrorType.PluginManifestInvalid:
|
|
19
|
+
case PluginErrorType.PluginManifestNotFound:
|
|
20
|
+
case PluginErrorType.PluginApiNotFound:
|
|
21
|
+
case PluginErrorType.PluginApiParamsError: {
|
|
22
|
+
return <PluginError content={(error as any).body} id={message.id} />;
|
|
23
|
+
}
|
|
24
|
+
case PluginErrorType.PluginSettingsInvalid: {
|
|
25
|
+
return (
|
|
26
|
+
message.plugin?.identifier && (
|
|
27
|
+
<PluginSettings id={message.id} pluginIdentifier={message.plugin?.identifier} />
|
|
28
|
+
)
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
switch (error.type as ErrorType) {
|
|
34
|
+
case ChatErrorType.InvalidAccessCode: {
|
|
35
|
+
return <InvalidAccess id={message.id} />;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
case ChatErrorType.OpenAIBizError: {
|
|
39
|
+
return <OpenAiBizError content={(error as any).body} id={message.id} />;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ChatList,
|
|
1
|
+
import { ChatList, RenderMessage } from '@lobehub/ui';
|
|
2
2
|
import isEqual from 'fast-deep-equal';
|
|
3
3
|
import { memo, useCallback } from 'react';
|
|
4
4
|
import { useTranslation } from 'react-i18next';
|
|
@@ -11,27 +11,14 @@ import {
|
|
|
11
11
|
useSessionStore,
|
|
12
12
|
} from '@/store/session';
|
|
13
13
|
import { ChatMessage } from '@/types/chatMessage';
|
|
14
|
-
import { ErrorType } from '@/types/fetch';
|
|
15
14
|
import { isFunctionMessage } from '@/utils/message';
|
|
16
15
|
|
|
17
|
-
import
|
|
18
|
-
import OpenAiBizError from './Error/OpenAiBizError';
|
|
16
|
+
import { renderErrorMessage } from './Error';
|
|
19
17
|
import MessageExtra from './MessageExtra';
|
|
20
18
|
import FunctionCall from './Plugins/FunctionCall';
|
|
21
19
|
import PluginMessage from './Plugins/PluginMessage';
|
|
22
20
|
import SkeletonList from './SkeletonList';
|
|
23
21
|
|
|
24
|
-
const renderErrorMessage: RenderErrorMessage = (error, message) => {
|
|
25
|
-
switch (error.type as ErrorType) {
|
|
26
|
-
case 'InvalidAccessCode': {
|
|
27
|
-
return <InvalidAccess id={message.id} />;
|
|
28
|
-
}
|
|
29
|
-
case 'OpenAIBizError': {
|
|
30
|
-
return <OpenAiBizError content={(error as any).body} id={message.id} />;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
|
|
35
22
|
const List = () => {
|
|
36
23
|
const init = useSessionChatInit();
|
|
37
24
|
const { t } = useTranslation('common');
|
package/src/services/plugin.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { PluginRequestPayload } from '@lobehub/chat-plugin-sdk';
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { LOBE_PLUGIN_SETTINGS } from '@/const/fetch';
|
|
4
|
+
import { usePluginStore } from '@/store/plugin';
|
|
5
|
+
import { getMessageError } from '@/utils/fetch';
|
|
6
6
|
|
|
7
7
|
import { URLS } from './url';
|
|
8
8
|
|
|
@@ -12,30 +12,27 @@ interface FetchChatModelOptions {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
15
|
+
* 请求插件结果´
|
|
16
16
|
*/
|
|
17
17
|
export const fetchPlugin = async (
|
|
18
18
|
params: PluginRequestPayload,
|
|
19
19
|
options?: FetchChatModelOptions,
|
|
20
20
|
) => {
|
|
21
|
+
const settings = usePluginStore.getState().pluginsSettings?.[params.identifier];
|
|
22
|
+
|
|
21
23
|
const res = await fetch(URLS.plugins, {
|
|
22
24
|
body: JSON.stringify(params),
|
|
23
25
|
headers: {
|
|
24
26
|
'Content-Type': 'application/json',
|
|
25
|
-
[
|
|
27
|
+
[LOBE_PLUGIN_SETTINGS]: JSON.stringify(settings) || '',
|
|
26
28
|
},
|
|
27
29
|
method: 'POST',
|
|
28
30
|
signal: options?.signal,
|
|
29
31
|
});
|
|
30
32
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
+
if (!res.ok) {
|
|
34
|
+
throw await getMessageError(res);
|
|
35
|
+
}
|
|
33
36
|
|
|
34
|
-
|
|
35
|
-
* 请求插件列表
|
|
36
|
-
*/
|
|
37
|
-
export const getPluginList = async () => {
|
|
38
|
-
const res = await fetch(PLUGINS_INDEX_URL);
|
|
39
|
-
|
|
40
|
-
return res.json();
|
|
37
|
+
return await res.text();
|
|
41
38
|
};
|
|
@@ -11,10 +11,10 @@ const currentSettings = (s: GlobalStore) => merge({}, DEFAULT_SETTINGS, s.settin
|
|
|
11
11
|
const currentDefaultAgent = (s: GlobalStore) => merge({}, DEFAULT_AGENT, s.settings.defaultAgent);
|
|
12
12
|
|
|
13
13
|
const currentAgentConfig = (s: GlobalStore) =>
|
|
14
|
-
merge({}, DEFAULT_AGENT_CONFIG, s.
|
|
14
|
+
merge({}, DEFAULT_AGENT_CONFIG, currentDefaultAgent(s).config);
|
|
15
15
|
|
|
16
16
|
const currentAgentMeta = (s: GlobalStore) =>
|
|
17
|
-
merge({}, DEFAULT_AGENT_META, s.
|
|
17
|
+
merge({}, DEFAULT_AGENT_META, currentDefaultAgent(s).meta);
|
|
18
18
|
|
|
19
19
|
export const exportSettings = (s: GlobalStore) => {
|
|
20
20
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
@@ -3,6 +3,7 @@ import { shallow } from 'zustand/shallow';
|
|
|
3
3
|
import { createWithEqualityFn } from 'zustand/traditional';
|
|
4
4
|
import { StateCreator } from 'zustand/vanilla';
|
|
5
5
|
|
|
6
|
+
import { DEFAULT_AGENT } from '@/const/settings';
|
|
6
7
|
import { isDev } from '@/utils/env';
|
|
7
8
|
|
|
8
9
|
import { type GlobalState, initialState } from './initialState';
|
|
@@ -25,6 +26,12 @@ const createStore: StateCreator<GlobalStore, [['zustand/devtools', never]]> = (.
|
|
|
25
26
|
type GlobalPersist = Pick<GlobalStore, 'preference' | 'settings'>;
|
|
26
27
|
|
|
27
28
|
const persistOptions: PersistOptions<GlobalStore, GlobalPersist> = {
|
|
29
|
+
merge: (persistedState, currentState) => {
|
|
30
|
+
if (!(persistedState as GlobalPersist).settings.defaultAgent) {
|
|
31
|
+
currentState.settings.defaultAgent = DEFAULT_AGENT;
|
|
32
|
+
}
|
|
33
|
+
return currentState;
|
|
34
|
+
},
|
|
28
35
|
name: 'LOBE_SETTINGS',
|
|
29
36
|
partialize: (s) => ({
|
|
30
37
|
preference: s.preference,
|
|
@@ -5,11 +5,11 @@ import {
|
|
|
5
5
|
} from '@lobehub/chat-plugin-sdk';
|
|
6
6
|
import { message } from 'antd';
|
|
7
7
|
import { produce } from 'immer';
|
|
8
|
-
import { uniq } from 'lodash-es';
|
|
8
|
+
import { merge, uniq } from 'lodash-es';
|
|
9
9
|
import useSWR, { SWRResponse } from 'swr';
|
|
10
10
|
import { StateCreator } from 'zustand/vanilla';
|
|
11
11
|
|
|
12
|
-
import { getPluginList } from '@/services/
|
|
12
|
+
import { getPluginList } from '@/services/pluginMarket';
|
|
13
13
|
import { pluginSelectors } from '@/store/plugin/selectors';
|
|
14
14
|
import { LobeSessions } from '@/types/session';
|
|
15
15
|
import { setNamespace } from '@/utils/storeDebug';
|
|
@@ -27,6 +27,7 @@ export interface PluginAction {
|
|
|
27
27
|
dispatchPluginManifest: (payload: PluginDispatch) => void;
|
|
28
28
|
fetchPluginManifest: (name: string) => Promise<void>;
|
|
29
29
|
updateManifestLoadingState: (key: string, value: boolean | undefined) => void;
|
|
30
|
+
updatePluginSettings: <T>(id: string, settings: Partial<T>) => void;
|
|
30
31
|
useFetchPluginList: () => SWRResponse<LobeChatPluginsMarketIndex>;
|
|
31
32
|
}
|
|
32
33
|
|
|
@@ -112,6 +113,16 @@ export const createPluginSlice: StateCreator<
|
|
|
112
113
|
t('updateManifestLoadingState'),
|
|
113
114
|
);
|
|
114
115
|
},
|
|
116
|
+
|
|
117
|
+
updatePluginSettings: (id, settings) => {
|
|
118
|
+
set(
|
|
119
|
+
produce((draft) => {
|
|
120
|
+
draft.pluginsSettings[id] = merge({}, draft.pluginsSettings[id], settings);
|
|
121
|
+
}),
|
|
122
|
+
false,
|
|
123
|
+
t('updatePluginSettings'),
|
|
124
|
+
);
|
|
125
|
+
},
|
|
115
126
|
useFetchPluginList: () =>
|
|
116
127
|
useSWR<LobeChatPluginsMarketIndex>('fetchPluginList', getPluginList, {
|
|
117
128
|
onSuccess: (pluginMarketIndex) => {
|
|
@@ -3,12 +3,14 @@ import { LobeChatPluginMeta } from '@lobehub/chat-plugin-sdk';
|
|
|
3
3
|
import { PluginManifestMap } from '@/types/plugin';
|
|
4
4
|
|
|
5
5
|
export type PluginManifestLoadingState = Record<string, boolean>;
|
|
6
|
+
export type PluginsSettings = Record<string, any>;
|
|
6
7
|
|
|
7
8
|
export interface PluginStoreState {
|
|
8
9
|
manifestPrepared: boolean;
|
|
9
10
|
pluginList: LobeChatPluginMeta[];
|
|
10
11
|
pluginManifestLoading: PluginManifestLoadingState;
|
|
11
12
|
pluginManifestMap: PluginManifestMap;
|
|
13
|
+
pluginsSettings: PluginsSettings;
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
export const initialState: PluginStoreState = {
|
|
@@ -16,4 +18,5 @@ export const initialState: PluginStoreState = {
|
|
|
16
18
|
pluginList: [],
|
|
17
19
|
pluginManifestLoading: {},
|
|
18
20
|
pluginManifestMap: {},
|
|
21
|
+
pluginsSettings: {},
|
|
19
22
|
};
|
|
@@ -25,7 +25,11 @@ const enabledSchema =
|
|
|
25
25
|
const getPluginMetaById = (id: string) => (s: PluginStoreState) =>
|
|
26
26
|
s.pluginList?.find((p) => p.identifier === id);
|
|
27
27
|
|
|
28
|
+
const getPluginManifestSettingsById = (id: string) => (s: PluginStoreState) =>
|
|
29
|
+
s.pluginManifestMap[id];
|
|
30
|
+
|
|
28
31
|
export const pluginSelectors = {
|
|
29
32
|
enabledSchema,
|
|
33
|
+
getPluginManifestSettingsById,
|
|
30
34
|
getPluginMetaById,
|
|
31
35
|
};
|
|
@@ -19,7 +19,7 @@ const createStore: StateCreator<PluginStore, [['zustand/devtools', never]]> = (.
|
|
|
19
19
|
|
|
20
20
|
// =============== persist 本地缓存中间件配置 ============ //
|
|
21
21
|
|
|
22
|
-
type SessionPersist = Pick<PluginStore, 'pluginList' | 'pluginManifestMap'>;
|
|
22
|
+
type SessionPersist = Pick<PluginStore, 'pluginList' | 'pluginManifestMap' | 'pluginsSettings'>;
|
|
23
23
|
|
|
24
24
|
const storeName = 'LOBE_PLUGIN';
|
|
25
25
|
|
|
@@ -29,6 +29,7 @@ const persistOptions: PersistOptions<PluginStore, SessionPersist> = {
|
|
|
29
29
|
partialize: (s) => ({
|
|
30
30
|
pluginList: s.pluginList,
|
|
31
31
|
pluginManifestMap: s.pluginManifestMap,
|
|
32
|
+
pluginsSettings: s.pluginsSettings,
|
|
32
33
|
}),
|
|
33
34
|
|
|
34
35
|
// 手动控制 Hydration ,避免 ssr 报错
|
|
@@ -346,9 +346,18 @@ export const chatMessage: StateCreator<
|
|
|
346
346
|
dispatchMessage({ id, key: 'name', type: 'updateMessage', value: payload.identifier });
|
|
347
347
|
dispatchMessage({ id, key: 'plugin', type: 'updateMessage', value: payload });
|
|
348
348
|
|
|
349
|
-
|
|
350
|
-
|
|
349
|
+
let data: string;
|
|
350
|
+
try {
|
|
351
|
+
const abortController = toggleChatLoading(true, id);
|
|
352
|
+
data = await fetchPlugin(payload, { signal: abortController?.signal });
|
|
353
|
+
} catch (error) {
|
|
354
|
+
dispatchMessage({ id, key: 'error', type: 'updateMessage', value: error });
|
|
355
|
+
|
|
356
|
+
data = '';
|
|
357
|
+
}
|
|
351
358
|
toggleChatLoading(false);
|
|
359
|
+
// 如果报错则结束了
|
|
360
|
+
if (!data) return;
|
|
352
361
|
|
|
353
362
|
dispatchMessage({ id, key: 'content', type: 'updateMessage', value: data });
|
|
354
363
|
|
package/src/types/fetch.ts
CHANGED
|
@@ -1,25 +1,29 @@
|
|
|
1
|
-
|
|
1
|
+
/* eslint-disable sort-keys-fix/sort-keys-fix */
|
|
2
|
+
export const ChatErrorType = {
|
|
2
3
|
// ******* 业务错误语义 ******* //
|
|
3
4
|
|
|
4
5
|
// 密码无效
|
|
5
|
-
InvalidAccessCode
|
|
6
|
+
InvalidAccessCode: 'InvalidAccessCode',
|
|
6
7
|
// OpenAI 返回的业务错误
|
|
7
|
-
OpenAIBizError
|
|
8
|
+
OpenAIBizError: 'OpenAIBizError',
|
|
8
9
|
|
|
9
10
|
// ******* 客户端错误 ******* //
|
|
10
|
-
BadRequest
|
|
11
|
-
Unauthorized
|
|
12
|
-
Forbidden
|
|
13
|
-
ContentNotFound
|
|
14
|
-
MethodNotAllowed
|
|
15
|
-
TooManyRequests
|
|
11
|
+
BadRequest: 400,
|
|
12
|
+
Unauthorized: 401,
|
|
13
|
+
Forbidden: 403,
|
|
14
|
+
ContentNotFound: 404, // 没找到接口
|
|
15
|
+
MethodNotAllowed: 405, // 不支持
|
|
16
|
+
TooManyRequests: 429,
|
|
16
17
|
|
|
17
18
|
// ******* 服务端错误 ******* //
|
|
18
|
-
InternalServerError
|
|
19
|
-
BadGateway
|
|
20
|
-
ServiceUnavailable
|
|
21
|
-
GatewayTimeout
|
|
22
|
-
}
|
|
19
|
+
InternalServerError: 500,
|
|
20
|
+
BadGateway: 502,
|
|
21
|
+
ServiceUnavailable: 503,
|
|
22
|
+
GatewayTimeout: 504,
|
|
23
|
+
} as const;
|
|
24
|
+
/* eslint-enable */
|
|
25
|
+
|
|
26
|
+
export type ErrorType = (typeof ChatErrorType)[keyof typeof ChatErrorType];
|
|
23
27
|
|
|
24
28
|
export interface ErrorResponse {
|
|
25
29
|
body: any;
|
package/src/utils/fetch.ts
CHANGED
|
@@ -2,7 +2,29 @@ import { t } from 'i18next';
|
|
|
2
2
|
|
|
3
3
|
import { fetchChatModel } from '@/services/chatModel';
|
|
4
4
|
import { ChatMessageError } from '@/types/chatMessage';
|
|
5
|
-
import { ErrorResponse } from '@/types/fetch';
|
|
5
|
+
import { ErrorResponse, ErrorType } from '@/types/fetch';
|
|
6
|
+
|
|
7
|
+
export const getMessageError = async (response: Response) => {
|
|
8
|
+
let chatMessageError: ChatMessageError;
|
|
9
|
+
|
|
10
|
+
// 尝试取一波业务错误语义
|
|
11
|
+
try {
|
|
12
|
+
const data = (await response.json()) as ErrorResponse;
|
|
13
|
+
chatMessageError = {
|
|
14
|
+
body: data.body,
|
|
15
|
+
message: t(`response.${data.errorType}`),
|
|
16
|
+
type: data.errorType,
|
|
17
|
+
};
|
|
18
|
+
} catch {
|
|
19
|
+
// 如果无法正常返回,说明是常规报错
|
|
20
|
+
chatMessageError = {
|
|
21
|
+
message: t(`response.${response.status}`),
|
|
22
|
+
type: response.status as ErrorType,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return chatMessageError;
|
|
27
|
+
};
|
|
6
28
|
|
|
7
29
|
export interface FetchSSEOptions {
|
|
8
30
|
onErrorHandle?: (error: ChatMessageError) => void;
|
|
@@ -19,23 +41,7 @@ export const fetchSSE = async (fetchFn: () => Promise<Response>, options: FetchS
|
|
|
19
41
|
|
|
20
42
|
// 如果不 ok 说明有请求错误
|
|
21
43
|
if (!response.ok) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
// 尝试取一波业务错误语义
|
|
25
|
-
try {
|
|
26
|
-
const data = (await response.json()) as ErrorResponse;
|
|
27
|
-
chatMessageError = {
|
|
28
|
-
body: data.body,
|
|
29
|
-
message: t(`response.${data.errorType}`),
|
|
30
|
-
type: data.errorType,
|
|
31
|
-
};
|
|
32
|
-
} catch {
|
|
33
|
-
// 如果无法正常返回,说明是常规报错
|
|
34
|
-
chatMessageError = {
|
|
35
|
-
message: t(`response.${response.status}`),
|
|
36
|
-
type: response.status,
|
|
37
|
-
};
|
|
38
|
-
}
|
|
44
|
+
const chatMessageError = await getMessageError(response);
|
|
39
45
|
|
|
40
46
|
options.onErrorHandle?.(chatMessageError);
|
|
41
47
|
return;
|