@lobehub/chat 0.111.6 → 0.112.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 (130) hide show
  1. package/.i18nrc.js +1 -0
  2. package/CHANGELOG.md +59 -0
  3. package/locales/en_US/chat.json +12 -12
  4. package/locales/en_US/common.json +4 -4
  5. package/locales/en_US/error.json +16 -14
  6. package/locales/en_US/market.json +2 -2
  7. package/locales/en_US/migration.json +4 -4
  8. package/locales/en_US/plugin.json +75 -38
  9. package/locales/en_US/setting.json +7 -7
  10. package/locales/fr_FR/chat.json +11 -11
  11. package/locales/fr_FR/common.json +4 -4
  12. package/locales/fr_FR/error.json +12 -10
  13. package/locales/fr_FR/plugin.json +42 -5
  14. package/locales/fr_FR/setting.json +12 -12
  15. package/locales/fr_FR/welcome.json +3 -3
  16. package/locales/ja_JP/chat.json +1 -1
  17. package/locales/ja_JP/common.json +4 -4
  18. package/locales/ja_JP/error.json +16 -14
  19. package/locales/ja_JP/migration.json +4 -4
  20. package/locales/ja_JP/plugin.json +70 -33
  21. package/locales/ja_JP/setting.json +1 -1
  22. package/locales/ko_KR/chat.json +1 -1
  23. package/locales/ko_KR/common.json +4 -4
  24. package/locales/ko_KR/error.json +16 -14
  25. package/locales/ko_KR/migration.json +4 -4
  26. package/locales/ko_KR/plugin.json +71 -34
  27. package/locales/ko_KR/setting.json +1 -1
  28. package/locales/ru_RU/chat.json +1 -1
  29. package/locales/ru_RU/common.json +4 -4
  30. package/locales/ru_RU/error.json +16 -14
  31. package/locales/ru_RU/market.json +2 -2
  32. package/locales/ru_RU/migration.json +4 -4
  33. package/locales/ru_RU/plugin.json +71 -34
  34. package/locales/ru_RU/setting.json +1 -1
  35. package/locales/zh_CN/chat.json +1 -1
  36. package/locales/zh_CN/error.json +2 -0
  37. package/locales/zh_CN/plugin.json +42 -5
  38. package/locales/zh_CN/setting.json +1 -1
  39. package/locales/zh_TW/chat.json +1 -1
  40. package/locales/zh_TW/common.json +4 -4
  41. package/locales/zh_TW/error.json +16 -14
  42. package/locales/zh_TW/market.json +2 -2
  43. package/locales/zh_TW/migration.json +4 -4
  44. package/locales/zh_TW/plugin.json +98 -61
  45. package/locales/zh_TW/setting.json +1 -1
  46. package/package.json +4 -2
  47. package/scripts/i18nWorkflow/genDiff.ts +7 -8
  48. package/scripts/i18nWorkflow/utils.ts +0 -14
  49. package/scripts/readmeWorkflow/syncPluginIndex.ts +9 -11
  50. package/src/app/api/plugins/route.ts +2 -4
  51. package/src/app/api/proxy/route.ts +10 -0
  52. package/src/app/chat/features/ChatInput/ActionBar/Tools/ToolItem.tsx +3 -8
  53. package/src/app/chat/features/ChatInput/ActionBar/Tools/index.tsx +3 -3
  54. package/src/app/chat/features/Conversation/ChatList/Error/index.tsx +6 -0
  55. package/src/app/chat/features/Conversation/ChatList/Messages/Function.tsx +1 -1
  56. package/src/app/chat/features/Conversation/ChatList/Plugins/Inspector/Settings.tsx +7 -5
  57. package/src/app/chat/features/Conversation/ChatList/Plugins/Inspector/index.tsx +2 -2
  58. package/src/app/chat/features/Conversation/ChatList/Plugins/Render/DefaultType/index.tsx +6 -4
  59. package/src/app/chat/features/Conversation/ChatList/Plugins/Render/index.tsx +2 -2
  60. package/src/app/chat/features/Migration/ExportConfigButton.tsx +1 -1
  61. package/src/app/market/features/AgentDetailContent/AgentInfo/index.tsx +1 -0
  62. package/src/app/market/features/AgentDetailContent/AgentInfo/style.ts +1 -9
  63. package/src/app/settings/common/Common.tsx +5 -7
  64. package/src/components/FileList/FileItem.tsx +4 -2
  65. package/src/components/HotKeys/index.tsx +5 -4
  66. package/src/const/plugin.ts +1 -0
  67. package/src/features/AgentSetting/AgentPlugin/AddPluginButton.tsx +2 -2
  68. package/src/features/AgentSetting/AgentPlugin/LocalPluginItem.tsx +19 -53
  69. package/src/features/AgentSetting/AgentPlugin/index.tsx +5 -11
  70. package/src/features/AgentSetting/store/initialState.ts +1 -1
  71. package/src/features/PluginDetailModal/APIs.tsx +44 -0
  72. package/src/features/PluginDetailModal/Meta.tsx +59 -0
  73. package/src/features/PluginDetailModal/index.tsx +79 -0
  74. package/src/features/PluginDevModal/PluginPreview.tsx +8 -10
  75. package/src/features/PluginDevModal/UrlManifestForm.tsx +131 -0
  76. package/src/features/PluginDevModal/index.tsx +31 -32
  77. package/src/features/PluginSettings/index.tsx +1 -1
  78. package/src/features/PluginStore/AddPluginButton.tsx +47 -0
  79. package/src/features/PluginStore/InstalledPluginList.tsx +54 -0
  80. package/src/features/PluginStore/OnlineList.tsx +95 -0
  81. package/src/features/PluginStore/PluginItem/Action.tsx +118 -0
  82. package/src/features/PluginStore/PluginItem/EditCustomPlugin.tsx +50 -0
  83. package/src/features/PluginStore/PluginItem/PluginTag.tsx +58 -0
  84. package/src/features/PluginStore/PluginItem/index.tsx +73 -0
  85. package/src/features/PluginStore/index.tsx +20 -92
  86. package/src/hooks/usePlatform.ts +15 -0
  87. package/src/locales/default/chat.ts +1 -1
  88. package/src/locales/default/error.ts +2 -0
  89. package/src/locales/default/plugin.ts +41 -4
  90. package/src/locales/default/setting.ts +1 -1
  91. package/src/services/__tests__/__snapshots__/plugin.test.ts.snap +154 -0
  92. package/src/services/__tests__/chat.test.ts +1 -1
  93. package/src/services/__tests__/global.test.ts +57 -0
  94. package/src/services/__tests__/openai/OpenAPI_V3.json +245 -0
  95. package/src/services/__tests__/openai/plugin.json +22 -0
  96. package/src/services/__tests__/plugin.test.ts +90 -1
  97. package/src/services/__tests__/share.test.ts +86 -0
  98. package/src/services/_url.ts +2 -0
  99. package/src/services/global.ts +15 -0
  100. package/src/services/plugin.ts +99 -15
  101. package/src/services/share.ts +34 -0
  102. package/src/store/chat/actions/enhance.test.ts +15 -15
  103. package/src/store/chat/actions/message.test.ts +3 -2
  104. package/src/store/chat/actions/plugin.test.ts +281 -0
  105. package/src/store/chat/actions/plugin.ts +22 -3
  106. package/src/store/chat/actions/share.ts +2 -2
  107. package/src/store/chat/actions/topic.test.ts +12 -4
  108. package/src/store/global/slices/common.ts +2 -2
  109. package/src/store/session/selectors.ts +1 -1
  110. package/src/store/session/slices/agent/selectors.ts +1 -1
  111. package/src/store/tool/helpers.ts +8 -1
  112. package/src/store/tool/slices/customPlugin/action.ts +4 -1
  113. package/src/store/tool/slices/plugin/selectors.test.ts +22 -3
  114. package/src/store/tool/slices/plugin/selectors.ts +24 -5
  115. package/src/store/tool/slices/store/selectors.test.ts +18 -1
  116. package/src/store/tool/slices/store/selectors.ts +14 -3
  117. package/src/styles/antdOverride.ts +7 -0
  118. package/src/types/openai/chat.ts +0 -1
  119. package/src/types/openai/plugin.ts +57 -0
  120. package/src/types/tool/index.ts +1 -2
  121. package/src/types/tool/plugin.ts +13 -1
  122. package/src/types/tool/tool.ts +1 -0
  123. package/src/types/translate.ts +0 -1
  124. package/src/utils/platform.ts +8 -5
  125. package/src/features/PluginDevModal/UrlModeForm.tsx +0 -98
  126. package/src/features/PluginSettingsModal/index.tsx +0 -59
  127. package/src/features/PluginStore/PluginItem.tsx +0 -82
  128. package/src/features/PluginStore/PluginSettings.tsx +0 -40
  129. package/src/services/latestVersion.ts +0 -11
  130. package/src/services/shareGPT.ts +0 -28
