@lobehub/chat 1.141.4 → 1.141.5

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 (71) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/apps/desktop/src/main/controllers/BrowserWindowsCtr.ts +41 -10
  3. package/apps/desktop/src/main/controllers/__tests__/BrowserWindowsCtr.test.ts +14 -7
  4. package/apps/desktop/src/main/core/browser/BrowserManager.ts +56 -18
  5. package/changelog/v1.json +9 -0
  6. package/package.json +3 -1
  7. package/packages/electron-client-ipc/src/events/index.ts +2 -0
  8. package/packages/electron-client-ipc/src/events/windows.ts +32 -19
  9. package/packages/utils/src/server/responsive.ts +1 -1
  10. package/playwright.config.ts +1 -1
  11. package/src/app/(backend)/trpc/desktop/[trpc]/route.ts +5 -0
  12. package/src/app/[variants]/(main)/discover/(detail)/_layout/DetailLayout.tsx +22 -0
  13. package/src/app/[variants]/(main)/discover/(detail)/_layout/Mobile/Header.tsx +6 -7
  14. package/src/app/[variants]/(main)/discover/(detail)/assistant/AssistantDetailPage.tsx +47 -0
  15. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Header.tsx +10 -9
  16. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Sidebar/Related/index.tsx +3 -3
  17. package/src/app/[variants]/(main)/discover/(detail)/components/NotFound.tsx +23 -0
  18. package/src/app/[variants]/(main)/discover/(detail)/features/Back.tsx +2 -2
  19. package/src/app/[variants]/(main)/discover/(detail)/features/Breadcrumb.tsx +3 -4
  20. package/src/app/[variants]/(main)/discover/(detail)/mcp/McpDetailPage.tsx +51 -0
  21. package/src/app/[variants]/(main)/discover/(detail)/mcp/[slug]/features/Sidebar/Related/index.tsx +3 -3
  22. package/src/app/[variants]/(main)/discover/(detail)/model/ModelDetailPage.tsx +44 -0
  23. package/src/app/[variants]/(main)/discover/(detail)/model/[...slugs]/features/Sidebar/Related/index.tsx +3 -3
  24. package/src/app/[variants]/(main)/discover/(detail)/provider/ProviderDetailPage.tsx +44 -0
  25. package/src/app/[variants]/(main)/discover/(detail)/provider/[...slugs]/features/Sidebar/ActionButton/ProviderConfig.tsx +16 -2
  26. package/src/app/[variants]/(main)/discover/(detail)/provider/[...slugs]/features/Sidebar/Related/index.tsx +3 -3
  27. package/src/app/[variants]/(main)/discover/(list)/(home)/HomePage.tsx +45 -0
  28. package/src/app/[variants]/(main)/discover/(list)/_layout/Desktop/Nav.tsx +7 -8
  29. package/src/app/[variants]/(main)/discover/(list)/_layout/ListLayout.tsx +22 -0
  30. package/src/app/[variants]/(main)/discover/(list)/_layout/Mobile/Nav.tsx +4 -5
  31. package/src/app/[variants]/(main)/discover/(list)/assistant/AssistantLayout.tsx +21 -0
  32. package/src/app/[variants]/(main)/discover/(list)/assistant/AssistantPage.tsx +44 -0
  33. package/src/app/[variants]/(main)/discover/(list)/assistant/features/Category/index.tsx +5 -6
  34. package/src/app/[variants]/(main)/discover/(list)/assistant/features/List/Item.tsx +8 -8
  35. package/src/app/[variants]/(main)/discover/(list)/features/Pagination.tsx +7 -8
  36. package/src/app/[variants]/(main)/discover/(list)/mcp/McpLayout.tsx +21 -0
  37. package/src/app/[variants]/(main)/discover/(list)/mcp/McpPage.tsx +44 -0
  38. package/src/app/[variants]/(main)/discover/(list)/mcp/features/Category/index.tsx +5 -6
  39. package/src/app/[variants]/(main)/discover/(list)/mcp/features/List/Item.tsx +12 -8
  40. package/src/app/[variants]/(main)/discover/(list)/model/ModelLayout.tsx +21 -0
  41. package/src/app/[variants]/(main)/discover/(list)/model/ModelPage.tsx +44 -0
  42. package/src/app/[variants]/(main)/discover/(list)/model/_layout/Desktop.tsx +1 -1
  43. package/src/app/[variants]/(main)/discover/(list)/model/features/Category/index.tsx +5 -6
  44. package/src/app/[variants]/(main)/discover/(list)/model/features/List/Item.tsx +5 -6
  45. package/src/app/[variants]/(main)/discover/(list)/provider/ProviderPage.tsx +43 -0
  46. package/src/app/[variants]/(main)/discover/(list)/provider/features/List/Item.tsx +16 -11
  47. package/src/app/[variants]/(main)/discover/DiscoverRouter.tsx +167 -0
  48. package/src/app/[variants]/(main)/discover/[[...path]]/page.tsx +11 -0
  49. package/src/app/[variants]/(main)/discover/_layout/Desktop/Header.tsx +2 -2
  50. package/src/app/[variants]/(main)/discover/_layout/DiscoverLayout.tsx +22 -0
  51. package/src/app/[variants]/(main)/discover/components/Title.tsx +5 -2
  52. package/src/app/[variants]/(main)/discover/features/Title.tsx +35 -12
  53. package/src/app/[variants]/(main)/discover/features/useNav.tsx +11 -12
  54. package/src/app/[variants]/(main)/labs/components/LabCard.tsx +6 -7
  55. package/src/layout/GlobalProvider/Query.tsx +7 -2
  56. package/vercel.json +1 -1
  57. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/page.tsx +0 -110
  58. package/src/app/[variants]/(main)/discover/(detail)/layout.tsx +0 -12
  59. package/src/app/[variants]/(main)/discover/(detail)/mcp/[slug]/page.tsx +0 -103
  60. package/src/app/[variants]/(main)/discover/(detail)/model/[...slugs]/page.tsx +0 -104
  61. package/src/app/[variants]/(main)/discover/(detail)/provider/[...slugs]/page.tsx +0 -103
  62. package/src/app/[variants]/(main)/discover/(list)/(home)/page.tsx +0 -44
  63. package/src/app/[variants]/(main)/discover/(list)/assistant/layout.tsx +0 -12
  64. package/src/app/[variants]/(main)/discover/(list)/assistant/page.tsx +0 -46
  65. package/src/app/[variants]/(main)/discover/(list)/layout.tsx +0 -12
  66. package/src/app/[variants]/(main)/discover/(list)/mcp/layout.tsx +0 -12
  67. package/src/app/[variants]/(main)/discover/(list)/mcp/page.tsx +0 -46
  68. package/src/app/[variants]/(main)/discover/(list)/model/layout.tsx +0 -12
  69. package/src/app/[variants]/(main)/discover/(list)/model/page.tsx +0 -44
  70. package/src/app/[variants]/(main)/discover/(list)/provider/page.tsx +0 -44
  71. package/src/app/[variants]/(main)/discover/layout.tsx +0 -12
