@lobehub/chat 0.111.6 → 0.112.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/.i18nrc.js +1 -1
  2. package/CHANGELOG.md +84 -0
  3. package/README.md +14 -13
  4. package/README.zh-CN.md +14 -13
  5. package/locales/en_US/chat.json +12 -12
  6. package/locales/en_US/common.json +5 -4
  7. package/locales/en_US/error.json +16 -14
  8. package/locales/en_US/market.json +2 -2
  9. package/locales/en_US/migration.json +4 -4
  10. package/locales/en_US/plugin.json +75 -38
  11. package/locales/en_US/setting.json +7 -7
  12. package/locales/fr_FR/chat.json +11 -11
  13. package/locales/fr_FR/common.json +5 -4
  14. package/locales/fr_FR/error.json +12 -10
  15. package/locales/fr_FR/plugin.json +42 -5
  16. package/locales/fr_FR/setting.json +12 -12
  17. package/locales/fr_FR/welcome.json +3 -3
  18. package/locales/ja_JP/chat.json +1 -1
  19. package/locales/ja_JP/common.json +5 -4
  20. package/locales/ja_JP/error.json +16 -14
  21. package/locales/ja_JP/migration.json +4 -4
  22. package/locales/ja_JP/plugin.json +70 -33
  23. package/locales/ja_JP/setting.json +1 -1
  24. package/locales/ko_KR/chat.json +1 -1
  25. package/locales/ko_KR/common.json +5 -4
  26. package/locales/ko_KR/error.json +16 -14
  27. package/locales/ko_KR/migration.json +4 -4
  28. package/locales/ko_KR/plugin.json +71 -34
  29. package/locales/ko_KR/setting.json +1 -1
  30. package/locales/ru_RU/chat.json +1 -1
  31. package/locales/ru_RU/common.json +5 -4
  32. package/locales/ru_RU/error.json +16 -14
  33. package/locales/ru_RU/market.json +2 -2
  34. package/locales/ru_RU/migration.json +4 -4
  35. package/locales/ru_RU/plugin.json +71 -34
  36. package/locales/ru_RU/setting.json +1 -1
  37. package/locales/tr_TR/chat.json +77 -0
  38. package/locales/tr_TR/common.json +90 -0
  39. package/locales/tr_TR/empty.json +6 -0
  40. package/locales/tr_TR/error.json +53 -0
  41. package/locales/tr_TR/market.json +30 -0
  42. package/locales/tr_TR/migration.json +45 -0
  43. package/locales/tr_TR/plugin.json +163 -0
  44. package/locales/tr_TR/setting.json +301 -0
  45. package/locales/tr_TR/welcome.json +14 -0
  46. package/locales/zh_CN/chat.json +1 -1
  47. package/locales/zh_CN/common.json +1 -0
  48. package/locales/zh_CN/error.json +2 -0
  49. package/locales/zh_CN/plugin.json +42 -5
  50. package/locales/zh_CN/setting.json +1 -1
  51. package/locales/zh_TW/chat.json +1 -1
  52. package/locales/zh_TW/common.json +5 -4
  53. package/locales/zh_TW/error.json +16 -14
  54. package/locales/zh_TW/market.json +2 -2
  55. package/locales/zh_TW/migration.json +4 -4
  56. package/locales/zh_TW/plugin.json +98 -61
  57. package/locales/zh_TW/setting.json +1 -1
  58. package/package.json +4 -2
  59. package/scripts/i18nWorkflow/genDiff.ts +7 -8
  60. package/scripts/i18nWorkflow/utils.ts +0 -14
  61. package/scripts/readmeWorkflow/syncPluginIndex.ts +9 -11
  62. package/src/app/api/plugins/route.ts +2 -4
  63. package/src/app/api/proxy/route.ts +10 -0
  64. package/src/app/chat/features/ChatInput/ActionBar/Tools/ToolItem.tsx +3 -8
  65. package/src/app/chat/features/ChatInput/ActionBar/Tools/index.tsx +3 -3
  66. package/src/app/chat/features/Conversation/ChatList/Error/index.tsx +6 -0
  67. package/src/app/chat/features/Conversation/ChatList/Messages/Function.tsx +1 -1
  68. package/src/app/chat/features/Conversation/ChatList/Plugins/Inspector/Settings.tsx +7 -5
  69. package/src/app/chat/features/Conversation/ChatList/Plugins/Inspector/index.tsx +2 -2
  70. package/src/app/chat/features/Conversation/ChatList/Plugins/Render/DefaultType/index.tsx +6 -4
  71. package/src/app/chat/features/Conversation/ChatList/Plugins/Render/index.tsx +2 -2
  72. package/src/app/chat/features/Migration/ExportConfigButton.tsx +1 -1
  73. package/src/app/market/features/AgentDetailContent/AgentInfo/index.tsx +1 -0
  74. package/src/app/market/features/AgentDetailContent/AgentInfo/style.ts +1 -9
  75. package/src/app/settings/common/Common.tsx +5 -7
  76. package/src/components/FileList/FileItem.tsx +4 -2
  77. package/src/components/HotKeys/index.tsx +5 -4
  78. package/src/const/plugin.ts +1 -0
  79. package/src/features/AgentSetting/AgentPlugin/AddPluginButton.tsx +2 -2
  80. package/src/features/AgentSetting/AgentPlugin/LocalPluginItem.tsx +19 -53
  81. package/src/features/AgentSetting/AgentPlugin/index.tsx +5 -11
  82. package/src/features/AgentSetting/store/initialState.ts +1 -1
  83. package/src/features/PluginDetailModal/APIs.tsx +44 -0
  84. package/src/features/PluginDetailModal/Meta.tsx +59 -0
  85. package/src/features/PluginDetailModal/index.tsx +79 -0
  86. package/src/features/PluginDevModal/PluginPreview.tsx +8 -10
  87. package/src/features/PluginDevModal/UrlManifestForm.tsx +131 -0
  88. package/src/features/PluginDevModal/index.tsx +31 -32
  89. package/src/features/PluginSettings/index.tsx +1 -1
  90. package/src/features/PluginStore/AddPluginButton.tsx +47 -0
  91. package/src/features/PluginStore/InstalledPluginList.tsx +54 -0
  92. package/src/features/PluginStore/OnlineList.tsx +95 -0
  93. package/src/features/PluginStore/PluginItem/Action.tsx +118 -0
  94. package/src/features/PluginStore/PluginItem/EditCustomPlugin.tsx +50 -0
  95. package/src/features/PluginStore/PluginItem/PluginTag.tsx +58 -0
  96. package/src/features/PluginStore/PluginItem/index.tsx +73 -0
  97. package/src/features/PluginStore/index.tsx +20 -92
  98. package/src/hooks/usePlatform.ts +15 -0
  99. package/src/locales/default/chat.ts +1 -1
  100. package/src/locales/default/common.ts +1 -0
  101. package/src/locales/default/error.ts +2 -0
  102. package/src/locales/default/plugin.ts +41 -4
  103. package/src/locales/default/setting.ts +1 -1
  104. package/src/locales/options.ts +4 -0
  105. package/src/locales/resources/index.ts +2 -0
  106. package/src/locales/resources/tr_TR.ts +23 -0
  107. package/src/services/__tests__/__snapshots__/plugin.test.ts.snap +154 -0
  108. package/src/services/__tests__/chat.test.ts +1 -1
  109. package/src/services/__tests__/global.test.ts +57 -0
  110. package/src/services/__tests__/openai/OpenAPI_V3.json +245 -0
  111. package/src/services/__tests__/openai/plugin.json +22 -0
  112. package/src/services/__tests__/plugin.test.ts +90 -1
  113. package/src/services/__tests__/share.test.ts +86 -0
  114. package/src/services/_url.ts +2 -0
  115. package/src/services/global.ts +15 -0
  116. package/src/services/plugin.ts +99 -15
  117. package/src/services/share.ts +34 -0
  118. package/src/store/chat/actions/enhance.test.ts +15 -15
  119. package/src/store/chat/actions/message.test.ts +3 -2
  120. package/src/store/chat/actions/plugin.test.ts +281 -0
  121. package/src/store/chat/actions/plugin.ts +22 -3
  122. package/src/store/chat/actions/share.ts +2 -2
  123. package/src/store/chat/actions/topic.test.ts +12 -4
  124. package/src/store/global/slices/common.ts +2 -2
  125. package/src/store/session/selectors.ts +1 -1
  126. package/src/store/session/slices/agent/selectors.ts +1 -1
  127. package/src/store/tool/helpers.ts +8 -1
  128. package/src/store/tool/slices/customPlugin/action.ts +4 -1
  129. package/src/store/tool/slices/plugin/selectors.test.ts +22 -3
  130. package/src/store/tool/slices/plugin/selectors.ts +24 -5
  131. package/src/store/tool/slices/store/selectors.test.ts +18 -1
  132. package/src/store/tool/slices/store/selectors.ts +14 -3
  133. package/src/styles/antdOverride.ts +7 -0
  134. package/src/types/openai/chat.ts +0 -1
  135. package/src/types/openai/plugin.ts +57 -0
  136. package/src/types/tool/index.ts +1 -2
  137. package/src/types/tool/plugin.ts +13 -1
  138. package/src/types/tool/tool.ts +1 -0
  139. package/src/types/translate.ts +0 -1
  140. package/src/utils/platform.ts +8 -5
  141. package/src/features/PluginDevModal/UrlModeForm.tsx +0 -98
  142. package/src/features/PluginSettingsModal/index.tsx +0 -59
  143. package/src/features/PluginStore/PluginItem.tsx +0 -82
  144. package/src/features/PluginStore/PluginSettings.tsx +0 -40
  145. package/src/services/latestVersion.ts +0 -11
  146. package/src/services/shareGPT.ts +0 -28
