@lobehub/chat 0.151.9 → 0.151.11

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 (51) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/locales/ar/common.json +5 -0
  3. package/locales/bg-BG/common.json +5 -0
  4. package/locales/de-DE/common.json +5 -0
  5. package/locales/en-US/common.json +5 -0
  6. package/locales/es-ES/common.json +5 -0
  7. package/locales/fr-FR/common.json +5 -0
  8. package/locales/it-IT/common.json +5 -0
  9. package/locales/ja-JP/common.json +5 -0
  10. package/locales/ko-KR/common.json +5 -0
  11. package/locales/nl-NL/common.json +5 -0
  12. package/locales/pl-PL/common.json +5 -0
  13. package/locales/pt-BR/common.json +5 -0
  14. package/locales/ru-RU/common.json +5 -0
  15. package/locales/tr-TR/common.json +5 -0
  16. package/locales/vi-VN/common.json +5 -0
  17. package/locales/zh-CN/common.json +5 -0
  18. package/locales/zh-TW/common.json +5 -0
  19. package/package.json +32 -32
  20. package/src/app/(main)/chat/features/TelemetryNotification/index.tsx +3 -1
  21. package/src/app/(main)/settings/agent/Agent.tsx +1 -3
  22. package/src/app/(main)/welcome/_layout/Desktop.tsx +21 -19
  23. package/src/app/(main)/welcome/_layout/Mobile.tsx +9 -6
  24. package/src/app/(main)/welcome/features/Actions.tsx +39 -0
  25. package/src/app/(main)/welcome/features/Hero.tsx +63 -0
  26. package/src/app/(main)/welcome/features/Logo.tsx +30 -0
  27. package/src/app/(main)/welcome/{layout.ts → layout.tsx} +2 -0
  28. package/src/app/(main)/welcome/page.tsx +16 -9
  29. package/src/const/url.ts +3 -0
  30. package/src/features/ChatInput/ActionBar/Temperature.tsx +1 -1
  31. package/src/features/Follow/index.tsx +64 -0
  32. package/src/locales/default/common.ts +7 -0
  33. package/src/services/session/client.test.ts +26 -0
  34. package/src/services/session/client.ts +1 -1
  35. package/src/store/global/action.test.ts +38 -0
  36. package/src/store/global/action.ts +6 -3
  37. package/src/store/user/slices/preference/action.test.ts +22 -1
  38. package/src/store/user/slices/preference/action.ts +1 -3
  39. package/src/store/user/slices/preference/initialState.ts +2 -0
  40. package/src/store/user/slices/preference/selectors.ts +2 -0
  41. package/src/store/user/slices/settings/actions/llm.test.ts +3 -9
  42. package/src/store/user/slices/settings/selectors/settings.ts +3 -1
  43. package/src/app/(main)/welcome/(desktop)/features/Footer.tsx +0 -45
  44. package/src/app/(main)/welcome/(desktop)/features/Showcase.tsx +0 -23
  45. package/src/app/(main)/welcome/(desktop)/index.tsx +0 -17
  46. package/src/app/(main)/welcome/(mobile)/index.tsx +0 -16
  47. package/src/app/(main)/welcome/features/Banner/AgentCard.tsx +0 -71
  48. package/src/app/(main)/welcome/features/Banner/AgentTemplate.tsx +0 -43
  49. package/src/app/(main)/welcome/features/Banner/Hero.tsx +0 -50
  50. package/src/app/(main)/welcome/features/Banner/index.tsx +0 -56
  51. package/src/app/(main)/welcome/features/Banner/style.ts +0 -71
@@ -4,6 +4,7 @@ import { withSWR } from '~test-utils';
4
4
 
5
5
  import { globalService } from '@/services/global';
6
6
  import { useGlobalStore } from '@/store/global/index';
7
+ import { initialState } from '@/store/global/initialState';
7
8
 
8
9
  vi.mock('zustand/traditional');
9
10
 
@@ -141,4 +142,41 @@ describe('createPreferenceSlice', () => {
141
142
  expect(useGlobalStore.getState().latestVersion).toBe(latestVersion);
142
143
  });
143
144
  });