@@ -3,11 +3,10 @@
3
3
  import { Pagination as Page } from 'antd';
4
4
  import { createStyles } from 'antd-style';
5
5
  import { memo } from 'react';
6
- import urlJoin from 'url-join';
6
+ import { useLocation, useNavigate } from 'react-router-dom';
7
7
 
8
8
  import { SCROLL_PARENT_ID } from '@/app/[variants]/(main)/discover/features/const';
9
9
  import { useQuery } from '@/hooks/useQuery';
10
- import { useQueryRoute } from '@/hooks/useQueryRoute';
11
10
  import { DiscoverTab } from '@/types/discover';
12
11
 
13
12
  const useStyles = createStyles(({ css, token, prefixCls }) => {
@@ -36,14 +35,14 @@ interface PaginationProps {
36
35
  const Pagination = memo<PaginationProps>(({ tab, currentPage, total, pageSize }) => {
37
36
  const { styles } = useStyles();
38
37
  const { page } = useQuery();
39
- const router = useQueryRoute();
38
+ const navigate = useNavigate();
39
+ const location = useLocation();
40
40
 
41
41
  const handlePageChange = (newPage: number) => {
42
- router.push(urlJoin('/discover', tab), {
43
- query: {
44
- page: String(newPage),
45
- },
46
- });
42
+ const searchParams = new URLSearchParams(location.search);
43
+ searchParams.set('page', String(newPage));
44
+ navigate(`/${tab}?${searchParams.toString()}`);
45
+
47
46
  const scrollableElement = document?.querySelector(`#${SCROLL_PARENT_ID}`);
48
47
  if (!scrollableElement) return;
49
48
  scrollableElement.scrollTo({ behavior: 'smooth', top: 0 });
@@ -0,0 +1,21 @@
1
+ 'use client';
2
+
3
+ import { memo, PropsWithChildren } from 'react';
4
+
5
+ import Desktop from './_layout/Desktop';
6
+ import Mobile from './_layout/Mobile';
7
+
8
+ interface McpLayoutProps extends PropsWithChildren {
9
+ mobile?: boolean;
10
+ }
11
+
12
+ const McpLayout = memo<McpLayoutProps>(({ children, mobile }) => {
13
+ if (mobile) {
14
+ return <Mobile>{children}</Mobile>;
15
+ }
16
+ return <Desktop>{children}</Desktop>;
17
+ });
18
+
19
+ McpLayout.displayName = 'McpLayout';
20
+
21
+ export default McpLayout;
@@ -0,0 +1,44 @@
1
+ 'use client';
2
+
3
+ import { memo } from 'react';
4
+ import { Flexbox } from 'react-layout-kit';
5
+
6
+ import { withSuspense } from '@/components/withSuspense';
7
+ import { useQuery } from '@/hooks/useQuery';
8
+ import { useDiscoverStore } from '@/store/discover';
9
+ import { DiscoverTab, McpQueryParams } from '@/types/discover';
10
+
11
+ import Pagination from '../features/Pagination';
12
+ import List from './features/List';
13
+ import Loading from './loading';
14
+
15
+ const McpPage = memo<{ mobile?: boolean }>(() => {
16
+ const { q, page, category, sort, order } = useQuery() as McpQueryParams;
17
+ const useMcpList = useDiscoverStore((s) => s.useFetchMcpList);
18
+ const { data, isLoading } = useMcpList({
19
+ category,
20
+ order,
21
+ page,
22
+ pageSize: 21,
23
+ q,
24
+ sort,
25
+ });
26
+
27
+ if (isLoading || !data) return <Loading />;
28
+
29
+ const { items, currentPage, pageSize, totalCount } = data;
30
+
31
+ return (
32
+ <Flexbox gap={32} width={'100%'}>
33
+ <List data={items} />
34
+ <Pagination
35
+ currentPage={currentPage}
36
+ pageSize={pageSize}
37
+ tab={DiscoverTab.Mcp}
38
+ total={totalCount}
39
+ />
40
+ </Flexbox>
41
+ );
42
+ });
43
+
44
+ export default withSuspense(McpPage);
@@ -1,8 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { Icon, Tag } from '@lobehub/ui';
4
- import Link from 'next/link';
5
- import { useRouter } from 'nextjs-toploader/app';
4
+ import { Link, useNavigate } from 'react-router-dom';
6
5
  import qs from 'query-string';
7
6
  import { memo, useMemo } from 'react';
8
7
 
@@ -19,20 +18,20 @@ const Category = memo(() => {
19
18
  const useMcpCategories = useDiscoverStore((s) => s.useMcpCategories);
20
19
  const { category = 'all', q } = useQuery() as { category?: McpCategory; q?: string };
21
20
  const { data: items = [] } = useMcpCategories({ q });
22
- const route = useRouter();
21
+ const navigate = useNavigate();
23
22
  const cates = useCategory();
24
23
 
25
24
  const genUrl = (key: McpCategory) =>
26
25
  qs.stringifyUrl(
27
26
  {
28
27
  query: { category: key === McpCategory.All ? null : key, q },
29
- url: '/discover/mcp',
28
+ url: '/mcp',
30
29
  },
31
30
  { skipNull: true },
32
31
  );
33
32
 
34
33
  const handleClick = (key: McpCategory) => {
35
- route.push(genUrl(key));
34
+ navigate(genUrl(key));
36
35
  const scrollableElement = document?.querySelector(`#${SCROLL_PARENT_ID}`);
37
36
  if (!scrollableElement) return;
38
37
  scrollableElement.scrollTo({ behavior: 'smooth', top: 0 });
@@ -70,7 +69,7 @@ const Category = memo(() => {
70
69
  ),
71
70
  ...item,
72
71
  icon: <Icon icon={item.icon} size={18} />,
73
- label: <Link href={genUrl(item.key)}>{item.label}</Link>,
72
+ label: <Link to={genUrl(item.key)}>{item.label}</Link>,
74
73
  };
75
74
  })}
76
75
  mode={'inline'}
@@ -5,11 +5,10 @@ import { ActionIcon, Avatar, Block, Icon, Tag, Text, Tooltip } from '@lobehub/ui
5
5
  import { Spotlight } from '@lobehub/ui/awesome';
6
6
  import { createStyles } from 'antd-style';
7
7
  import { ClockIcon } from 'lucide-react';
8
- import Link from 'next/link';
9
- import { useRouter } from 'nextjs-toploader/app';
10
8
  import { memo } from 'react';
11
9
  import { useTranslation } from 'react-i18next';
12
10
  import { Flexbox } from 'react-layout-kit';
11
+ import { Link, useNavigate } from 'react-router-dom';
13
12
  import urlJoin from 'url-join';
14
13
 
15
14
  import InstallationIcon from '@/components/MCPDepsIcon';
@@ -78,14 +77,14 @@ const McpItem = memo<DiscoverMcpItem>(
78
77
  }) => {
79
78
  const { t } = useTranslation('discover');
80
79
  const { styles, theme } = useStyles();
81
- const router = useRouter();
82
- const link = urlJoin('/discover/mcp', identifier);
80
+ const navigate = useNavigate();
81
+ const link = urlJoin('/mcp', identifier);
83
82
  return (
84
83
  <Block
85
84
  clickable
86
85
  height={'100%'}
87
86
  onClick={() => {
88
- router.push(link);
87
+ navigate(link);
89
88
  }}
90
89
  style={{
91
90
  overflow: 'hidden',
@@ -128,7 +127,7 @@ const McpItem = memo<DiscoverMcpItem>(
128
127
  overflow: 'hidden',
129
128
  }}
130
129
  >
131
- <Link href={link} style={{ color: 'inherit', overflow: 'hidden' }}>
130
+ <Link style={{ color: 'inherit', overflow: 'hidden' }} to={link}>
132
131
  <Text as={'h2'} className={styles.title} ellipsis>
133
132
  {name}
134
133
  </Text>
@@ -145,9 +144,14 @@ const McpItem = memo<DiscoverMcpItem>(
145
144
  <Flexbox align={'center'} gap={4} horizontal>
146
145
  {installationMethods && <InstallationIcon type={installationMethods} />}
147
146
  {github && (
148
- <Link href={github.url} onClick={(e) => e.stopPropagation()} target={'_blank'}>
147
+ <a
148
+ href={github.url}
149
+ onClick={(e) => e.stopPropagation()}
150
+ rel="noopener noreferrer"
151
+ target={'_blank'}
152
+ >
149
153
  <ActionIcon fill={theme.colorTextDescription} icon={Github} />
150
- </Link>
154
+ </a>
151
155
  )}
152
156
  </Flexbox>
153
157
  </Flexbox>
@@ -0,0 +1,21 @@
1
+ 'use client';
2
+
3
+ import { memo, PropsWithChildren } from 'react';
4
+
5
+ import Desktop from './_layout/Desktop';
6
+ import Mobile from './_layout/Mobile';
7
+
8
+ interface ModelLayoutProps extends PropsWithChildren {
9
+ mobile?: boolean;
10
+ }
11
+
12
+ const ModelLayout = memo<ModelLayoutProps>(({ children, mobile }) => {
13
+ if (mobile) {
14
+ return <Mobile>{children}</Mobile>;
15
+ }
16
+ return <Desktop>{children}</Desktop>;
17
+ });
18
+
19
+ ModelLayout.displayName = 'ModelLayout';
20
+
21
+ export default ModelLayout;
@@ -0,0 +1,44 @@
1
+ 'use client';
2
+
3
+ import { memo } from 'react';
4
+ import { Flexbox } from 'react-layout-kit';
5
+
6
+ import { withSuspense } from '@/components/withSuspense';
7
+ import { useQuery } from '@/hooks/useQuery';
8
+ import { useDiscoverStore } from '@/store/discover';
9
+ import { DiscoverTab, ModelQueryParams } from '@/types/discover';
10
+
11
+ import Pagination from '../features/Pagination';
12
+ import List from './features/List';
13
+ import Loading from './loading';
14
+
15
+ const ModelPage = memo<{ mobile?: boolean }>(() => {
16
+ const { q, page, category, sort, order } = useQuery() as ModelQueryParams;
17
+ const useModelList = useDiscoverStore((s) => s.useModelList);
18
+ const { data, isLoading } = useModelList({
19
+ category,
20
+ order,
21
+ page,
22
+ pageSize: 21,
23
+ q,
24
+ sort,
25
+ });
26
+
27
+ if (isLoading || !data) return <Loading />;
28
+
29
+ const { items, currentPage, pageSize, totalCount } = data;
30
+
31
+ return (
32
+ <Flexbox gap={32} width={'100%'}>
33
+ <List data={items} />
34
+ <Pagination
35
+ currentPage={currentPage}
36
+ pageSize={pageSize}
37
+ tab={DiscoverTab.Models}
38
+ total={totalCount}
39
+ />
40
+ </Flexbox>
41
+ );
42
+ });
43
+
44
+ export default withSuspense(ModelPage);
@@ -4,7 +4,7 @@ import { Flexbox } from 'react-layout-kit';
4
4
  import CategoryContainer from '../../../components/CategoryContainer';
5
5
  import Category from '../features/Category';
6
6
 
7
- const Layout = async ({ children }: PropsWithChildren) => {
7
+ const Layout = ({ children }: PropsWithChildren) => {
8
8
  return (
9
9
  <Flexbox gap={24} horizontal style={{ position: 'relative' }} width={'100%'}>
10
10
  <CategoryContainer>
@@ -1,8 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { Icon, Tag } from '@lobehub/ui';
4
- import Link from 'next/link';
5
- import { useRouter } from 'nextjs-toploader/app';
4
+ import { Link, useNavigate } from 'react-router-dom';
6
5
  import qs from 'query-string';
7
6
  import { memo, useMemo } from 'react';
8
7
 
@@ -18,20 +17,20 @@ const Category = memo(() => {
18
17
  const useModelCategories = useDiscoverStore((s) => s.useModelCategories);
19
18
  const { category = 'all', q } = useQuery() as { category?: string; q?: string };
20
19
  const { data: items = [] } = useModelCategories({ q });
21
- const route = useRouter();
20
+ const navigate = useNavigate();
22
21
  const cates = useCategory();
23
22
 
24
23
  const genUrl = (key: string) =>
25
24
  qs.stringifyUrl(
26
25
  {
27
26
  query: { category: key === 'all' ? null : key, q },
28
- url: '/discover/model',
27
+ url: '/model',
29
28
  },
30
29
  { skipNull: true },
31
30
  );
32
31
 
33
32
  const handleClick = (key: string) => {
34
- route.push(genUrl(key));
33
+ navigate(genUrl(key));
35
34
  const scrollableElement = document?.querySelector(`#${SCROLL_PARENT_ID}`);
36
35
  if (!scrollableElement) return;
37
36
  scrollableElement.scrollTo({ behavior: 'smooth', top: 0 });
@@ -69,7 +68,7 @@ const Category = memo(() => {
69
68
  ),
70
69
  ...item,
71
70
  icon: <Icon icon={item.icon} size={18} />,
72
- label: <Link href={genUrl(item.key)}>{item.label}</Link>,
71
+ label: <Link to={genUrl(item.key)}>{item.label}</Link>,
73
72
  };
74
73
  })}
75
74
  mode={'inline'}
@@ -6,11 +6,10 @@ import { Popover } from 'antd';
6
6
  import { createStyles } from 'antd-style';
7
7
  import dayjs from 'dayjs';
8
8
  import { ClockIcon } from 'lucide-react';
9
- import Link from 'next/link';
10
- import { useRouter } from 'nextjs-toploader/app';
11
9
  import { memo } from 'react';
12
10
  import { useTranslation } from 'react-i18next';
13
11
  import { Flexbox } from 'react-layout-kit';
12
+ import { Link, useNavigate } from 'react-router-dom';
14
13
  import urlJoin from 'url-join';
15
14
 
16
15
  import { ModelInfoTags } from '@/components/ModelSelect';
@@ -57,14 +56,14 @@ const ModelItem = memo<DiscoverModelItem>(
57
56
  ({ identifier, displayName, contextWindowTokens, releasedAt, type, abilities, providers }) => {
58
57
  const { t } = useTranslation(['models', 'discover']);
59
58
  const { styles } = useStyles();
60
- const router = useRouter();
61
- const link = urlJoin('/discover/model', identifier);
59
+ const navigate = useNavigate();
60
+ const link = urlJoin('/model', identifier);
62
61
  return (
63
62
  <Block
64
63
  clickable
65
64
  height={'100%'}
66
65
  onClick={() => {
67
- router.push(link);
66
+ navigate(link);
68
67
  }}
69
68
  style={{
70
69
  overflow: 'hidden',
@@ -106,7 +105,7 @@ const ModelItem = memo<DiscoverModelItem>(
106
105
  overflow: 'hidden',
107
106
  }}
108
107
  >
109
- <Link href={link} style={{ color: 'inherit', overflow: 'hidden' }}>
108
+ <Link style={{ color: 'inherit', overflow: 'hidden' }} to={link}>
110
109
  <Text as={'h2'} className={styles.title} ellipsis>
111
110
  {displayName}
112
111
  </Text>
@@ -0,0 +1,43 @@
1
+ 'use client';
2
+
3
+ import { memo } from 'react';
4
+ import { Flexbox } from 'react-layout-kit';
5
+
6
+ import { withSuspense } from '@/components/withSuspense';
7
+ import { useQuery } from '@/hooks/useQuery';
8
+ import { useDiscoverStore } from '@/store/discover';
9
+ import { DiscoverTab, ProviderQueryParams } from '@/types/discover';
10
+
11
+ import Pagination from '../features/Pagination';
12
+ import List from './features/List';
13
+ import Loading from './loading';
14
+
15
+ const ProviderPage = memo<{ mobile?: boolean }>(() => {
16
+ const { q, page, sort, order } = useQuery() as ProviderQueryParams;
17
+ const useProviderList = useDiscoverStore((s) => s.useProviderList);
18
+ const { data, isLoading } = useProviderList({
19
+ order,
20
+ page,
21
+ pageSize: 21,
22
+ q,
23
+ sort,
24
+ });
25
+
26
+ if (isLoading || !data) return <Loading />;
27
+
28
+ const { items, currentPage, pageSize, totalCount } = data;
29
+
30
+ return (
31
+ <Flexbox gap={32} width={'100%'}>
32
+ <List data={items} />
33
+ <Pagination
34
+ currentPage={currentPage}
35
+ pageSize={pageSize}
36
+ tab={DiscoverTab.Providers}
37
+ total={totalCount}
38
+ />
39
+ </Flexbox>
40
+ );
41
+ });
42
+
43
+ export default withSuspense(ProviderPage);
@@ -2,11 +2,10 @@ import { Github, ModelTag, ProviderCombine } from '@lobehub/icons';
2
2
  import { ActionIcon, Block, MaskShadow, Text } from '@lobehub/ui';
3
3
  import { createStyles } from 'antd-style';
4
4
  import { GlobeIcon } from 'lucide-react';
5
- import Link from 'next/link';
6
- import { useRouter } from 'nextjs-toploader/app';
7
5
  import { memo } from 'react';
8
6
  import { useTranslation } from 'react-i18next';
9
7
  import { Flexbox } from 'react-layout-kit';
8
+ import { Link, useNavigate } from 'react-router-dom';
10
9
  import urlJoin from 'url-join';
11
10
 
12
11
  import { DiscoverProviderItem } from '@/types/discover';
@@ -48,8 +47,8 @@ const useStyles = createStyles(({ css, token }) => {
48
47
  const ProviderItem = memo<DiscoverProviderItem>(
49
48
  ({ url, name, description, identifier, models }) => {
50
49
  const { styles, theme } = useStyles();
51
- const router = useRouter();
52
- const link = urlJoin('/discover/provider', identifier);
50
+ const navigate = useNavigate();
51
+ const link = urlJoin('/provider', identifier);
53
52
  const { t } = useTranslation(['discover', 'providers']);
54
53
 
55
54
  return (
@@ -57,7 +56,7 @@ const ProviderItem = memo<DiscoverProviderItem>(
57
56
  clickable
58
57
  height={'100%'}
59
58
  onClick={() => {
60
- router.push(link);
59
+ navigate(link);
61
60
  }}
62
61
  style={{
63
62
  overflow: 'hidden',
@@ -80,22 +79,28 @@ const ProviderItem = memo<DiscoverProviderItem>(
80
79
  }}
81
80
  title={identifier}
82
81
  >
83
- <Link href={link} style={{ color: 'inherit', overflow: 'hidden' }}>
82
+ <Link style={{ color: 'inherit', overflow: 'hidden' }} to={link}>
84
83
  <ProviderCombine provider={identifier} size={28} style={{ flex: 'none' }} />
85
84
  </Link>
86
85
  <div className={styles.author}>@{name}</div>
87
86
  </Flexbox>
88
87
  <Flexbox align={'center'} horizontal>
89
- <Link href={url} onClick={(e) => e.stopPropagation()} target={'_blank'}>
88
+ <a
89
+ href={url}
90
+ onClick={(e) => e.stopPropagation()}
91
+ rel="noopener noreferrer"
92
+ target={'_blank'}
93
+ >
90
94
  <ActionIcon color={theme.colorTextDescription} icon={GlobeIcon} />
91
- </Link>
92
- <Link
95
+ </a>
96
+ <a
93
97
  href={`https://github.com/lobehub/lobe-chat/blob/main/src/config/modelProviders/${identifier}.ts`}
94
98
  onClick={(e) => e.stopPropagation()}
99
+ rel="noopener noreferrer"
95
100
  target={'_blank'}
96
101
  >
97
102
  <ActionIcon fill={theme.colorTextDescription} icon={Github} />
98
- </Link>
103
+ </a>
99
104
  </Flexbox>
100
105
  </Flexbox>
101
106
  <Flexbox flex={1} gap={12} paddingInline={16}>
@@ -122,7 +127,7 @@ const ProviderItem = memo<DiscoverProviderItem>(
122
127
  .slice(0, 6)
123
128
  .filter(Boolean)
124
129
  .map((tag: string) => (
125
- <Link href={urlJoin('/discover/model', tag)} key={tag}>
130
+ <Link key={tag} to={urlJoin('/model', tag)}>
126
131
  <ModelTag model={tag} style={{ margin: 0 }} />
127
132
  </Link>
128
133
  ))}
@@ -0,0 +1,167 @@
1
+ 'use client';
2
+
3
+ import { memo, useEffect } from 'react';
4
+ import { MemoryRouter, Navigate, Route, Routes, useLocation, useNavigate } from 'react-router-dom';
5
+ import { useMediaQuery } from 'react-responsive';
6
+
7
+ import DiscoverLayout from './_layout/DiscoverLayout';
8
+ import ListLayout from './(list)/_layout/ListLayout';
9
+ import DetailLayout from './(detail)/_layout/DetailLayout';
10
+ import HomePage from './(list)/(home)/HomePage';
11
+ import AssistantPage from './(list)/assistant/AssistantPage';
12
+ import AssistantLayout from './(list)/assistant/AssistantLayout';
13
+ import McpPage from './(list)/mcp/McpPage';
14
+ import McpLayout from './(list)/mcp/McpLayout';
15
+ import ModelPage from './(list)/model/ModelPage';
16
+ import ModelLayout from './(list)/model/ModelLayout';
17
+ import ProviderPage from './(list)/provider/ProviderPage';
18
+ import AssistantDetailPage from './(detail)/assistant/AssistantDetailPage';
19
+ import McpDetailPage from './(detail)/mcp/McpDetailPage';
20
+ import ModelDetailPage from './(detail)/model/ModelDetailPage';
21
+ import ProviderDetailPage from './(detail)/provider/ProviderDetailPage';
22
+
23
+ // Get initial path from URL
24
+ const getInitialPath = () => {
25
+ if (typeof window === 'undefined') return '/';
26
+ const fullPath = window.location.pathname;
27
+ const searchParams = window.location.search;
28
+ const discoverIndex = fullPath.indexOf('/discover');
29
+
30
+ if (discoverIndex !== -1) {
31
+ const pathAfterDiscover = fullPath.slice(discoverIndex + '/discover'.length) || '/';
32
+ return pathAfterDiscover + searchParams;
33
+ }
34
+ return '/';
35
+ };
36
+
37
+ // Helper component to sync URL with MemoryRouter
38
+ const UrlSynchronizer = () => {
39
+ const location = useLocation();
40
+ const navigate = useNavigate();
41
+
42
+ // Sync initial URL
43
+ useEffect(() => {
44
+ const fullPath = window.location.pathname;
45
+ const searchParams = window.location.search;
46
+ const discoverIndex = fullPath.indexOf('/discover');
47
+
48
+ if (discoverIndex !== -1) {
49
+ const pathAfterDiscover = fullPath.slice(discoverIndex + '/discover'.length) || '/';
50
+ const targetPath = pathAfterDiscover + searchParams;
51
+
52
+ if (location.pathname + location.search !== targetPath) {
53
+ navigate(targetPath, { replace: true });
54
+ }
55
+ }
56
+ }, []);
57
+
58
+ // Update browser URL when location changes
59
+ useEffect(() => {
60
+ const newUrl = `/discover${location.pathname}${location.search}`;
61
+ if (window.location.pathname + window.location.search !== newUrl) {
62
+ window.history.replaceState({}, '', newUrl);
63
+ }
64
+ }, [location.pathname, location.search]);
65
+
66
+ return null;
67
+ };
68
+
69
+ const DiscoverRouter = memo(() => {
70
+ const mobile = useMediaQuery({ maxWidth: 768 });
71
+
72
+ return (
73
+ <MemoryRouter initialEntries={[getInitialPath()]} initialIndex={0}>
74
+ <UrlSynchronizer />
75
+ <DiscoverLayout mobile={mobile}>
76
+ <Routes>
77
+ {/* List routes with ListLayout */}
78
+ <Route
79
+ element={
80
+ <ListLayout mobile={mobile}>
81
+ <HomePage mobile={mobile} />
82
+ </ListLayout>
83
+ }
84
+ path="/"
85
+ />
86
+ <Route
87
+ element={
88
+ <ListLayout mobile={mobile}>
89
+ <AssistantLayout mobile={mobile}>
90
+ <AssistantPage mobile={mobile} />
91
+ </AssistantLayout>
92
+ </ListLayout>
93
+ }
94
+ path="/assistant"
95
+ />
96
+ <Route
97
+ element={
98
+ <ListLayout mobile={mobile}>
99
+ <ModelLayout mobile={mobile}>
100
+ <ModelPage mobile={mobile} />
101
+ </ModelLayout>
102
+ </ListLayout>
103
+ }
104
+ path="/model"
105
+ />
106
+ <Route
107
+ element={
108
+ <ListLayout mobile={mobile}>
109
+ <ProviderPage mobile={mobile} />
110
+ </ListLayout>
111
+ }
112
+ path="/provider"
113
+ />
114
+ <Route
115
+ element={
116
+ <ListLayout mobile={mobile}>
117
+ <McpLayout mobile={mobile}>
118
+ <McpPage mobile={mobile} />
119
+ </McpLayout>
120
+ </ListLayout>
121
+ }
122
+ path="/mcp"
123
+ />
124
+
125
+ {/* Detail routes with DetailLayout */}
126
+ <Route
127
+ element={
128
+ <DetailLayout mobile={mobile}>
129
+ <AssistantDetailPage mobile={mobile} />
130
+ </DetailLayout>
131
+ }
132
+ path="/assistant/*"
133
+ />
134
+ <Route
135
+ element={
136
+ <DetailLayout mobile={mobile}>
137
+ <ModelDetailPage mobile={mobile} />
138
+ </DetailLayout>
139
+ }
140
+ path="/model/*"
141
+ />
142
+ <Route
143
+ element={
144
+ <DetailLayout mobile={mobile}>
145
+ <ProviderDetailPage mobile={mobile} />
146
+ </DetailLayout>
147
+ }
148
+ path="/provider/*"
149
+ />
150
+ <Route
151
+ element={
152
+ <DetailLayout mobile={mobile}>
153
+ <McpDetailPage mobile={mobile} />
154
+ </DetailLayout>
155
+ }
156
+ path="/mcp/*"
157
+ />
158
+
159
+ {/* Fallback */}
160
+ <Route element={<Navigate replace to="/" />} path="*" />
161
+ </Routes>
162
+ </DiscoverLayout>
163
+ </MemoryRouter>
164
+ );
165
+ });
166
+
167
+ export default DiscoverRouter;
@@ -0,0 +1,11 @@
1
+ 'use client';
2
+
3
+ import DiscoverRouter from '../DiscoverRouter';
4
+
5
+ const DiscoverPage = () => {
6
+ return <DiscoverRouter />;
7
+ };
8
+
9
+ DiscoverPage.displayName = 'DiscoverPage';
10
+
11
+ export default DiscoverPage;