@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.
- package/CHANGELOG.md +50 -0
- package/changelog/v1.json +18 -0
- package/package.json +1 -1
- package/src/app/(main)/discover/(list)/(home)/features/AssistantList.tsx +13 -7
- package/src/app/(main)/discover/(list)/(home)/features/ModelList.tsx +1 -4
- package/src/app/(main)/discover/(list)/(home)/features/PluginList.tsx +7 -4
- package/src/app/(main)/discover/(list)/assistants/features/Card.tsx +74 -55
- package/src/app/(main)/discover/(list)/assistants/features/List.tsx +20 -10
- package/src/app/(main)/discover/(list)/models/features/Card.tsx +34 -32
- package/src/app/(main)/discover/(list)/models/features/List.tsx +12 -7
- package/src/app/(main)/discover/(list)/plugins/features/Card.tsx +52 -43
- package/src/app/(main)/discover/(list)/plugins/features/List.tsx +20 -10
- package/src/app/(main)/discover/features/StoreSearchBar.tsx +3 -5
- package/src/app/(main)/discover/features/useNav.tsx +3 -3
- package/src/app/(main)/discover/search/_layout/Mobile/Header.tsx +2 -2
- package/src/app/(main)/profile/@category/features/CategoryContent.tsx +5 -10
- package/src/app/(main)/settings/@category/features/CategoryContent.tsx +3 -10
- package/src/app/(main)/settings/_layout/Mobile/Header.tsx +9 -6
- package/src/app/@modal/chat/(.)settings/modal/features/CategoryContent.tsx +2 -3
- package/src/app/@modal/chat/(.)settings/modal/layout.tsx +2 -2
- package/src/app/@modal/chat/(.)settings/modal/page.tsx +3 -2
- package/src/components/withSuspense.tsx +8 -0
- package/src/features/AgentSetting/AgentTTS/index.tsx +2 -2
- package/src/hooks/useActiveTabKey.ts +3 -3
- package/src/hooks/useChatSettingsTab.ts +12 -0
- package/src/hooks/useDiscoverTab.ts +12 -0
- package/src/hooks/useQueryRoute.test.ts +6 -4
- package/src/hooks/useQueryRoute.ts +4 -3
- package/src/hooks/useSettingsTab.ts +12 -0
- package/src/layout/GlobalProvider/ReactScan.tsx +3 -1
- package/src/hooks/useQuery.test.ts +0 -19
- 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
|
+
[](#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
|
+
[](#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.
|
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
|
-
<
|
15
|
-
|
16
|
-
|
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
|
-
<
|
20
|
-
|
21
|
-
|
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
|
-
<
|
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
|
-
<
|
15
|
-
|
16
|
-
|
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 &&
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
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
|
-
<
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
{
|
114
|
-
|
115
|
-
|
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
|
-
<
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
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
|
-
|
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
|
-
<
|
46
|
-
|
47
|
-
|
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
|
-
<
|
63
|
-
|
64
|
-
|
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
|
-
<
|
75
|
-
|
76
|
-
|
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
|
-
<
|
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
|
-
|
86
|
+
padding={16}
|
93
87
|
style={{ overflow: 'hidden', position: 'relative' }}
|
94
88
|
width={'100%'}
|
95
89
|
>
|
96
|
-
<
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
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
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
)}
|
107
|
+
{description && (
|
108
|
+
<Paragraph className={styles.desc} ellipsis={{ rows: 2 }}>
|
109
|
+
{t(`${identifier}.description`)}
|
110
|
+
</Paragraph>
|
111
|
+
)}
|
111
112
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
113
|
+
<ModelFeatureTags
|
114
|
+
functionCall={functionCall}
|
115
|
+
tokens={contextWindowTokens}
|
116
|
+
vision={vision}
|
117
|
+
/>
|
118
|
+
</Flexbox>
|
117
119
|
</Flexbox>
|
118
|
-
</
|
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
|
-
<
|
36
|
-
|
37
|
-
|
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
|
-
<
|
55
|
-
|
56
|
-
|
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
|
85
|
-
<
|
86
|
-
<
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
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
|
-
<
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
style={{ display: 'block' }}
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
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
|
-
<
|
45
|
-
|
46
|
-
|
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
|
-
<
|
62
|
-
|
63
|
-
|
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
|
-
<
|
74
|
-
|
75
|
-
|
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 (!
|
49
|
-
}, [
|
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 {
|
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
|
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
|
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 {
|
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
|
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
|
9
|
+
import { ProfileTabs } from '@/store/global/initialState';
|
11
10
|
|
12
11
|
import { useCategory } from '../../hooks/useCategory';
|
13
12
|
|
14
|
-
const CategoryContent = memo
|
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
|
-
|
26
|
-
|
27
|
-
} else {
|
28
|
-
router.push(urlJoin('/profile', activeKey));
|
29
|
-
}
|
23
|
+
|
24
|
+
router.push(urlJoin('/profile', activeKey));
|
30
25
|
}}
|
31
26
|
selectable
|
32
|
-
selectedKeys={[
|
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
|
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
|
-
|
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={[
|
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 =
|
20
|
-
const
|
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 (
|
26
|
-
router.push(
|
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 {
|
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
|
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 {
|
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
|
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 {
|
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
|
40
|
+
const tab = useChatSettingsTab();
|
41
|
+
|
41
42
|
return (
|
42
43
|
<>
|
43
44
|
{tab === ChatSettingsTabs.Meta && <AgentMeta />}
|
@@ -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
|
32
|
-
s.config.tts
|
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
|
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
|
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
|
-
|
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
|
-
[
|
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
|
-
});
|
package/src/hooks/useQuery.ts
DELETED