@@ -1,9 +1,10 @@
1
1
  import { Avatar, Form } from '@lobehub/ui';
2
- import { Form as AForm, Card, FormInstance, Switch, Tag } from 'antd';
2
+ import { Form as AForm, Card, FormInstance } 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 PluginTag from '@/features/PluginStore/PluginItem/PluginTag';
7
8
  import { pluginHelpers } from '@/store/tool';
8
9
  import { LobeToolCustomPlugin } from '@/types/tool/plugin';
9
10
 
@@ -11,26 +12,23 @@ const PluginPreview = memo<{ form: FormInstance }>(({ form }) => {
11
12
  const { t } = useTranslation('plugin');
12
13
 
13
14
  const plugin: LobeToolCustomPlugin = AForm.useWatch([], form);
15
+ const meta = plugin?.manifest?.meta;
14
16
 
15
17
  const items = {
16
- avatar: <Avatar avatar={pluginHelpers.getPluginAvatar(plugin?.manifest?.meta)} />,
17
- children: <Switch disabled />,
18
- desc: pluginHelpers.getPluginDesc(plugin?.manifest?.meta),
18
+ avatar: <Avatar avatar={pluginHelpers.getPluginAvatar(meta)} style={{ flex: 'none' }} />,
19
+ desc: pluginHelpers.getPluginDesc(meta) || 'Plugin Description',
19
20
  label: (
20
21
  <Flexbox align={'center'} gap={8} horizontal>
21
- {pluginHelpers.getPluginTitle(plugin?.manifest?.meta) ?? t('dev.preview.title')}
22
- <Tag bordered={false} color={'gold'}>
23
- {t('list.item.local.title', { ns: 'plugin' })}
24
- </Tag>
22
+ {pluginHelpers.getPluginTitle(meta) || 'Plugin Title'}
23
+ <PluginTag type={'customPlugin'} />
25
24
  </Flexbox>
26
25
  ),
27
26
  minWidth: undefined,
28
- tag: !!plugin?.identifier ? plugin?.identifier : 'id',
29
27
  };
30
28
 
31
29
  return (
32
30
  <Card size={'small'} title={t('dev.preview.card')}>
33
- <Form.Item {...items} colon={false} style={{ marginBottom: 0 }} />
31
+ <Form.Item {...items} colon={false} style={{ alignItems: 'center', marginBottom: 0 }} />
34
32
  </Card>
35
33
  );
36
34
  });