@@ -13,14 +13,14 @@ export interface PluginRenderProps {
13
13
  type?: LobePluginType;
14
14
  }
15
15
 
16
- const PluginRender = memo<PluginRenderProps>(({ content, id, payload, name, type }) => {
16
+ const PluginRender = memo<PluginRenderProps>(({ content, id, payload, name, type, loading }) => {
17
17
  switch (type) {
18
18
  case 'standalone': {
19
19
  return <Standalone id={id} name={name} payload={payload} />;
20
20
  }
21
21
 
22
22
  default: {
23
- return <DefaultType content={content} name={name} />;
23
+ return <DefaultType content={content} loading={loading} name={name} />;
24
24
  }
25
25
  }
26
26
  });
@@ -2,7 +2,7 @@ import { Button } from 'antd';
2
2
  import { memo } from 'react';
3
3
  import { useTranslation } from 'react-i18next';
4
4
 
5
- const ExportConfigButton = memo<{ primary?: boolean, state: any; }>(({ state, primary }) => {
5
+ const ExportConfigButton = memo<{ primary?: boolean; state: any }>(({ state, primary }) => {
6
6
  const { t } = useTranslation('migration');
7
7
 
8
8
  const exportData = () => {
@@ -55,6 +55,7 @@ const AgentModalInner = memo(() => {
55
55
  },
56
56
  ]}
57
57
  onChange={setTab}
58
+ variant={'compact'}
58
59
  />
59
60
  </Flexbox>
60
61
  <Flexbox style={{ padding: 16 }}>
@@ -29,15 +29,7 @@ export const useStyles = createStyles(({ css, token, prefixCls, stylish }) => ({
29
29
  `,
30
30
  markdown: stylish.markdownInChat,
31
31
  nav: css`
32
- padding-top: 4px;
33
-
34
- .${prefixCls}-tabs-tab {
35
- margin: 4px !important;
36
-
37
- + .${prefixCls}-tabs-tab {
38
- margin: 4px !important;
39
- }
40
- }
32
+ padding-top: 8px;
41
33
  `,
42
34
  title: css`
43
35
  font-size: 20px;
@@ -69,13 +69,11 @@ const Common = memo<SettingsCommonProps>(({ showAccessCodeConfig }) => {
69
69
  },
70
70
  okText: t('ok', { ns: 'common' }),
71
71
  onOk: async () => {
72
- await Promise.all([
73
- clearSessions,
74
- removeAllPlugins,
75
- clearTopics,
76
- removeAllFiles,
77
- clearAllMessages,
78
- ]);
72
+ await clearSessions();
73
+ await removeAllPlugins();
74
+ await clearTopics();
75
+ await removeAllFiles();
76
+ await clearAllMessages();
79
77
 
80
78
  message.success(t('danger.clear.success'));
81
79
  },
@@ -3,6 +3,7 @@ import { createStyles } from 'antd-style';
3
3
  import { Trash } from 'lucide-react';
4
4
  import { CSSProperties, memo, useCallback } from 'react';
5
5
 
6
+ import { usePlatform } from '@/hooks/usePlatform';
6
7
  import { useFileStore } from '@/store/file';
7
8
 
8
9
  import { MIN_IMAGE_SIZE } from './style';
@@ -38,6 +39,7 @@ const FileItem = memo<FileItemProps>(({ editable, id, alwaysShowClose }) => {
38
39
  const IMAGE_SIZE = editable ? MIN_IMAGE_SIZE : '100%';
39
40
  const { data, isLoading } = useFetchFile(id);
40
41
  const { styles, cx } = useStyles();
42
+ const { isSafari } = usePlatform();
41
43
 
42
44
  const handleRemoveFile = useCallback(
43
45
  (e: any) => {
@@ -62,11 +64,11 @@ const FileItem = memo<FileItemProps>(({ editable, id, alwaysShowClose }) => {
62
64
  }
63
65
  alt={data?.name || id || ''}
64
66
  alwaysShowActions={alwaysShowClose}
65
- height={'auto'}
67
+ height={isSafari ? 'auto' : '100%'}
66
68
  isLoading={isLoading}
67
69
  size={IMAGE_SIZE as any}
68
70
  src={data?.url}
69
- style={{ height: 'auto' }}
71
+ style={{ height: isSafari ? 'auto' : '100%' }}
70
72
  wrapperClassName={cx(styles.image, editable && styles.editableImage)}
71
73
  />
72
74
  );
@@ -8,7 +8,7 @@ import { memo, useEffect, useState } from 'react';
8
8
  import { Flexbox } from 'react-layout-kit';
9
9
 
10
10
  import { CLEAN_MESSAGE_KEY, META_KEY, PREFIX_KEY } from '@/const/hotkeys';
11
- import { isApplePlatform } from '@/utils/platform';
11
+ import { usePlatform } from '@/hooks/usePlatform';
12
12
 
13
13
  const useStyles = createStyles(
14
14
  ({ css, token }) => css`
@@ -44,12 +44,13 @@ const HotKeys = memo<HotKeysProps>(({ keys, desc }) => {
44
44
  const { styles } = useStyles();
45
45
  const [keysGroup, setKeysGroup] = useState(keys.split('+'));
46
46
  const visibility = typeof window === 'undefined' ? 'hidden' : 'visible';
47
+ const { isApple } = usePlatform();
47
48
 
48
49
  useEffect(() => {
49
50
  const mapping: Record<string, any> = {
50
- [CLEAN_MESSAGE_KEY]: isApplePlatform() ? <Icon icon={Delete} /> : 'backspace',
51
- [META_KEY]: isApplePlatform() ? <Icon icon={Command} /> : 'ctrl',
52
- [PREFIX_KEY]: isApplePlatform() ? <Icon icon={Option} /> : 'alt',
51
+ [CLEAN_MESSAGE_KEY]: isApple ? <Icon icon={Delete} /> : 'backspace',
52
+ [META_KEY]: isApple ? <Icon icon={Command} /> : 'ctrl',
53
+ [PREFIX_KEY]: isApple ? <Icon icon={Option} /> : 'alt',
53
54
  };
54
55
  const newValue = keys
55
56
  .split('+')
@@ -1 +1,2 @@
1
1
  export const PLUGIN_SCHEMA_SEPARATOR = '____';
2
+ export const PLUGIN_SCHEMA_API_MD5_PREFIX = 'MD5HASH_';
@@ -1,6 +1,6 @@
1
1
  import { Icon } from '@lobehub/ui';
2
2
  import { Button } from 'antd';
3
- import { LucideBlocks } from 'lucide-react';
3
+ import { PackagePlus } from 'lucide-react';
4
4
  import { forwardRef, useState } from 'react';
5
5
  import { useTranslation } from 'react-i18next';
6
6
 
@@ -33,7 +33,7 @@ const AddPluginButton = forwardRef<HTMLButtonElement>((props, ref) => {
33
33
  open={showModal}
34
34
  />
35
35
  <Button
36
- icon={<Icon icon={LucideBlocks} />}
36
+ icon={<Icon icon={PackagePlus} />}
37
37
  onClick={() => {
38
38
  setModal(true);
39
39
  }}
@@ -1,75 +1,41 @@
1
- import { Button, Switch } from 'antd';
1
+ import { Switch } from 'antd';
2
2
  import isEqual from 'fast-deep-equal';
3
- import { memo, useState } from 'react';
4
- import { useTranslation } from 'react-i18next';
3
+ import { memo } from 'react';
5
4
  import { Flexbox } from 'react-layout-kit';
6
5
 
7
- import DevModal from '@/features/PluginDevModal';
8
6
  import { useToolStore } from '@/store/tool';
9
- import { pluginSelectors } from '@/store/tool/selectors';
10
7
 
11
8
  import { useStore } from '../store';
12
9
 
13
10
  const MarketList = memo<{ id: string }>(({ id }) => {
14
- const { t } = useTranslation('plugin');
15
-
16
- const [showModal, setModal] = useState(false);
17
-
18
11
  const [toggleAgentPlugin, hasPlugin] = useStore((s) => [s.toggleAgentPlugin, !!s.config.plugins]);
19
12
  const plugins = useStore((s) => s.config.plugins || []);
20
13
 
21
- const [useFetchPluginList, fetchPluginManifest, deleteCustomPlugin, updateCustomPlugin] =
22
- useToolStore((s) => [
23
- s.useFetchPluginStore,
24
- s.installPlugin,
25
- s.uninstallCustomPlugin,
26
- s.updateCustomPlugin,
27
- ]);
14
+ const [useFetchPluginList, fetchPluginManifest] = useToolStore((s) => [
15
+ s.useFetchPluginStore,
16
+ s.installPlugin,
17
+ ]);
28
18
 
29
19
  const pluginManifestLoading = useToolStore((s) => s.pluginInstallLoading, isEqual);
30
- const devPlugin = useToolStore(pluginSelectors.getCustomPluginById(id), isEqual);
31
20
 
32
21
  useFetchPluginList();
33
22
 
34
23
  return (
35
- <>
36
- <DevModal
37
- mode={'edit'}
38
- onDelete={() => {
39
- deleteCustomPlugin(id);
40
- toggleAgentPlugin(id, false);
41
- }}
42
- onOpenChange={setModal}
43
- onSave={(value) => {
44
- updateCustomPlugin(id, value);
24
+ <Flexbox align={'center'} gap={8} horizontal>
25
+ <Switch
26
+ checked={
27
+ // 如果在加载中,说明激活了
28
+ pluginManifestLoading[id] || !hasPlugin ? false : plugins.includes(id)
29
+ }
30
+ loading={pluginManifestLoading[id]}
31
+ onChange={(checked) => {
32
+ toggleAgentPlugin(id);
33
+ if (checked) {
34
+ fetchPluginManifest(id);
35
+ }
45
36
  }}
46
- open={showModal}
47
- value={devPlugin}
48
37
  />
49
-
50
- <Flexbox align={'center'} gap={8} horizontal>
51
- <Switch
52
- checked={
53
- // 如果在加载中,说明激活了
54
- pluginManifestLoading[id] || !hasPlugin ? false : plugins.includes(id)
55
- }
56
- loading={pluginManifestLoading[id]}
57
- onChange={(checked) => {
58
- toggleAgentPlugin(id);
59
- if (checked) {
60
- fetchPluginManifest(id);
61
- }
62
- }}
63
- />
64
- <Button
65
- onClick={() => {
66
- setModal(true);
67
- }}
68
- >
69
- {t('list.item.local.config')}
70
- </Button>
71
- </Flexbox>
72
- </>
38
+ </Flexbox>
73
39
  );
74
40
  });
75
41
 
@@ -8,6 +8,7 @@ import { Center, Flexbox } from 'react-layout-kit';
8
8
 
9
9
  import { FORM_STYLE } from '@/const/layoutTokens';
10
10
  import PluginStore from '@/features/PluginStore';
11
+ import PluginTag from '@/features/PluginStore/PluginItem/PluginTag';
11
12
  import { pluginHelpers, useToolStore } from '@/store/tool';
12
13
  import { pluginSelectors } from '@/store/tool/selectors';
13
14
 
@@ -36,31 +37,24 @@ const AgentPlugin = memo(() => {
36
37
 
37
38
  // =========== Plugin List =========== //
38
39
 
39
- const list = installedPlugins.map(({ identifier, type, meta }) => {
40
+ const list = installedPlugins.map(({ identifier, type, meta, author }) => {
40
41
  const isCustomPlugin = type === 'customPlugin';
41
42
 
42
43
  return {
43
- avatar: <Avatar avatar={pluginHelpers.getPluginAvatar(meta)} />,
44
+ avatar: <Avatar avatar={pluginHelpers.getPluginAvatar(meta)} style={{ flex: 'none' }} />,
44
45
  children: isCustomPlugin ? (
45
46
  <LocalPluginItem id={identifier} />
46
47
  ) : (
47
48
  <PluginAction identifier={identifier} />
48
49
  ),
49
-
50
50
  desc: pluginHelpers.getPluginDesc(meta),
51
-
52
- label: isCustomPlugin ? (
51
+ label: (
53
52
  <Flexbox align={'center'} gap={8} horizontal>
54
53
  {pluginHelpers.getPluginTitle(meta)}
55
- <Tag bordered={false} color={'gold'}>
56
- {t('list.item.local.title', { ns: 'plugin' })}
57
- </Tag>
54
+ <PluginTag author={author} type={type} />
58
55
  </Flexbox>
59
- ) : (
60
- pluginHelpers.getPluginTitle(meta)
61
56
  ),
62
57
  minWidth: undefined,
63
- tag: identifier,
64
58
  };
65
59
  });
66
60
 
@@ -1,7 +1,7 @@
1
1
  import { DEFAULT_AGENT_META } from '@/const/meta';
2
2
  import { DEFAULT_AGENT_CONFIG } from '@/const/settings';
3
- import { MetaData } from '@/types/meta';
4
3
  import { LobeAgentConfig } from '@/types/agent';
4
+ import { MetaData } from '@/types/meta';
5
5
 
6
6
  export interface State {
7
7
  autocompleteLoading: SessionLoadingState;
@@ -0,0 +1,44 @@
1
+ import { Empty, Table } from 'antd';
2
+ import isEqual from 'fast-deep-equal';
3
+ import { memo } from 'react';
4
+ import { useTranslation } from 'react-i18next';
5
+ import { Flexbox } from 'react-layout-kit';
6
+
7
+ import { useToolStore } from '@/store/tool';
8
+ import { pluginSelectors } from '@/store/tool/selectors';
9
+
10
+ const APIs = memo<{
11
+ id: string;
12
+ }>(({ id }) => {
13
+ const { t } = useTranslation('plugin');
14
+ const pluginManifest = useToolStore(pluginSelectors.getPluginManifestById(id), isEqual);
15
+
16
+ if (!pluginManifest?.api) return <Empty />;
17
+
18
+ return (
19
+ <Flexbox paddingBlock={16} width={'100%'}>
20
+ <Table
21
+ bordered
22
+ columns={[
23
+ {
24
+ dataIndex: 'name',
25
+ ellipsis: true,
26
+ render: (name: string) => <code>{name}</code>,
27
+ title: t('detailModal.info.name'),
28
+ },
29
+ {
30
+ dataIndex: 'description',
31
+ ellipsis: true,
32
+ title: t('detailModal.info.description'),
33
+ },
34
+ ]}
35
+ dataSource={pluginManifest.api}
36
+ pagination={false}
37
+ rowKey={'name'}
38
+ size={'small'}
39
+ />
40
+ </Flexbox>
41
+ );
42
+ });
43
+
44
+ export default APIs;
@@ -0,0 +1,59 @@
1
+ import { Avatar, Tag } from '@lobehub/ui';
2
+ import { createStyles } from 'antd-style';
3
+ import isEqual from 'fast-deep-equal';
4
+ import { startCase } from 'lodash-es';
5
+ import { memo } from 'react';
6
+ import { Center } from 'react-layout-kit';
7
+
8
+ import { pluginHelpers, useToolStore } from '@/store/tool';
9
+ import { pluginSelectors } from '@/store/tool/selectors';
10
+
11
+ const useStyles = createStyles(({ css, token, stylish }) => ({
12
+ avatar: css`
13
+ flex: none;
14
+ `,
15
+ desc: css`
16
+ color: ${token.colorTextDescription};
17
+ text-align: center;
18
+ `,
19
+ markdown: stylish.markdownInChat,
20
+
21
+ title: css`
22
+ font-size: 20px;
23
+ font-weight: 600;
24
+ text-align: center;
25
+ `,
26
+ }));
27
+
28
+ const Meta = memo<{
29
+ id: string;
30
+ }>(({ id }) => {
31
+ const pluginMeta = useToolStore(pluginSelectors.getPluginMetaById(id), isEqual);
32
+ const { styles, theme } = useStyles();
33
+
34
+ return (
35
+ <Center gap={16}>
36
+ <Avatar
37
+ animation
38
+ avatar={pluginHelpers.getPluginAvatar(pluginMeta)}
39
+ background={theme.colorFillTertiary}
40
+ className={styles.avatar}
41
+ size={100}
42
+ />
43
+
44
+ <div className={styles.title}>{pluginHelpers.getPluginTitle(pluginMeta)}</div>
45
+ {(pluginMeta?.tags as string[])?.length > 0 && (
46
+ <Center gap={6} horizontal style={{ flexWrap: 'wrap' }}>
47
+ {(pluginHelpers.getPluginTags(pluginMeta) as string[]).map((tag: string, index) => (
48
+ <Tag key={index} style={{ margin: 0 }}>
49
+ {startCase(tag).trim()}
50
+ </Tag>
51
+ ))}
52
+ </Center>
53
+ )}
54
+ <div className={styles.desc}>{pluginHelpers.getPluginDesc(pluginMeta)}</div>
55
+ </Center>
56
+ );
57
+ });
58
+
59
+ export default Meta;
@@ -0,0 +1,79 @@
1
+ import { Modal, TabsNav } from '@lobehub/ui';
2
+ import { Divider, TabsProps } from 'antd';
3
+ import { memo } from 'react';
4
+ import { useTranslation } from 'react-i18next';
5
+ import { Center } from 'react-layout-kit';
6
+ import useMergeState from 'use-merge-value';
7
+
8
+ import MobilePadding from '@/components/MobilePadding';
9
+ import PluginSettingsConfig from '@/features/PluginSettings';
10
+ import { pluginHelpers } from '@/store/tool';
11
+
12
+ import APIs from './APIs';
13
+ import Meta from './Meta';
14
+
15
+ interface PluginDetailModalProps {
16
+ id: string;
17
+ onClose: () => void;
18
+ onTabChange?: (key: string) => void;
19
+ open?: boolean;
20
+ schema: any;
21
+ tab?: string;
22
+ }
23
+
24
+ const PluginDetailModal = memo<PluginDetailModalProps>(
25
+ ({ schema, onClose, id, onTabChange, open, tab }) => {
26
+ const [tabKey, setTabKey] = useMergeState('info', {
27
+ onChange: onTabChange,
28
+ value: tab,
29
+ });
30
+ const { t } = useTranslation('plugin');
31
+
32
+ const hasSettings = pluginHelpers.isSettingSchemaNonEmpty(schema);
33
+
34
+ return (
35
+ <Modal
36
+ cancelText={t('cancel', { ns: 'common' })}
37
+ okText={t('ok', { ns: 'common' })}
38
+ onCancel={onClose}
39
+ onOk={() => {
40
+ onClose();
41
+ }}
42
+ open={open}
43
+ title={t('detailModal.title')}
44
+ width={650}
45
+ >
46
+ <MobilePadding>
47
+ <Center gap={8}>
48
+ <Meta id={id} />
49
+ <Divider style={{ marginBottom: 0, marginTop: 8 }} />
50
+ <TabsNav
51
+ activeKey={tabKey}
52
+ items={
53
+ [
54
+ {
55
+ key: 'info',
56
+ label: t('detailModal.tabs.info'),
57
+ },
58
+ hasSettings && {
59
+ key: 'settings',
60
+ label: t('detailModal.tabs.settings'),
61
+ },
62
+ ].filter(Boolean) as TabsProps['items']
63
+ }
64
+ onChange={setTabKey}
65
+ variant={'compact'}
66
+ />
67
+ {tabKey === 'settings' ? (
68
+ hasSettings && <PluginSettingsConfig id={id} schema={schema} />
69
+ ) : (
70
+ <APIs id={id} />
71
+ )}
72
+ </Center>
73
+ </MobilePadding>
74
+ </Modal>
75
+ );
76
+ },
77
+ );
78
+
79
+ export default PluginDetailModal;
@@ -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;