@lobehub/chat 1.49.1 → 1.49.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.
Files changed (32) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/changelog/v1.json +18 -0
  3. package/package.json +1 -1
  4. package/src/app/(main)/discover/(list)/(home)/features/AssistantList.tsx +13 -7
  5. package/src/app/(main)/discover/(list)/(home)/features/ModelList.tsx +1 -4
  6. package/src/app/(main)/discover/(list)/(home)/features/PluginList.tsx +7 -4
  7. package/src/app/(main)/discover/(list)/assistants/features/Card.tsx +74 -55
  8. package/src/app/(main)/discover/(list)/assistants/features/List.tsx +20 -10
  9. package/src/app/(main)/discover/(list)/models/features/Card.tsx +34 -32
  10. package/src/app/(main)/discover/(list)/models/features/List.tsx +12 -7
  11. package/src/app/(main)/discover/(list)/plugins/features/Card.tsx +52 -43
  12. package/src/app/(main)/discover/(list)/plugins/features/List.tsx +20 -10
  13. package/src/app/(main)/discover/features/StoreSearchBar.tsx +3 -5
  14. package/src/app/(main)/discover/features/useNav.tsx +3 -3
  15. package/src/app/(main)/discover/search/_layout/Mobile/Header.tsx +2 -2
  16. package/src/app/(main)/profile/@category/features/CategoryContent.tsx +5 -10
  17. package/src/app/(main)/settings/@category/features/CategoryContent.tsx +3 -10
  18. package/src/app/(main)/settings/_layout/Mobile/Header.tsx +9 -6
  19. package/src/app/@modal/chat/(.)settings/modal/features/CategoryContent.tsx +2 -3
  20. package/src/app/@modal/chat/(.)settings/modal/layout.tsx +2 -2
  21. package/src/app/@modal/chat/(.)settings/modal/page.tsx +3 -2
  22. package/src/components/withSuspense.tsx +8 -0
  23. package/src/features/AgentSetting/AgentTTS/index.tsx +2 -2
  24. package/src/hooks/useActiveTabKey.ts +3 -3
  25. package/src/hooks/useChatSettingsTab.ts +12 -0
  26. package/src/hooks/useDiscoverTab.ts +12 -0
  27. package/src/hooks/useQueryRoute.test.ts +6 -4
  28. package/src/hooks/useQueryRoute.ts +4 -3
  29. package/src/hooks/useSettingsTab.ts +12 -0
  30. package/src/layout/GlobalProvider/ReactScan.tsx +3 -1
  31. package/src/hooks/useQuery.test.ts +0 -19
  32. package/src/hooks/useQuery.ts +0 -8