@@ -0,0 +1,131 @@
1
+ import { LobeChatPluginManifest } from '@lobehub/chat-plugin-sdk';
2
+ import { ActionIcon, FormItem, Input } from '@lobehub/ui';
3
+ import { Checkbox, Form, FormInstance } from 'antd';
4
+ import { FileCode, RotateCwIcon } from 'lucide-react';
5
+ import { memo, useState } from 'react';
6
+ import { useTranslation } from 'react-i18next';
7
+ import { Flexbox } from 'react-layout-kit';
8
+
9
+ import ManifestPreviewer from '@/components/ManifestPreviewer';
10
+ import { pluginService } from '@/services/plugin';
11
+ import { useToolStore } from '@/store/tool';
12
+ import { pluginSelectors } from '@/store/tool/selectors';
13
+ import { PluginInstallError } from '@/types/tool/plugin';
14
+
15
+ const ProxyChecker = memo<{ onChange?: (value: boolean) => void; value?: boolean }>(
16
+ ({ value, onChange }) => {
17
+ const { t } = useTranslation('plugin');
18
+
19
+ return (
20
+ <Flexbox
21
+ gap={8}
22
+ horizontal
23
+ onClick={() => {
24
+ onChange?.(!value);
25
+ }}
26
+ style={{ cursor: 'pointer' }}
27
+ >
28
+ <Checkbox checked={value} /> {t('dev.customParams.useProxy.label')}
29
+ </Flexbox>
30
+ );
31
+ },
32
+ );
33
+
34
+ const UrlManifestForm = memo<{ form: FormInstance; isEditMode: boolean }>(
35
+ ({ form, isEditMode }) => {
36
+ const { t } = useTranslation('plugin');
37
+
38
+ const [manifest, setManifest] = useState<LobeChatPluginManifest>();
39
+
40
+ const urlKey = ['customParams', 'manifestUrl'];
41
+ const proxyKey = ['customParams', 'useProxy'];
42
+ const pluginIds = useToolStore(pluginSelectors.storeAndInstallPluginsIdList);
43
+
44
+ return (
45
+ <Form form={form} layout={'vertical'}>
46
+ <FormItem
47
+ extra={
48
+ <Flexbox horizontal justify={'space-between'} style={{ marginTop: 8 }}>
49
+ {t('dev.meta.manifest.desc')}
50
+ {manifest && (
51
+ <ManifestPreviewer manifest={manifest}>
52
+ <ActionIcon
53
+ icon={FileCode}
54
+ size={'small'}
55
+ title={t('dev.meta.manifest.preview')}
56
+ />
57
+ </ManifestPreviewer>
58
+ )}
59
+ </Flexbox>
60
+ }
61
+ hasFeedback
62
+ label={t('dev.meta.manifest.label')}
63
+ name={urlKey}
64
+ required
65
+ rules={[
66
+ { required: true },
67
+ {
68
+ message: t('error.urlError'),
69
+ pattern: /^https?:\/\/.*/,
70
+ },
71
+ {
72
+ validator: async (_, value) => {
73
+ if (!value) return true;
74
+
75
+ try {
76
+ const useProxy = form.getFieldValue(proxyKey);
77
+ const data = await pluginService.getPluginManifest(value, useProxy);
78
+ setManifest(data);
79
+
80
+ form.setFieldsValue({ identifier: data.identifier, manifest: data });
81
+ } catch (error) {
82
+ const err = error as PluginInstallError;
83
+ throw t(`error.${err.message}`, { error: err.cause! });
84
+ }
85
+ },
86
+ },
87
+ // 编辑模式下,不进行重复校验
88
+ isEditMode
89
+ ? {}
90
+ : {
91
+ message: t('dev.meta.identifier.errorDuplicate'),
92
+ validator: async () => {
93
+ const id = form.getFieldValue('identifier');
94
+ if (!id) return true;
95
+
96
+ if (pluginIds.includes(id)) {
97
+ throw new Error('Duplicate');
98
+ }
99
+ },
100
+ },
101
+ ]}
102
+ style={{ marginBottom: 0 }}
103
+ >
104
+ <Input
105
+ placeholder={'http://localhost:3400/manifest-dev.json'}
106
+ suffix={
107
+ <ActionIcon
108
+ icon={RotateCwIcon}
109
+ onClick={(e) => {
110
+ e.stopPropagation();
111
+ form.validateFields([urlKey, 'identifier']);
112
+ }}
113
+ size={'small'}
114
+ title={t('dev.meta.manifest.refresh')}
115
+ />
116
+ }
117
+ />
118
+ </FormItem>
119
+
120
+ <FormItem name={proxyKey} noStyle>
121
+ <ProxyChecker />
122
+ </FormItem>
123
+
124
+ <FormItem name={'identifier'} noStyle />
125
+ <FormItem name={'manifest'} noStyle />
126
+ </Form>
127
+ );
128
+ },
129
+ );
130
+
131
+ export default UrlManifestForm;
@@ -1,5 +1,5 @@
1
1
  import { Alert, Icon, Modal, Tooltip } from '@lobehub/ui';
