@lobehub/chat 0.116.0 → 0.116.2

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 (26) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +3 -0
  3. package/package.json +2 -1
  4. package/src/app/chat/(mobile)/features/TopicList.tsx +6 -1
  5. package/src/app/chat/features/ChatHeader/ShareButton/Preview.tsx +23 -77
  6. package/src/app/chat/features/ChatHeader/ShareButton/ShareModal.tsx +141 -0
  7. package/src/app/chat/features/ChatHeader/ShareButton/index.tsx +3 -11
  8. package/src/app/chat/features/ChatHeader/ShareButton/type.ts +14 -0
  9. package/src/app/chat/features/ChatHeader/ShareButton/useScreenshot.ts +60 -0
  10. package/src/app/chat/features/SystemRole/index.tsx +0 -1
  11. package/src/app/chat/features/TopicListContent/index.tsx +3 -9
  12. package/src/app/chat/settings/features/SubmitAgentButton/{Inner.tsx → SubmitAgentModal.tsx} +23 -17
  13. package/src/app/chat/settings/features/SubmitAgentButton/index.tsx +3 -10
  14. package/src/app/market/(mobile)/features/AgentDetail.tsx +7 -1
  15. package/src/app/market/features/ShareAgentButton/Inner.tsx +2 -3
  16. package/src/features/PluginDetailModal/index.tsx +27 -29
  17. package/src/features/PluginDevModal/PluginPreview.tsx +1 -1
  18. package/src/features/PluginDevModal/index.tsx +73 -76
  19. package/src/features/PluginSettings/PluginSettingRender.tsx +22 -2
  20. package/src/features/PluginSettings/index.tsx +7 -1
  21. package/src/features/PluginStore/InstalledPluginList.tsx +2 -2
  22. package/src/features/PluginStore/OnlineList.tsx +2 -2
  23. package/src/features/PluginStore/index.tsx +17 -22
  24. package/src/app/chat/features/ChatHeader/ShareButton/Inner.tsx +0 -125
  25. package/src/components/MobilePadding/index.tsx +0 -32
  26. package/src/features/PluginStore/StoreSetting.tsx +0 -56
@@ -5,7 +5,6 @@ import { useTranslation } from 'react-i18next';
5
5
  import { Center } from 'react-layout-kit';
6
6
  import useMergeState from 'use-merge-value';
7
7
 
8
- import MobilePadding from '@/components/MobilePadding';
9
8
  import PluginSettingsConfig from '@/features/PluginSettings';
10
9
  import { pluginHelpers } from '@/store/tool';
11
10
 