145
+
146
+ describe('useInitGlobalPreference', () => {
147
+ it('should init global preference if there is empty object', async () => {
148
+ vi.spyOn(
149
+ useGlobalStore.getState().preferenceStorage,
150
+ 'getFromLocalStorage',
151
+ ).mockReturnValueOnce({} as any);
152
+
153
+ const { result } = renderHook(() => useGlobalStore().useInitGlobalPreference(), {
154
+ wrapper: withSWR,
155
+ });
156
+
157
+ await waitFor(() => {
158
+ expect(result.current.data).toEqual({});
159
+ });
160
+
161
+ expect(useGlobalStore.getState().preference).toEqual(initialState.preference);
162
+ });
163
+
164
+ it('should update with data', async () => {
165
+ const { result } = renderHook(() => useGlobalStore());
166
+ vi.spyOn(
167
+ useGlobalStore.getState().preferenceStorage,
168
+ 'getFromLocalStorage',
169
+ ).mockReturnValueOnce({ inputHeight: 300 } as any);
170
+
171
+ const { result: hooks } = renderHook(() => result.current.useInitGlobalPreference(), {
172
+ wrapper: withSWR,
173
+ });
174
+
175
+ await waitFor(() => {
176
+ expect(hooks.current.data).toEqual({ inputHeight: 300 });
177
+ });
178
+
179
+ expect(result.current.preference.inputHeight).toEqual(300);
180
+ });
181
+ });
144
182
  });
@@ -1,3 +1,4 @@
1
+ import isEqual from 'fast-deep-equal';
1
2
  import { produce } from 'immer';
2
3
  import { gt } from 'semver';
3
4
  import useSWR, { SWRResponse } from 'swr';
@@ -94,9 +95,11 @@ export const globalActionSlice: StateCreator<
94
95
  () => get().preferenceStorage.getFromLocalStorage(),
95
96
  {
96
97
  onSuccess: (preference) => {
97
- if (preference) {
98
- set({ preference }, false, n('initPreference'));
99
- }
98
+ const nextPreference = merge(get().preference, preference);
99
+
100
+ if (isEqual(get().preference, nextPreference)) return;
101
+
102
+ set({ preference: nextPreference }, false, n('initPreference'));
100
103
  },
101
104
  },
102
105
  ),
@@ -1,6 +1,8 @@
1
- import { act, renderHook } from '@testing-library/react';
1
+ import { act, renderHook, waitFor } from '@testing-library/react';
2
2
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3
+ import { withSWR } from '~test-utils';
3
4
 
5
+ import { globalService } from '@/services/global';
4
6
  import { useUserStore } from '@/store/user';
5
7
 
6
8
  import { type Guide } from './initialState';
@@ -38,4 +40,23 @@ describe('createPreferenceSlice', () => {
38
40
  expect(result.current.preference.hideSyncAlert).toEqual(true);
39
41
  });
40
42
  });
43
+
44
+ describe('useInitPreference', () => {
45
+ it('should return false when userId is empty', async () => {
46
+ const { result } = renderHook(() => useUserStore());
47
+
48
+ vi.spyOn(result.current.preferenceStorage, 'getFromLocalStorage').mockResolvedValueOnce(
49
+ {} as any,
50
+ );
51
+
52
+ const { result: prefernce } = renderHook(() => result.current.useInitPreference(), {
53
+ wrapper: withSWR,
54
+ });
55
+
56
+ await waitFor(() => {
57
+ expect(prefernce.current.data).toEqual({});
58
+ expect(result.current.isPreferenceInit).toBeTruthy();
59
+ });
60
+ });
61
+ });
41
62
  });
@@ -41,9 +41,7 @@ export const createPreferenceSlice: StateCreator<
41
41
  () => get().preferenceStorage.getFromLocalStorage(),
42
42
  {
43
43
  onSuccess: (preference) => {
44
- if (preference) {
45
- set({ preference }, false, n('initPreference'));
46
- }
44
+ set({ isPreferenceInit: true, preference }, false, n('initPreference'));
47
45
  },
48
46
  },
49
47
  ),
@@ -16,6 +16,7 @@ export interface UserPreference {
16
16
  }
17
17
 