2
- import { App, Button, Form, Popconfirm, Segmented } from 'antd';
2
+ import { App, Button, Divider, Form, Popconfirm, Segmented } from 'antd';
3
3
  import { useResponsive } from 'antd-style';
4
4
  import { MoveUpRight } from 'lucide-react';
5
5
  import { memo, useEffect, useState } from 'react';
@@ -10,9 +10,8 @@ import MobilePadding from '@/components/MobilePadding';
10
10
  import { WIKI_PLUGIN_GUIDE } from '@/const/url';
11
11
  import { LobeToolCustomPlugin } from '@/types/tool/plugin';
12
12
 
13
- import LocalForm from './LocalForm';
14
13
  import PluginPreview from './PluginPreview';
15
- import UrlModeForm from './UrlModeForm';
14
+ import UrlManifestForm from './UrlManifestForm';
16
15
 
17
16
  interface DevModalProps {
18
17
  mode?: 'edit' | 'create';
@@ -108,7 +107,7 @@ const DevModal = memo<DevModalProps>(
108
107
  form.submit();
109
108
  }}
110
109
  open={open}
111
- title={t('dev.title')}
110
+ title={t(isEditMode ? 'dev.title.edit' : 'dev.title.create')}
112
111
  >
113
112
  <Flexbox
