@lobehub/chat 1.39.3 → 1.40.0
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/.env.example +19 -8
- package/.eslintignore +1 -1
- package/CHANGELOG.md +33 -0
- package/changelog/v1.json +12 -0
- package/docs/.cdn.cache.json +25 -0
- package/docs/changelog/2023-09-09-plugin-system.mdx +1 -1
- package/docs/changelog/2023-09-09-plugin-system.zh-CN.mdx +1 -1
- package/docs/changelog/2024-09-20-artifacts.mdx +1 -1
- package/docs/changelog/2024-09-20-artifacts.zh-CN.mdx +1 -1
- package/docs/changelog/2024-10-27-pin-assistant.mdx +2 -2
- package/docs/changelog/2024-10-27-pin-assistant.zh-CN.mdx +2 -2
- package/docs/changelog/2024-11-06-share-text-json.mdx +2 -2
- package/docs/changelog/2024-11-06-share-text-json.zh-CN.mdx +2 -2
- package/docs/changelog/index.json +16 -16
- package/locales/ar/changelog.json +18 -0
- package/locales/ar/common.json +1 -0
- package/locales/ar/metadata.json +4 -0
- package/locales/bg-BG/changelog.json +18 -0
- package/locales/bg-BG/common.json +1 -0
- package/locales/bg-BG/metadata.json +4 -0
- package/locales/de-DE/changelog.json +18 -0
- package/locales/de-DE/common.json +1 -0
- package/locales/de-DE/metadata.json +4 -0
- package/locales/en-US/changelog.json +18 -0
- package/locales/en-US/common.json +1 -0
- package/locales/en-US/metadata.json +4 -0
- package/locales/es-ES/changelog.json +18 -0
- package/locales/es-ES/common.json +1 -0
- package/locales/es-ES/metadata.json +4 -0
- package/locales/fa-IR/changelog.json +18 -0
- package/locales/fa-IR/common.json +1 -0
- package/locales/fa-IR/metadata.json +4 -0
- package/locales/fr-FR/changelog.json +18 -0
- package/locales/fr-FR/common.json +1 -0
- package/locales/fr-FR/metadata.json +4 -0
- package/locales/it-IT/changelog.json +18 -0
- package/locales/it-IT/common.json +1 -0
- package/locales/it-IT/metadata.json +4 -0
- package/locales/ja-JP/changelog.json +18 -0
- package/locales/ja-JP/common.json +1 -0
- package/locales/ja-JP/metadata.json +4 -0
- package/locales/ko-KR/changelog.json +18 -0
- package/locales/ko-KR/common.json +1 -0
- package/locales/ko-KR/metadata.json +4 -0
- package/locales/nl-NL/changelog.json +18 -0
- package/locales/nl-NL/common.json +1 -0
- package/locales/nl-NL/metadata.json +4 -0
- package/locales/pl-PL/changelog.json +18 -0
- package/locales/pl-PL/common.json +1 -0
- package/locales/pl-PL/metadata.json +4 -0
- package/locales/pt-BR/changelog.json +18 -0
- package/locales/pt-BR/common.json +1 -0
- package/locales/pt-BR/metadata.json +4 -0
- package/locales/ru-RU/changelog.json +18 -0
- package/locales/ru-RU/common.json +1 -0
- package/locales/ru-RU/metadata.json +4 -0
- package/locales/tr-TR/changelog.json +18 -0
- package/locales/tr-TR/common.json +1 -0
- package/locales/tr-TR/metadata.json +4 -0
- package/locales/vi-VN/changelog.json +18 -0
- package/locales/vi-VN/common.json +1 -0
- package/locales/vi-VN/metadata.json +4 -0
- package/locales/zh-CN/changelog.json +18 -0
- package/locales/zh-CN/common.json +1 -0
- package/locales/zh-CN/metadata.json +4 -0
- package/locales/zh-TW/changelog.json +18 -0
- package/locales/zh-TW/common.json +1 -0
- package/locales/zh-TW/metadata.json +4 -0
- package/package.json +6 -1
- package/scripts/cdnWorkflow/index.ts +217 -0
- package/scripts/cdnWorkflow/optimized.ts +21 -0
- package/scripts/cdnWorkflow/s3/index.ts +120 -0
- package/scripts/cdnWorkflow/s3/types.ts +25 -0
- package/scripts/cdnWorkflow/s3/utils.ts +106 -0
- package/scripts/cdnWorkflow/uploader.ts +73 -0
- package/scripts/cdnWorkflow/utils.ts +93 -0
- package/src/app/(main)/(mobile)/me/(home)/__tests__/useCategory.test.tsx +25 -12
- package/src/app/(main)/(mobile)/me/(home)/features/useCategory.tsx +19 -9
- package/src/app/(main)/_layout/Desktop.tsx +4 -1
- package/src/app/(main)/_layout/Mobile.tsx +2 -1
- package/src/app/(main)/changelog/_layout/Desktop.tsx +25 -0
- package/src/app/(main)/changelog/_layout/Mobile/Header.tsx +33 -0
- package/src/app/(main)/changelog/_layout/Mobile/index.tsx +21 -0
- package/src/app/(main)/changelog/error.tsx +5 -0
- package/src/app/(main)/changelog/features/GridLayout.tsx +22 -0
- package/src/app/(main)/changelog/features/Hero.tsx +40 -0
- package/src/app/(main)/changelog/features/Post.tsx +56 -0
- package/src/app/(main)/changelog/features/PublishedTime.tsx +50 -0
- package/src/app/(main)/changelog/features/VersionTag.tsx +27 -0
- package/src/app/(main)/changelog/layout.tsx +10 -0
- package/src/app/(main)/changelog/loading.tsx +3 -0
- package/src/app/(main)/changelog/modal/page.tsx +23 -0
- package/src/app/(main)/changelog/not-found.tsx +3 -0
- package/src/app/(main)/changelog/page.tsx +73 -0
- package/src/app/(main)/chat/(workspace)/page.tsx +9 -2
- package/src/app/(main)/settings/about/features/Version.tsx +2 -2
- package/src/app/@modal/(.)changelog/modal/features/Cover.tsx +48 -0
- package/src/app/@modal/(.)changelog/modal/features/Hero.tsx +29 -0
- package/src/app/@modal/(.)changelog/modal/features/Pagination.tsx +54 -0
- package/src/app/@modal/(.)changelog/modal/features/Post.tsx +57 -0
- package/src/app/@modal/(.)changelog/modal/features/PublishedTime.tsx +50 -0
- package/src/app/@modal/(.)changelog/modal/features/ReadDetail.tsx +94 -0
- package/src/app/@modal/(.)changelog/modal/features/UpdateChangelogStatus.tsx +21 -0
- package/src/app/@modal/(.)changelog/modal/features/VersionTag.tsx +27 -0
- package/src/app/@modal/(.)changelog/modal/layout.tsx +39 -0
- package/src/app/@modal/(.)changelog/modal/loading.tsx +10 -0
- package/src/app/@modal/(.)changelog/modal/page.tsx +37 -0
- package/src/app/@modal/(.)settings/modal/layout.tsx +19 -16
- package/src/app/@modal/_layout/ModalLayout.tsx +63 -0
- package/src/app/@modal/chat/(.)settings/modal/layout.tsx +20 -17
- package/src/app/@modal/layout.tsx +5 -69
- package/src/components/mdx/Image.tsx +50 -0
- package/src/components/mdx/index.tsx +2 -0
- package/src/const/url.ts +1 -0
- package/src/features/ChangelogModal/index.tsx +22 -0
- package/src/features/User/UserPanel/useMenu.tsx +50 -46
- package/src/features/User/__tests__/useMenu.test.tsx +7 -6
- package/src/hooks/useInterceptingRoutes.ts +1 -6
- package/src/hooks/useShare.tsx +1 -0
- package/src/locales/default/changelog.ts +18 -0
- package/src/locales/default/common.ts +1 -0
- package/src/locales/default/index.ts +2 -0
- package/src/locales/default/metadata.ts +4 -0
- package/src/server/metadata.ts +5 -3
- package/src/server/routers/edge/appStatus.ts +3 -0
- package/src/server/routers/edge/index.ts +2 -0
- package/src/server/routers/lambda/agent.ts +1 -1
- package/src/server/services/changelog/index.test.ts +310 -0
- package/src/server/services/changelog/index.ts +196 -0
- package/src/server/services/discover/index.test.ts +0 -1
- package/src/server/sitemap.ts +4 -1
- package/src/services/__tests__/chat.test.ts +1 -1
- package/src/services/__tests__/global.test.ts +5 -2
- package/src/services/_auth.ts +1 -1
- package/src/services/agent.ts +25 -21
- package/src/services/chat.ts +2 -2
- package/src/services/file/ClientS3/index.ts +6 -6
- package/src/services/file/client.ts +14 -15
- package/src/services/file/server.ts +20 -25
- package/src/services/global.ts +2 -2
- package/src/services/import/client.ts +6 -5
- package/src/services/import/server.ts +6 -5
- package/src/services/import/type.ts +7 -0
- package/src/services/knowledgeBase.ts +19 -19
- package/src/services/message/_deprecated.ts +5 -0
- package/src/services/message/client.ts +52 -48
- package/src/services/message/server.ts +50 -53
- package/src/services/message/type.ts +2 -2
- package/src/services/plugin/client.ts +16 -22
- package/src/services/plugin/server.ts +15 -19
- package/src/services/rag.ts +18 -18
- package/src/services/ragEval.ts +29 -26
- package/src/services/session/_deprecated.ts +2 -2
- package/src/services/session/client.ts +55 -81
- package/src/services/session/server.ts +50 -74
- package/src/services/session/type.ts +4 -6
- package/src/services/share.ts +4 -4
- package/src/services/textToImage.ts +5 -2
- package/src/services/thread/client.ts +9 -15
- package/src/services/thread/server.ts +10 -15
- package/src/services/topic/client.ts +25 -25
- package/src/services/topic/server.ts +25 -42
- package/src/services/trace.ts +4 -4
- package/src/services/user/client.ts +13 -17
- package/src/services/user/server.ts +9 -13
- package/src/services/user/type.ts +1 -1
- package/src/store/chat/slices/message/reducer.ts +3 -2
- package/src/store/global/action.ts +27 -22
- package/src/store/global/initialState.ts +1 -0
- package/src/types/changelog.ts +6 -0
- package/src/types/message/index.ts +10 -8
- package/src/app/@modal/features/InterceptingContext.tsx +0 -9
@@ -1,10 +1,15 @@
|
|
1
1
|
import { act, renderHook } from '@testing-library/react';
|
2
2
|
import { describe, expect, it, vi } from 'vitest';
|
3
3
|
|
4
|
+
import { ServerConfigStoreProvider } from '@/store/serverConfig';
|
4
5
|
import { useUserStore } from '@/store/user';
|
5
6
|
|
6
7
|
import { useCategory } from '../features/useCategory';
|
7
8
|
|
9
|
+
const wrapper: React.JSXElementConstructor<{ children: React.ReactNode }> = ({ children }) => (
|
10
|
+
<ServerConfigStoreProvider>{children}</ServerConfigStoreProvider>
|
11
|
+
);
|
12
|
+
|
8
13
|
// Mock dependencies
|
9
14
|
vi.mock('next/navigation', () => ({
|
10
15
|
useRouter: vi.fn(() => ({
|
@@ -24,7 +29,7 @@ vi.mock('../../settings/features/useCategory', () => ({
|
|
24
29
|
|
25
30
|
// 定义一个变量来存储 enableAuth 的值
|
26
31
|
let enableAuth = true;
|
27
|
-
let enableClerk =
|
32
|
+
let enableClerk = true;
|
28
33
|
// 模拟 @/const/auth 模块
|
29
34
|
vi.mock('@/const/auth', () => ({
|
30
35
|
get enableAuth() {
|
@@ -37,7 +42,7 @@ vi.mock('@/const/auth', () => ({
|
|
37
42
|
|
38
43
|
afterEach(() => {
|
39
44
|
enableAuth = true;
|
40
|
-
enableClerk =
|
45
|
+
enableClerk = true;
|
41
46
|
});
|
42
47
|
|
43
48
|
// 目前对 enableAuth 的判定是在 useUserStore 中,所以需要 mock useUserStore
|
@@ -47,8 +52,10 @@ describe('useCategory', () => {
|
|
47
52
|
act(() => {
|
48
53
|
useUserStore.setState({ isSignedIn: true, enableAuth: () => true });
|
49
54
|
});
|
55
|
+
enableAuth = true;
|
56
|
+
enableClerk = false;
|
50
57
|
|
51
|
-
const { result } = renderHook(() => useCategory());
|
58
|
+
const { result } = renderHook(() => useCategory(), { wrapper });
|
52
59
|
|
53
60
|
act(() => {
|
54
61
|
const items = result.current;
|
@@ -57,7 +64,7 @@ describe('useCategory', () => {
|
|
57
64
|
expect(items.some((item) => item.key === 'data')).toBe(true);
|
58
65
|
expect(items.some((item) => item.key === 'docs')).toBe(true);
|
59
66
|
expect(items.some((item) => item.key === 'feedback')).toBe(true);
|
60
|
-
expect(items.some((item) => item.key === '
|
67
|
+
expect(items.some((item) => item.key === 'changelog')).toBe(true);
|
61
68
|
});
|
62
69
|
});
|
63
70
|
|
@@ -65,9 +72,10 @@ describe('useCategory', () => {
|
|
65
72
|
act(() => {
|
66
73
|
useUserStore.setState({ isSignedIn: true });
|
67
74
|
});
|
75
|
+
enableAuth = true;
|
68
76
|
enableClerk = true;
|
69
77
|
|
70
|
-
const { result } = renderHook(() => useCategory());
|
78
|
+
const { result } = renderHook(() => useCategory(), { wrapper });
|
71
79
|
|
72
80
|
act(() => {
|
73
81
|
const items = result.current;
|
@@ -76,16 +84,21 @@ describe('useCategory', () => {
|
|
76
84
|
expect(items.some((item) => item.key === 'data')).toBe(true);
|
77
85
|
expect(items.some((item) => item.key === 'docs')).toBe(true);
|
78
86
|
expect(items.some((item) => item.key === 'feedback')).toBe(true);
|
79
|
-
expect(items.some((item) => item.key === '
|
87
|
+
expect(items.some((item) => item.key === 'changelog')).toBe(true);
|
80
88
|
});
|
81
89
|
});
|
82
90
|
|
83
91
|
it('should return correct items when the user is logged in with NextAuth', () => {
|
84
92
|
act(() => {
|
85
|
-
useUserStore.setState({
|
93
|
+
useUserStore.setState({
|
94
|
+
isSignedIn: true,
|
95
|
+
enableAuth: () => true,
|
96
|
+
enabledNextAuth: true,
|
97
|
+
});
|
86
98
|
});
|
99
|
+
enableClerk = false;
|
87
100
|
|
88
|
-
const { result } = renderHook(() => useCategory());
|
101
|
+
const { result } = renderHook(() => useCategory(), { wrapper });
|
89
102
|
|
90
103
|
act(() => {
|
91
104
|
const items = result.current;
|
@@ -95,7 +108,7 @@ describe('useCategory', () => {
|
|
95
108
|
expect(items.some((item) => item.key === 'data')).toBe(true);
|
96
109
|
expect(items.some((item) => item.key === 'docs')).toBe(true);
|
97
110
|
expect(items.some((item) => item.key === 'feedback')).toBe(true);
|
98
|
-
expect(items.some((item) => item.key === '
|
111
|
+
expect(items.some((item) => item.key === 'changelog')).toBe(true);
|
99
112
|
expect(items.some((item) => item.key === 'nextauthSignout')).toBe(true);
|
100
113
|
});
|
101
114
|
});
|
@@ -105,7 +118,7 @@ describe('useCategory', () => {
|
|
105
118
|
useUserStore.setState({ isSignedIn: false, enableAuth: () => true });
|
106
119
|
});
|
107
120
|
|
108
|
-
const { result } = renderHook(() => useCategory());
|
121
|
+
const { result } = renderHook(() => useCategory(), { wrapper });
|
109
122
|
|
110
123
|
act(() => {
|
111
124
|
const items = result.current;
|
@@ -114,7 +127,7 @@ describe('useCategory', () => {
|
|
114
127
|
expect(items.some((item) => item.key === 'data')).toBe(false);
|
115
128
|
expect(items.some((item) => item.key === 'docs')).toBe(true);
|
116
129
|
expect(items.some((item) => item.key === 'feedback')).toBe(true);
|
117
|
-
expect(items.some((item) => item.key === '
|
130
|
+
expect(items.some((item) => item.key === 'changelog')).toBe(true);
|
118
131
|
expect(items.some((item) => item.key === 'nextauthSignout')).toBe(false);
|
119
132
|
});
|
120
133
|
});
|
@@ -125,7 +138,7 @@ describe('useCategory', () => {
|
|
125
138
|
});
|
126
139
|
enableClerk = false;
|
127
140
|
|
128
|
-
const { result } = renderHook(() => useCategory());
|
141
|
+
const { result } = renderHook(() => useCategory(), { wrapper });
|
129
142
|
|
130
143
|
act(() => {
|
131
144
|
const items = result.current;
|
@@ -1,10 +1,11 @@
|
|
1
|
-
import { DiscordIcon } from '@lobehub/ui';
|
2
1
|
import {
|
3
2
|
Book,
|
4
3
|
CircleUserRound,
|
4
|
+
Cloudy,
|
5
5
|
Database,
|
6
6
|
Download,
|
7
7
|
Feather,
|
8
|
+
FileClockIcon,
|
8
9
|
LogOut,
|
9
10
|
Settings2,
|
10
11
|
} from 'lucide-react';
|
@@ -12,11 +13,13 @@ import { useRouter } from 'next/navigation';
|
|
12
13
|
import { useTranslation } from 'react-i18next';
|
13
14
|
|
14
15
|
import { CellProps } from '@/components/Cell';
|
15
|
-
import {
|
16
|
+
import { LOBE_CHAT_CLOUD } from '@/const/branding';
|
17
|
+
import { DOCUMENTS, FEEDBACK, OFFICIAL_URL, UTM_SOURCE } from '@/const/url';
|
16
18
|
import { isServerMode } from '@/const/version';
|
17
19
|
import { usePWAInstall } from '@/hooks/usePWAInstall';
|
20
|
+
import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
|
18
21
|
import { useUserStore } from '@/store/user';
|
19
|
-
import { authSelectors } from '@/store/user/
|
22
|
+
import { authSelectors } from '@/store/user/selectors';
|
20
23
|
|
21
24
|
import { useCategory as useSettingsCategory } from '../../settings/features/useCategory';
|
22
25
|
|
@@ -24,6 +27,7 @@ export const useCategory = () => {
|
|
24
27
|
const router = useRouter();
|
25
28
|
const { canInstall, install } = usePWAInstall();
|
26
29
|
const { t } = useTranslation(['common', 'setting', 'auth']);
|
30
|
+
const { showCloudPromotion, hideDocs } = useServerConfigStore(featureFlagsSelectors);
|
27
31
|
const [isLogin, isLoginWithAuth, isLoginWithClerk, enableAuth, signOut, isLoginWithNextAuth] =
|
28
32
|
useUserStore((s) => [
|
29
33
|
authSelectors.isLogin(s),
|
@@ -91,6 +95,12 @@ export const useCategory = () => {
|
|
91
95
|
];
|
92
96
|
|
93
97
|
const helps: CellProps[] = [
|
98
|
+
showCloudPromotion && {
|
99
|
+
icon: Cloudy,
|
100
|
+
key: 'cloud',
|
101
|
+
label: t('userPanel.cloud', { name: LOBE_CHAT_CLOUD }),
|
102
|
+
onClick: () => window.open(`${OFFICIAL_URL}?utm_source=${UTM_SOURCE}`, '__blank'),
|
103
|
+
},
|
94
104
|
{
|
95
105
|
icon: Book,
|
96
106
|
key: 'docs',
|
@@ -104,12 +114,12 @@ export const useCategory = () => {
|
|
104
114
|
onClick: () => window.open(FEEDBACK, '__blank'),
|
105
115
|
},
|
106
116
|
{
|
107
|
-
icon:
|
108
|
-
key: '
|
109
|
-
label: '
|
110
|
-
onClick: () =>
|
117
|
+
icon: FileClockIcon,
|
118
|
+
key: 'changelog',
|
119
|
+
label: t('changelog'),
|
120
|
+
onClick: () => router.push('/changelog'),
|
111
121
|
},
|
112
|
-
];
|
122
|
+
].filter(Boolean) as CellProps[];
|
113
123
|
|
114
124
|
const nextAuthSignOut: CellProps[] = [
|
115
125
|
{
|
@@ -131,7 +141,7 @@ export const useCategory = () => {
|
|
131
141
|
/* ↑ cloud slot ↑ */
|
132
142
|
...(canInstall ? pwa : []),
|
133
143
|
...(isLogin && !isServerMode ? data : []),
|
134
|
-
...helps,
|
144
|
+
...(!hideDocs ? helps : []),
|
135
145
|
...(enableAuth && isLoginWithNextAuth ? nextAuthSignOut : []),
|
136
146
|
].filter(Boolean) as CellProps[];
|
137
147
|
|
@@ -1,15 +1,18 @@
|
|
1
1
|
'use client';
|
2
2
|
|
3
3
|
import { useTheme } from 'antd-style';
|
4
|
+
import dynamic from 'next/dynamic';
|
4
5
|
import { memo } from 'react';
|
5
6
|
import { Flexbox } from 'react-layout-kit';
|
6
7
|
|
7
|
-
import
|
8
|
+
import { BANNER_HEIGHT } from '@/features/AlertBanner/CloudBanner';
|
8
9
|
import { usePlatform } from '@/hooks/usePlatform';
|
9
10
|
import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
|
10
11
|
|
11
12
|
import { LayoutProps } from './type';
|
12
13
|
|
14
|
+
const CloudBanner = dynamic(() => import('@/features/AlertBanner/CloudBanner'));
|
15
|
+
|
13
16
|
const Layout = memo<LayoutProps>(({ children, nav }) => {
|
14
17
|
const { isPWA } = usePlatform();
|
15
18
|
const theme = useTheme();
|
@@ -1,15 +1,16 @@
|
|
1
1
|
'use client';
|
2
2
|
|
3
|
+
import dynamic from 'next/dynamic';
|
3
4
|
import { usePathname } from 'next/navigation';
|
4
5
|
import qs from 'query-string';
|
5
6
|
import { memo } from 'react';
|
6
7
|
|
7
|
-
import CloudBanner from '@/features/AlertBanner/CloudBanner';
|
8
8
|
import { useQuery } from '@/hooks/useQuery';
|
9
9
|
import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
|
10
10
|
|
11
11
|
import { LayoutProps } from './type';
|
12
12
|
|
13
|
+
const CloudBanner = dynamic(() => import('@/features/AlertBanner/CloudBanner'));
|
13
14
|
const MOBILE_NAV_ROUTES = new Set([
|
14
15
|
'/chat',
|
15
16
|
'/discover',
|
@@ -0,0 +1,25 @@
|
|
1
|
+
import { ReactNode } from 'react';
|
2
|
+
import { Flexbox } from 'react-layout-kit';
|
3
|
+
|
4
|
+
import Hero from '../features/Hero';
|
5
|
+
|
6
|
+
type Props = { children: ReactNode };
|
7
|
+
|
8
|
+
const Layout = ({ children }: Props) => {
|
9
|
+
return (
|
10
|
+
<Flexbox
|
11
|
+
align={'center'}
|
12
|
+
style={{ height: '100%', overflowX: 'hidden', overflowY: 'auto' }}
|
13
|
+
width={'100%'}
|
14
|
+
>
|
15
|
+
<Flexbox gap={24} paddingBlock={24} paddingInline={16} style={{ width: 'min(100%, 1024px)' }}>
|
16
|
+
<Hero />
|
17
|
+
{children}
|
18
|
+
</Flexbox>
|
19
|
+
</Flexbox>
|
20
|
+
);
|
21
|
+
};
|
22
|
+
|
23
|
+
Layout.displayName = 'DesktopChangelogLayout';
|
24
|
+
|
25
|
+
export default Layout;
|
@@ -0,0 +1,33 @@
|
|
1
|
+
'use client';
|
2
|
+
|
3
|
+
import { MobileNavBar, MobileNavBarTitle } from '@lobehub/ui';
|
4
|
+
import { useRouter } from 'next/navigation';
|
5
|
+
import { memo } from 'react';
|
6
|
+
import { useTranslation } from 'react-i18next';
|
7
|
+
import { Flexbox } from 'react-layout-kit';
|
8
|
+
|
9
|
+
import { mobileHeaderSticky } from '@/styles/mobileHeader';
|
10
|
+
|
11
|
+
const Header = memo(() => {
|
12
|
+
const { t } = useTranslation('changelog');
|
13
|
+
|
14
|
+
const router = useRouter();
|
15
|
+
return (
|
16
|
+
<MobileNavBar
|
17
|
+
center={
|
18
|
+
<MobileNavBarTitle
|
19
|
+
title={
|
20
|
+
<Flexbox align={'center'} gap={4} horizontal>
|
21
|
+
{t('title')}
|
22
|
+
</Flexbox>
|
23
|
+
}
|
24
|
+
/>
|
25
|
+
}
|
26
|
+
onBackClick={() => router.back()}
|
27
|
+
showBackButton
|
28
|
+
style={mobileHeaderSticky}
|
29
|
+
/>
|
30
|
+
);
|
31
|
+
});
|
32
|
+
|
33
|
+
export default Header;
|
@@ -0,0 +1,21 @@
|
|
1
|
+
import { ReactNode } from 'react';
|
2
|
+
|
3
|
+
import MobileContentLayout from '@/components/server/MobileNavLayout';
|
4
|
+
|
5
|
+
import Hero from '../../features/Hero';
|
6
|
+
import Header from './Header';
|
7
|
+
|
8
|
+
type Props = { children: ReactNode };
|
9
|
+
|
10
|
+
const Layout = ({ children }: Props) => {
|
11
|
+
return (
|
12
|
+
<MobileContentLayout header={<Header />} padding={16}>
|
13
|
+
<Hero />
|
14
|
+
{children}
|
15
|
+
</MobileContentLayout>
|
16
|
+
);
|
17
|
+
};
|
18
|
+
|
19
|
+
Layout.displayName = 'MobileChangelogLayout';
|
20
|
+
|
21
|
+
export default Layout;
|
@@ -0,0 +1,22 @@
|
|
1
|
+
import { FC, PropsWithChildren, ReactNode } from 'react';
|
2
|
+
import { Flexbox } from 'react-layout-kit';
|
3
|
+
|
4
|
+
const GridLayout: FC<PropsWithChildren<{ date?: ReactNode; mobile?: boolean }>> = ({
|
5
|
+
mobile,
|
6
|
+
children,
|
7
|
+
date,
|
8
|
+
}) => {
|
9
|
+
return (
|
10
|
+
<Flexbox horizontal={!mobile} wrap={'wrap'}>
|
11
|
+
<Flexbox flex={1} style={{ minWidth: 150, position: 'relative' }}>
|
12
|
+
{date}
|
13
|
+
</Flexbox>
|
14
|
+
<Flexbox flex={3} gap={16} style={{ minWidth: 'min(600px, 100%)', position: 'relative' }}>
|
15
|
+
{children}
|
16
|
+
</Flexbox>
|
17
|
+
{!mobile && <Flexbox flex={1} style={{ minWidth: 150, position: 'relative' }} />}
|
18
|
+
</Flexbox>
|
19
|
+
);
|
20
|
+
};
|
21
|
+
|
22
|
+
export default GridLayout;
|
@@ -0,0 +1,40 @@
|
|
1
|
+
'use client';
|
2
|
+
|
3
|
+
import { useResponsive, useTheme } from 'antd-style';
|
4
|
+
import Link from 'next/link';
|
5
|
+
import { memo } from 'react';
|
6
|
+
import { useTranslation } from 'react-i18next';
|
7
|
+
import { Flexbox } from 'react-layout-kit';
|
8
|
+
import urlJoin from 'url-join';
|
9
|
+
|
10
|
+
import { BRANDING_NAME } from '@/const/branding';
|
11
|
+
import { OFFICIAL_SITE, X } from '@/const/url';
|
12
|
+
|
13
|
+
import GridLayout from './GridLayout';
|
14
|
+
|
15
|
+
const Hero = memo(() => {
|
16
|
+
const { t } = useTranslation('changelog');
|
17
|
+
const theme = useTheme();
|
18
|
+
const { mobile } = useResponsive();
|
19
|
+
return (
|
20
|
+
<GridLayout>
|
21
|
+
<Flexbox gap={16} style={{ paddingTop: 32, zIndex: 1 }}>
|
22
|
+
<h1 style={{ fontSize: mobile ? 28 : 40, fontWeight: 'bold', margin: 0 }}>{t('title')}</h1>
|
23
|
+
<div style={{ fontSize: mobile ? 18 : 24, opacity: 0.6 }}>
|
24
|
+
{t('description', { appName: BRANDING_NAME })}
|
25
|
+
</div>
|
26
|
+
<Flexbox gap={8} horizontal style={{ fontSize: 16 }}>
|
27
|
+
<Link href={urlJoin(OFFICIAL_SITE, '/changelog/versions')} target={'_blank'}>
|
28
|
+
{t('actions.versions')}
|
29
|
+
</Link>
|
30
|
+
<div style={{ color: theme.colorInfo }}>·</div>
|
31
|
+
<Link href={X} target={'_blank'}>
|
32
|
+
{t('actions.followOnX')}
|
33
|
+
</Link>
|
34
|
+
</Flexbox>
|
35
|
+
</Flexbox>
|
36
|
+
</GridLayout>
|
37
|
+
);
|
38
|
+
});
|
39
|
+
|
40
|
+
export default Hero;
|
@@ -0,0 +1,56 @@
|
|
1
|
+
import { Typography } from '@lobehub/ui';
|
2
|
+
import { Divider } from 'antd';
|
3
|
+
import Link from 'next/link';
|
4
|
+
import urlJoin from 'url-join';
|
5
|
+
|
6
|
+
import { CustomMDX } from '@/components/mdx';
|
7
|
+
import Image from '@/components/mdx/Image';
|
8
|
+
import { OFFICIAL_SITE } from '@/const/url';
|
9
|
+
import { Locales } from '@/locales/resources';
|
10
|
+
import { ChangelogService } from '@/server/services/changelog';
|
11
|
+
import { ChangelogIndexItem } from '@/types/changelog';
|
12
|
+
|
13
|
+
import GridLayout from './GridLayout';
|
14
|
+
import PublishedTime from './PublishedTime';
|
15
|
+
import VersionTag from './VersionTag';
|
16
|
+
|
17
|
+
const Post = async ({
|
18
|
+
id,
|
19
|
+
mobile,
|
20
|
+
versionRange,
|
21
|
+
locale,
|
22
|
+
}: ChangelogIndexItem & { branch?: string; locale: Locales; mobile?: boolean }) => {
|
23
|
+
const changelogService = new ChangelogService();
|
24
|
+
const data = await changelogService.getPostById(id, { locale });
|
25
|
+
|
26
|
+
if (!data || !data.title) return null;
|
27
|
+
|
28
|
+
return (
|
29
|
+
<>
|
30
|
+
<Divider />
|
31
|
+
<GridLayout
|
32
|
+
date={
|
33
|
+
<PublishedTime
|
34
|
+
date={data.date.toISOString()}
|
35
|
+
style={{ lineHeight: mobile ? undefined : '60px' }}
|
36
|
+
template={'MMMM D, YYYY'}
|
37
|
+
/>
|
38
|
+
}
|
39
|
+
mobile={mobile}
|
40
|
+
>
|
41
|
+
<Typography headerMultiple={mobile ? 0.2 : 0.3}>
|
42
|
+
<Link href={urlJoin(OFFICIAL_SITE, '/changelog', id)} style={{ color: 'inherit' }}>
|
43
|
+
<h1 id={id}>{data.rawTitle || data.title}</h1>
|
44
|
+
</Link>
|
45
|
+
<Image alt={data.title} src={data.image} />
|
46
|
+
<CustomMDX source={data.content} />
|
47
|
+
<Link href={urlJoin(OFFICIAL_SITE, '/changelog', id)} style={{ color: 'inherit' }}>
|
48
|
+
<VersionTag range={versionRange} />
|
49
|
+
</Link>
|
50
|
+
</Typography>
|
51
|
+
</GridLayout>
|
52
|
+
</>
|
53
|
+
);
|
54
|
+
};
|
55
|
+
|
56
|
+
export default Post;
|
@@ -0,0 +1,50 @@
|
|
1
|
+
'use client';
|
2
|
+
|
3
|
+
import { createStyles } from 'antd-style';
|
4
|
+
import dayjs from 'dayjs';
|
5
|
+
import 'dayjs/locale/zh.js';
|
6
|
+
import { CSSProperties, FC } from 'react';
|
7
|
+
import { useTranslation } from 'react-i18next';
|
8
|
+
|
9
|
+
const useStyles = createStyles(({ css, token }) => {
|
10
|
+
return {
|
11
|
+
time: css`
|
12
|
+
margin-block: calc(var(--lobe-markdown-margin-multiple) * 1em);
|
13
|
+
|
14
|
+
font-size: 14px;
|
15
|
+
line-height: var(--lobe-markdown-line-height);
|
16
|
+
color: ${token.colorTextSecondary};
|
17
|
+
letter-spacing: 0.02em;
|
18
|
+
`,
|
19
|
+
};
|
20
|
+
});
|
21
|
+
|
22
|
+
interface PrivacyUpdatedProps {
|
23
|
+
className?: string;
|
24
|
+
date: string;
|
25
|
+
style?: CSSProperties;
|
26
|
+
template?: string;
|
27
|
+
}
|
28
|
+
const PublishedTime: FC<PrivacyUpdatedProps> = ({
|
29
|
+
date = new Date().toISOString(),
|
30
|
+
style,
|
31
|
+
className,
|
32
|
+
template = 'dddd, MMMM D YYYY',
|
33
|
+
}) => {
|
34
|
+
const { i18n } = useTranslation();
|
35
|
+
const { styles, cx } = useStyles();
|
36
|
+
const time = dayjs(date).locale(i18n.language).format(template);
|
37
|
+
|
38
|
+
return (
|
39
|
+
<time
|
40
|
+
aria-label={'published-date'}
|
41
|
+
className={cx(styles.time, className)}
|
42
|
+
dateTime={time}
|
43
|
+
style={style}
|
44
|
+
>
|
45
|
+
{time}
|
46
|
+
</time>
|
47
|
+
);
|
48
|
+
};
|
49
|
+
|
50
|
+
export default PublishedTime;
|
@@ -0,0 +1,27 @@
|
|
1
|
+
'use client';
|
2
|
+
|
3
|
+
import { Tag } from '@lobehub/ui';
|
4
|
+
import { createStyles } from 'antd-style';
|
5
|
+
import { memo } from 'react';
|
6
|
+
|
7
|
+
const useStyles = createStyles(({ token, css }) => {
|
8
|
+
return {
|
9
|
+
tag: css`
|
10
|
+
margin: 0;
|
11
|
+
padding-block: 4px;
|
12
|
+
padding-inline: 12px;
|
13
|
+
|
14
|
+
color: ${token.colorTextSecondary};
|
15
|
+
|
16
|
+
border-radius: 16px;
|
17
|
+
`,
|
18
|
+
};
|
19
|
+
});
|
20
|
+
|
21
|
+
const VersionTag = memo<{ range: string[] }>(({ range }) => {
|
22
|
+
const { styles } = useStyles();
|
23
|
+
|
24
|
+
return <Tag className={styles.tag}>{range.map((v) => 'v' + v).join(' ~ ')}</Tag>;
|
25
|
+
});
|
26
|
+
|
27
|
+
export default VersionTag;
|
@@ -0,0 +1,10 @@
|
|
1
|
+
import ServerLayout from '@/components/server/ServerLayout';
|
2
|
+
|
3
|
+
import Desktop from './_layout/Desktop';
|
4
|
+
import Mobile from './_layout/Mobile';
|
5
|
+
|
6
|
+
const MainLayout = ServerLayout({ Desktop, Mobile });
|
7
|
+
|
8
|
+
MainLayout.displayName = 'ChangelogLayout';
|
9
|
+
|
10
|
+
export default MainLayout;
|
@@ -0,0 +1,23 @@
|
|
1
|
+
'use client';
|
2
|
+
|
3
|
+
import { useLayoutEffect } from 'react';
|
4
|
+
|
5
|
+
import { useQueryRoute } from '@/hooks/useQueryRoute';
|
6
|
+
|
7
|
+
/**
|
8
|
+
* @description: Changelog Modal (intercepting routes fallback when hard refresh)
|
9
|
+
* @example: /changelog/modal => /changelog
|
10
|
+
* @refs: https://github.com/lobehub/lobe-chat/discussions/2295#discussioncomment-9290942
|
11
|
+
*/
|
12
|
+
|
13
|
+
const ChangelogModalFallback = () => {
|
14
|
+
const router = useQueryRoute();
|
15
|
+
|
16
|
+
useLayoutEffect(() => {
|
17
|
+
router.replace('/changelog');
|
18
|
+
}, []);
|
19
|
+
|
20
|
+
return null;
|
21
|
+
};
|
22
|
+
|
23
|
+
export default ChangelogModalFallback;
|
@@ -0,0 +1,73 @@
|
|
1
|
+
import { Divider, Skeleton } from 'antd';
|
2
|
+
import { notFound } from 'next/navigation';
|
3
|
+
import { Fragment, Suspense } from 'react';
|
4
|
+
import { Flexbox } from 'react-layout-kit';
|
5
|
+
import urlJoin from 'url-join';
|
6
|
+
|
7
|
+
import Pagination from '@/app/@modal/(.)changelog/modal/features/Pagination';
|
8
|
+
import StructuredData from '@/components/StructuredData';
|
9
|
+
import { serverFeatureFlags } from '@/config/featureFlags';
|
10
|
+
import { BRANDING_NAME } from '@/const/branding';
|
11
|
+
import { OFFICIAL_SITE } from '@/const/url';
|
12
|
+
import { ldModule } from '@/server/ld';
|
13
|
+
import { metadataModule } from '@/server/metadata';
|
14
|
+
import { ChangelogService } from '@/server/services/changelog';
|
15
|
+
import { translation } from '@/server/translation';
|
16
|
+
import { isMobileDevice } from '@/utils/server/responsive';
|
17
|
+
|
18
|
+
import GridLayout from './features/GridLayout';
|
19
|
+
import Post from './features/Post';
|
20
|
+
|
21
|
+
export const generateMetadata = async () => {
|
22
|
+
const { t } = await translation('metadata');
|
23
|
+
return metadataModule.generate({
|
24
|
+
canonical: urlJoin(OFFICIAL_SITE, 'changelog'),
|
25
|
+
description: t('changelog.description', { appName: BRANDING_NAME }),
|
26
|
+
title: t('changelog.title'),
|
27
|
+
url: '/changelog',
|
28
|
+
});
|
29
|
+
};
|
30
|
+
|
31
|
+
const Page = async () => {
|
32
|
+
const hideDocs = serverFeatureFlags().hideDocs;
|
33
|
+
|
34
|
+
if (hideDocs) return notFound();
|
35
|
+
|
36
|
+
const mobile = await isMobileDevice();
|
37
|
+
const { t, locale } = await translation('metadata');
|
38
|
+
const changelogService = new ChangelogService();
|
39
|
+
const data = await changelogService.getChangelogIndex();
|
40
|
+
|
41
|
+
const ld = ldModule.generate({
|
42
|
+
description: t('changelog.description', { appName: BRANDING_NAME }),
|
43
|
+
title: t('changelog.title', { appName: BRANDING_NAME }),
|
44
|
+
url: '/changelog',
|
45
|
+
});
|
46
|
+
|
47
|
+
return (
|
48
|
+
<>
|
49
|
+
<StructuredData ld={ld} />
|
50
|
+
<Flexbox gap={mobile ? 16 : 48}>
|
51
|
+
{data.map((item) => (
|
52
|
+
<Fragment key={item.id}>
|
53
|
+
<Suspense
|
54
|
+
fallback={
|
55
|
+
<GridLayout>
|
56
|
+
<Divider />
|
57
|
+
<Skeleton active paragraph={{ rows: 5 }} />
|
58
|
+
</GridLayout>
|
59
|
+
}
|
60
|
+
>
|
61
|
+
<Post locale={locale} mobile={mobile} {...item} />
|
62
|
+
</Suspense>
|
63
|
+
</Fragment>
|
64
|
+
))}
|
65
|
+
</Flexbox>
|
66
|
+
<GridLayout>
|
67
|
+
<Pagination />
|
68
|
+
</GridLayout>
|
69
|
+
</>
|
70
|
+
);
|
71
|
+
};
|
72
|
+
|
73
|
+
export default Page;
|