@lobehub/chat 0.116.1 → 0.116.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,56 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ### [Version 0.116.3](https://github.com/lobehub/lobe-chat/compare/v0.116.2...v0.116.3)
6
+
7
+ <sup>Released on **2023-12-26**</sup>
8
+
9
+ #### 💄 Styles
10
+
11
+ - **misc**: Fix typo.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### Styles
19
+
20
+ - **misc**: Fix typo ([9d329a9](https://github.com/lobehub/lobe-chat/commit/9d329a9))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
30
+ ### [Version 0.116.2](https://github.com/lobehub/lobe-chat/compare/v0.116.1...v0.116.2)
31
+
32
+ <sup>Released on **2023-12-26**</sup>
33
+
34
+ #### 💄 Styles
35
+
36
+ - **misc**: Update Modal style.
37
+
38
+ <br/>
39
+
40
+ <details>
41
+ <summary><kbd>Improvements and Fixes</kbd></summary>
42
+
43
+ #### Styles
44
+
45
+ - **misc**: Update Modal style ([ac7d309](https://github.com/lobehub/lobe-chat/commit/ac7d309))
46
+
47
+ </details>
48
+
49
+ <div align="right">
50
+
51
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
52
+
53
+ </div>
54
+
5
55
  ### [Version 0.116.1](https://github.com/lobehub/lobe-chat/compare/v0.116.0...v0.116.1)
6
56
 
7
57
  <sup>Released on **2023-12-26**</sup>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "0.116.1",
3
+ "version": "0.116.3",
4
4
  "description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
5
5
  "keywords": [
6
6
  "framework",
@@ -15,7 +15,12 @@ const Topics = memo(() => {
15
15
  const { t } = useTranslation('chat');
16
16
 
17
17
  return (
18
- <Modal onCancel={() => toggleConfig(false)} open={showAgentSettings} title={t('topic.title')}>
18
+ <Modal
19
+ allowFullscreen
20
+ onCancel={() => toggleConfig(false)}
21
+ open={showAgentSettings}
22
+ title={t('topic.title')}
23
+ >
19
24
  <TopicListContent mobile />
20
25
  </Modal>
21
26
  );
@@ -1,9 +1,6 @@
1
1
  import { SiOpenai } from '@icons-pack/react-simple-icons';
2
2
  import { Avatar, ChatHeaderTitle, Logo, Markdown, Tag } from '@lobehub/ui';
3
- import { Button, SegmentedProps } from 'antd';
4
- import dayjs from 'dayjs';
5
- import { domToJpeg, domToPng, domToSvg, domToWebp } from 'modern-screenshot';
6
- import { memo, useCallback, useState } from 'react';
3
+ import { memo } from 'react';
7
4
  import { useTranslation } from 'react-i18next';
8
5
  import { Flexbox } from 'react-layout-kit';
9
6
 
@@ -14,102 +11,27 @@ import { agentSelectors, sessionSelectors } from '@/store/session/selectors';
14
11
 
15
12
  import PluginTag from '../../ChatHeader/PluginTag';
16
13
  import { useStyles } from './style';
14
+ import { FieldType } from './type';
17
15
 
18
- export enum ImageType {
19
- JPG = 'jpg',
20
- PNG = 'png',
21
- SVG = 'svg',
22
- WEBP = 'webp',
23
- }
16
+ const Preview = memo<FieldType & { title?: string }>(
17
+ ({ title, withSystemRole, withBackground, withFooter }) => {
18
+ const [isInbox, description, avatar, backgroundColor, model, plugins, systemRole] =
19
+ useSessionStore((s) => [
20
+ sessionSelectors.isInboxSession(s),
21
+ agentSelectors.currentAgentDescription(s),
22
+ agentSelectors.currentAgentAvatar(s),
23
+ agentSelectors.currentAgentBackgroundColor(s),
24
+ agentSelectors.currentAgentModel(s),
25
+ agentSelectors.currentAgentPlugins(s),
26
+ agentSelectors.currentAgentSystemRole(s),
27
+ ]);
28
+ const { t } = useTranslation('chat');
29
+ const { styles } = useStyles(withBackground);
24
30
 
25
- export const imageTypeOptions: SegmentedProps['options'] = [
26
- {
27
- label: 'JPG',
28
- value: ImageType.JPG,
29
- },
30
- {
31
- label: 'PNG',
32
- value: ImageType.PNG,
33
- },
34
- {
35
- label: 'SVG',
36
- value: ImageType.SVG,
37
- },
38
- {
39
- label: 'WEBP',
40
- value: ImageType.WEBP,
41
- },
42
- ];
43
-
44
- interface PreviewProps {
45
- imageType: ImageType;
46
- withBackground: boolean;
47
- withFooter: boolean;
48
- withSystemRole: boolean;
49
- }
31
+ const displayTitle = isInbox ? t('inbox.title') : title;
32
+ const displayDesc = isInbox ? t('inbox.desc') : description;
50
33
 
51
- const Preview = memo<PreviewProps>(({ withSystemRole, imageType, withBackground, withFooter }) => {
52
- const [loading, setLoading] = useState(false);
53
- const [isInbox, title, description, avatar, backgroundColor, model, plugins, systemRole] =
54
- useSessionStore((s) => [
55
- sessionSelectors.isInboxSession(s),
56
- agentSelectors.currentAgentTitle(s),
57
- agentSelectors.currentAgentDescription(s),
58
- agentSelectors.currentAgentAvatar(s),
59
- agentSelectors.currentAgentBackgroundColor(s),
60
- agentSelectors.currentAgentModel(s),
61
- agentSelectors.currentAgentPlugins(s),
62
- agentSelectors.currentAgentSystemRole(s),
63
- ]);
64
- const { t } = useTranslation('chat');
65
- const { styles } = useStyles(withBackground);
66
-
67
- const displayTitle = isInbox ? t('inbox.title') : title;
68
- const displayDesc = isInbox ? t('inbox.desc') : description;
69
-
70
- const handleDownload = useCallback(async () => {
71
- setLoading(true);
72
- try {
73
- let screenshotFn: any;
74
- switch (imageType) {
75
- case ImageType.JPG: {
76
- screenshotFn = domToJpeg;
77
- break;
78
- }
79
- case ImageType.PNG: {
80
- screenshotFn = domToPng;
81
- break;
82
- }
83
- case ImageType.SVG: {
84
- screenshotFn = domToSvg;
85
- break;
86
- }
87
- case ImageType.WEBP: {
88
- screenshotFn = domToWebp;
89
- break;
90
- }
91
- }
92
-
93
- const dataUrl = await screenshotFn(document.querySelector('#preview') as HTMLDivElement, {
94
- features: {
95
- // 不启用移除控制符,否则会导致 safari emoji 报错
96
- removeControlCharacter: false,
97
- },
98
- scale: 2,
99
- });
100
- const link = document.createElement('a');
101
- link.download = `LobeChat_${title}_${dayjs().format('YYYY-MM-DD')}.${imageType}`;
102
- link.href = dataUrl;
103
- link.click();
104
- setLoading(false);
105
- } catch (error) {
106
- console.error('Failed to download image', error);
107
- setLoading(false);
108
- }
109
- }, [imageType, title]);
110
-
111
- return (
112
- <>
34
+ return (
113
35
  <div className={styles.preview}>
114
36
  <div className={withBackground ? styles.background : undefined} id={'preview'}>
115
37
  <Flexbox className={styles.container} gap={16}>
@@ -145,11 +67,8 @@ const Preview = memo<PreviewProps>(({ withSystemRole, imageType, withBackground,
145
67
  </Flexbox>
146
68
  </div>
147
69
  </div>
148
- <Button block loading={loading} onClick={handleDownload} size={'large'} type={'primary'}>
149
- {t('shareModal.download')}
150
- </Button>
151
- </>
152
- );
153
- });
70
+ );
71
+ },
72
+ );
154
73
 
155
74
  export default Preview;
@@ -0,0 +1,160 @@
1
+ import { Form, type FormItemProps, Modal, type ModalProps } from '@lobehub/ui';
2
+ import { Button, Segmented, SegmentedProps, Switch } from 'antd';
3
+ import { memo, useMemo, useState } from 'react';
4
+ import { useTranslation } from 'react-i18next';
5
+ import { Flexbox } from 'react-layout-kit';
6
+
7
+ import { useScreenshot } from '@/app/chat/features/ChatHeader/ShareButton/useScreenshot';
8
+ import { FORM_STYLE } from '@/const/layoutTokens';
9
+ import { useChatStore } from '@/store/chat';
10
+ import { useGlobalStore } from '@/store/global';
11
+
12
+ import Preview from './Preview';
13
+ import { FieldType, ImageType } from './type';
14
+
15
+ enum Tab {
16
+ Screenshot = 'screenshot',
17
+ ShareGPT = 'sharegpt',
18
+ }
19
+
20
+ export const imageTypeOptions: SegmentedProps['options'] = [
21
+ {
22
+ label: 'JPG',
23
+ value: ImageType.JPG,
24
+ },
25
+ {
26
+ label: 'PNG',
27
+ value: ImageType.PNG,
28
+ },
29
+ {
30
+ label: 'SVG',
31
+ value: ImageType.SVG,
32
+ },
33
+ {
34
+ label: 'WEBP',
35
+ value: ImageType.WEBP,
36
+ },
37
+ ];
38
+
39
+ const DEFAULT_FIELD_VALUE: FieldType = {
40
+ imageType: ImageType.JPG,
41
+ withBackground: true,
42
+ withFooter: false,
43
+ withPluginInfo: false,
44
+ withSystemRole: false,
45
+ };
46
+
47
+ const ShareModal = memo<ModalProps>(({ onCancel, open }) => {
48
+ const [fieldValue, setFieldValue] = useState<FieldType>(DEFAULT_FIELD_VALUE);
49
+ const [tab, setTab] = useState<Tab>(Tab.Screenshot);
50
+ const { t } = useTranslation('chat');
51
+ const avatar = useGlobalStore((s) => s.settings.avatar);
52
+ const [shareLoading, shareToShareGPT] = useChatStore((s) => [s.shareLoading, s.shareToShareGPT]);
53
+ const { loading, onDownload, title } = useScreenshot(fieldValue.imageType);
54
+
55
+ const options: SegmentedProps['options'] = useMemo(
56
+ () => [
57
+ {
58
+ label: t('shareModal.screenshot'),
59
+ value: Tab.Screenshot,
60
+ },
61
+ {
62
+ label: 'ShareGPT',
63
+ value: Tab.ShareGPT,
64
+ },
65
+ ],
66
+ [],
67
+ );
68
+
69
+ const settings: FormItemProps[] = useMemo(
70
+ () =>
71
+ [
72
+ {
73
+ children: <Switch />,
74
+ label: t('shareModal.withSystemRole'),
75
+ minWidth: undefined,
76
+ name: 'withSystemRole',
77
+ valuePropName: 'checked',
78
+ },
79
+ tab === Tab.Screenshot && {
80
+ children: <Switch />,
81
+ label: t('shareModal.withBackground'),
82
+ minWidth: undefined,
83
+ name: 'withBackground',
84
+ valuePropName: 'checked',
85
+ },
86
+ tab === Tab.Screenshot && {
87
+ children: <Switch />,
88
+ label: t('shareModal.withFooter'),
89
+ minWidth: undefined,
90
+ name: 'withFooter',
91
+ valuePropName: 'checked',
92
+ },
93
+ tab === Tab.Screenshot && {
94
+ children: <Segmented options={imageTypeOptions} />,
95
+ label: t('shareModal.imageType'),
96
+ minWidth: undefined,
97
+ name: 'imageType',
98
+ },
99
+ tab === Tab.ShareGPT && {
100
+ children: <Switch />,
101
+ label: t('shareModal.withPluginInfo'),
102
+ minWidth: undefined,
103
+ name: 'withPluginInfo',
104
+ valuePropName: 'checked',
105
+ },
106
+ ].filter(Boolean) as FormItemProps[],
107
+ [],
108
+ );
109
+
110
+ return (
111
+ <Modal
112
+ allowFullscreen
113
+ centered={false}
114
+ footer={
115
+ <>
116
+ {tab === Tab.Screenshot && (
117
+ <Button block loading={loading} onClick={onDownload} size={'large'} type={'primary'}>
118
+ {t('shareModal.download')}
119
+ </Button>
120
+ )}
121
+ {tab === Tab.ShareGPT && (
122
+ <Button
123
+ block
124
+ loading={shareLoading}
125
+ onClick={() => shareToShareGPT({ avatar, ...fieldValue })}
126
+ size={'large'}
127
+ type={'primary'}
128
+ >
129
+ {t('shareModal.shareToShareGPT')}
130
+ </Button>
131
+ )}
132
+ </>
133
+ }
134
+ maxHeight={false}
135
+ onCancel={onCancel}
136
+ open={open}
137
+ title={t('share', { ns: 'common' })}
138
+ >
139
+ <Flexbox gap={16}>
140
+ <Segmented
141
+ block
142
+ onChange={(value) => setTab(value as Tab)}
143
+ options={options}
144
+ style={{ width: '100%' }}
145
+ value={tab}
146
+ />
147
+ <Form
148
+ initialValues={DEFAULT_FIELD_VALUE}
149
+ items={settings}
150
+ itemsType={'flat'}
151
+ onValuesChange={(_, v) => setFieldValue(v)}
152
+ {...FORM_STYLE}
153
+ />
154
+ {tab === Tab.Screenshot && <Preview title={title} {...fieldValue} />}
155
+ </Flexbox>
156
+ </Modal>
157
+ );
158
+ });
159
+
160
+ export default ShareModal;
@@ -1,4 +1,4 @@
1
- import { ActionIcon, Modal } from '@lobehub/ui';
1
+ import { ActionIcon } from '@lobehub/ui';
2
2
  import { Share2 } from 'lucide-react';
3
3
  import dynamic from 'next/dynamic';
4
4
  import { memo } from 'react';
@@ -8,7 +8,7 @@ import useMergeState from 'use-merge-value';
8
8
  import { DESKTOP_HEADER_ICON_SIZE, MOBILE_HEADER_ICON_SIZE } from '@/const/layoutTokens';
9
9
  import { useChatStore } from '@/store/chat';
10
10
 
11
- const Inner = dynamic(() => import('./Inner'));
11
+ const ShareModal = dynamic(() => import('./ShareModal'));
12
12
  interface ShareButtonProps {
13
13
  mobile?: boolean;
14
14
  open?: boolean;
@@ -34,15 +34,7 @@ const ShareButton = memo<ShareButtonProps>(({ mobile, setOpen, open }) => {
34
34
  title={t('share')}
35
35
  />
36
36
 
37
- <Modal
38
- centered={false}
39
- footer={null}
40
- onCancel={() => setIsModalOpen(false)}
41
- open={isModalOpen}
42
- title={t('share')}
43
- >
44
- <Inner />
45
- </Modal>
37
+ <ShareModal onCancel={() => setIsModalOpen(false)} open={isModalOpen} />
46
38
  </>
47
39
  );
48
40
  });
@@ -0,0 +1,14 @@
1
+ export enum ImageType {
2
+ JPG = 'jpg',
3
+ PNG = 'png',
4
+ SVG = 'svg',
5
+ WEBP = 'webp',
6
+ }
7
+
8
+ export type FieldType = {
9
+ imageType: ImageType;
10
+ withBackground: boolean;
11
+ withFooter: boolean;
12
+ withPluginInfo: boolean;
13
+ withSystemRole: boolean;
14
+ };
@@ -0,0 +1,60 @@
1
+ import dayjs from 'dayjs';
2
+ import { domToJpeg, domToPng, domToSvg, domToWebp } from 'modern-screenshot';
3
+ import { useCallback, useState } from 'react';
4
+
5
+ import { useSessionStore } from '@/store/session';
6
+ import { agentSelectors } from '@/store/session/slices/agent';
7
+
8
+ import { ImageType } from './type';
9
+
10
+ export const useScreenshot = (imageType: ImageType) => {
11
+ const [loading, setLoading] = useState(false);
12
+ const title = useSessionStore(agentSelectors.currentAgentTitle);
13
+
14
+ const handleDownload = useCallback(async () => {
15
+ setLoading(true);
16
+ try {
17
+ let screenshotFn: any;
18
+ switch (imageType) {
19
+ case ImageType.JPG: {
20
+ screenshotFn = domToJpeg;
21
+ break;
22
+ }
23
+ case ImageType.PNG: {
24
+ screenshotFn = domToPng;
25
+ break;
26
+ }
27
+ case ImageType.SVG: {
28
+ screenshotFn = domToSvg;
29
+ break;
30
+ }
31
+ case ImageType.WEBP: {
32
+ screenshotFn = domToWebp;
33
+ break;
34
+ }
35
+ }
36
+
37
+ const dataUrl = await screenshotFn(document.querySelector('#preview') as HTMLDivElement, {
38
+ features: {
39
+ // 不启用移除控制符,否则会导致 safari emoji 报错
40
+ removeControlCharacter: false,
41
+ },
42
+ scale: 2,
43
+ });
44
+ const link = document.createElement('a');
45
+ link.download = `LobeChat_${title}_${dayjs().format('YYYY-MM-DD')}.${imageType}`;
46
+ link.href = dataUrl;
47
+ link.click();
48
+ setLoading(false);
49
+ } catch (error) {
50
+ console.error('Failed to download image', error);
51
+ setLoading(false);
52
+ }
53
+ }, [imageType, title]);
54
+
55
+ return {
56
+ loading,
57
+ onDownload: handleDownload,
58
+ title,
59
+ };
60
+ };
@@ -78,7 +78,6 @@ const SystemRole = memo(() => {
78
78
  <EditableMessage
79
79
  classNames={{ markdown: styles.prompt }}
80
80
  editing={editing}
81
- height={editing ? 'calc(70vh - 108px)' : 'calc(70vh - 357.43px)'}
82
81
  model={{
83
82
  extra: (
84
83
  <AgentInfo
@@ -7,17 +7,11 @@ import TopicSearchBar from './TopicSearchBar';
7
7
 
8
8
  const TopicListContent = memo<{ mobile?: boolean }>(({ mobile }) => {
9
9
  return (
10
- <Flexbox height={'100%'} style={{ overflow: 'hidden' }}>
11
- {mobile ? (
12
- <Flexbox padding={'12px 16px'}>
13
- <TopicSearchBar />
14
- </Flexbox>
15
- ) : (
16
- <Header />
17
- )}
10
+ <Flexbox gap={mobile ? 8 : 0} height={'100%'} style={{ overflow: 'hidden' }}>
11
+ {mobile ? <TopicSearchBar /> : <Header />}
18
12
  <Flexbox
19
13
  gap={16}
20
- paddingInline={mobile ? 16 : 8}
14
+ paddingInline={mobile ? 0 : 8}
21
15
  style={{ overflowY: 'auto', paddingTop: 6, position: 'relative' }}
22
16
  >
23
17
  <Topic />
@@ -1,4 +1,4 @@
1
- import { Alert } from '@lobehub/ui';
1
+ import { Alert, Modal, type ModalProps } from '@lobehub/ui';
2
2
  import { Button, Divider, Input } from 'antd';
3
3
  import { useTheme } from 'antd-style';
4
4
  import isEqual from 'fast-deep-equal';
@@ -8,14 +8,13 @@ import { memo, useState } from 'react';
8
8
  import { useTranslation } from 'react-i18next';
9
9
  import { Flexbox } from 'react-layout-kit';
10
10
 
11
- import MobilePadding from '@/components/MobilePadding';
12
11
  import { AGENTS_INDEX_GITHUB_ISSUE } from '@/const/url';
13
12
  import AgentInfo from '@/features/AgentInfo';
14
13
  import { useGlobalStore } from '@/store/global';
15
14
  import { useSessionStore } from '@/store/session';
16
15
  import { agentSelectors } from '@/store/session/selectors';
17
16
 
18
- const Inner = memo(() => {
17
+ const SubmitAgentModal = memo<ModalProps>(({ open, onCancel }) => {
19
18
  const { t } = useTranslation('setting');
20
19
  const [identifier, setIdentifier] = useState('');
21
20
  const systemRole = useSessionStore(agentSelectors.currentAgentSystemRole);
@@ -54,8 +53,24 @@ const Inner = memo(() => {
54
53
  };
55
54
 
56
55
  return (
57
- <Flexbox gap={16}>
58
- <MobilePadding gap={16}>
56
+ <Modal
57
+ allowFullscreen
58
+ footer={
59
+ <Button
60
+ block
61
+ disabled={!isMetaPass || !identifier}
62
+ onClick={handleSubmit}
63
+ size={'large'}
64
+ type={'primary'}
65
+ >
66
+ {t('submitAgentModal.button')}
67
+ </Button>
68
+ }
69
+ onCancel={onCancel}
70
+ open={open}
71
+ title={t('submitAgentModal.tooltips')}
72
+ >
73
+ <Flexbox gap={16}>
59
74
  {!isMetaPass && (
60
75
  <Alert message={t('submitAgentModal.metaMiss')} showIcon type={'warning'} />
61
76
  )}
@@ -70,18 +85,9 @@ const Inner = memo(() => {
70
85
  placeholder={t('submitAgentModal.placeholder')}
71
86
  value={identifier}
72
87
  />
73
- <Button
74
- block
75
- disabled={!isMetaPass || !identifier}
76
- onClick={handleSubmit}
77
- size={'large'}
78
- type={'primary'}
79
- >
80
- {t('submitAgentModal.button')}
81
- </Button>
82
- </MobilePadding>
83
- </Flexbox>
88
+ </Flexbox>
89
+ </Modal>
84
90
  );
85
91
  });
86
92
 
87
- export default Inner;
93
+ export default SubmitAgentModal;
@@ -1,4 +1,4 @@
1
- import { ActionIcon, Modal } from '@lobehub/ui';
1
+ import { ActionIcon } from '@lobehub/ui';
2
2
  import { useResponsive } from 'antd-style';
3
3
  import { Share2 } from 'lucide-react';
4
4
  import { memo, useState } from 'react';
@@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next';
6
6
 
7
7
  import { HEADER_ICON_SIZE } from '@/const/layoutTokens';
8
8
 
9
- import Inner from './Inner';
9
+ import SubmitAgentModal from './SubmitAgentModal';
10
10
 
11
11
  const SubmitAgentButton = memo(() => {
12
12
  const { t } = useTranslation('setting');
@@ -20,14 +20,7 @@ const SubmitAgentButton = memo(() => {
20
20
  size={HEADER_ICON_SIZE(mobile)}
21
21
  title={t('submitAgentModal.tooltips')}
22
22
  />
23
- <Modal
24
- footer={null}
25
- onCancel={() => setIsModalOpen(false)}
26
- open={isModalOpen}
27
- title={t('submitAgentModal.tooltips')}
28
- >
29
- <Inner />
30
- </Modal>
23
+ <SubmitAgentModal onCancel={() => setIsModalOpen(false)} open={isModalOpen} />
31
24
  </>
32
25
  );
33
26
  });
@@ -15,7 +15,13 @@ const AgentDetail = memo(() => {
15
15
  const { t } = useTranslation('market');
16
16
 
17
17
  return (
18
- <Modal onCancel={deactivateAgent} open={showAgentSidebar} title={t('sidebar.title')}>
18
+ <Modal
19
+ allowFullscreen
20
+ onCancel={deactivateAgent}
21
+ open={showAgentSidebar}
22
+ styles={{ body: { padding: 0 } }}
23
+ title={t('sidebar.title')}
24
+ >
19
25
  <AgentDetailContent />
20
26
  </Modal>
21
27
  );
@@ -5,13 +5,12 @@ import Image from 'next/image';
5
5
  import { memo } from 'react';
6
6
  import { useTranslation } from 'react-i18next';
7
7
 
8
- import MobilePadding from '@/components/MobilePadding';
9
8
  import { AGENTS_INDEX_GITHUB } from '@/const/url';
10
9
 
11
10
  const Inner = memo(() => {
12
11
  const { t } = useTranslation('market');
13
12
  return (
14
- <MobilePadding>
13
+ <>
15
14
  <Image
16
15
  alt={'banner'}
17
16
  height={602}
@@ -46,7 +45,7 @@ const Inner = memo(() => {
46
45
  >
47
46
  {t('guide.func2.button')}
48
47
  </Button>
49
- </MobilePadding>
48
+ </>
50
49
  );
51
50
  });
52
51
 
@@ -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>
@@ -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;
@@ -1,56 +0,0 @@
1
- import { Alert, Form, Input, Modal } from '@lobehub/ui';
2
- import { memo } from 'react';
3
- import { useTranslation } from 'react-i18next';
4
- import { Flexbox } from 'react-layout-kit';
5
-
6
- import MobilePadding from '@/components/MobilePadding';
7
- import { PLUGINS_INDEX_URL, WIKI_PLUGIN_GUIDE } from '@/const/url';
8
-
9
- interface MarketSettingModalProps {
10
- onOpenChange: (open: boolean) => void;
11
- open?: boolean;
12
- }
13
-
14
- const StoreSetting = memo<MarketSettingModalProps>(({ open, onOpenChange }) => {
15
- const { t } = useTranslation('plugin');
16
-
17
- return (
18
- <Modal footer={null} onCancel={() => onOpenChange(false)} open={open} title={t('setting')}>
19
- <Flexbox gap={16}>
20
- <MobilePadding bottom={0} gap={16}>
21
- <Alert message={t('settings.indexUrl.tooltip')} showIcon type={'warning'} />
22
- </MobilePadding>
23
- <Form
24
- items={[
25
- {
26
- children: [
27
- {
28
- children: (
29
- <Input
30
- defaultValue={PLUGINS_INDEX_URL}
31
- disabled
32
- placeholder={PLUGINS_INDEX_URL}
33
- style={{ width: '100%' }}
34
- />
35
- ),
36
- desc: (
37
- <>
38
- {t('dev.modalDesc')}
39
- <a href={WIKI_PLUGIN_GUIDE} rel="noreferrer" target={'_blank'}>
40
- {WIKI_PLUGIN_GUIDE}
41
- </a>
42
- </>
43
- ),
44
- label: t('settings.indexUrl.title'),
45
- },
46
- ],
47
- title: t('settings.title'),
48
- },
49
- ]}
50
- />
51
- </Flexbox>
52
- </Modal>
53
- );
54
- });
55
-
56
- export default StoreSetting;