114
113
  gap={mobile ? 0 : 16}
@@ -135,35 +134,35 @@ const DevModal = memo<DevModalProps>(
135
134
  showIcon
136
135
  type={'info'}
137
136
  />
137
+ <Segmented
138
+ block
139
+ onChange={(e) => {
140
+ setConfigMode(e as any);
141
+ }}
142
+ options={[
143
+ {
144
+ label: t('dev.manifest.mode.url'),
145
+ value: 'url',
146
+ },
147
+ {
148
+ disabled: true,
149
+ label: (
150
+ <Tooltip title={t('dev.manifest.mode.local-tooltip')}>
151
+ {t('dev.manifest.mode.local')}
152
+ </Tooltip>
153
+ ),
154
+ value: 'local',
155
+ },
156
+ ]}
157
+ />
158
+ <Flexbox>
159
+ {configMode === 'url' ? (
160
+ <UrlManifestForm form={form} isEditMode={mode === 'edit'} />
161
+ ) : null}
162
+ <Divider />
163
+ <PluginPreview form={form} />
164
+ </Flexbox>
138
165
  </MobilePadding>
139
- <PluginPreview form={form} />
140
- <Segmented
141
- block
142
- onChange={(e) => {
143
- setConfigMode(e as any);
144
- }}
145
- options={[
146
- {
147
- label: t('dev.manifest.mode.url'),
148
- value: 'url',
149
- },
150
- {
151
- disabled: true,
152
- label: (
153
- <Tooltip title={t('dev.manifest.mode.local-tooltip')}>
154
- {t('dev.manifest.mode.local')}
155
- </Tooltip>
156
- ),
157
- value: 'local',
158
- },
159
- ]}
160
- />
161
- {configMode === 'url' ? (
162
- <UrlModeForm form={form} isEditMode={mode === 'edit'} />
163
- ) : (
164
- <LocalForm form={form} mode={mode} />
165
- )}
166
- {/*<MetaForm form={form} mode={mode} />*/}
167
166
  </Flexbox>
168
167
  </Modal>
169
168
  </Form.Provider>
@@ -53,7 +53,7 @@ const PluginSettingsConfig = memo<PluginSettingsConfigProps>(({ schema, id }) =>
53
53
  const items = transformPluginSettings(schema);
54
54
 
55
55
  return (
56
- <Form form={form} layout={'vertical'}>
56
+ <Form form={form} layout={'vertical'} style={{ width: '100%' }}>
57
57
  {items.map((item) => (
58
58
  <Form.Item
59
59
  desc={item.desc && <Markdown className={styles.markdown}>{item.desc as string}</Markdown>}
@@ -0,0 +1,47 @@
1
+ import { Icon } from '@lobehub/ui';
2
+ import { Button } from 'antd';
3
+ import { PackagePlus } from 'lucide-react';
4
+ import { forwardRef, useState } from 'react';
5
+ import { useTranslation } from 'react-i18next';
6
+
7
+ import DevModal from '@/features/PluginDevModal';
8
+ import { useToolStore } from '@/store/tool';
9
+
10
+ const AddPluginButton = forwardRef<HTMLButtonElement>((props, ref) => {
11
+ const { t } = useTranslation('setting');
12
+ const [showModal, setModal] = useState(false);
13
+
14
+ const [installCustomPlugin, updateNewDevPlugin] = useToolStore((s) => [
15
+ s.installCustomPlugin,
16
+ s.updateNewCustomPlugin,
17
+ ]);
18
+
19
+ return (
20
+ <div
21
+ onClick={(e) => {
22
+ e.stopPropagation();
23
+ }}
24
+ >
25
+ <DevModal
26
+ onOpenChange={setModal}
27
+ onSave={async (devPlugin) => {
28
+ await installCustomPlugin(devPlugin);
29
+ // toggleAgentPlugin(devPlugin.identifier);
30
+ }}
31
+ onValueChange={updateNewDevPlugin}
32
+ open={showModal}
33
+ />
34
+ <Button
35
+ icon={<Icon icon={PackagePlus} />}
36
+ onClick={() => {
37
+ setModal(true);
38
+ }}
39
+ ref={ref}
40
+ >
41
+ {t('plugin.addTooltip')}
42
+ </Button>
43
+ </div>
44
+ );
45
+ });
46
+
47
+ export default AddPluginButton;
@@ -0,0 +1,54 @@
1
+ import { SearchBar } from '@lobehub/ui';
2
+ import { useResponsive } from 'antd-style';
3
+ import isEqual from 'fast-deep-equal';
4
+ import { memo, useState } from 'react';
5
+ import { useTranslation } from 'react-i18next';
6
+ import { Flexbox } from 'react-layout-kit';
7
+
8
+ import { useToolStore } from '@/store/tool';
9
+ import { pluginSelectors } from '@/store/tool/selectors';
10
+
11
+ import AddPluginButton from './AddPluginButton';
12
+ import PluginItem from './PluginItem';
13
+
14
+ export const InstalledPluginList = memo(() => {
15
+ const { t } = useTranslation('plugin');
16
+ const [keywords, setKeywords] = useState<string>();
17
+ const { mobile } = useResponsive();
18
+ const installedPlugins = useToolStore(pluginSelectors.installedPluginMetaList, isEqual);
19
+
20
+ return (
21
+ <Flexbox gap={16}>
22
+ <Flexbox align={'center'} gap={16} horizontal justify={'space-between'}>
23
+ <Flexbox flex={1}>
24
+ <SearchBar
25
+ allowClear
26
+ onChange={(e) => setKeywords(e.target.value)}
27
+ placeholder={t('store.placeholder')}
28
+ type={mobile ? 'block' : 'ghost'}
29
+ value={keywords}
30
+ />
31
+ </Flexbox>
32
+ <Flexbox gap={8} horizontal>
33
+ <AddPluginButton />
34
+ </Flexbox>
35
+ </Flexbox>
36
+
37
+ <Flexbox gap={24}>
38
+ {installedPlugins
39
+ .filter((item) =>
40
+ [item.meta?.title, item.meta?.description, item.author, ...(item.meta?.tags || [])]
41
+ .filter(Boolean)
42
+ .join('')
43
+ .toLowerCase()
44
+ .includes((keywords || '')?.toLowerCase()),
45
+ )
46
+ .map((i) => (
47
+ <PluginItem {...i} key={i.identifier} />
48
+ ))}
49
+ </Flexbox>
50
+ </Flexbox>
51
+ );
52
+ });
53
+
54
+ export default InstalledPluginList;
@@ -0,0 +1,95 @@
1
+ import { Icon, SearchBar } from '@lobehub/ui';
2
+ import { Button, Empty } from 'antd';
3
+ import { useResponsive } from 'antd-style';
4
+ import isEqual from 'fast-deep-equal';
5
+ import { ServerCrash } from 'lucide-react';
6
+ import { memo, useState } from 'react';
7
+ import { useTranslation } from 'react-i18next';
8
+ import { Center, Flexbox } from 'react-layout-kit';
9
+
10
+ import AddPluginButton from '@/features/PluginStore/AddPluginButton';
11
+ import { useToolStore } from '@/store/tool';
12
+ import { pluginSelectors, pluginStoreSelectors } from '@/store/tool/selectors';
13
+
14
+ import Loading from './Loading';
15
+ import PluginItem from './PluginItem';
16
+
17
+ export const OnlineList = memo(() => {
18
+ const { t } = useTranslation('plugin');
19
+ const [keywords, setKeywords] = useState<string>();
20
+ const { mobile } = useResponsive();
21
+ const pluginStoreList = useToolStore((s) => {
22
+ const custom = pluginSelectors.installedCustomPluginMetaList(s);
23
+ const store = pluginStoreSelectors.onlinePluginStore(s);
24
+
25
+ return [...custom, ...store];
26
+ }, isEqual);
27
+ const storePluginIds = useToolStore(
28
+ (s) => pluginStoreSelectors.onlinePluginStore(s).map((s) => s.identifier),
29
+ isEqual,
30
+ );
31
+
32
+ const [useFetchPluginList, installPlugins] = useToolStore((s) => [
33
+ s.useFetchPluginStore,
34
+ s.installPlugins,
35
+ ]);
36
+
37
+ const { isLoading, error } = useFetchPluginList();
38
+
39
+ const isEmpty = pluginStoreList.length === 0;
40
+
41
+ return (
42
+ <Flexbox gap={16}>
43
+ <Flexbox align={'center'} gap={8} horizontal justify={'space-between'}>
44
+ <Flexbox flex={1}>
45
+ <SearchBar
46
+ allowClear
47
+ onChange={(e) => setKeywords(e.target.value)}
48
+ placeholder={t('store.placeholder')}
49
+ type={mobile ? 'block' : 'ghost'}
50
+ value={keywords}
51
+ />
52
+ </Flexbox>
53
+ <AddPluginButton />
54
+ <Button
55
+ onClick={() => {
56
+ installPlugins(storePluginIds);
57
+ }}
58
+ >
59
+ {t('store.installAllPlugins')}
60
+ </Button>
61
+ </Flexbox>
62
+
63
+ {isLoading ? (
64
+ <Loading />
65
+ ) : isEmpty ? (
66
+ <Center gap={12} padding={40}>
67
+ {error ? (
68
+ <>
69
+ <Icon icon={ServerCrash} size={{ fontSize: 80 }} />
70
+ {t('store.networkError')}
71
+ </>
72
+ ) : (
73
+ <Empty description={t('store.empty')} image={Empty.PRESENTED_IMAGE_SIMPLE} />
74
+ )}
75
+ </Center>
76
+ ) : (
77
+ <Flexbox gap={24}>
78
+ {pluginStoreList
79
+ .filter((item) =>
80
+ [item.meta?.title, item.meta?.description, item.author, ...(item.meta?.tags || [])]
81
+ .filter(Boolean)
82
+ .join('')
83
+ .toLowerCase()
84
+ .includes((keywords || '')?.toLowerCase()),
85
+ )
86
+ .map((item) => (
87
+ <PluginItem key={item.identifier} {...item} />
88
+ ))}
89
+ </Flexbox>
90
+ )}
91
+ </Flexbox>
92
+ );
93
+ });
94
+
95
+ export default OnlineList;
@@ -0,0 +1,118 @@
1
+ import { ActionIcon, Icon } from '@lobehub/ui';
2
+ import { Button, Dropdown, Popconfirm } from 'antd';
3
+ import { useResponsive } from 'antd-style';
4
+ import { InfoIcon, MoreVerticalIcon, Settings, Trash2 } from 'lucide-react';
5
+ import { memo, useState } from 'react';
6
+ import { useTranslation } from 'react-i18next';
7
+ import { Flexbox } from 'react-layout-kit';
8
+
9
+ import PluginDetailModal from '@/features/PluginDetailModal';
10
+ import { pluginHelpers, useToolStore } from '@/store/tool';
11
+ import { pluginSelectors, pluginStoreSelectors } from '@/store/tool/selectors';
12
+ import { LobeToolType } from '@/types/tool/tool';
13
+
14
+ import EditCustomPlugin from './EditCustomPlugin';
15
+
16
+ interface ActionsProps {
17
+ identifier: string;
18
+ type: LobeToolType;
19
+ }
20
+
21
+ const Actions = memo<ActionsProps>(({ identifier, type }) => {
22
+ const [installed, installing, installPlugin, unInstallPlugin] = useToolStore((s) => [
23
+ pluginSelectors.isPluginInstalled(identifier)(s),
24
+ pluginStoreSelectors.isPluginInstallLoading(identifier)(s),
25
+ s.installPlugin,
26
+ s.uninstallPlugin,
27
+ ]);
28
+ const { mobile } = useResponsive();
29
+ const isCustomPlugin = type === 'customPlugin';
30
+ const { t } = useTranslation('plugin');
31
+ const [open, setOpen] = useState(false);
32
+ const plugin = useToolStore(pluginSelectors.getPluginManifestById(identifier));
33
+
34
+ const [tab, setTab] = useState('info');
35
+ const hasSettings = pluginHelpers.isSettingSchemaNonEmpty(plugin?.settings);
36
+
37
+ return (
38
+ <>
39
+ <Flexbox align={'center'} horizontal>
40
+ {installed ? (
41
+ <>
42
+ {isCustomPlugin && <EditCustomPlugin identifier={identifier} />}
43
+ {hasSettings && (
44
+ <ActionIcon
45
+ icon={Settings}
46
+ onClick={() => {
47
+ setOpen(true);
48
+ setTab('settings');
49
+ }}
50
+ title={t('store.actions.settings')}
51
+ />
52
+ )}
53
+ <Dropdown
54
+ menu={{
55
+ items: [
56
+ {
57
+ icon: <Icon icon={InfoIcon} />,
58
+ key: 'detail',
59
+ label: t('store.actions.detail'),
60
+ onClick: () => {
61
+ setOpen(true);
62
+ setTab('info');
63
+ },
64
+ },
65
+ {
66
+ danger: true,
67
+ icon: <Icon icon={Trash2} />,
68
+ key: 'uninstall',
69
+ label: (
70
+ <Popconfirm
71
+ arrow={false}
72
+ okButtonProps={{ danger: true }}
73
+ onConfirm={() => {
74
+ unInstallPlugin(identifier);
75
+ }}
76
+ placement={'topRight'}
77
+ title={t('store.actions.confirmUninstall')}
78
+ >
79
+ {t('store.actions.uninstall')}
80
+ </Popconfirm>
81
+ ),
82
+ },
83
+ ],
84
+ }}
85
+ placement="bottomRight"
86
+ trigger={['click']}
87
+ >
88
+ <ActionIcon icon={MoreVerticalIcon} loading={installing} />
89
+ </Dropdown>
90
+ </>
91
+ ) : (
92
+ <Button
93
+ loading={installing}
94
+ onClick={() => {
95
+ installPlugin(identifier);
96
+ }}
97
+ size={mobile ? 'small' : undefined}
98
+ type={'primary'}
99
+ >
100
+ {t('store.actions.install')}
101
+ </Button>
102
+ )}
103
+ </Flexbox>
104
+ <PluginDetailModal
105
+ id={identifier}
106
+ onClose={() => {
107
+ setOpen(false);
108
+ }}
109
+ onTabChange={setTab}
110
+ open={open}
111
+ schema={plugin?.settings}
112
+ tab={tab}
113
+ />
114
+ </>
115
+ );
116
+ });
117
+
118
+ export default Actions;
@@ -0,0 +1,50 @@
1
+ import { ActionIcon } from '@lobehub/ui';
2
+ import isEqual from 'fast-deep-equal';
3
+ import { PackageSearch } from 'lucide-react';
4
+ import { memo, useState } from 'react';
5
+ import { useTranslation } from 'react-i18next';
6
+
7
+ import DevModal from '@/features/PluginDevModal';
8
+ import { useToolStore } from '@/store/tool';
9
+ import { pluginSelectors } from '@/store/tool/slices/plugin/selectors';
10
+
11
+ const EditCustomPlugin = memo<{ identifier: string }>(({ identifier }) => {
12
+ const { t } = useTranslation('plugin');
13
+ const [showModal, setModal] = useState(false);
14
+
15
+ const [installCustomPlugin, updateNewDevPlugin] = useToolStore((s) => [
16
+ s.installCustomPlugin,
17
+ s.updateNewCustomPlugin,
18
+ ]);
19
+
20
+ const customPlugin = useToolStore(pluginSelectors.getCustomPluginById(identifier), isEqual);
21
+
22
+ return (
23
+ <div
24
+ onClick={(e) => {
25
+ e.stopPropagation();
26
+ }}
27
+ >
28
+ <DevModal
29
+ mode={'edit'}
30
+ onOpenChange={setModal}
31
+ onSave={async (devPlugin) => {
32
+ await installCustomPlugin(devPlugin);
33
+ // toggleAgentPlugin(devPlugin.identifier);
34
+ }}
35
+ onValueChange={updateNewDevPlugin}
36
+ open={showModal}
37
+ value={customPlugin}
38
+ />
39
+ <ActionIcon
40
+ icon={PackageSearch}
41
+ onClick={() => {
42
+ setModal(true);
43
+ }}
44
+ title={t('store.actions.manifest')}
45
+ />
46
+ </div>
47
+ );
48
+ });
49
+
50
+ export default EditCustomPlugin;