@@ -33,6 +32,7 @@ const PluginDetailModal = memo<PluginDetailModalProps>(
33
32
 
34
33
  return (
35
34
  <Modal
35
+ allowFullscreen
36
36
  cancelText={t('cancel', { ns: 'common' })}
37
37
  okText={t('ok', { ns: 'common' })}
38
38
  onCancel={onClose}
@@ -43,34 +43,32 @@ const PluginDetailModal = memo<PluginDetailModalProps>(
43
43
  title={t('detailModal.title')}
44
44
  width={650}
45
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>
46
+ <Center gap={8}>
47
+ <Meta id={id} />
48
+ <Divider style={{ marginBottom: 0, marginTop: 8 }} />
49
+ <TabsNav
50
+ activeKey={tabKey}
51
+ items={
52
+ [
53
+ {
54
+ key: 'info',
55
+ label: t('detailModal.tabs.info'),
56
+ },
57
+ hasSettings && {
58
+ key: 'settings',
59
+ label: t('detailModal.tabs.settings'),
60
+ },
61
+ ].filter(Boolean) as TabsProps['items']
62
+ }
63
+ onChange={setTabKey}
64
+ variant={'compact'}
65
+ />
66
+ {tabKey === 'settings' ? (
67
+ hasSettings && <PluginSettingsConfig id={id} schema={schema} />
68
+ ) : (
69
+ <APIs id={id} />
70
+ )}
71
+ </Center>
74
72
  </Modal>
75
73
  );
76
74
  },
@@ -27,7 +27,7 @@ const PluginPreview = memo<{ form: FormInstance }>(({ form }) => {
27
27
  };
28
28
 
29
29
  return (
30
- <Card size={'small'} title={t('dev.preview.card')}>
30
+ <Card bodyStyle={{ padding: '0 16px' }} size={'small'} title={t('dev.preview.card')}>
31
31
  <Form.Item {...items} colon={false} style={{ alignItems: 'center', marginBottom: 0 }} />
32
32
  </Card>
33
33
  );
@@ -1,12 +1,11 @@
1
1
  import { Alert, Icon, Modal, Tooltip } from '@lobehub/ui';
2
- import { App, Button, Divider, Form, Popconfirm, Segmented } from 'antd';
2
+ import { App, Button, 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';
6
6
  import { Trans, useTranslation } from 'react-i18next';
7
7
  import { Flexbox } from 'react-layout-kit';
8
8
 
9
- import MobilePadding from '@/components/MobilePadding';
10
9
  import { WIKI_PLUGIN_GUIDE } from '@/const/url';
11
10
  import { LobeToolCustomPlugin } from '@/types/tool/plugin';
12
11
 
@@ -29,15 +28,17 @@ const DevModal = memo<DevModalProps>(
29
28
  const [configMode, setConfigMode] = useState<'url' | 'local'>('url');
30
29
  const { t } = useTranslation('plugin');
31
30
  const { message } = App.useApp();
32
- const { mobile } = useResponsive();
33
31
  const [submitting, setSubmitting] = useState(false);
32
+ const { mobile } = useResponsive();
34
33
  const [form] = Form.useForm();
35
34
  useEffect(() => {
36
35
  form.setFieldsValue(value);
37
36
  }, []);
38
37
 
38
+ const buttonStyle = mobile ? { flex: 1 } : { margin: 0 };
39
+
39
40
  const footer = (
40
- <Flexbox horizontal justify={'space-between'} style={{ marginTop: 24 }}>
41
+ <Flexbox flex={1} gap={12} horizontal justify={'flex-end'}>
41
42
  {isEditMode ? (
42
43
  <Popconfirm
43
44
  okButtonProps={{
@@ -51,30 +52,29 @@ const DevModal = memo<DevModalProps>(
51
52
  placement={'topLeft'}
52
53
  title={t('dev.confirmDeleteDevPlugin')}
53
54
  >
54
- <Button danger>{t('delete', { ns: 'common' })}</Button>
55
+ <Button danger style={buttonStyle}>
56
+ {t('delete', { ns: 'common' })}
57
+ </Button>
55
58
  </Popconfirm>
56
- ) : (
57
- <div />
58
- )}
59
-
60
- <Flexbox horizontal>
61
- <Button
62
- onClick={() => {
63
- onOpenChange(false);
64
- }}
65
- >
66
- {t('cancel', { ns: 'common' })}
67
- </Button>
68
- <Button
69
- loading={submitting}
70
- onClick={() => {
71
- form.submit();
72
- }}
73
- type={'primary'}
74
- >
75
- {t(isEditMode ? 'dev.update' : 'dev.save')}
76
- </Button>
77
- </Flexbox>
59
+ ) : null}
60
+ <Button
61
+ onClick={() => {
62
+ onOpenChange(false);
63
+ }}
64
+ style={buttonStyle}
65
+ >
66
+ {t('cancel', { ns: 'common' })}
67
+ </Button>
68
+ <Button
69
+ loading={submitting}
70
+ onClick={() => {
71
+ form.submit();
72
+ }}
73
+ style={buttonStyle}
74
+ type={'primary'}
75
+ >
76
+ {t(isEditMode ? 'dev.update' : 'dev.save')}
77
+ </Button>
78
78
  </Flexbox>
79
79
  );
80
80
 
@@ -95,6 +95,7 @@ const DevModal = memo<DevModalProps>(
95
95
  }}
96
96
  >
97
97
  <Modal
98
+ allowFullscreen
98
99
  cancelText={t('cancel', { ns: 'common' })}
99
100
  footer={footer}
100
101
  okText={t('dev.save')}
@@ -110,59 +111,55 @@ const DevModal = memo<DevModalProps>(
110
111
  title={t(isEditMode ? 'dev.title.edit' : 'dev.title.create')}
111
112
  >
112
113
  <Flexbox
113
- gap={mobile ? 0 : 16}
114
+ gap={16}
114
115
  onClick={(e) => {
115
116
  e.stopPropagation();
116
117
  }}
117
118
  >
118
- <MobilePadding bottom={0} gap={16}>
119
- <Alert
120
- message={
121
- <Trans i18nKey={'dev.modalDesc'} ns={'plugin'}>
122
- 添加自定义插件后,可用于插件开发验证,也可直接在会话中使用。插件开发文档请参考:
123
- <a
124
- href={WIKI_PLUGIN_GUIDE}
125
- rel="noreferrer"
126
- style={{ paddingInline: 8 }}
127
- target={'_blank'}
128
- >
129
- 文档
130
- </a>
131
- <Icon icon={MoveUpRight} />
132
- </Trans>
133
- }
134
- showIcon
135
- type={'info'}
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>
165
- </MobilePadding>
119
+ <Alert
120
+ message={
121
+ <Trans i18nKey={'dev.modalDesc'} ns={'plugin'}>
122
+ 添加自定义插件后,可用于插件开发验证,也可直接在会话中使用。插件开发文档请参考:
123
+ <a
124
+ href={WIKI_PLUGIN_GUIDE}
125
+ rel="noreferrer"
126
+ style={{ paddingInline: 8 }}
127
+ target={'_blank'}
128
+ >
129
+ 文档
130
+ </a>
131
+ <Icon icon={MoveUpRight} />
132
+ </Trans>
133
+ }
134
+ showIcon
135
+ type={'info'}
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
+
159
+ {configMode === 'url' ? (
160
+ <UrlManifestForm form={form} isEditMode={mode === 'edit'} />
161
+ ) : null}
162
+ <PluginPreview form={form} />
166
163
  </Flexbox>
167
164
  </Modal>
168
165
  </Form.Provider>
@@ -1,16 +1,20 @@
1
- import { Input, InputNumber, Switch } from 'antd';
1
+ import { Input, InputNumber, Select, Slider, Switch } from 'antd';
2
+ import { JSONSchema7Type } from 'json-schema';
2
3
  import { memo } from 'react';
3
4
 
4
5
  interface PluginSettingsProps {
5
6
  defaultValue?: any;
7
+ enum?: JSONSchema7Type[];
6
8
  format?: string;
9
+ maximum?: number;
10
+ minimum?: number;
7
11
  onChange: (value: any) => void;
8
12
  props?: any;
9
13
  type?: 'string' | 'number' | 'boolean';
10
14
  }
11
15
 
12
16
  const PluginSettingRender = memo<PluginSettingsProps>(
13
- ({ type, defaultValue, onChange, format, props }) => {
17
+ ({ type, defaultValue, enum: enumItems, onChange, format, minimum, maximum, props }) => {
14
18
  switch (type) {
15
19
  case 'string': {
16
20
  switch (format) {
@@ -26,6 +30,19 @@ const PluginSettingRender = memo<PluginSettingsProps>(
26
30
  );
27
31
  }
28
32
  }
33
+
34
+ if (enumItems) {
35
+ return (
36
+ <Select
37
+ defaultValue={defaultValue}
38
+ onChange={onChange}
39
+ options={enumItems.map((i) =>
40
+ typeof i === 'string' ? { label: i, value: i } : (i as any),
41
+ )}
42
+ />
43
+ );
44
+ }
45
+
29
46
  return (
30
47
  <Input
31
48
  {...props}
@@ -36,7 +53,10 @@ const PluginSettingRender = memo<PluginSettingsProps>(
36
53
  />
37
54
  );
38
55
  }
56
+
39
57
  case 'number': {
58
+ if (typeof minimum === 'number' || typeof maximum === 'number')
59
+ return <Slider defaultValue={defaultValue} max={maximum} min={minimum} {...props} />;
40
60
  return (
41
61
  <InputNumber
42
62
  {...props}
@@ -15,8 +15,11 @@ export const transformPluginSettings = (pluginSettings: PluginSchema) => {
15
15
 
16
16
  return Object.entries(pluginSettings.properties).map(([name, i]) => ({
17
17
  desc: i.description,
18
+ enum: i.enum,
18
19
  format: i.format,
19
- label: i.title,
20
+ label: i.title || name,
21
+ maximum: i.maximum,
22
+ minimum: i.minimum,
20
23
  name,
21
24
  tag: name,
22
25
  type: i.type,
@@ -63,7 +66,10 @@ const PluginSettingsConfig = memo<PluginSettingsConfigProps>(({ schema, id }) =>
63
66
  >
64
67
  <PluginSettingRender
65
68
  defaultValue={pluginSetting[item.name]}
69
+ enum={item.enum}
66
70
  format={item.format}
71
+ maximum={item.maximum}
72
+ minimum={item.minimum}
67
73
  onChange={(value) => {
68
74
  updatePluginSettings(id, { [item.name]: value });
69
75
  }}
@@ -18,7 +18,7 @@ export const InstalledPluginList = memo(() => {
18
18
  const installedPlugins = useToolStore(pluginSelectors.installedPluginMetaList, isEqual);
19
19
 
20
20
  return (
21
- <Flexbox gap={16}>
21
+ <>
22
22
  <Flexbox align={'center'} gap={16} horizontal justify={'space-between'}>
23
23
  <Flexbox flex={1}>
24
24
  <SearchBar
@@ -47,7 +47,7 @@ export const InstalledPluginList = memo(() => {
47
47
  <PluginItem {...i} key={i.identifier} />
48
48
  ))}
49
49
  </Flexbox>
50
- </Flexbox>
50
+ </>
51
51
  );
52
52
  });
53
53
 
@@ -39,7 +39,7 @@ export const OnlineList = memo(() => {
39
39
  const isEmpty = pluginStoreList.length === 0;
40
40
 
41
41
  return (
42
- <Flexbox gap={16}>
42
+ <>
43
43
  <Flexbox align={'center'} gap={8} horizontal justify={'space-between'}>
44
44
  <Flexbox flex={1}>
45
45
  <SearchBar
@@ -88,7 +88,7 @@ export const OnlineList = memo(() => {
88
88
  ))}
89
89
  </Flexbox>
90
90
  )}
91
- </Flexbox>
91
+ </>
92
92
  );
93
93
  });
94
94
 
@@ -2,9 +2,8 @@ import { Modal } from '@lobehub/ui';
2
2
  import { Segmented } from 'antd';
3
3
  import { memo } from 'react';
4
4
  import { useTranslation } from 'react-i18next';
5
- import { Center, Flexbox } from 'react-layout-kit';
5
+ import { Flexbox } from 'react-layout-kit';
6
6
 
7
- import MobilePadding from '@/components/MobilePadding';
8
7
  import { useToolStore } from '@/store/tool';
9
8
 
10
9
  import InstalledPluginList from './InstalledPluginList';
@@ -21,6 +20,7 @@ export const PluginStore = memo<PluginStoreProps>(({ setOpen, open }) => {
21
20
 
22
21
  return (
23
22
  <Modal
23
+ allowFullscreen
24
24
  footer={null}
25
25
  onCancel={() => {
26
26
  setOpen(false);
@@ -29,26 +29,21 @@ export const PluginStore = memo<PluginStoreProps>(({ setOpen, open }) => {
29
29
  title={t('store.title')}
30
30
  width={800}
31
31
  >
32
- <MobilePadding>
33
- <Center>
34
- <Flexbox gap={24} width={'100%'}>
35
- <Segmented
36
- block
37
- onChange={(v) => {
38
- useToolStore.setState({ listType: v as any });
39
- }}
40
- options={[
41
- { label: t('store.tabs.all'), value: 'all' },
42
- { label: t('store.tabs.installed'), value: 'installed' },
43
- ]}
44
- style={{ flex: 1 }}
45
- value={listType}
46
- />
47
-
48
- {listType === 'all' ? <OnlineList /> : <InstalledPluginList />}
49
- </Flexbox>
50
- </Center>
51
- </MobilePadding>
32
+ <Flexbox gap={16} width={'100%'}>
33
+ <Segmented
34
+ block
35
+ onChange={(v) => {
36
+ useToolStore.setState({ listType: v as any });
37
+ }}
38
+ options={[
39
+ { label: t('store.tabs.all'), value: 'all' },
40
+ { label: t('store.tabs.installed'), value: 'installed' },
41
+ ]}
42
+ style={{ flex: 1 }}
43
+ value={listType}
44
+ />
45
+ {listType === 'all' ? <OnlineList /> : <InstalledPluginList />}
46
+ </Flexbox>
52
47
  </Modal>
53
48
  );
54
49
  });
@@ -1,125 +0,0 @@
1
- import { Form, ItemGroup } from '@lobehub/ui';
2
- import { Button, Segmented, SegmentedProps, Switch } from 'antd';
3
- import { Share2 } from 'lucide-react';
4
- import { memo, useMemo, useState } from 'react';
5
- import { useTranslation } from 'react-i18next';
6
- import { Flexbox } from 'react-layout-kit';
7
-
8
- import MobilePadding from '@/components/MobilePadding';
9
- import { FORM_STYLE } from '@/const/layoutTokens';
10
- import { useChatStore } from '@/store/chat';
11
- import { useGlobalStore } from '@/store/global';
12
-
13
- import Preview, { ImageType, imageTypeOptions } from './Preview';
14
-
15
- enum Tab {
16
- Screenshot = 'screenshot',
17
- ShareGPT = 'sharegpt',
18
- }
19
-
20
- const Inner = memo(() => {
21
- const [withSystemRole, setWithSystemRole] = useState(false);
22
- const [withPluginInfo, setWithPluginInfo] = useState(false);
23
- const [withBackground, setWithBackground] = useState(true);
24
- const [withFooter, setWithFooter] = useState(false);
25
- const [imageType, setImageType] = useState<ImageType>(ImageType.JPG);
26
- const [tab, setTab] = useState<Tab>(Tab.Screenshot);
27
- const { t } = useTranslation('chat');
28
-
29
- const avatar = useGlobalStore((s) => s.settings.avatar);
30
- const [shareLoading, shareToShareGPT] = useChatStore((s) => [s.shareLoading, s.shareToShareGPT]);
31
-
32
- const options: SegmentedProps['options'] = useMemo(
33
- () => [
34
- {
35
- label: t('shareModal.screenshot'),
36
- value: Tab.Screenshot,
37
- },
38
- {
39
- label: 'ShareGPT',
40
- value: Tab.ShareGPT,
41
- },
42
- ],
43
- [],
44
- );
45
-
46
- const settings: ItemGroup = useMemo(
47
- () =>
48
- ({
49
- children: [
50
- {
51
- children: <Switch checked={withSystemRole} onChange={setWithSystemRole} />,
52
- label: t('shareModal.withSystemRole'),
53
- minWidth: undefined,
54
- },
55
- tab === Tab.Screenshot && {
56
- children: <Switch checked={withBackground} onChange={setWithBackground} />,
57
- label: t('shareModal.withBackground'),
58
- minWidth: undefined,
59
- },
60
- tab === Tab.Screenshot && {
61
- children: <Switch checked={withFooter} onChange={setWithFooter} />,
62
- label: t('shareModal.withFooter'),
63
- minWidth: undefined,
64
- },
65
- tab === Tab.Screenshot && {
66
- children: (
67
- <Segmented
68
- onChange={(value) => setImageType(value as ImageType)}
69
- options={imageTypeOptions}
70
- value={imageType}
71
- />
72
- ),
73
- label: t('shareModal.imageType'),
74
- minWidth: undefined,
75
- },
76
- tab === Tab.ShareGPT && {
77
- children: <Switch checked={withPluginInfo} onChange={setWithPluginInfo} />,
78
- label: t('shareModal.withPluginInfo'),
79
- minWidth: undefined,
80
- },
81
- ].filter(Boolean),
82
- icon: Share2,
83
- title: t('shareModal.settings'),
84
- }) as ItemGroup,
85
- [tab, withSystemRole, withPluginInfo, withBackground, withFooter, imageType],
86
- );
87
-
88
- return (
89
- <Flexbox gap={16}>
90
- <MobilePadding bottom={0}>
91
- <Segmented
92
- block
93
- onChange={(value) => setTab(value as Tab)}
94
- options={options}
95
- style={{ width: '100%' }}
96
- value={tab}
97
- />
98
- </MobilePadding>
99
- <Form items={[settings]} {...FORM_STYLE} />
100
- <MobilePadding gap={16} top={0}>
101
- {tab === Tab.Screenshot && (
102
- <Preview
103
- imageType={imageType}
104
- withBackground={withBackground}
105
- withFooter={withFooter}
106
- withSystemRole={withSystemRole}
107
- />
108
- )}
109
- {tab === Tab.ShareGPT && (
110
- <Button
111
- block
112
- loading={shareLoading}
113
- onClick={() => shareToShareGPT({ avatar, withPluginInfo, withSystemRole })}
114
- size={'large'}
115
- type={'primary'}
116
- >
117
- {t('shareModal.shareToShareGPT')}
118
- </Button>
119
- )}
120
- </MobilePadding>
121
- </Flexbox>
122
- );
123
- });
124
-
125
- export default Inner;
@@ -1,32 +0,0 @@
1
- import { useResponsive } from 'antd-style';
2
- import { ReactNode, memo } from 'react';
3
- import { Flexbox } from 'react-layout-kit';
4
-
5
- interface MobilePaddingProps {
6
- bottom?: number;
7
- children: ReactNode;
8
- gap?: number;
9
- left?: number;
10
- right?: number;
11
- top?: number;
12
- }
13
-
14
- const MobilePadding = memo<MobilePaddingProps>(
15
- ({ children, top = 16, right = 16, left = 16, bottom = 16, gap }) => {
16
- const { mobile } = useResponsive();
17
-
18
- if (mobile)
19
- return (
20
- <Flexbox
21
- gap={gap}
22
- style={{ paddingBottom: bottom, paddingLeft: left, paddingRight: right, paddingTop: top }}
23
- >
24
- {children}
25
- </Flexbox>
26
- );
27
-
28
- return children;
29
- },
30
- );
31
-
32
- export default MobilePadding;