package/CHANGELOG.md CHANGED
@@ -2,6 +2,56 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ### [Version 1.49.3](https://github.com/lobehub/lobe-chat/compare/v1.49.2...v1.49.3)
6
+
7
+ <sup>Released on **2025-01-27**</sup>
8
+
9
+ #### 🐛 Bug Fixes
10
+
11
+ - **misc**: Fix discover ssr hydration error.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's fixed
19
+
20
+ - **misc**: Fix discover ssr hydration error, closes [#5605](https://github.com/lobehub/lobe-chat/issues/5605) ([e3702a6](https://github.com/lobehub/lobe-chat/commit/e3702a6))
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 1.49.2](https://github.com/lobehub/lobe-chat/compare/v1.49.1...v1.49.2)
31
+
32
+ <sup>Released on **2025-01-27**</sup>
33
+
34
+ #### ♻ Code Refactoring
35
+
36
+ - **misc**: Remove use query.
37
+
38
+ <br/>
39
+
40
+ <details>
41
+ <summary><kbd>Improvements and Fixes</kbd></summary>
42
+
43
+ #### Code refactoring
44
+
45
+ - **misc**: Remove use query, closes [#5604](https://github.com/lobehub/lobe-chat/issues/5604) ([58c60de](https://github.com/lobehub/lobe-chat/commit/58c60de))
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 1.49.1](https://github.com/lobehub/lobe-chat/compare/v1.49.0...v1.49.1)
6
56
 
7
57
  <sup>Released on **2025-01-27**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,22 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "fixes": [
5
+ "Fix discover ssr hydration error."
6
+ ]
7
+ },
8
+ "date": "2025-01-27",
9
+ "version": "1.49.3"
10
+ },
11
+ {
12
+ "children": {
13
+ "improvements": [
14
+ "Remove use query."
15
+ ]
16
+ },
17
+ "date": "2025-01-27",
18
+ "version": "1.49.2"
19
+ },
2
20
  {
3
21
  "children": {
4
22
  "improvements": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "1.49.1",
3
+ "version": "1.49.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",
@@ -1,5 +1,4 @@
1
1
  import { Grid } from '@lobehub/ui';
2
- import Link from 'next/link';
3
2
  import { memo } from 'react';
4
3
  import urlJoin from 'url-join';
5
4
 
@@ -11,14 +10,21 @@ const AssistantList = memo<{ data: DiscoverAssistantItem[] }>(({ data }) => {
11
10
  return (
12
11
  <Grid maxItemWidth={280} rows={4}>
13
12
  {data.slice(0, 8).map((item) => (
14
- <Link href={urlJoin('/discover/assistant/', item.identifier)} key={item.identifier}>
15
- <Card showCategory {...item} />
16
- </Link>
13
+ <Card
14
+ href={urlJoin('/discover/assistant/', item.identifier)}
15
+ key={item.identifier}
16
+ showCategory
17
+ {...item}
18
+ />
17
19
  ))}
18
20
  {data.slice(8, 16).map((item) => (
19
- <Link href={urlJoin('/discover/assistant/', item.identifier)} key={item.identifier}>
20
- <Card showCategory variant={'compact'} {...item} />
21
- </Link>
21
+ <Card
22
+ href={urlJoin('/discover/assistant/', item.identifier)}
23
+ key={item.identifier}
24
+ showCategory
25
+ variant={'compact'}
26
+ {...item}
27
+ />
22
28
  ))}
23
29
  </Grid>
24
30
  );
@@ -1,5 +1,4 @@
1
1
  import { Grid } from '@lobehub/ui';
2
- import Link from 'next/link';
3
2
  import { memo } from 'react';
4
3
  import urlJoin from 'url-join';
5
4
 
@@ -11,9 +10,7 @@ const ModelList = memo<{ data: DiscoverModelItem[] }>(({ data }) => {
11
10
  return (
12
11
  <Grid maxItemWidth={280} rows={4}>
13
12
  {data.map((item) => (
14
- <Link href={urlJoin('/discover/model/', item.identifier)} key={item.identifier}>
15
- <Card {...item} />
16
- </Link>
13
+ <Card {...item} href={urlJoin('/discover/model/', item.identifier)} key={item.identifier} />
17
14
  ))}
18
15
  </Grid>
19
16
  );
@@ -1,5 +1,4 @@
1
1
  import { Grid } from '@lobehub/ui';
2
- import Link from 'next/link';
3
2
  import { memo } from 'react';
4
3
  import urlJoin from 'url-join';
5
4
 
@@ -11,9 +10,13 @@ const PluginList = memo<{ data: DiscoverPlugintem[] }>(({ data }) => {
11
10
  return (
12
11
  <Grid maxItemWidth={280} rows={4}>
13
12
  {data.map((item) => (
14
- <Link href={urlJoin('/discover/plugin/', item.identifier)} key={item.identifier}>
15
- <Card showCategory variant={'compact'} {...item} />
16
- </Link>
13
+ <Card
14
+ showCategory
15
+ variant={'compact'}
16
+ {...item}
17
+ href={urlJoin('/discover/plugin/', item.identifier)}
18
+ key={item.identifier}
19
+ />
17
20
  ))}
18
21
  </Grid>
19
22
  );
@@ -3,6 +3,7 @@ import { Skeleton, Typography } from 'antd';
3
3
  import { createStyles } from 'antd-style';
4
4
  import { startCase } from 'lodash-es';
5
5
  import dynamic from 'next/dynamic';
6
+ import { useRouter } from 'next/navigation';
6
7
  import qs from 'query-string';
7
8
  import { CSSProperties, memo } from 'react';
8
9
  import { Center, Flexbox } from 'react-layout-kit';
@@ -26,8 +27,6 @@ const useStyles = createStyles(({ css, token, isDarkMode }) => ({
26
27
  opacity: ${isDarkMode ? 0.9 : 0.4};
27
28
  `,
28
29
  container: css`
29
- cursor: pointer;
30
-
31
30
  position: relative;
32
31
 
33
32
  overflow: hidden;
@@ -64,18 +63,19 @@ const useStyles = createStyles(({ css, token, isDarkMode }) => ({
64
63
  export interface AssistantCardProps
65
64
  extends Omit<DiscoverAssistantItem, 'suggestions' | 'socialData' | 'config'> {
66
65
  className?: string;
66
+ href: string;
67
67
  showCategory?: boolean;
68
68
  style?: CSSProperties;
69
69
  variant?: 'default' | 'compact';
70
70
  }
71
71
 
72
72
  const AssistantCard = memo<AssistantCardProps>(
73
- ({ showCategory, className, meta, createdAt, author, variant, style }) => {
73
+ ({ showCategory, className, meta, createdAt, author, variant, style, href }) => {
74
74
  const { avatar, title, description, tags = [], category } = meta;
75
75
  const { cx, styles, theme } = useStyles();
76
76
  const categoryItem = useCategoryItem(category, 12);
77
77
  const isCompact = variant === 'compact';
78
-
78
+ const router = useRouter();
79
79
  const user = (
80
80
  <Flexbox
81
81
  align={'center'}
@@ -90,60 +90,79 @@ const AssistantCard = memo<AssistantCardProps>(
90
90
 
91
91
  return (
92
92
  <Flexbox className={cx(styles.container, className)} gap={24} style={style}>
93
- {!isCompact && <CardBanner avatar={avatar} />}
94
- <Flexbox gap={12} padding={16}>
95
- <Flexbox
96
- align={isCompact ? 'flex-start' : 'flex-end'}
97
- gap={16}
98
- horizontal
99
- justify={'space-between'}
100
- style={{ position: 'relative' }}
101
- width={'100%'}
93
+ {!isCompact && (
94
+ <div
95
+ onClick={() => {
96
+ router.push(href);
97
+ }}
102
98
  >
103
- <Flexbox
104
- gap={8}
105
- style={{ overflow: 'hidden', paddingTop: isCompact ? 4 : 0, position: 'relative' }}
106
- >
107
- <Title
108
- className={styles.title}
109
- ellipsis={{ rows: 1, tooltip: title }}
110
- level={3}
111
- style={{ fontSize: isCompact ? 16 : 18 }}
112
- >
113
- {title}
114
- </Title>
115
- {isCompact && user}
116
- </Flexbox>
117
- {isCompact ? (
118
- <Avatar avatar={avatar} size={40} style={{ display: 'block' }} title={title} />
119
- ) : (
120
- <Center
121
- flex={'none'}
122
- height={64}
123
- style={{
124
- background: theme.colorBgContainer,
125
- borderRadius: '50%',
126
- marginTop: -6,
127
- overflow: 'hidden',
128
- zIndex: 2,
129
- }}
130
- width={64}
99
+ <CardBanner avatar={avatar} />
100
+ </div>
101
+ )}
102
+ <Flexbox gap={12} padding={16}>
103
+ <Link href={href}>
104
+ <Flexbox gap={12}>
105
+ <Flexbox
106
+ align={isCompact ? 'flex-start' : 'flex-end'}
107
+ gap={16}
108
+ horizontal
109
+ justify={'space-between'}
110
+ style={{ position: 'relative' }}
111
+ width={'100%'}
131
112
  >
132
- <Avatar avatar={avatar} size={56} style={{ display: 'block' }} title={title} />
133
- </Center>
134
- )}
135
- </Flexbox>
136
- {!isCompact && (
137
- <Flexbox gap={8} horizontal style={{ fontSize: 12 }}>
138
- {user}
139
- <time className={styles.time} dateTime={new Date(createdAt).toISOString()}>
140
- {createdAt}
141
- </time>
113
+ <Flexbox
114
+ gap={8}
115
+ style={{
116
+ overflow: 'hidden',
117
+ paddingTop: isCompact ? 4 : 0,
118
+ position: 'relative',
119
+ }}
120
+ >
121
+ <Title
122
+ className={styles.title}
123
+ ellipsis={{ rows: 1, tooltip: title }}
124
+ level={3}
125
+ style={{ fontSize: isCompact ? 16 : 18 }}
126
+ >
127
+ {title}
128
+ </Title>
129
+ {isCompact && user}
130
+ </Flexbox>
131
+
132
+ {isCompact ? (
133
+ <Avatar avatar={avatar} size={40} style={{ display: 'block' }} title={title} />
134
+ ) : (
135
+ <Center
136
+ flex={'none'}
137
+ height={64}
138
+ style={{
139
+ background: theme.colorBgContainer,
140
+ borderRadius: '50%',
141
+ marginTop: -6,
142
+ overflow: 'hidden',
143
+ zIndex: 2,
144
+ }}
145
+ width={64}
146
+ >
147
+ <Avatar avatar={avatar} size={56} style={{ display: 'block' }} title={title} />
148
+ </Center>
149
+ )}
150
+ </Flexbox>
151
+
152
+ {!isCompact && (
153
+ <Flexbox gap={8} horizontal style={{ fontSize: 12 }}>
154
+ {user}
155
+ <time className={styles.time} dateTime={new Date(createdAt).toISOString()}>
156
+ {createdAt}
157
+ </time>
158
+ </Flexbox>
159
+ )}
160
+ <Paragraph className={styles.desc} ellipsis={{ rows: 2 }}>
161
+ {description}
162
+ </Paragraph>
142
163
  </Flexbox>
143
- )}
144
- <Paragraph className={styles.desc} ellipsis={{ rows: 2 }}>
145
- {description}
146
- </Paragraph>
164
+ </Link>
165
+
147
166
  <Flexbox gap={6} horizontal style={{ flexWrap: 'wrap' }}>
148
167
  {showCategory && categoryItem ? (
149
168
  <Link href={urlJoin('/discover/assistants', categoryItem.key)}>
@@ -2,7 +2,6 @@
2
2
 
3
3
  import { Grid } from '@lobehub/ui';
4
4
  import { Empty } from 'antd';
5
- import Link from 'next/link';
6
5
  import { memo, useMemo } from 'react';
7
6
  import { useTranslation } from 'react-i18next';
8
7
  import urlJoin from 'url-join';
@@ -42,9 +41,13 @@ const List = memo<ListProps>(({ category, mobile, searchKeywords, items = [] })
42
41
  data={all}
43
42
  initialItemCount={24}
44
43
  itemContent={(_, item) => (
45
- <Link href={urlJoin('/discover/assistant/', item.identifier)} key={item.identifier}>
46
- <Card showCategory variant={'compact'} {...item} />
47
- </Link>
44
+ <Card
45
+ href={urlJoin('/discover/assistant/', item.identifier)}
46
+ key={item.identifier}
47
+ showCategory
48
+ variant={'compact'}
49
+ {...item}
50
+ />
48
51
  )}
49
52
  style={{
50
53
  minHeight: '75vh',
@@ -59,9 +62,12 @@ const List = memo<ListProps>(({ category, mobile, searchKeywords, items = [] })
59
62
  <Title>{t('assistants.recentSubmits')}</Title>
60
63
  <Grid maxItemWidth={280} rows={4}>
61
64
  {recent.map((item) => (
62
- <Link href={urlJoin('/discover/assistant/', item.identifier)} key={item.identifier}>
63
- <Card showCategory={!category} {...item} />
64
- </Link>
65
+ <Card
66
+ href={urlJoin('/discover/assistant/', item.identifier)}
67
+ key={item.identifier}
68
+ showCategory={!category}
69
+ {...item}
70
+ />
65
71
  ))}
66
72
  </Grid>
67
73
  {last && last?.length > 0 && (
@@ -71,9 +77,13 @@ const List = memo<ListProps>(({ category, mobile, searchKeywords, items = [] })
71
77
  data={last}
72
78
  initialItemCount={12}
73
79
  itemContent={(_, item) => (
74
- <Link href={urlJoin('/discover/assistant/', item.identifier)} key={item.identifier}>
75
- <Card showCategory={!category} variant={'compact'} {...item} />
76
- </Link>
80
+ <Card
81
+ href={urlJoin('/discover/assistant/', item.identifier)}
82
+ key={item.identifier}
83
+ showCategory={!category}
84
+ variant={'compact'}
85
+ {...item}
86
+ />
77
87
  )}
78
88
  style={{
79
89
  minHeight: '75vh',
@@ -1,6 +1,7 @@
1
1
  import { ModelIcon } from '@lobehub/icons';
2
2
  import { Typography } from 'antd';
3
3
  import { createStyles } from 'antd-style';
4
+ import Link from 'next/link';
4
5
  import { CSSProperties, memo } from 'react';
5
6
  import { useTranslation } from 'react-i18next';
6
7
  import { Flexbox } from 'react-layout-kit';
@@ -16,8 +17,6 @@ const useStyles = createStyles(({ css, token, isDarkMode }) => ({
16
17
  opacity: ${isDarkMode ? 0.9 : 0.4};
17
18
  `,
18
19
  container: css`
19
- cursor: pointer;
20
-
21
20
  position: relative;
22
21
 
23
22
  overflow: hidden;
@@ -69,53 +68,56 @@ const useStyles = createStyles(({ css, token, isDarkMode }) => ({
69
68
 
70
69
  export interface ModelCardProps extends DiscoverModelItem {
71
70
  className?: string;
71
+ href: string;
72
72
  showCategory?: boolean;
73
73
  style?: CSSProperties;
74
74
  }
75
75
 
76
- const ModelCard = memo<ModelCardProps>(({ className, meta, identifier, style }) => {
76
+ const ModelCard = memo<ModelCardProps>(({ className, meta, identifier, style, href }) => {
77
77
  const { description, title, functionCall, vision, contextWindowTokens } = meta;
78
78
  const { t } = useTranslation('models');
79
79
  const { cx, styles } = useStyles();
80
80
 
81
81
  return (
82
- <Flexbox className={cx(styles.container, className)} gap={24} key={identifier} style={style}>
83
- <Flexbox
84
- gap={12}
85
- padding={16}
86
- style={{ overflow: 'hidden', position: 'relative' }}
87
- width={'100%'}
88
- >
82
+ <Link href={href}>
83
+ <Flexbox className={cx(styles.container, className)} gap={24} key={identifier} style={style}>
89
84
  <Flexbox
90
- align={'center'}
91
85
  gap={12}
92
- horizontal
86
+ padding={16}
93
87
  style={{ overflow: 'hidden', position: 'relative' }}
94
88
  width={'100%'}
95
89
  >
96
- <ModelIcon model={identifier} size={32} type={'avatar'} />
97
- <Flexbox style={{ overflow: 'hidden', position: 'relative' }}>
98
- <Title className={styles.title} ellipsis={{ rows: 1, tooltip: title }} level={3}>
99
- {title}
100
- </Title>
101
- <Paragraph className={styles.id} ellipsis={{ rows: 1 }}>
102
- {identifier}
103
- </Paragraph>
90
+ <Flexbox
91
+ align={'center'}
92
+ gap={12}
93
+ horizontal
94
+ style={{ overflow: 'hidden', position: 'relative' }}
95
+ width={'100%'}
96
+ >
97
+ <ModelIcon model={identifier} size={32} type={'avatar'} />
98
+ <Flexbox style={{ overflow: 'hidden', position: 'relative' }}>
99
+ <Title className={styles.title} ellipsis={{ rows: 1, tooltip: title }} level={3}>
100
+ {title}
101
+ </Title>
102
+ <Paragraph className={styles.id} ellipsis={{ rows: 1 }}>
103
+ {identifier}
104
+ </Paragraph>
105
+ </Flexbox>
104
106
  </Flexbox>
105
- </Flexbox>
106
- {description && (
107
- <Paragraph className={styles.desc} ellipsis={{ rows: 2 }}>
108
- {t(`${identifier}.description`)}
109
- </Paragraph>
110
- )}
107
+ {description && (
108
+ <Paragraph className={styles.desc} ellipsis={{ rows: 2 }}>
109
+ {t(`${identifier}.description`)}
110
+ </Paragraph>
111
+ )}
111
112
 
112
- <ModelFeatureTags
113
- functionCall={functionCall}
114
- tokens={contextWindowTokens}
115
- vision={vision}
116
- />
113
+ <ModelFeatureTags
114
+ functionCall={functionCall}
115
+ tokens={contextWindowTokens}
116
+ vision={vision}
117
+ />
118
+ </Flexbox>
117
119
  </Flexbox>
118
- </Flexbox>
120
+ </Link>
119
121
  );
120
122
  });
121
123
 
@@ -1,7 +1,6 @@
1
1
  'use client';
2
2
 
3
3
  import { Empty } from 'antd';
4
- import Link from 'next/link';
5
4
  import { memo } from 'react';
6
5
  import { useTranslation } from 'react-i18next';
7
6
  import urlJoin from 'url-join';
@@ -32,9 +31,12 @@ const List = memo<ListProps>(({ category, searchKeywords, items = [] }) => {
32
31
  data={items}
33
32
  initialItemCount={24}
34
33
  itemContent={(_, item) => (
35
- <Link href={urlJoin('/discover/model/', item.identifier)} key={item.identifier}>
36
- <Card showCategory {...item} />
37
- </Link>
34
+ <Card
35
+ href={urlJoin('/discover/model', item.identifier)}
36
+ key={item.identifier}
37
+ showCategory
38
+ {...item}
39
+ />
38
40
  )}
39
41
  style={{
40
42
  minHeight: '75vh',
@@ -51,9 +53,12 @@ const List = memo<ListProps>(({ category, searchKeywords, items = [] }) => {
51
53
  data={items}
52
54
  initialItemCount={24}
53
55
  itemContent={(_, item) => (
54
- <Link href={urlJoin('/discover/model/', item.identifier)} key={item.identifier}>
55
- <Card showCategory={!category} {...item} />
56
- </Link>
56
+ <Card
57
+ href={urlJoin('/discover/model/', item.identifier)}
58
+ key={item.identifier}
59
+ showCategory={!category}
60
+ {...item}
61
+ />
57
62
  )}
58
63
  style={{
59
64
  minHeight: '75vh',
@@ -25,8 +25,6 @@ const useStyles = createStyles(({ css, token, isDarkMode }) => ({
25
25
  opacity: ${isDarkMode ? 0.9 : 0.4};
26
26
  `,
27
27
  container: css`
28
- cursor: pointer;
29
-
30
28
  position: relative;
31
29
 
32
30
  overflow: hidden;
@@ -66,13 +64,14 @@ const useStyles = createStyles(({ css, token, isDarkMode }) => ({
66
64
  interface PluginCardProps
67
65
  extends Omit<DiscoverPlugintem, 'manifest' | 'suggestions' | 'socialData'> {
68
66
  className?: string;
67
+ href: string;
69
68
  showCategory?: boolean;
70
69
  style?: CSSProperties;
71
70
  variant?: 'default' | 'compact';
72
71
  }
73
72
 
74
73
  const PluginCard = memo<PluginCardProps>(
75
- ({ className, showCategory, meta, createdAt, author, variant, style }) => {
74
+ ({ className, showCategory, meta, createdAt, author, variant, style, href }) => {
76
75
  const { avatar, title, description, tags = [], category } = meta;
77
76
  const categoryItem = useCategoryItem(category, 12);
78
77
  const { cx, styles, theme } = useStyles();
@@ -81,47 +80,57 @@ const PluginCard = memo<PluginCardProps>(
81
80
  return (
82
81
  <Flexbox className={cx(styles.container, className)} gap={24} style={style}>
83
82
  {!isCompact && <CardBanner avatar={avatar} />}
84
- <Flexbox className={styles.inner} gap={12}>
85
- <Flexbox align={'flex-end'} gap={16} horizontal justify={'space-between'} width={'100%'}>
86
- <Title className={styles.title} ellipsis={{ rows: 1, tooltip: title }} level={3}>
87
- {title}
88
- </Title>
89
- {isCompact ? (
90
- <Avatar avatar={avatar} size={40} style={{ display: 'block' }} title={title} />
91
- ) : (
92
- <Center
93
- flex={'none'}
94
- height={64}
95
- style={{
96
- background: theme.colorBgContainer,
97
- borderRadius: '50%',
98
- marginTop: -6,
99
- overflow: 'hidden',
100
- zIndex: 2,
101
- }}
102
- width={64}
83
+ <Flexbox gap={12} padding={16}>
84
+ <Link href={href}>
85
+ <Flexbox gap={12}>
86
+ <Flexbox
87
+ align={'flex-end'}
88
+ gap={16}
89
+ horizontal
90
+ justify={'space-between'}
91
+ width={'100%'}
103
92
  >
104
- <Avatar
105
- alt={title}
106
- avatar={avatar}
107
- size={56}
108
- style={{ display: 'block' }}
109
- title={title}
110
- />
111
- </Center>
112
- )}
113
- </Flexbox>
114
- <Flexbox gap={8} horizontal style={{ fontSize: 12 }}>
115
- <div style={{ color: theme.colorTextSecondary }}>@{author}</div>
116
- {!isCompact && (
117
- <time className={styles.time} dateTime={new Date(createdAt).toISOString()}>
118
- {createdAt}
119
- </time>
120
- )}
121
- </Flexbox>
122
- <Paragraph className={styles.desc} ellipsis={{ rows: 2 }}>
123
- {description}
124
- </Paragraph>
93
+ <Title className={styles.title} ellipsis={{ rows: 1, tooltip: title }} level={3}>
94
+ {title}
95
+ </Title>
96
+ {isCompact ? (
97
+ <Avatar avatar={avatar} size={40} style={{ display: 'block' }} title={title} />
98
+ ) : (
99
+ <Center
100
+ flex={'none'}
101
+ height={64}
102
+ style={{
103
+ background: theme.colorBgContainer,
104
+ borderRadius: '50%',
105
+ marginTop: -6,
106
+ overflow: 'hidden',
107
+ zIndex: 2,
108
+ }}
109
+ width={64}
110
+ >
111
+ <Avatar
112
+ alt={title}
113
+ avatar={avatar}
114
+ size={56}
115
+ style={{ display: 'block' }}
116
+ title={title}
117
+ />
118
+ </Center>
119
+ )}
120
+ </Flexbox>
121
+ <Flexbox gap={8} horizontal style={{ fontSize: 12 }}>
122
+ <div style={{ color: theme.colorTextSecondary }}>@{author}</div>
123
+ {!isCompact && (
124
+ <time className={styles.time} dateTime={new Date(createdAt).toISOString()}>
125
+ {createdAt}
126
+ </time>
127
+ )}
128
+ </Flexbox>
129
+ <Paragraph className={styles.desc} ellipsis={{ rows: 2 }}>
130
+ {description}
131
+ </Paragraph>
132
+ </Flexbox>
133
+ </Link>
125
134
  <Flexbox gap={6} horizontal style={{ flexWrap: 'wrap' }}>
126
135
  {showCategory && categoryItem ? (
127
136
  <Link href={urlJoin('/discover/plugins', categoryItem.key)}>
@@ -2,7 +2,6 @@
2
2
 
3
3
  import { Grid } from '@lobehub/ui';
4
4
  import { Empty } from 'antd';
5
- import Link from 'next/link';
6
5
  import { memo, useMemo } from 'react';
7
6
  import { useTranslation } from 'react-i18next';
8
7
  import urlJoin from 'url-join';
@@ -41,9 +40,13 @@ const List = memo<ListProps>(({ category, mobile, searchKeywords, items = [] })
41
40
  data={all}
42
41
  initialItemCount={24}
43
42
  itemContent={(_, item) => (
44
- <Link href={urlJoin('/discover/plugin/', item.identifier)} key={item.identifier}>
45
- <Card showCategory variant={'compact'} {...item} />
46
- </Link>
43
+ <Card
44
+ showCategory
45
+ variant={'compact'}
46
+ {...item}
47
+ href={urlJoin('/discover/plugin/', item.identifier)}
48
+ key={item.identifier}
49
+ />
47
50
  )}
48
51
  style={{
49
52
  minHeight: '75vh',
@@ -58,9 +61,12 @@ const List = memo<ListProps>(({ category, mobile, searchKeywords, items = [] })
58
61
  <Title>{t('plugins.recentSubmits')}</Title>
59
62
  <Grid maxItemWidth={280} rows={4}>
60
63
  {recent.map((item) => (
61
- <Link href={urlJoin('/discover/plugin/', item.identifier)} key={item.identifier}>
62
- <Card showCategory={!category} {...item} />
63
- </Link>
64
+ <Card
65
+ showCategory={!category}
66
+ {...item}
67
+ href={urlJoin('/discover/plugin/', item.identifier)}
68
+ key={item.identifier}
69
+ />
64
70
  ))}
65
71
  </Grid>
66
72
  {last && last?.length > 0 && (
@@ -70,9 +76,13 @@ const List = memo<ListProps>(({ category, mobile, searchKeywords, items = [] })
70
76
  data={last}
71
77
  initialItemCount={12}
72
78
  itemContent={(_, item) => (
73
- <Link href={urlJoin('/discover/plugin/', item.identifier)} key={item.identifier}>
74
- <Card showCategory={!category} variant={'compact'} {...item} />
75
- </Link>
79
+ <Card
80
+ showCategory={!category}
81
+ variant={'compact'}
82
+ {...item}
83
+ href={urlJoin('/discover/plugin/', item.identifier)}
84
+ key={item.identifier}
85
+ />
76
86
  )}
77
87
  style={{
78
88
  minHeight: '75vh',
@@ -8,7 +8,6 @@ import { memo, useEffect, useState } from 'react';
8
8
  import { useTranslation } from 'react-i18next';
9
9
  import urlJoin from 'url-join';
10
10
 
11
- import { useQuery } from '@/hooks/useQuery';
12
11
  import { useQueryRoute } from '@/hooks/useQueryRoute';
13
12
  import { DiscoverTab } from '@/types/discover';
14
13
 
@@ -32,9 +31,8 @@ interface StoreSearchBarProps extends SearchBarProps {
32
31
  const StoreSearchBar = memo<StoreSearchBarProps>(({ mobile, onBlur, onFocus, ...rest }) => {
33
32
  const [active, setActive] = useState(false);
34
33
  const pathname = usePathname();
35
- const { q } = useQuery();
36
34
  const { activeKey } = useNav();
37
- const [searchKey, setSearchKey] = useQueryState('q');
35
+ const [searchKey, setSearchKey] = useQueryState('q', { clearOnDefault: true, defaultValue: '' });
38
36
 
39
37
  const { t } = useTranslation('discover');
40
38
  const { cx, styles } = useStyles();
@@ -45,8 +43,8 @@ const StoreSearchBar = memo<StoreSearchBarProps>(({ mobile, onBlur, onFocus, ...
45
43
  useEffect(() => {
46
44
  if (!pathname.includes('/discover/search')) return;
47
45
  // 使用 useQueryState 时,当 handleSearch 为空时无法回跳
48
- if (!q) router.push(urlJoin('/discover', activeType), { query: {}, replace: true });
49
- }, [q, pathname, activeType]);
46
+ if (!searchKey) router.push(urlJoin('/discover', activeType), { query: {}, replace: true });
47
+ }, [searchKey, pathname, activeType]);
50
48
 
51
49
  const handleSearch = (value: string) => {
52
50
  router.push('/discover/search', { query: { q: value, type: activeType } });
@@ -6,19 +6,19 @@ import { useTranslation } from 'react-i18next';
6
6
  import urlJoin from 'url-join';
7
7
 
8
8
  import type { MenuProps } from '@/components/Menu';
9
- import { useQuery } from '@/hooks/useQuery';
9
+ import { useDiscoverTab } from '@/hooks/useDiscoverTab';
10
10
  import { DiscoverTab } from '@/types/discover';
11
11
 
12
12
  export const useNav = () => {
13
13
  const pathname = usePathname();
14
- const { type } = useQuery();
14
+ const type = useDiscoverTab();
15
15
  const { t } = useTranslation('discover');
16
16
  const iconSize = { fontSize: 16 };
17
17
 
18
18
  const activeKey = useMemo(() => {
19
19
  for (const value of Object.values(DiscoverTab)) {
20
20
  if (pathname === '/discover/search') {
21
- return (type as DiscoverTab) || DiscoverTab.Assistants;
21
+ return type;
22
22
  } else if (pathname.includes(urlJoin('/discover', value))) {
23
23
  return value;
24
24
  }
@@ -5,14 +5,14 @@ import { useRouter } from 'next/navigation';
5
5
  import { memo } from 'react';
6
6
  import urlJoin from 'url-join';
7
7
 
8
- import { useQuery } from '@/hooks/useQuery';
8
+ import { useDiscoverTab } from '@/hooks/useDiscoverTab';
9
9
  import { mobileHeaderSticky } from '@/styles/mobileHeader';
10
10
 
11
11
  import StoreSearchBar from '../../../features/StoreSearchBar';
12
12
 
13
13
  const Header = memo(() => {
14
14
  const router = useRouter();
15
- const { type = 'assistants' } = useQuery();
15
+ const type = useDiscoverTab();
16
16
 
17
17
  return (
18
18
  <MobileNavBar
@@ -5,15 +5,13 @@ import urlJoin from 'url-join';
5
5
 
6
6
  import Menu from '@/components/Menu';
7
7
  import { useActiveSettingsKey } from '@/hooks/useActiveTabKey';
8
- import { useQuery } from '@/hooks/useQuery';
9
8
  import { useQueryRoute } from '@/hooks/useQueryRoute';
10
- import { ProfileTabs, SettingsTabs } from '@/store/global/initialState';
9
+ import { ProfileTabs } from '@/store/global/initialState';
11
10
 
12
11
  import { useCategory } from '../../hooks/useCategory';
13
12
 
14
- const CategoryContent = memo<{ modal?: boolean }>(({ modal }) => {
13
+ const CategoryContent = memo(() => {
15
14
  const activeTab = useActiveSettingsKey();
16
- const { tab = SettingsTabs.Common } = useQuery();
17
15
  const cateItems = useCategory();
18
16
  const router = useQueryRoute();
19
17
 
@@ -22,14 +20,11 @@ const CategoryContent = memo<{ modal?: boolean }>(({ modal }) => {
22
20
  items={cateItems}
23
21
  onClick={({ key }) => {
24
22
  const activeKey = key === ProfileTabs.Profile ? '/' : key;
25
- if (modal) {
26
- router.replace('/profile/modal', { query: { tab: activeKey } });
27
- } else {
28
- router.push(urlJoin('/profile', activeKey));
29
- }
23
+
24
+ router.push(urlJoin('/profile', activeKey));
30
25
  }}
31
26
  selectable
32
- selectedKeys={[modal ? tab : (activeTab as any)]}
27
+ selectedKeys={[activeTab]}
33
28
  variant={'compact'}
34
29
  />
35
30
  );
@@ -5,15 +5,12 @@ import urlJoin from 'url-join';
5
5
 
6
6
  import Menu from '@/components/Menu';
7
7
  import { useActiveSettingsKey } from '@/hooks/useActiveTabKey';
8
- import { useQuery } from '@/hooks/useQuery';
9
8
  import { useQueryRoute } from '@/hooks/useQueryRoute';
10
- import { SettingsTabs } from '@/store/global/initialState';
11
9
 
12
10
  import { useCategory } from '../../hooks/useCategory';
13
11
 
14
- const CategoryContent = memo<{ modal?: boolean }>(({ modal }) => {
12
+ const CategoryContent = memo(() => {
15
13
  const activeTab = useActiveSettingsKey();
16
- const { tab = SettingsTabs.Common } = useQuery();
17
14
  const cateItems = useCategory();
18
15
  const router = useQueryRoute();
19
16
 
@@ -21,14 +18,10 @@ const CategoryContent = memo<{ modal?: boolean }>(({ modal }) => {
21
18
  <Menu
22
19
  items={cateItems}
23
20
  onClick={({ key }) => {
24
- if (modal) {
25
- router.replace('/settings/modal', { query: { tab: key } });
26
- } else {
27
- router.push(urlJoin('/settings', key));
28
- }
21
+ router.push(urlJoin('/settings', key));
29
22
  }}
30
23
  selectable
31
- selectedKeys={[modal ? tab : (activeTab as any)]}
24
+ selectedKeys={[activeTab]}
32
25
  variant={'compact'}
33
26
  />
34
27
  );
@@ -2,13 +2,15 @@
2
2
 
3
3
  import { MobileNavBar, MobileNavBarTitle } from '@lobehub/ui';
4
4
  import { Tag } from 'antd';
5
- import { useRouter, useSearchParams } from 'next/navigation';
6
5
  import { memo } from 'react';
7
6
  import { useTranslation } from 'react-i18next';
8
7
  import { Flexbox } from 'react-layout-kit';
9
8
 
10
9
  import { useActiveSettingsKey } from '@/hooks/useActiveTabKey';
10
+ import { useQueryRoute } from '@/hooks/useQueryRoute';
11
+ import { useShowMobileWorkspace } from '@/hooks/useShowMobileWorkspace';
11
12
  import { SettingsTabs } from '@/store/global/initialState';
13
+ import { useSessionStore } from '@/store/session';
12
14
  import { useUserStore } from '@/store/user';
13
15
  import { authSelectors } from '@/store/user/selectors';
14
16
  import { mobileHeaderSticky } from '@/styles/mobileHeader';
@@ -16,14 +18,15 @@ import { mobileHeaderSticky } from '@/styles/mobileHeader';
16
18
  const Header = memo(() => {
17
19
  const { t } = useTranslation('setting');
18
20
 
19
- const router = useRouter();
20
- const searchParams = useSearchParams();
21
+ const router = useQueryRoute();
22
+ const showMobileWorkspace = useShowMobileWorkspace();
21
23
  const activeSettingsKey = useActiveSettingsKey();
22
-
24
+ const isSessionActive = useSessionStore((s) => !!s.activeId);
23
25
  const enableAuth = useUserStore(authSelectors.enabledAuth);
26
+
24
27
  const handleBackClick = () => {
25
- if (searchParams.has('session') && searchParams.has('showMobileWorkspace')) {
26
- router.push(`/chat?${searchParams.toString()}`);
28
+ if (isSessionActive && showMobileWorkspace) {
29
+ router.push('/chat');
27
30
  } else {
28
31
  router.push(enableAuth ? '/me/settings' : '/me');
29
32
  }
@@ -5,15 +5,14 @@ import { Flexbox } from 'react-layout-kit';
5
5
 
6
6
  import HeaderContent from '@/app/(main)/chat/settings/features/HeaderContent';
7
7
  import Menu from '@/components/Menu';
8
- import { useQuery } from '@/hooks/useQuery';
8
+ import { useChatSettingsTab } from '@/hooks/useChatSettingsTab';
9
9
  import { useQueryRoute } from '@/hooks/useQueryRoute';
10
- import { ChatSettingsTabs } from '@/store/global/initialState';
11
10
 
12
11
  import { useCategory } from './useCategory';
13
12
 
14
13
  const CategoryContent = memo(() => {
15
14
  const cateItems = useCategory();
16
- const { tab = ChatSettingsTabs.Meta } = useQuery();
15
+ const tab = useChatSettingsTab();
17
16
  const router = useQueryRoute();
18
17
 
19
18
  return (
@@ -9,7 +9,7 @@ import { useTranslation } from 'react-i18next';
9
9
  import ModalLayout from '@/app/@modal/_layout/ModalLayout';
10
10
  import StoreUpdater from '@/features/AgentSetting/StoreUpdater';
11
11
  import { Provider, createStore } from '@/features/AgentSetting/store';
12
- import { useQuery } from '@/hooks/useQuery';
12
+ import { useChatSettingsTab } from '@/hooks/useChatSettingsTab';
13
13
  import { useAgentStore } from '@/store/agent';
14
14
  import { agentSelectors } from '@/store/agent/slices/chat';
15
15
  import { ChatSettingsTabs } from '@/store/global/initialState';
@@ -24,7 +24,7 @@ const CategoryContent = dynamic(() => import('./features/CategoryContent'), {
24
24
  });
25
25
 
26
26
  const Layout = memo<PropsWithChildren>(({ children }) => {
27
- const { tab = ChatSettingsTabs.Meta } = useQuery();
27
+ const tab = useChatSettingsTab();
28
28
  const { t } = useTranslation('setting');
29
29
  const id = useSessionStore((s) => s.activeId);
30
30
  const config = useAgentStore(agentSelectors.currentAgentConfig, isEqual);
@@ -2,7 +2,7 @@
2
2
 
3
3
  import dynamic from 'next/dynamic';
4
4
 
5
- import { useQuery } from '@/hooks/useQuery';
5
+ import { useChatSettingsTab } from '@/hooks/useChatSettingsTab';
6
6
  import { ChatSettingsTabs } from '@/store/global/initialState';
7
7
 
8
8
  import Skeleton from './loading';
@@ -37,7 +37,8 @@ const AgentTTS = dynamic(() => import('@/features/AgentSetting/AgentTTS'), { loa
37
37
  */
38
38
 
39
39
  const Page = () => {
40
- const { tab = ChatSettingsTabs.Meta } = useQuery();
40
+ const tab = useChatSettingsTab();
41
+
41
42
  return (
42
43
  <>
43
44
  {tab === ChatSettingsTabs.Meta && <AgentMeta />}
@@ -0,0 +1,8 @@
1
+ import { ComponentType, Suspense } from 'react';
2
+
3
+ // @ts-ignore
4
+ export const withSuspense: <T>(Comp: T) => T = (Component: ComponentType<any>) => (props: any) => (
5
+ <Suspense>
6
+ <Component {...props} />
7
+ </Suspense>
8
+ );
@@ -28,8 +28,8 @@ const AgentTTS = memo(() => {
28
28
  return (all?: boolean) => new VoiceList(all ? undefined : locale);
29
29
  });
30
30
  const [showAllLocaleVoice, ttsService, updateConfig] = useStore((s) => [
31
- s.config.tts.showAllLocaleVoice,
32
- s.config.tts.ttsService,
31
+ s.config.tts?.showAllLocaleVoice,
32
+ s.config.tts?.ttsService,
33
33
  s.setAgentConfig,
34
34
  ]);
35
35
 
@@ -1,6 +1,6 @@
1
1
  import { usePathname } from 'next/navigation';
2
+ import { useQueryState } from 'nuqs';
2
3
 
3
- import { useQuery } from '@/hooks/useQuery';
4
4
  import { ProfileTabs, SettingsTabs, SidebarTabKey } from '@/store/global/initialState';
5
5
 
6
6
  /**
@@ -17,7 +17,7 @@ export const useActiveTabKey = () => {
17
17
  */
18
18
  export const useActiveSettingsKey = () => {
19
19
  const pathname = usePathname();
20
- const { tab } = useQuery();
20
+ const [tab] = useQueryState('tab');
21
21
 
22
22
  const tabs = pathname.split('/').at(-1);
23
23
 
@@ -33,7 +33,7 @@ export const useActiveSettingsKey = () => {
33
33
  */
34
34
  export const useActiveProfileKey = () => {
35
35
  const pathname = usePathname();
36
- const { tab } = useQuery();
36
+ const [tab] = useQueryState('tab');
37
37
 
38
38
  const tabs = pathname.split('/').at(-1);
39
39
 
@@ -0,0 +1,12 @@
1
+ import { useQueryState } from 'nuqs';
2
+
3
+ import { ChatSettingsTabs } from '@/store/global/initialState';
4
+
5
+ export const useChatSettingsTab = () => {
6
+ const [type] = useQueryState('tab', {
7
+ clearOnDefault: true,
8
+ defaultValue: ChatSettingsTabs.Meta,
9
+ });
10
+
11
+ return type as ChatSettingsTabs;
12
+ };
@@ -0,0 +1,12 @@
1
+ import { useQueryState } from 'nuqs';
2
+
3
+ import { DiscoverTab } from '@/types/discover';
4
+
5
+ export const useDiscoverTab = () => {
6
+ const [type] = useQueryState('type', {
7
+ clearOnDefault: true,
8
+ defaultValue: DiscoverTab.Assistants,
9
+ });
10
+
11
+ return type as DiscoverTab;
12
+ };
@@ -1,5 +1,5 @@
1
1
  import { renderHook } from '@testing-library/react';
2
- import { describe, expect, it, vi } from 'vitest';
2
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
3
3
 
4
4
  import { useQueryRoute } from './useQueryRoute';
5
5
 
@@ -10,13 +10,15 @@ vi.mock('next/navigation', () => ({
10
10
  replace: vi.fn((href) => href),
11
11
  })),
12
12
  }));
13
- vi.mock('@/hooks/useQuery', () => ({
14
- useQuery: vi.fn(() => ({ foo: 'bar' })),
15
- }));
13
+
16
14
  vi.mock('@/utils/env', () => ({
17
15
  isOnServerSide: false,
18
16
  }));
19
17
 
18
+ beforeEach(() => {
19
+ location.search = 'foo=bar';
20
+ });
21
+
20
22
  describe('useQueryRoute', () => {
21
23
  it('should generate correct href without hash and replace', () => {
22
24
  const { result } = renderHook(() =>
@@ -2,7 +2,6 @@ import { useRouter } from 'next/navigation';
2
2
  import qs, { type ParsedQuery } from 'query-string';
3
3
  import { useMemo } from 'react';
4
4
 
5
- import { useQuery } from '@/hooks/useQuery';
6
5
  import { isOnServerSide } from '@/utils/env';
7
6
 
8
7
  interface QueryRouteOptions {
@@ -30,17 +29,19 @@ const genHref = ({ hash, replace, url, prevQuery = {}, query = {} }: GenHrefOpti
30
29
 
31
30
  export const useQueryRoute = () => {
32
31
  const router = useRouter();
33
- const prevQuery = useQuery();
34
32
 
35
33
  return useMemo(
36
34
  () => ({
37
35
  push: (url: string, options: QueryRouteOptions = {}) => {
36
+ const prevQuery = qs.parse(window.location.search);
37
+
38
38
  return router.push(genHref({ prevQuery, url, ...options }));
39
39
  },
40
40
  replace: (url: string, options: QueryRouteOptions = {}) => {
41
+ const prevQuery = qs.parse(window.location.search);
41
42
  return router.replace(genHref({ prevQuery, url, ...options }));
42
43
  },
43
44
  }),
44
- [prevQuery],
45
+ [],
45
46
  );
46
47
  };
@@ -0,0 +1,12 @@
1
+ import { useQueryState } from 'nuqs';
2
+
3
+ import { SettingsTabs } from '@/store/global/initialState';
4
+
5
+ export const useSettingsTab = () => {
6
+ const [type] = useQueryState('tab', {
7
+ clearOnDefault: true,
8
+ defaultValue: SettingsTabs.Common,
9
+ });
10
+
11
+ return type as SettingsTabs;
12
+ };
@@ -4,6 +4,8 @@ import { useSearchParams } from 'next/navigation';
4
4
  import Script from 'next/script';
5
5
  import React, { memo } from 'react';
6
6
 
7
+ import { withSuspense } from '@/components/withSuspense';
8
+
7
9
  const ReactScan = memo(() => {
8
10
  const searchParams = useSearchParams();
9
11
 
@@ -12,4 +14,4 @@ const ReactScan = memo(() => {
12
14
  return !!debug && <Script src="https://unpkg.com/react-scan/dist/auto.global.js" />;
13
15
  });
14
16
 
15
- export default ReactScan;
17
+ export default withSuspense(ReactScan);
@@ -1,19 +0,0 @@
1
- import { renderHook } from '@testing-library/react';
2
- import { describe, expect, it, vi } from 'vitest';
3
-
4
- import { useQuery } from './useQuery';
5
-
6
- // Mocks
7
- vi.mock('next/navigation', () => ({
8
- useSearchParams: vi.fn(() => 'baz=qux&foo=bar'),
9
- }));
10
-
11
- describe('useQuery', () => {
12
- it('should parse query', () => {
13
- const { result } = renderHook(() => useQuery());
14
- expect(result.current).toEqual({
15
- baz: 'qux',
16
- foo: 'bar',
17
- });
18
- });
19
- });
@@ -1,8 +0,0 @@
1
- import { useSearchParams } from 'next/navigation';
2
- import qs from 'query-string';
3
- import { useMemo } from 'react';
4
-
5
- export const useQuery = () => {
6
- const rawQuery = useSearchParams();
7
- return useMemo(() => qs.parse(rawQuery.toString()), [rawQuery]);
8
- };