@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 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
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#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.56.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": "^2",
133
+ "prettier": "^3",
134
134
  "remark": "^14",
135
135
  "remark-cli": "^11",
136
136
  "semantic-release": "^21",
@@ -3,3 +3,5 @@ export const OPENAI_END_POINT = 'X-OPENAI-END_POINT';
3
3
  export const OPENAI_API_KEY_HEADER_KEY = 'X-OPENAI-API-KEY';
4
4
 
5
5
  export const LOBE_CHAT_ACCESS_CODE = 'X-LOBE_CHAT_ACCESS_CODE';
6
+
7
+ export const LOBE_PLUGIN_SETTINGS = 'X-LOBE_PLUGIN_SETTINGS';
@@ -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, useMemo } from 'react';
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 { AgentAction } from '@/store/session/slices/agentConfig';
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 plugin: ItemGroup = useMemo(() => {
47
- let children;
57
+ const plugins = useSessionStore(agentSelectors.currentAgentPlugins);
48
58
 
49
- const loadingItem = {
50
- avatar: (
51
- <Skeleton
52
- active
53
- avatar={{ size: 40 }}
54
- className={styles.avatar}
55
- paragraph={false}
56
- title={false}
57
- />
58
- ),
59
- label: (
60
- <Skeleton
61
- active
62
- avatar={false}
63
- paragraph={{ className: styles.label, style: { marginBottom: 0 } }}
64
- style={{ width: 300 }}
65
- title={false}
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
- children = !data
71
- ? [loadingItem, loadingItem, loadingItem]
72
- : data.plugins.map((item) => ({
73
- avatar: <Avatar avatar={item.meta.avatar} />,
74
- children: (
75
- <Switch
76
- checked={
77
- // 如果在加载中,说明激活了
78
- pluginManifestLoading[item.identifier] || !config.plugins
79
- ? false
80
- : config.plugins.includes(item.identifier)
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
- loading={pluginManifestLoading[item.identifier]}
83
- onChange={(checked) => {
84
- updateConfig(item.identifier, checked);
85
- if (checked) {
86
- fetchPluginManifest(item.identifier);
87
- }
88
- }}
89
- />
90
- ),
91
- desc: item.meta?.description,
92
- label: t(`plugins.${item.identifier}` as any, { ns: 'plugin' }),
93
- minWidth: undefined,
94
- tag: item.identifier,
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
- return {
98
- children,
99
- icon: ToyBrick,
100
- title: t('settingPlugin.title'),
101
- };
102
- }, [config, data?.plugins, pluginManifestLoading]);
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 <Form items={[plugin]} {...FORM_STYLE} />;
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
- InvalidAccessCode: '密码不正确或为空,请输入正确的访问密码,或者添加自定义 OpenAI API Key',
15
- OpenAIBizError: '请求 OpenAI 服务出错,请根据以下信息排查或重试',
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: {
@@ -128,6 +128,7 @@ export default {
128
128
  },
129
129
  },
130
130
  settingPlugin: {
131
+ config: '{{id}} 插件配置',
131
132
  title: '插件列表',
132
133
  },
133
134
  settingSystem: {
@@ -1,5 +1,5 @@
1
1
  import { getServerConfig } from '@/config/server';
2
- import { ErrorType } from '@/types/fetch';
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: ErrorType.InvalidAccessCode };
21
+ return { auth: false, error: ChatErrorType.InvalidAccessCode };
22
22
  }
23
23
 
24
24
  return { auth: true };
@@ -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 ErrorType.InvalidAccessCode: {
5
+ case ChatErrorType.InvalidAccessCode: {
6
6
  return 401;
7
7
  }
8
8
 
9
- case ErrorType.OpenAIBizError: {
9
+ case ChatErrorType.OpenAIBizError: {
10
10
  return 577;
11
11
  }
12
12
  }
@@ -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 { ErrorType } from '@/types/fetch';
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(ErrorType.GatewayTimeout, { message: error });
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(ErrorType.OpenAIBizError, { ...error, endpoint });
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.settings.defaultAgent.config?.model]);
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, RenderErrorMessage, RenderMessage } from '@lobehub/ui';
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 InvalidAccess from './Error/InvalidAccess';
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');
@@ -1,8 +1,8 @@
1
1
  import { PluginRequestPayload } from '@lobehub/chat-plugin-sdk';
2
2
 
3
- import { LOBE_CHAT_ACCESS_CODE } from '@/const/fetch';
4
- import { PLUGINS_INDEX_URL } from '@/const/url';
5
- import { useGlobalStore } from '@/store/global';
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
- [LOBE_CHAT_ACCESS_CODE]: useGlobalStore.getState().settings.password || '',
27
+ [LOBE_PLUGIN_SETTINGS]: JSON.stringify(settings) || '',
26
28
  },
27
29
  method: 'POST',
28
30
  signal: options?.signal,
29
31
  });
30
32
 
31
- return await res.text();
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
  };
@@ -0,0 +1,10 @@
1
+ import { PLUGINS_INDEX_URL } from '@/const/url';
2
+
3
+ /**
4
+ * 请求插件列表
5
+ */
6
+ export const getPluginList = async () => {
7
+ const res = await fetch(PLUGINS_INDEX_URL);
8
+
9
+ return res.json();
10
+ };
@@ -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.settings.defaultAgent.config);
14
+ merge({}, DEFAULT_AGENT_CONFIG, currentDefaultAgent(s).config);
15
15
 
16
16
  const currentAgentMeta = (s: GlobalStore) =>
17
- merge({}, DEFAULT_AGENT_META, s.settings.defaultAgent.meta);
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/plugin';
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
- const abortController = toggleChatLoading(true, id);
350
- const data = await fetchPlugin(payload, { signal: abortController?.signal });
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
 
@@ -1,25 +1,29 @@
1
- export enum ErrorType {
1
+ /* eslint-disable sort-keys-fix/sort-keys-fix */
2
+ export const ChatErrorType = {
2
3
  // ******* 业务错误语义 ******* //
3
4
 
4
5
  // 密码无效
5
- InvalidAccessCode = 'InvalidAccessCode',
6
+ InvalidAccessCode: 'InvalidAccessCode',
6
7
  // OpenAI 返回的业务错误
7
- OpenAIBizError = 'OpenAIBizError',
8
+ OpenAIBizError: 'OpenAIBizError',
8
9
 
9
10
  // ******* 客户端错误 ******* //
10
- BadRequest = 400,
11
- Unauthorized = 401,
12
- Forbidden = 403,
13
- ContentNotFound = 404, // 没找到接口
14
- MethodNotAllowed = 405, // 不支持
15
- TooManyRequests = 429,
11
+ BadRequest: 400,
12
+ Unauthorized: 401,
13
+ Forbidden: 403,
14
+ ContentNotFound: 404, // 没找到接口
15
+ MethodNotAllowed: 405, // 不支持
16
+ TooManyRequests: 429,
16
17
 
17
18
  // ******* 服务端错误 ******* //
18
- InternalServerError = 500,
19
- BadGateway = 502,
20
- ServiceUnavailable = 503,
21
- GatewayTimeout = 504,
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;
@@ -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
- let chatMessageError: ChatMessageError;
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;