@lobehub/chat 0.65.1 → 0.67.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/locales/ru_RU/common.json +85 -0
  3. package/locales/ru_RU/empty.json +6 -0
  4. package/locales/ru_RU/error.json +41 -0
  5. package/locales/ru_RU/plugin.json +111 -0
  6. package/locales/ru_RU/setting.json +175 -0
  7. package/locales/ru_RU/welcome.json +14 -0
  8. package/package.json +1 -1
  9. package/src/features/AgentSetting/AgentPlugin/LocalPluginItem.tsx +10 -8
  10. package/src/features/AgentSetting/AgentPlugin/MarketList.tsx +29 -13
  11. package/src/features/PluginDevModal/ManifestForm.tsx +150 -86
  12. package/src/features/PluginDevModal/MetaForm.tsx +16 -17
  13. package/src/features/PluginDevModal/PluginPreview.tsx +9 -8
  14. package/src/features/PluginDevModal/index.tsx +26 -23
  15. package/src/locales/default/plugin.ts +9 -5
  16. package/src/locales/options.ts +4 -0
  17. package/src/locales/resources/index.ts +2 -0
  18. package/src/locales/resources/ru_RU.ts +17 -0
  19. package/src/store/plugin/initialState.ts +5 -34
  20. package/src/store/plugin/selectors.ts +2 -2
  21. package/src/store/plugin/slices/customPlugin/action.ts +64 -0
  22. package/src/store/plugin/slices/customPlugin/index.ts +3 -0
  23. package/src/store/plugin/slices/customPlugin/initialState.ts +16 -0
  24. package/src/store/plugin/{reducers/devPluginList.ts → slices/customPlugin/reducers/customPluginList.ts} +6 -6
  25. package/src/store/plugin/{action.ts → slices/plugin/action.ts} +13 -25
  26. package/src/store/plugin/slices/plugin/index.ts +3 -0
  27. package/src/store/plugin/slices/plugin/initialState.ts +22 -0
  28. package/src/store/plugin/store.ts +6 -4
  29. package/src/types/plugin.ts +8 -1
  30. /package/src/store/plugin/{reducers → slices/plugin/reducers}/manifest.ts +0 -0