18
18
  export interface UserPreferenceState {
19
+ isPreferenceInit: boolean;
19
20
  /**
20
21
  * the user preference, which only store in local storage
21
22
  */
@@ -24,6 +25,7 @@ export interface UserPreferenceState {
24
25
  }
25
26
 
26
27
  export const initialPreferenceState: UserPreferenceState = {
28
+ isPreferenceInit: false,
27
29
  preference: {
28
30
  guide: {},
29
31
  telemetry: null,
@@ -5,9 +5,11 @@ const useCmdEnterToSend = (s: UserStore): boolean => s.preference.useCmdEnterToS
5
5
  const userAllowTrace = (s: UserStore) => s.preference.telemetry;
6
6
 
7
7
  const hideSyncAlert = (s: UserStore) => s.preference.hideSyncAlert;
8
+ const isPreferenceInit = (s: UserStore) => s.isPreferenceInit;
8
9
 
9
10
  export const preferenceSelectors = {
10
11
  hideSyncAlert,
12
+ isPreferenceInit,
11
13
  useCmdEnterToSend,
12
14
  userAllowTrace,
13
15
  };
@@ -2,17 +2,11 @@ import { act, renderHook } from '@testing-library/react';
2
2
  import { describe, expect, it, vi } from 'vitest';
3
3
 
4
4
  import { userService } from '@/services/user';
5
- import { UserStore, useUserStore } from '@/store/user';
6
- import { UserSettingsState, initialSettingsState } from '@/store/user/slices/settings/initialState';
7
- import {
8
- modelConfigSelectors,
9
- modelProviderSelectors,
10
- settingsSelectors,
11
- } from '@/store/user/slices/settings/selectors';
5
+ import { useUserStore } from '@/store/user';
12
6
  import { GeneralModelProviderConfig } from '@/types/settings';
13
- import { merge } from '@/utils/merge';
14
7
 
15
- import { CustomModelCardDispatch, customModelCardsReducer } from '../reducers/customModelCard';
8
+ import { CustomModelCardDispatch } from '../reducers/customModelCard';
9
+ import { modelProviderSelectors, settingsSelectors } from '../selectors';
16
10
 
17
11
  // Mock userService
18
12
  vi.mock('@/services/user', () => ({
@@ -1,6 +1,6 @@
1
1
  import { DEFAULT_LANG } from '@/const/locale';
2
2
  import { DEFAULT_AGENT_META } from '@/const/meta';
3
- import { DEFAULT_AGENT, DEFAULT_TTS_CONFIG } from '@/const/settings';
3
+ import { DEFAULT_AGENT, DEFAULT_AGENT_CONFIG, DEFAULT_TTS_CONFIG } from '@/const/settings';
4
4
  import { Locales } from '@/locales/resources';
5
5
  import { GeneralModelProviderConfig, GlobalLLMProviderKey, GlobalSettings } from '@/types/settings';
6
6
  import { isOnServerSide } from '@/utils/env';
@@ -21,6 +21,7 @@ const password = (s: UserStore) => currentSettings(s).password;
21
21
  const currentTTS = (s: UserStore) => merge(DEFAULT_TTS_CONFIG, currentSettings(s).tts);
22
22
 
23
23
  const defaultAgent = (s: UserStore) => merge(DEFAULT_AGENT, currentSettings(s).defaultAgent);
24
+ const defaultAgentConfig = (s: UserStore) => merge(DEFAULT_AGENT_CONFIG, defaultAgent(s).config);
24
25
 
25
26
  const defaultAgentMeta = (s: UserStore) => merge(DEFAULT_AGENT_META, defaultAgent(s).meta);
26
27
 
@@ -53,6 +54,7 @@ export const settingsSelectors = {
53
54
  currentTTS,
54
55
  dalleConfig,
55
56
  defaultAgent,
57
+ defaultAgentConfig,
56
58
  defaultAgentMeta,
57
59
  exportSettings,
58
60
  isDalleAutoGenerating,
@@ -1,45 +0,0 @@
1
- 'use client';
2
-
3
- import { ActionIcon, DiscordIcon } from '@lobehub/ui';
4
- import { useTheme } from 'antd-style';
5
- import { Book, Github } from 'lucide-react';
6
- import { memo } from 'react';
7
- import { useTranslation } from 'react-i18next';
8
- import { Flexbox } from 'react-layout-kit';
9
-
10
- import { DISCORD, DOCUMENTS, GITHUB } from '@/const/url';
11
-
12
- const Footer = memo(() => {
13
- const theme = useTheme();
14
- const { t } = useTranslation('common');
15
-
16
- return (
17
- <Flexbox align={'center'} horizontal justify={'space-between'} style={{ padding: 16 }}>
18
- <span style={{ color: theme.colorTextDescription }}>
19
- © 2023 - {new Date().getFullYear()} LobeHub, LLC
20
- </span>
21
- <Flexbox horizontal>
22
- <ActionIcon
23
- icon={DiscordIcon}
24
- onClick={() => window.open(DISCORD, '__blank')}
25
- size={'site'}
26
- title={'Discord'}
27
- />
28
- <ActionIcon
29
- icon={Book}
30
- onClick={() => window.open(DOCUMENTS, '__blank')}
31
- size={'site'}
32
- title={t('document')}
33
- />
34
- <ActionIcon
35
- icon={Github}
36
- onClick={() => window.open(GITHUB, '__blank')}
37
- size={'site'}
38
- title={'GitHub'}
39
- />
40
- </Flexbox>
41
- </Flexbox>
42
- );
43
- });
44
-
45
- export default Footer;
@@ -1,23 +0,0 @@
1
- 'use client';
2
-
3
- import { GridShowcase } from '@lobehub/ui';
4
- import { memo } from 'react';
5
- import { Flexbox } from 'react-layout-kit';
6
-
7
- import Banner from '@/app/(main)/welcome/features/Banner';
8
-
9
- const Showcase = memo(() => (
10
- <Flexbox
11
- flex={1}
12
- justify={'center'}
13
- style={{ height: '100%', position: 'relative', width: '100%' }}
14
- >
15
- <GridShowcase>
16
- <Banner />
17
- </GridShowcase>
18
- {/*TODO:暂时隐藏,待模板完成后再补回*/}
19
- {/*<AgentTemplate width={width} />*/}
20
- </Flexbox>
21
- ));
22
-
23
- export default Showcase;
@@ -1,17 +0,0 @@
1
- 'use client';
2
-
3
- import { memo } from 'react';
4
-
5
- import ClientResponsiveContent from '@/components/client/ClientResponsiveContent';
6
-
7
- import Footer from './features/Footer';
8
- import Showcase from './features/Showcase';
9
-
10
- const Desktop = memo(() => (
11
- <>
12
- <Showcase />
13
- <Footer />
14
- </>
15
- ));
16
-
17
- export default ClientResponsiveContent({ Desktop, Mobile: () => import('../(mobile)') });
@@ -1,16 +0,0 @@
1
- import { memo } from 'react';
2
- import { Center, Flexbox } from 'react-layout-kit';
3
-
4
- import Banner from '../features/Banner';
5
-
6
- const Showcase = memo(() => (
7
- <Flexbox align={'center'} justify={'center'} style={{ height: 'calc(100% - 44px)' }}>
8
- <Center gap={16}>
9
- <Banner mobile />
10
- </Center>
11
- {/*TODO:暂时隐藏,待模板完成后再补回*/}
12
- {/*<AgentTemplate width={width} />*/}
13
- </Flexbox>
14
- ));
15
-
16
- export default Showcase;
@@ -1,71 +0,0 @@
1
- import { Avatar, Spotlight } from '@lobehub/ui';
2
- import { Typography } from 'antd';
3
- import { createStyles } from 'antd-style';
4
- import { rgba } from 'polished';
5
- import { memo } from 'react';
6
- import { Flexbox } from 'react-layout-kit';
7
-
8
- import { LobeAgentSession } from '@/types/session';
9
-
10
- const { Paragraph, Title } = Typography;
11
-
12
- const useStyles = createStyles(({ css, token, cx, stylish }) => ({
13
- container: cx(
14
- stylish.blur,
15
- css`
16
- cursor: pointer;
17
-
18
- position: relative;
19
-
20
- overflow: hidden;
21
- flex: 1;
22
-
23
- padding: 16px;
24
-
25
- background-color: ${rgba(token.colorBgContainer, 0.5)};
26
- border: 1px solid ${rgba(token.colorText, 0.2)};
27
- border-radius: ${token.borderRadiusLG}px;
28
-
29
- transition: all 400ms ${token.motionEaseOut};
30
-
31
- &:hover {
32
- background-color: ${rgba(token.colorBgElevated, 0.2)};
33
- border-color: ${token.colorText};
34
- box-shadow: 0 0 0 1px ${token.colorText};
35
- }
36
-
37
- ,
38
- &:active {
39
- scale: 0.98;
40
- }
41
- `,
42
- ),
43
- desc: css`
44
- margin: 0 !important;
45
- `,
46
- title: css`
47
- margin: 0 !important;
48
- `,
49
- }));
50
-
51
- export interface AgentCardProps {
52
- meta: LobeAgentSession['meta'];
53
- }
54
-
55
- const AgentCard = memo<AgentCardProps>(({ meta }) => {
56
- const { styles } = useStyles();
57
- return (
58
- <Flexbox className={styles.container} gap={8}>
59
- <Spotlight size={200} />
60
- <Avatar avatar={meta.avatar} background={meta.backgroundColor} />
61
- <Title className={styles.title} ellipsis level={5}>
62
- {meta.title}
63
- </Title>
64
- <Paragraph className={styles.desc} ellipsis={{ rows: 2 }} type="secondary">
65
- {meta.description}
66
- </Paragraph>
67
- </Flexbox>
68
- );
69
- });
70
-
71
- export default AgentCard;
@@ -1,43 +0,0 @@
1
- import { useResponsive } from 'antd-style';
2
- import { memo } from 'react';
3
- import { Flexbox } from 'react-layout-kit';
4
-
5
- import AgentCard, { type AgentCardProps } from './AgentCard';
6
- import { useStyles } from './style';
7
-
8
- const items: AgentCardProps['meta'][] = [
9
- {
10
- avatar: '😀',
11
- description: 'dddddd',
12
- title: 'Title',
13
- },
14
- {
15
- avatar: '😀',
16
- description: 'dddddd',
17
- title: 'Title',
18
- },
19
- {
20
- avatar: '😀',
21
- description: 'dddddd',
22
- title: 'Title',
23
- },
24
- ];
25
-
26
- const AgentTemplate = memo<{ width: number }>(({ width }) => {
27
- const { styles } = useStyles();
28
- const { mobile } = useResponsive();
29
- return (
30
- <Flexbox
31
- className={styles.templateContainer}
32
- gap={16}
33
- horizontal={!mobile}
34
- style={{ marginTop: -width / 20 }}
35
- >
36
- {items.map((meta, index) => (
37
- <AgentCard key={index} meta={meta} />
38
- ))}
39
- </Flexbox>
40
- );
41
- });
42
-
43
- export default AgentTemplate;
@@ -1,50 +0,0 @@
1
- import dynamic from 'next/dynamic';
2
- import { memo } from 'react';
3
- import { useTranslation } from 'react-i18next';
4
- import { Flexbox } from 'react-layout-kit';
5
-
6
- import { genSize, useStyles } from './style';
7
-
8
- const LogoThree = dynamic(() => import('@lobehub/ui/es/LogoThree'));
9
- const LogoSpline = dynamic(() => import('@lobehub/ui/es/LogoThree/LogoSpline'));
10
-
11
- const Hero = memo<{ mobile?: boolean; width: number }>(({ width, mobile }) => {
12
- const size: any = {
13
- base: genSize(width / 3.5, 240),
14
- desc: genSize(width / 50, 14),
15
- logo: genSize(width / 2.5, 180),
16
- title: genSize(width / 20, 32),
17
- };
18
-
19
- size.marginTop = mobile ? -size.logo / 9 : -size.logo / 3;
20
- size.marginBottom = mobile ? -size.logo / 9 : -size.logo / 4;
21
-
22
- const { styles } = useStyles(size.base);
23
-
24
- const { t } = useTranslation('welcome');
25
-
26
- return (
27
- <>
28
- <Flexbox
29
- style={{
30
- height: size.logo,
31
- marginBottom: size.marginBottom,
32
- marginTop: size.marginTop,
33
- position: 'relative',
34
- }}
35
- >
36
- {mobile ? <LogoThree size={size.logo} /> : <LogoSpline height={'100%'} width={'100%'} />}
37
- </Flexbox>
38
- <div className={styles.title} style={{ fontSize: size.title }}>
39
- <strong style={mobile ? { fontSize: '1.2em' } : {}}>LobeChat</strong>
40
- {mobile ? <br /> : ' '}
41
- {t('slogan.title')}
42
- </div>
43
- <div className={styles.desc} style={{ fontSize: size.desc }}>
44
- {t('slogan.desc1')}
45
- </div>
46
- </>
47
- );
48
- });
49
-
50
- export default Hero;
@@ -1,56 +0,0 @@
1
- 'use client';
2
-
3
- import { Icon } from '@lobehub/ui';
4
- import { Button } from 'antd';
5
- import { SendHorizonal } from 'lucide-react';
6
- import Link from 'next/link';
7
- import { useRouter } from 'next/navigation';
8
- import { memo } from 'react';
9
- import { useTranslation } from 'react-i18next';
10
- import { Flexbox } from 'react-layout-kit';
11
-
12
- import { useGlobalStore } from '@/store/global';
13
-
14
- import Hero from './Hero';
15
- import { useStyles } from './style';
16
-
17
- const Banner = memo<{ mobile?: boolean }>(({ mobile }) => {
18
- const { t } = useTranslation('welcome');
19
- const router = useRouter();
20
- const { styles } = useStyles();
21
- const [switchBackToChat, isMobile] = useGlobalStore((s) => [s.switchBackToChat, s.isMobile]);
22
-
23
- return (
24
- <>
25
- <div className={styles.container}>
26
- <Hero mobile={mobile} width={mobile ? 640 : 1024} />
27
- </div>
28
- <Flexbox
29
- className={styles.buttonGroup}
30
- gap={16}
31
- horizontal={!mobile}
32
- justify={'center'}
33
- width={'100%'}
34
- >
35
- <Link href={'/market'}>
36
- <Button block={mobile} size={'large'} type={'default'}>
37
- {t('button.market')}
38
- </Button>
39
- </Link>
40
- <Button
41
- block={mobile}
42
- onClick={() => (isMobile ? router.push('/chat') : switchBackToChat())}
43
- size={'large'}
44
- type={'primary'}
45
- >
46
- <Flexbox align={'center'} gap={4} horizontal justify={'center'}>
47
- {t('button.start')}
48
- <Icon icon={SendHorizonal} />
49
- </Flexbox>
50
- </Button>
51
- </Flexbox>
52
- </>
53
- );
54
- });
55
-
56
- export default Banner;
@@ -1,71 +0,0 @@
1
- import { createStyles } from 'antd-style';
2
- import { rgba } from 'polished';
3
-
4
- export const useStyles = createStyles(({ css, token, stylish, cx, prefixCls }) => {
5
- return {
6
- buttonGroup: css`
7
- .${prefixCls}-upload {
8
- width: 100% !important;
9
- }
10
- `,
11
- container: css`
12
- z-index: 10;
13
-
14
- display: flex;
15
- flex-direction: column;
16
- align-items: center;
17
-
18
- width: 100%;
19
- margin-bottom: 24px;
20
- `,
21
- desc: css`
22
- font-weight: 400;
23
- color: ${rgba(token.colorText, 0.8)};
24
- text-align: center;
25
- `,
26
- layout: css`
27
- background: ${token.colorBgContainer};
28
- `,
29
- logo: css`
30
- position: absolute;
31
- top: 16px;
32
- left: 16px;
33
- fill: ${token.colorText};
34
- `,
35
- note: css`
36
- z-index: 10;
37
- margin-top: 16px;
38
- color: ${token.colorTextDescription};
39
- `,
40
- skip: css`
41
- color: ${token.colorTextDescription};
42
- `,
43
- templateContainer: css`
44
- flex-wrap: wrap;
45
- width: 100%;
46
- padding: 16px;
47
- `,
48
- title: css`
49
- margin-bottom: 0.25em;
50
- font-weight: 800;
51
- line-height: 1.4;
52
- text-align: center;
53
- `,
54
- view: cx(
55
- stylish.noScrollbar,
56
- css`
57
- position: relative;
58
-
59
- overflow: hidden auto;
60
-
61
- max-width: 1024px;
62
- height: 100%;
63
- padding: 32px 16px;
64
- `,
65
- ),
66
- };
67
- });
68
-
69
- export const genSize = (size: number, minSize: number) => {
70
- return size < minSize ? minSize : size;
71
- };