@@ -41,22 +41,33 @@ const MarketList = memo(() => {
41
41
  // setModal(true);
42
42
  // }, []);
43
43
 
44
- const updateConfig = useStore((s) => s.toggleAgentPlugin);
45
- const [plugins, hasPlugin] = useStore((s) => [s.config.plugins || [], !!s.config.plugins]);
44
+ const [plugins, hasPlugin, toggleAgentPlugin] = useStore((s) => [
45
+ s.config.plugins || [],
46
+ !!s.config.plugins,
47
+ s.toggleAgentPlugin,
48
+ ]);
46
49
 
47
50
  const [useFetchPluginList, fetchPluginManifest, saveToDevList, updateNewDevPlugin] =
48
51
  usePluginStore((s) => [
49
52
  s.useFetchPluginList,
50
53
  s.fetchPluginManifest,
51
- s.saveToDevList,
52
- s.updateNewDevPlugin,
54
+ s.saveToCustomPluginList,
55
+ s.updateNewCustomPlugin,
53
56
  ]);
54
57
  const pluginManifestLoading = usePluginStore((s) => s.pluginManifestLoading, isEqual);
55
58
  const pluginList = usePluginStore((s) => s.pluginList, isEqual);
56
- const devPluginList = usePluginStore((s) => s.devPluginList, isEqual);
59
+ const customPluginList = usePluginStore((s) => s.customPluginList, isEqual);
57
60
 
58
61
  useFetchPluginList();
59
62
 
63
+ const togglePlugin = async (pluginId: string, fetchManifest?: boolean) => {
64
+ toggleAgentPlugin(pluginId);
65
+ if (fetchManifest) {
66
+ await fetchPluginManifest(pluginId);
67
+ }
68
+ };
69
+
70
+ // =========== Skeleton Loading =========== //
60
71
  const loadingItem = {
61
72
  avatar: (
62
73
  <Skeleton
@@ -77,11 +88,12 @@ const MarketList = memo(() => {
77
88
  />
78
89
  ),
79
90
  };
80
-
81
91
  const loadingList = [loadingItem, loadingItem, loadingItem];
82
92
 
83
93
  const isEmpty = pluginList.length === 0;
84
94
 
95
+ // =========== Plugin List =========== //
96
+
85
97
  const list = pluginList.map(({ identifier, meta }) => ({
86
98
  avatar: <Avatar avatar={meta.avatar} />,
87
99
  children: (
@@ -92,10 +104,7 @@ const MarketList = memo(() => {
92
104
  }
93
105
  loading={pluginManifestLoading[identifier]}
94
106
  onChange={(checked) => {
95
- updateConfig(identifier);
96
- if (checked) {
97
- fetchPluginManifest(identifier);
98
- }
107
+ togglePlugin(identifier, checked);
99
108
  }}
100
109
  />
101
110
  ),
@@ -105,7 +114,9 @@ const MarketList = memo(() => {
105
114
  tag: identifier,
106
115
  }));
107
116
 
108
- const devList = devPluginList.map(({ identifier, meta }) => ({
117
+ // =========== Custom Plugin List =========== //
118
+
119
+ const customList = customPluginList.map(({ identifier, meta }) => ({
109
120
  avatar: <Avatar avatar={pluginHelpers.getPluginAvatar(meta) || '🧩'} />,
110
121
  children: <LocalPluginItem id={identifier} />,
111
122
  desc: pluginHelpers.getPluginDesc(meta),
@@ -125,14 +136,19 @@ const MarketList = memo(() => {
125
136
  <>
126
137
  <DevModal
127
138
  onOpenChange={setModal}
128
- onSave={saveToDevList}
139
+ onSave={async (devPlugin) => {
140
+ // 先保存
141
+ saveToDevList(devPlugin);
142
+ // 再开启插件
143
+ await togglePlugin(devPlugin.identifier, true);
144
+ }}
129
145
  onValueChange={updateNewDevPlugin}
130
146
  open={showModal}
131
147
  />
132
148
  <Form
133
149
  items={[
134
150
  {
135
- children: isEmpty ? loadingList : [...devList, ...list],
151
+ children: isEmpty ? loadingList : [...customList, ...list],
136
152
  extra: (
137
153
  <Tooltip title={t('settingPlugin.addTooltip')}>
138
154
  <Button
@@ -1,96 +1,160 @@
1
- import { pluginManifestSchema } from '@lobehub/chat-plugin-sdk';
2
- import { Form, FormItemProps, Input, Tooltip } from '@lobehub/ui';
3
- import { FormInstance, Radio } from 'antd';
4
- import { memo } from 'react';
1
+ import { LobeChatPluginManifest, pluginManifestSchema } from '@lobehub/chat-plugin-sdk';
2
+ import { ActionIcon, Form, FormItemProps, Highlighter, Input, Tooltip } from '@lobehub/ui';
3
+ import { FormInstance, Popover, Radio } from 'antd';
4
+ import { FileCode, RotateCwIcon } from 'lucide-react';
5
+ import { memo, useState } from 'react';
5
6
  import { useTranslation } from 'react-i18next';
7
+ import { Flexbox } from 'react-layout-kit';
6
8
 
7
- const ManifestForm = memo<{ form: FormInstance; mode: 'url' | 'local' }>(({ form, mode }) => {
8
- const { t } = useTranslation('plugin');
9
+ const ManifestForm = memo<{ form: FormInstance; mode?: 'url' | 'local' }>(
10
+ ({ form, mode = 'url' }) => {
11
+ const { t } = useTranslation('plugin');
9
12
 
10
- const isUrl = mode === 'url';
13
+ const [manifest, setManifest] = useState<LobeChatPluginManifest>();
11
14
 
12
- const configItem: FormItemProps[] = isUrl
13
- ? [
14
- {
15
- children: <Input placeholder={'http://localhost/manifest.json'} />,
16
- desc: t('dev.meta.manifest.desc'),
17
- hasFeedback: true,
18
- label: t('dev.meta.manifest.label'),
19
- name: 'manifest',
20
- required: true,
21
- rules: [
22
- { required: true },
23
- {
24
- message: t('dev.meta.manifest.urlError'),
25
- pattern: /^https?:\/\/.*/,
26
- },
27
- {
28
- message: t('dev.meta.manifest.invalid'),
29
- validator: async (_, value) => {
30
- const res = await fetch(value);
31
- if (!res.ok) return true;
15
+ const isUrl = mode === 'url';
32
16
 
33
- const json = await res.json();
34
- pluginManifestSchema.parse(json);
17
+ const configItem: FormItemProps[] = isUrl
18
+ ? [
19
+ {
20
+ children: (
21
+ <Input
22
+ placeholder={'http://localhost:3400/manifest-dev.json'}
23
+ suffix={
24
+ manifest && (
25
+ <ActionIcon
26
+ icon={RotateCwIcon}
27
+ onClick={(e) => {
28
+ e.stopPropagation();
29
+ form.validateFields(['manifest']);
30
+ }}
31
+ size={'small'}
32
+ title={t('dev.meta.manifest.refresh')}
33
+ />
34
+ )
35
+ }
36
+ />
37
+ ),
38
+ extra: (
39
+ <Flexbox horizontal justify={'space-between'} style={{ marginTop: 8 }}>
40
+ {t('dev.meta.manifest.desc')}
41
+ {manifest && (
42
+ <Popover
43
+ arrow={false}
44
+ content={
45
+ <Highlighter language={'json'}>
46
+ {JSON.stringify(manifest, null, 2)}
47
+ </Highlighter>
48
+ }
49
+ placement={'right'}
50
+ style={{ width: 400 }}
51
+ title={'Manifest JSON'}
52
+ trigger={'click'}
53
+ >
54
+ <ActionIcon
55
+ icon={FileCode}
56
+ size={'small'}
57
+ title={t('dev.meta.manifest.preview')}
58
+ />
59
+ </Popover>
60
+ )}
61
+ </Flexbox>
62
+ ),
63
+ // extra: <div>123</div>,
64
+ hasFeedback: true,
65
+ label: t('dev.meta.manifest.label'),
66
+ name: 'manifest',
67
+ required: true,
68
+ rules: [
69
+ { required: true },
70
+ {
71
+ message: t('dev.meta.manifest.urlError'),
72
+ pattern: /^https?:\/\/.*/,
35
73
  },
36
- },
37
- ],
38
- },
39
- ]
40
- : // TODO: 后续做成本地配置模式
41
- [
42
- {
43
- children: <Input placeholder={'searchEngine'} />,
44
- desc: t('dev.meta.identifier.desc'),
45
- label: t('dev.meta.identifier.label'),
46
- name: 'name',
47
- required: true,
48
- },
74
+ {
75
+ // message: t('dev.meta.manifest.invalid'),
76
+ validator: async (_, value) => {
77
+ if (!value) return true;
49
78
 
50
- {
51
- children: <Input placeholder={t('dev.meta.description.placeholder')} />,
52
- desc: t('dev.meta.description.desc'),
53
- label: t('dev.meta.description.label'),
54
- name: 'description',
55
- required: true,
56
- },
57
- {
58
- children: <Input placeholder={'searchEngine'} />,
59
- desc: t('dev.meta.identifier.desc'),
60
- label: t('dev.meta.identifier.label'),
61
- name: 'identifier',
62
- required: true,
63
- },
64
- ];
79
+ let res: Response;
65
80
 
66
- return (
67
- <Form
68
- form={form}
69
- items={[
70
- {
71
- children: configItem,
72
- extra: (
73
- <Radio.Group
74
- onChange={(v) => {
75
- form.setFieldValue('manifestMode', v.target.value);
76
- }}
77
- size={'small'}
78
- value={mode}
79
- >
80
- <Radio.Button value={'url'}>{t('dev.manifest.mode.url')}</Radio.Button>
81
- <Tooltip title={t('dev.manifest.mode.local-tooltip')}>
82
- <Radio.Button disabled value={'local'}>
83
- {t('dev.manifest.mode.local')}
84
- </Radio.Button>
85
- </Tooltip>
86
- </Radio.Group>
87
- ),
88
- title: t('dev.tabs.manifest'),
89
- },
90
- ]}
91
- layout={isUrl ? 'vertical' : undefined}
92
- />
93
- );
94
- });
81
+ try {
82
+ res = await fetch(value);
83
+ } catch {
84
+ throw t('dev.meta.manifest.requestError');
85
+ }
86
+
87
+ const json = await res.json().catch(() => {
88
+ throw t('dev.meta.manifest.urlError');
89
+ });
90
+
91
+ const valid = pluginManifestSchema.safeParse(json);
92
+ if (!valid.success) {
93
+ throw t('dev.meta.manifest.jsonInvalid', { error: valid.error });
94
+ }
95
+
96
+ setManifest(json);
97
+ form.setFieldValue('identifier', valid.data.identifier);
98
+ },
99
+ },
100
+ ],
101
+ },
102
+ ]
103
+ : // TODO: 后续做成本地配置模式
104
+ [
105
+ {
106
+ children: <Input placeholder={'searchEngine'} />,
107
+ desc: t('dev.meta.identifier.desc'),
108
+ label: t('dev.meta.identifier.label'),
109
+ name: 'name',
110
+ required: true,
111
+ },
112
+
113
+ {
114
+ children: <Input placeholder={t('dev.meta.description.placeholder')} />,
115
+ desc: t('dev.meta.description.desc'),
116
+ label: t('dev.meta.description.label'),
117
+ name: 'description',
118
+ required: true,
119
+ },
120
+ {
121
+ children: <Input placeholder={'searchEngine'} />,
122
+ desc: t('dev.meta.identifier.desc'),
123
+ label: t('dev.meta.identifier.label'),
124
+ name: 'identifier',
125
+ required: true,
126
+ },
127
+ ];
128
+
129
+ return (
130
+ <Form
131
+ form={form}
132
+ items={[
133
+ {
134
+ children: configItem,
135
+ extra: (
136
+ <Radio.Group
137
+ onChange={(v) => {
138
+ form.setFieldValue('manifestMode', v.target.value);
139
+ }}
140
+ size={'small'}
141
+ value={mode}
142
+ >
143
+ <Radio.Button value={'url'}>{t('dev.manifest.mode.url')}</Radio.Button>
144
+ <Tooltip title={t('dev.manifest.mode.local-tooltip')}>
145
+ <Radio.Button disabled value={'local'}>
146
+ {t('dev.manifest.mode.local')}
147
+ </Radio.Button>
148
+ </Tooltip>
149
+ </Radio.Group>
150
+ ),
151
+ title: t('dev.tabs.manifest'),
152
+ },
153
+ ]}
154
+ layout={isUrl ? 'vertical' : undefined}
155
+ />
156
+ );
157
+ },
158
+ );
95
159
 
96
160
  export default ManifestForm;
@@ -1,24 +1,20 @@
1
1
  import { Form, FormItemProps, Input } from '@lobehub/ui';
2
2
  import { FormInstance } from 'antd';
3
- import { memo, useEffect } from 'react';
3
+ import { memo } from 'react';
4
4
  import { useTranslation } from 'react-i18next';
5
5
 
6
6
  import EmojiPicker from '@/components/EmojiPicker';
7
7
  import { pluginSelectors, usePluginStore } from '@/store/plugin';
8
8
 
9
- const MetaForm = memo<{ form: FormInstance }>(({ form }) => {
9
+ const MetaForm = memo<{ form: FormInstance; mode?: 'edit' | 'create' }>(({ form, mode }) => {
10
+ const isEditMode = mode === 'edit';
11
+
10
12
  const { t } = useTranslation('plugin');
11
13
  const [plugins] = usePluginStore((s) => [pluginSelectors.pluginList(s).map((i) => i.identifier)]);
12
14
 
13
- useEffect(() => {
14
- if (usePluginStore.getState().newDevPlugin) {
15
- form.setFieldsValue(usePluginStore.getState().newDevPlugin);
16
- }
17
- }, []);
18
-
19
15
  const configItem: FormItemProps[] = [
20
16
  {
21
- children: <Input placeholder={'searchEngine'} />,
17
+ children: <Input disabled placeholder={'searchEngine'} />,
22
18
  desc: t('dev.meta.identifier.desc'),
23
19
  label: t('dev.meta.identifier.label'),
24
20
  name: 'identifier',
@@ -28,14 +24,17 @@ const MetaForm = memo<{ form: FormInstance }>(({ form }) => {
28
24
  message: t('dev.meta.identifier.pattenErrorMessage'),
29
25
  pattern: /^[\w-]+$/,
30
26
  },
31
- {
32
- message: t('dev.meta.identifier.errorDuplicate'),
33
- validator: async (_, value) => {
34
- if (plugins.includes(value)) {
35
- throw new Error('Duplicate');
36
- }
37
- },
38
- },
27
+ // 编辑模式下,不进行重复校验
28
+ isEditMode
29
+ ? {}
30
+ : {
31
+ message: t('dev.meta.identifier.errorDuplicate'),
32
+ validator: async (_, value) => {
33
+ if (plugins.includes(value)) {
34
+ throw new Error('Duplicate');
35
+ }
36
+ },
37
+ },
39
38
  ],
40
39
  },
41
40
  {
@@ -1,29 +1,30 @@
1
1
  import { Avatar, Form } from '@lobehub/ui';
2
- import { Card, Switch, Tag } from 'antd';
2
+ import { Form as AForm, Card, FormInstance, Switch, Tag } from 'antd';
3
3
  import { memo } from 'react';
4
4
  import { useTranslation } from 'react-i18next';
5
5
  import { Flexbox } from 'react-layout-kit';
6
6
 
7
- import { pluginHelpers, usePluginStore } from '@/store/plugin';
7
+ import { pluginHelpers } from '@/store/plugin';
8
+ import { CustomPlugin } from '@/types/plugin';
8
9
 
9
- const PluginPreview = memo(() => {
10
+ const PluginPreview = memo<{ form: FormInstance }>(({ form }) => {
10
11
  const { t } = useTranslation('plugin');
11
- const meta = usePluginStore((s) => s.newDevPlugin);
12
+ const plugin: CustomPlugin = AForm.useWatch([], form);
12
13
 
13
14
  const items = {
14
- avatar: <Avatar avatar={pluginHelpers.getPluginAvatar(meta?.meta) || '🧩'} />,
15
+ avatar: <Avatar avatar={pluginHelpers.getPluginAvatar(plugin?.meta) || '🧩'} />,
15
16
  children: <Switch disabled />,
16
- desc: pluginHelpers.getPluginDesc(meta?.meta),
17
+ desc: pluginHelpers.getPluginDesc(plugin?.meta),
17
18
  label: (
18
19
  <Flexbox align={'center'} gap={8} horizontal>
19
- {pluginHelpers.getPluginTitle(meta?.meta) ?? t('dev.preview.title')}
20
+ {pluginHelpers.getPluginTitle(plugin?.meta) ?? t('dev.preview.title')}
20
21
  <Tag bordered={false} color={'gold'}>
21
22
  {t('list.item.local.title', { ns: 'plugin' })}
22
23
  </Tag>
23
24
  </Flexbox>
24
25
  ),
25
26
  minWidth: undefined,
26
- tag: !!meta?.identifier ? meta?.identifier : 'id',
27
+ tag: !!plugin?.identifier ? plugin?.identifier : 'id',
27
28
  };
28
29
 
29
30
  return (
@@ -1,13 +1,13 @@
1
1
  import { Icon } from '@lobehub/ui';
2
2
  import { App, Button, ConfigProvider, Form, Modal, Popconfirm } from 'antd';
3
- import { createStyles, useTheme } from 'antd-style';
3
+ import { createStyles } from 'antd-style';
4
4
  import { LucideBlocks } from 'lucide-react';
5
5
  import { lighten } from 'polished';
6
- import { memo, useEffect } from 'react';
6
+ import { memo, useEffect, useState } from 'react';
7
7
  import { useTranslation } from 'react-i18next';
8
8
  import { Flexbox } from 'react-layout-kit';
9
9
 
10
- import { DevPlugin } from '@/store/plugin/initialState';
10
+ import { CustomPlugin } from '@/types/plugin';
11
11
 
12
12
  import ManifestForm from './ManifestForm';
13
13
  import MetaForm from './MetaForm';
@@ -26,33 +26,31 @@ const useStyles = createStyles(({ css, token, prefixCls }) => ({
26
26
  }));
27
27
 
28
28
  interface DevModalProps {
29
+ mode?: 'edit' | 'create';
29
30
  onDelete?: () => void;
30
31
  onOpenChange: (open: boolean) => void;
31
- onSave?: (value: DevPlugin) => void;
32
- onValueChange?: (value: Partial<DevPlugin>) => void;
32
+ onSave?: (value: CustomPlugin) => Promise<void> | void;
33
+ onValueChange?: (value: Partial<CustomPlugin>) => void;
33
34
  open?: boolean;
34
- showDelete?: boolean;
35
- value?: DevPlugin;
35
+ value?: CustomPlugin;
36
36
  }
37
37
 
38
38
  const DevModal = memo<DevModalProps>(
39
- ({ open, showDelete, value, onValueChange, onSave, onOpenChange, onDelete }) => {
39
+ ({ open, mode = 'create', value, onValueChange, onSave, onOpenChange, onDelete }) => {
40
+ const isEditMode = mode === 'edit';
40
41
  const { t } = useTranslation('plugin');
41
-
42
- const [form] = Form.useForm();
43
- const theme = useTheme();
44
-
45
- const { styles } = useStyles();
46
-
42
+ const { styles, theme } = useStyles();
47
43
  const { message } = App.useApp();
48
44
 
45
+ const [submitting, setSubmitting] = useState(false);
46
+ const [form] = Form.useForm();
49
47
  useEffect(() => {
50
48
  form.setFieldsValue(value);
51
49
  }, []);
52
50
 
53
51
  const footer = (
54
52
  <Flexbox horizontal justify={'space-between'} style={{ marginTop: 24 }}>
55
- {showDelete ? (
53
+ {isEditMode ? (
56
54
  <Popconfirm
57
55
  okButtonProps={{
58
56
  danger: true,
@@ -80,24 +78,30 @@ const DevModal = memo<DevModalProps>(
80
78
  {t('cancel', { ns: 'common' })}
81
79
  </Button>
82
80
  <Button
81
+ loading={submitting}
83
82
  onClick={() => {
84
83
  form.submit();
85
84
  }}
86
85
  type={'primary'}
87
86
  >
88
- {t('dev.save')}
87
+ {t(isEditMode ? 'dev.update' : 'dev.save')}
89
88
  </Button>
90
89
  </Flexbox>
91
90
  </Flexbox>
92
91
  );
92
+
93
93
  return (
94
94
  <Form.Provider
95
95
  onFormChange={() => {
96
96
  onValueChange?.(form.getFieldsValue());
97
97
  }}
98
- onFormFinish={(_, info) => {
99
- onSave?.(info.values as DevPlugin);
100
- message.success(t('dev.saveSuccess'));
98
+ onFormFinish={async (_, info) => {
99
+ if (onSave) {
100
+ setSubmitting(true);
101
+ await onSave?.(info.values as CustomPlugin);
102
+ setSubmitting(false);
103
+ }
104
+ message.success(t(isEditMode ? 'dev.updateSuccess' : 'dev.saveSuccess'));
101
105
  onOpenChange(false);
102
106
  }}
103
107
  >
@@ -121,7 +125,6 @@ const DevModal = memo<DevModalProps>(
121
125
  form.submit();
122
126
  }}
123
127
  open={open}
124
- style={{ height: 750 }}
125
128
  title={
126
129
  <Flexbox gap={8} horizontal>
127
130
  <Icon icon={LucideBlocks} />
@@ -139,9 +142,9 @@ const DevModal = memo<DevModalProps>(
139
142
  {/* { children: <ManifestForm />, key: 'manifest', label: t('dev.tabs.manifest') },*/}
140
143
  {/* ]}*/}
141
144
  {/*/>*/}
142
- <PluginPreview />
143
- <ManifestForm form={form} mode={'url'} />
144
- <MetaForm form={form} />
145
+ <PluginPreview form={form} />
146
+ <ManifestForm form={form} />
147
+ <MetaForm form={form} mode={mode} />
145
148
  </Flexbox>
146
149
  </Modal>
147
150
  </ConfigProvider>
@@ -43,16 +43,19 @@ export default {
43
43
  label: '首页',
44
44
  },
45
45
  identifier: {
46
- desc: '插件的唯一标识,仅支持英文字符、数字、破折号 - 、下划线 _',
46
+ desc: '插件的唯一标识,将从 manifest 中自动识别',
47
47
  errorDuplicate: '标识符和已有插件重复,请修改标识符',
48
48
  label: '标识符',
49
49
  pattenErrorMessage: '只能输入英文字符、数字 、- 和_ 这两个符号',
50
50
  },
51
51
  manifest: {
52
52
  desc: 'LobeChat 将会通过该链接安装插件',
53
- invalid: '输入的 manifest 链接无效,或 manifest 不符合规范',
53
+ jsonInvalid: ' manifest 不符合规范,校验结果: \n\n {{error}}',
54
54
  label: '插件描述文件 Url 地址',
55
- urlError: '请输入一个有效的网址',
55
+ preview: '预览 Manifest',
56
+ refresh: '刷新',
57
+ requestError: '请求该链接失败,请输入一个有效的链接,并检查链接是否允许跨域访问',
58
+ urlError: '该链接没有返回 JSON 格式的内容, 请输入一个有效的链接',
56
59
  },
57
60
  title: {
58
61
  desc: '插件标题',
@@ -73,13 +76,14 @@ export default {
73
76
  manifest: '功能描述清单 (Manifest)',
74
77
  meta: '插件元信息',
75
78
  },
76
-
77
79
  title: '添加自定义插件',
80
+ update: '更新',
81
+ updateSuccess: '插件设置更新成功',
78
82
  },
79
83
  list: {
80
84
  item: {
81
85
  'local.config': '配置',
82
- 'local.title': '本地',
86
+ 'local.title': '自定义',
83
87
  },
84
88
  },
85
89
 
@@ -16,4 +16,8 @@ export const options: LocaleOptions = [
16
16
  label: 'English',
17
17
  value: 'en-US',
18
18
  },
19
+ {
20
+ label: 'Russian',
21
+ value: 'ru-RU',
22
+ },
19
23
  ] as LocaleOptions;
@@ -1,8 +1,10 @@
1
1
  import en_US from './en_US';
2
+ import ru_RU from './ru_RU';
2
3
  import zh_CN from './zh_CN';
3
4
 
4
5
  const resources = {
5
6
  'en-US': en_US,
7
+ 'ru-RU': ru_RU,
6
8
  'zh-CN': zh_CN,
7
9
  } as const;
8
10
  export default resources;
@@ -0,0 +1,17 @@
1
+ import common from '../../../locales/ru_RU/common.json';
2
+ import empty from '../../../locales/ru_RU/empty.json';
3
+ import error from '../../../locales/ru_RU/error.json';
4
+ import plugin from '../../../locales/ru_RU/plugin.json';
5
+ import setting from '../../../locales/ru_RU/setting.json';
6
+ import welcome from '../../../locales/ru_RU/welcome.json';
7
+
8
+ const resources = {
9
+ common,
10
+ empty,
11
+ error,
12
+ plugin,
13
+ setting,
14
+ welcome,
15
+ } as const;
16
+
17
+ export default resources;