@lobehub/chat 1.39.2 → 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 +58 -0
- package/changelog/v1.json +21 -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)/(mobile)/me/(home)/loading.tsx +1 -1
- package/src/app/(main)/(mobile)/me/data/loading.tsx +1 -1
- package/src/app/(main)/(mobile)/me/profile/loading.tsx +1 -1
- package/src/app/(main)/(mobile)/me/settings/loading.tsx +1 -1
- 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)/@portal/default.tsx +1 -1
- package/src/app/(main)/chat/(workspace)/@portal/loading.tsx +1 -1
- package/src/app/(main)/chat/(workspace)/page.tsx +9 -2
- package/src/app/(main)/chat/@session/default.tsx +3 -2
- package/src/app/(main)/chat/loading.tsx +1 -1
- package/src/app/(main)/chat/settings/loading.tsx +1 -1
- package/src/app/(main)/discover/loading.tsx +1 -1
- package/src/app/(main)/files/loading.tsx +2 -22
- package/src/app/(main)/profile/loading.tsx +1 -1
- package/src/app/(main)/repos/[id]/evals/dataset/page.tsx +1 -1
- package/src/app/(main)/repos/[id]/evals/evaluation/page.tsx +1 -1
- package/src/app/(main)/settings/@category/default.tsx +6 -2
- package/src/app/(main)/settings/_layout/Desktop/SideBar.tsx +1 -1
- package/src/app/(main)/settings/about/features/Version.tsx +2 -2
- package/src/app/(main)/settings/loading.tsx +2 -8
- 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/app/loading/Client/Content.tsx +1 -1
- package/src/app/loading/Server/Content.tsx +1 -1
- package/src/components/Loading/BrandTextLoading/LobeChatText/SVG.tsx +44 -0
- package/src/components/Loading/BrandTextLoading/LobeChatText/index.tsx +6 -0
- package/src/components/Loading/BrandTextLoading/LobeChatText/style.css +32 -0
- package/src/components/Loading/BrandTextLoading/index.tsx +11 -0
- package/src/components/{SkeletonLoading → Loading/SkeletonLoading}/index.tsx +1 -1
- 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/FileViewer/Renderer/TXT/index.tsx +1 -1
- package/src/features/Portal/FilePreview/Body/index.tsx +1 -1
- package/src/features/Portal/Home/Body/Files/FileList/index.tsx +1 -1
- package/src/features/Setting/Footer.tsx +3 -1
- package/src/features/Setting/SettingContainer.tsx +1 -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
- /package/src/components/{CircleLoading → Loading/CircleLoading}/index.tsx +0 -0
- /package/src/components/{FullscreenLoading → Loading/FullscreenLoading}/index.tsx +0 -0
@@ -7,6 +7,7 @@ import {
|
|
7
7
|
Cloudy,
|
8
8
|
Download,
|
9
9
|
Feather,
|
10
|
+
FileClockIcon,
|
10
11
|
HardDriveDownload,
|
11
12
|
HardDriveUpload,
|
12
13
|
LifeBuoy,
|
@@ -174,15 +175,38 @@ export const useMenu = () => {
|
|
174
175
|
},
|
175
176
|
].filter(Boolean) as ItemType[]);
|
176
177
|
|
177
|
-
const helps: MenuProps['items'] =
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
178
|
+
const helps: MenuProps['items'] = [
|
179
|
+
showCloudPromotion && {
|
180
|
+
icon: <Icon icon={Cloudy} />,
|
181
|
+
key: 'cloud',
|
182
|
+
label: (
|
183
|
+
<Link href={`${OFFICIAL_URL}?utm_source=${UTM_SOURCE}`} target={'_blank'}>
|
184
|
+
{t('userPanel.cloud', { name: LOBE_CHAT_CLOUD })}
|
185
|
+
</Link>
|
186
|
+
),
|
187
|
+
},
|
188
|
+
{
|
189
|
+
icon: <Icon icon={FileClockIcon} />,
|
190
|
+
key: 'changelog',
|
191
|
+
label: <Link href={'/changelog'}>{t('changelog')}</Link>,
|
192
|
+
},
|
193
|
+
{
|
194
|
+
children: [
|
195
|
+
{
|
196
|
+
icon: <Icon icon={Book} />,
|
197
|
+
key: 'docs',
|
183
198
|
label: (
|
184
|
-
<Link href={
|
185
|
-
{t('userPanel.
|
199
|
+
<Link href={DOCUMENTS_REFER_URL} target={'_blank'}>
|
200
|
+
{t('userPanel.docs')}
|
201
|
+
</Link>
|
202
|
+
),
|
203
|
+
},
|
204
|
+
{
|
205
|
+
icon: <Icon icon={Feather} />,
|
206
|
+
key: 'feedback',
|
207
|
+
label: (
|
208
|
+
<Link href={GITHUB_ISSUES} target={'_blank'}>
|
209
|
+
{t('userPanel.feedback')}
|
186
210
|
</Link>
|
187
211
|
),
|
188
212
|
},
|
@@ -196,56 +220,36 @@ export const useMenu = () => {
|
|
196
220
|
),
|
197
221
|
},
|
198
222
|
{
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
</Link>
|
207
|
-
),
|
208
|
-
},
|
209
|
-
{
|
210
|
-
icon: <Icon icon={Feather} />,
|
211
|
-
key: 'feedback',
|
212
|
-
label: (
|
213
|
-
<Link href={GITHUB_ISSUES} target={'_blank'}>
|
214
|
-
{t('userPanel.feedback')}
|
215
|
-
</Link>
|
216
|
-
),
|
217
|
-
},
|
218
|
-
{
|
219
|
-
icon: <Icon icon={Mail} />,
|
220
|
-
key: 'email',
|
221
|
-
label: (
|
222
|
-
<Link href={mailTo(EMAIL_SUPPORT)} target={'_blank'}>
|
223
|
-
{t('userPanel.email')}
|
224
|
-
</Link>
|
225
|
-
),
|
226
|
-
},
|
227
|
-
],
|
228
|
-
icon: <Icon icon={LifeBuoy} />,
|
229
|
-
key: 'help',
|
230
|
-
label: t('userPanel.help'),
|
231
|
-
},
|
232
|
-
{
|
233
|
-
type: 'divider',
|
223
|
+
icon: <Icon icon={Mail} />,
|
224
|
+
key: 'email',
|
225
|
+
label: (
|
226
|
+
<Link href={mailTo(EMAIL_SUPPORT)} target={'_blank'}>
|
227
|
+
{t('userPanel.email')}
|
228
|
+
</Link>
|
229
|
+
),
|
234
230
|
},
|
235
|
-
]
|
231
|
+
],
|
232
|
+
icon: <Icon icon={LifeBuoy} />,
|
233
|
+
key: 'help',
|
234
|
+
label: t('userPanel.help'),
|
235
|
+
},
|
236
|
+
{
|
237
|
+
type: 'divider',
|
238
|
+
},
|
239
|
+
].filter(Boolean) as ItemType[];
|
236
240
|
|
237
241
|
const mainItems = [
|
238
242
|
{
|
239
243
|
type: 'divider',
|
240
244
|
},
|
241
|
-
...(isLogin ? settings : []),
|
242
245
|
...(isLoginWithClerk ? profile : []),
|
246
|
+
...(isLogin ? settings : []),
|
243
247
|
/* ↓ cloud slot ↓ */
|
244
248
|
|
245
249
|
/* ↑ cloud slot ↑ */
|
246
250
|
...(canInstall ? pwa : []),
|
247
251
|
...data,
|
248
|
-
...helps,
|
252
|
+
...(!hideDocs ? helps : []),
|
249
253
|
].filter(Boolean) as MenuProps['items'];
|
250
254
|
|
251
255
|
const logoutItems: MenuProps['items'] = isLoginWithAuth
|
@@ -1,13 +1,14 @@
|
|
1
1
|
import { act, renderHook } from '@testing-library/react';
|
2
2
|
import { describe, expect, it, vi } from 'vitest';
|
3
3
|
|
4
|
-
import { useUserStore } from '@/store/user';
|
5
4
|
import { ServerConfigStoreProvider } from '@/store/serverConfig';
|
5
|
+
import { useUserStore } from '@/store/user';
|
6
6
|
|
7
7
|
import { useMenu } from '../UserPanel/useMenu';
|
8
8
|
|
9
|
-
const wrapper: React.JSXElementConstructor<{ children: React.ReactNode }> = ({ children }) =>
|
9
|
+
const wrapper: React.JSXElementConstructor<{ children: React.ReactNode }> = ({ children }) => (
|
10
10
|
<ServerConfigStoreProvider>{children}</ServerConfigStoreProvider>
|
11
|
+
);
|
11
12
|
|
12
13
|
// Mock dependencies
|
13
14
|
vi.mock('next/link', () => ({
|
@@ -81,7 +82,7 @@ describe('useMenu', () => {
|
|
81
82
|
expect(mainItems?.some((item) => item?.key === 'setting')).toBe(true);
|
82
83
|
expect(mainItems?.some((item) => item?.key === 'import')).toBe(true);
|
83
84
|
expect(mainItems?.some((item) => item?.key === 'export')).toBe(true);
|
84
|
-
expect(mainItems?.some((item) => item?.key === '
|
85
|
+
expect(mainItems?.some((item) => item?.key === 'changelog')).toBe(true);
|
85
86
|
expect(logoutItems.some((item) => item?.key === 'logout')).toBe(true);
|
86
87
|
});
|
87
88
|
});
|
@@ -101,7 +102,7 @@ describe('useMenu', () => {
|
|
101
102
|
expect(mainItems?.some((item) => item?.key === 'setting')).toBe(true);
|
102
103
|
expect(mainItems?.some((item) => item?.key === 'import')).toBe(true);
|
103
104
|
expect(mainItems?.some((item) => item?.key === 'export')).toBe(true);
|
104
|
-
expect(mainItems?.some((item) => item?.key === '
|
105
|
+
expect(mainItems?.some((item) => item?.key === 'changelog')).toBe(true);
|
105
106
|
expect(logoutItems.some((item) => item?.key === 'logout')).toBe(true);
|
106
107
|
});
|
107
108
|
});
|
@@ -120,7 +121,7 @@ describe('useMenu', () => {
|
|
120
121
|
expect(mainItems?.some((item) => item?.key === 'setting')).toBe(true);
|
121
122
|
expect(mainItems?.some((item) => item?.key === 'import')).toBe(true);
|
122
123
|
expect(mainItems?.some((item) => item?.key === 'export')).toBe(true);
|
123
|
-
expect(mainItems?.some((item) => item?.key === '
|
124
|
+
expect(mainItems?.some((item) => item?.key === 'changelog')).toBe(true);
|
124
125
|
expect(logoutItems.some((item) => item?.key === 'logout')).toBe(false);
|
125
126
|
});
|
126
127
|
});
|
@@ -139,7 +140,7 @@ describe('useMenu', () => {
|
|
139
140
|
expect(mainItems?.some((item) => item?.key === 'setting')).toBe(false);
|
140
141
|
expect(mainItems?.some((item) => item?.key === 'import')).toBe(false);
|
141
142
|
expect(mainItems?.some((item) => item?.key === 'export')).toBe(false);
|
142
|
-
expect(mainItems?.some((item) => item?.key === '
|
143
|
+
expect(mainItems?.some((item) => item?.key === 'changelog')).toBe(true);
|
143
144
|
expect(logoutItems.some((item) => item?.key === 'logout')).toBe(false);
|
144
145
|
});
|
145
146
|
});
|
@@ -1,7 +1,6 @@
|
|
1
|
-
import {
|
1
|
+
import { useMemo } from 'react';
|
2
2
|
import urlJoin from 'url-join';
|
3
3
|
|
4
|
-
import { InterceptContext } from '@/app/@modal/features/InterceptingContext';
|
5
4
|
import { INBOX_SESSION_ID } from '@/const/session';
|
6
5
|
import { useIsMobile } from '@/hooks/useIsMobile';
|
7
6
|
import { useQueryRoute } from '@/hooks/useQueryRoute';
|
@@ -45,7 +44,3 @@ export const useOpenChatSettings = (tab: ChatSettingsTabs = ChatSettingsTabs.Met
|
|
45
44
|
}
|
46
45
|
}, [openSettings, mobile, activeId, router, tab]);
|
47
46
|
};
|
48
|
-
|
49
|
-
export const useInterceptingRoutes = () => {
|
50
|
-
return useContext(InterceptContext);
|
51
|
-
};
|
package/src/hooks/useShare.tsx
CHANGED
@@ -0,0 +1,18 @@
|
|
1
|
+
export default {
|
2
|
+
actions: {
|
3
|
+
followOnX: '在 X 上关注我们',
|
4
|
+
subscribeToUpdates: '订阅更新',
|
5
|
+
versions: '版本详情',
|
6
|
+
},
|
7
|
+
addedWhileAway: '在您离开期间,我们带来了新的特性。',
|
8
|
+
allChangelog: '查看所有更新日志',
|
9
|
+
description: '持续追踪 {{appName}} 的新功能和改进',
|
10
|
+
pagination: {
|
11
|
+
older: '查看历史变更',
|
12
|
+
prev: '上一页',
|
13
|
+
},
|
14
|
+
readDetails: '阅读详情',
|
15
|
+
title: '更新日志',
|
16
|
+
versionDetails: '版本详情',
|
17
|
+
welcomeBack: '欢迎回来!',
|
18
|
+
};
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import auth from './auth';
|
2
|
+
import changelog from './changelog';
|
2
3
|
import chat from './chat';
|
3
4
|
import clerk from './clerk';
|
4
5
|
import common from './common';
|
@@ -23,6 +24,7 @@ import welcome from './welcome';
|
|
23
24
|
|
24
25
|
const resources = {
|
25
26
|
auth,
|
27
|
+
changelog,
|
26
28
|
chat,
|
27
29
|
clerk,
|
28
30
|
common,
|
package/src/server/metadata.ts
CHANGED
@@ -18,8 +18,10 @@ export class Meta {
|
|
18
18
|
tags,
|
19
19
|
alternate,
|
20
20
|
locale = DEFAULT_LANG,
|
21
|
+
canonical,
|
21
22
|
}: {
|
22
23
|
alternate?: boolean;
|
24
|
+
canonical?: string;
|
23
25
|
description?: string;
|
24
26
|
image?: string;
|
25
27
|
locale?: Locales;
|
@@ -35,9 +37,9 @@ export class Meta {
|
|
35
37
|
const siteTitle = title.includes(BRANDING_NAME) ? title : title + ` · ${BRANDING_NAME}`;
|
36
38
|
return {
|
37
39
|
alternates: {
|
38
|
-
canonical:
|
39
|
-
|
40
|
-
|
40
|
+
canonical:
|
41
|
+
canonical ||
|
42
|
+
getCanonicalUrl(alternate ? qs.stringifyUrl({ query: { hl: locale }, url }) : url),
|
41
43
|
languages: alternate ? this.genAlternateLocales(locale, url) : undefined,
|
42
44
|
},
|
43
45
|
description: formatedDescription,
|
@@ -3,10 +3,12 @@
|
|
3
3
|
*/
|
4
4
|
import { publicProcedure, router } from '@/libs/trpc';
|
5
5
|
|
6
|
+
import { appStatusRouter } from './appStatus';
|
6
7
|
import { configRouter } from './config';
|
7
8
|
import { uploadRouter } from './upload';
|
8
9
|
|
9
10
|
export const edgeRouter = router({
|
11
|
+
appStatus: appStatusRouter,
|
10
12
|
config: configRouter,
|
11
13
|
healthcheck: publicProcedure.query(() => "i'm live!"),
|
12
14
|
upload: uploadRouter,
|
@@ -101,7 +101,7 @@ export const agentRouter = router({
|
|
101
101
|
if (!session) throw new Error('Session not found');
|
102
102
|
const sessionId = session.id;
|
103
103
|
|
104
|
-
return
|
104
|
+
return ctx.agentModel.findBySessionId(sessionId);
|
105
105
|
}),
|
106
106
|
|
107
107
|
getKnowledgeBasesAndFiles: agentProcedure
|
@@ -0,0 +1,310 @@
|
|
1
|
+
// @vitest-environment node
|
2
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
3
|
+
|
4
|
+
import { ChangelogIndexItem } from '@/types/changelog';
|
5
|
+
|
6
|
+
import { ChangelogService } from './index';
|
7
|
+
|
8
|
+
// Mock external dependencies
|
9
|
+
vi.mock('dayjs', () => ({
|
10
|
+
default: (date: string) => ({
|
11
|
+
format: vi.fn().mockReturnValue(date),
|
12
|
+
}),
|
13
|
+
}));
|
14
|
+
|
15
|
+
vi.mock('gray-matter', () => ({
|
16
|
+
default: vi.fn().mockImplementation((text) => ({
|
17
|
+
data: { date: '2023-01-01' },
|
18
|
+
content: text,
|
19
|
+
})),
|
20
|
+
}));
|
21
|
+
|
22
|
+
vi.mock('markdown-to-txt', () => ({
|
23
|
+
markdownToTxt: vi.fn().mockImplementation((text) => text),
|
24
|
+
}));
|
25
|
+
|
26
|
+
vi.mock('semver', async (importOriginal) => {
|
27
|
+
const actual: any = await importOriginal();
|
28
|
+
return {
|
29
|
+
...actual,
|
30
|
+
rcompare: vi.fn().mockImplementation((a, b) => b.localeCompare(a)),
|
31
|
+
lt: vi.fn().mockImplementation((a, b) => a < b),
|
32
|
+
gt: vi.fn().mockImplementation((a, b) => a > b),
|
33
|
+
parse: vi.fn().mockImplementation((v) => ({ toString: () => v })),
|
34
|
+
};
|
35
|
+
});
|
36
|
+
|
37
|
+
vi.mock('url-join', () => ({
|
38
|
+
default: vi.fn((...args) => args.join('/')),
|
39
|
+
}));
|
40
|
+
|
41
|
+
// 模拟 process.env
|
42
|
+
const originalEnv = process.env;
|
43
|
+
|
44
|
+
beforeEach(() => {
|
45
|
+
vi.resetModules();
|
46
|
+
process.env = { ...originalEnv };
|
47
|
+
});
|
48
|
+
|
49
|
+
afterEach(() => {
|
50
|
+
process.env = originalEnv;
|
51
|
+
});
|
52
|
+
|
53
|
+
describe('ChangelogService', () => {
|
54
|
+
let service: ChangelogService;
|
55
|
+
|
56
|
+
beforeEach(() => {
|
57
|
+
service = new ChangelogService();
|
58
|
+
// Mock fetch globally
|
59
|
+
global.fetch = vi.fn();
|
60
|
+
});
|
61
|
+
|
62
|
+
describe('getLatestChangelogId', () => {
|
63
|
+
it('should return the id of the first changelog item', async () => {
|
64
|
+
const mockIndex = [{ id: 'latest' }, { id: 'older' }];
|
65
|
+
vi.spyOn(service, 'getChangelogIndex').mockResolvedValue(mockIndex as ChangelogIndexItem[]);
|
66
|
+
|
67
|
+
const result = await service.getLatestChangelogId();
|
68
|
+
expect(result).toBe('latest');
|
69
|
+
});
|
70
|
+
|
71
|
+
it('should return undefined if the index is empty', async () => {
|
72
|
+
vi.spyOn(service, 'getChangelogIndex').mockResolvedValue([]);
|
73
|
+
|
74
|
+
const result = await service.getLatestChangelogId();
|
75
|
+
expect(result).toBeUndefined();
|
76
|
+
});
|
77
|
+
});
|
78
|
+
|
79
|
+
describe('getChangelogIndex', () => {
|
80
|
+
it('should fetch and merge changelog data', async () => {
|
81
|
+
const mockResponse = {
|
82
|
+
json: vi.fn().mockResolvedValue({
|
83
|
+
cloud: [{ id: 'cloud1', date: '2023-01-01', versionRange: ['1.0.0'] }],
|
84
|
+
community: [{ id: 'community1', date: '2023-01-02', versionRange: ['1.1.0'] }],
|
85
|
+
}),
|
86
|
+
};
|
87
|
+
(global.fetch as any).mockResolvedValue(mockResponse);
|
88
|
+
|
89
|
+
const result = await service.getChangelogIndex();
|
90
|
+
expect(result).toHaveLength(2);
|
91
|
+
expect(result[0].id).toBe('community1');
|
92
|
+
expect(result[1].id).toBe('cloud1');
|
93
|
+
});
|
94
|
+
|
95
|
+
it('should handle fetch errors', async () => {
|
96
|
+
(global.fetch as any).mockRejectedValue(new Error('Fetch failed'));
|
97
|
+
|
98
|
+
const result = await service.getChangelogIndex();
|
99
|
+
expect(result).toBe(false);
|
100
|
+
});
|
101
|
+
|
102
|
+
it('should return only community items when config type is community', async () => {
|
103
|
+
service.config.type = 'community';
|
104
|
+
const mockResponse = {
|
105
|
+
json: vi.fn().mockResolvedValue({
|
106
|
+
cloud: [{ id: 'cloud1', date: '2023-01-01', versionRange: ['1.0.0'] }],
|
107
|
+
community: [{ id: 'community1', date: '2023-01-02', versionRange: ['1.1.0'] }],
|
108
|
+
}),
|
109
|
+
};
|
110
|
+
(global.fetch as any).mockResolvedValue(mockResponse);
|
111
|
+
|
112
|
+
const result = await service.getChangelogIndex();
|
113
|
+
expect(result).toHaveLength(1);
|
114
|
+
expect(result[0].id).toBe('community1');
|
115
|
+
});
|
116
|
+
});
|
117
|
+
|
118
|
+
describe('getIndexItemById', () => {
|
119
|
+
it('should return the correct item by id', async () => {
|
120
|
+
const mockIndex = [
|
121
|
+
{ id: 'item1', date: '2023-01-01', versionRange: ['1.0.0'] },
|
122
|
+
{ id: 'item2', date: '2023-01-02', versionRange: ['1.1.0'] },
|
123
|
+
];
|
124
|
+
vi.spyOn(service, 'getChangelogIndex').mockResolvedValue(mockIndex as ChangelogIndexItem[]);
|
125
|
+
|
126
|
+
const result = await service.getIndexItemById('item2');
|
127
|
+
expect(result).toEqual({ id: 'item2', date: '2023-01-02', versionRange: ['1.1.0'] });
|
128
|
+
});
|
129
|
+
|
130
|
+
it('should return undefined for non-existent id', async () => {
|
131
|
+
vi.spyOn(service, 'getChangelogIndex').mockResolvedValue([]);
|
132
|
+
|
133
|
+
const result = await service.getIndexItemById('nonexistent');
|
134
|
+
expect(result).toBeUndefined();
|
135
|
+
});
|
136
|
+
});
|
137
|
+
|
138
|
+
describe('getPostById', () => {
|
139
|
+
it('should fetch and parse post content', async () => {
|
140
|
+
vi.spyOn(service, 'getIndexItemById').mockResolvedValue({
|
141
|
+
id: 'post1',
|
142
|
+
date: '2023-01-01',
|
143
|
+
versionRange: ['1.0.0'],
|
144
|
+
} as ChangelogIndexItem);
|
145
|
+
|
146
|
+
const mockResponse = {
|
147
|
+
text: vi.fn().mockResolvedValue('# Post Title\nPost content'),
|
148
|
+
};
|
149
|
+
(global.fetch as any).mockResolvedValue(mockResponse);
|
150
|
+
|
151
|
+
const result = await service.getPostById('post1');
|
152
|
+
expect(result).toMatchObject({
|
153
|
+
content: 'Post content',
|
154
|
+
date: expect.any(String), // 改为期望字符串而不是 Date 对象
|
155
|
+
description: 'Post content',
|
156
|
+
image: undefined,
|
157
|
+
rawTitle: 'Post Title',
|
158
|
+
tags: ['changelog'],
|
159
|
+
title: 'Post Title',
|
160
|
+
});
|
161
|
+
|
162
|
+
// 额外检查日期格式
|
163
|
+
expect(result.date).toMatch(/^\d{4}-\d{2}-\d{2}$/);
|
164
|
+
});
|
165
|
+
|
166
|
+
it('should handle fetch errors', async () => {
|
167
|
+
vi.spyOn(service, 'getIndexItemById').mockResolvedValue({} as ChangelogIndexItem);
|
168
|
+
(global.fetch as any).mockRejectedValue(new Error('Fetch failed'));
|
169
|
+
|
170
|
+
const result = await service.getPostById('error');
|
171
|
+
expect(result).toBe(false);
|
172
|
+
});
|
173
|
+
|
174
|
+
it('should use the correct locale for fetching content', async () => {
|
175
|
+
vi.spyOn(service, 'getIndexItemById').mockResolvedValue({
|
176
|
+
id: 'post1',
|
177
|
+
date: '2023-01-01',
|
178
|
+
versionRange: ['1.0.0'],
|
179
|
+
} as ChangelogIndexItem);
|
180
|
+
|
181
|
+
const mockResponse = {
|
182
|
+
text: vi.fn().mockResolvedValue('# Chinese Title\n中文内容'),
|
183
|
+
};
|
184
|
+
(global.fetch as any).mockResolvedValue(mockResponse);
|
185
|
+
|
186
|
+
const result = await service.getPostById('post1', { locale: 'zh-CN' });
|
187
|
+
expect(result).toEqual({
|
188
|
+
content: '中文内容',
|
189
|
+
date: '2023-01-01',
|
190
|
+
description: '中文内容',
|
191
|
+
image: undefined,
|
192
|
+
rawTitle: 'Chinese Title',
|
193
|
+
tags: ['changelog'],
|
194
|
+
title: 'Chinese Title',
|
195
|
+
});
|
196
|
+
});
|
197
|
+
});
|
198
|
+
|
199
|
+
describe('private methods', () => {
|
200
|
+
describe('mergeChangelogs', () => {
|
201
|
+
it('should merge and sort changelogs correctly', () => {
|
202
|
+
const cloud = [{ id: 'cloud1', date: '2023-01-01', versionRange: ['1.0.0'] }];
|
203
|
+
const community = [{ id: 'community1', date: '2023-01-02', versionRange: ['1.1.0'] }];
|
204
|
+
|
205
|
+
// @ts-ignore - accessing private method for testing
|
206
|
+
const result = service.mergeChangelogs(cloud, community);
|
207
|
+
expect(result).toHaveLength(2);
|
208
|
+
expect(result[0].id).toBe('community1');
|
209
|
+
expect(result[1].id).toBe('cloud1');
|
210
|
+
});
|
211
|
+
|
212
|
+
it('should override community items with cloud items when ids match', () => {
|
213
|
+
const cloud = [{ id: 'item1', date: '2023-01-01', versionRange: ['1.0.0'], type: 'cloud' }];
|
214
|
+
const community = [
|
215
|
+
{ id: 'item1', date: '2023-01-01', versionRange: ['1.0.0'], type: 'community' },
|
216
|
+
];
|
217
|
+
|
218
|
+
// @ts-ignore - accessing private method for testing
|
219
|
+
const result = service.mergeChangelogs(cloud, community);
|
220
|
+
expect(result).toHaveLength(1);
|
221
|
+
// @ts-ignore
|
222
|
+
expect(result[0].type).toBe('cloud');
|
223
|
+
});
|
224
|
+
});
|
225
|
+
|
226
|
+
describe('formatVersionRange', () => {
|
227
|
+
it('should format version range correctly', () => {
|
228
|
+
// @ts-ignore - accessing private method for testing
|
229
|
+
const result = service.formatVersionRange(['1.0.0', '1.1.0']);
|
230
|
+
expect(result).toEqual(['1.1.0', '1.0.0']);
|
231
|
+
});
|
232
|
+
|
233
|
+
it('should return single version as is', () => {
|
234
|
+
// @ts-ignore - accessing private method for testing
|
235
|
+
const result = service.formatVersionRange(['1.0.0']);
|
236
|
+
expect(result).toEqual(['1.0.0']);
|
237
|
+
});
|
238
|
+
});
|
239
|
+
|
240
|
+
describe('genUrl', () => {
|
241
|
+
it('should generate correct URL', () => {
|
242
|
+
// @ts-ignore - accessing private method for testing
|
243
|
+
const result = service.genUrl('test/path');
|
244
|
+
expect(result).toBe('https://raw.githubusercontent.com/lobehub/lobe-chat/main/test/path');
|
245
|
+
});
|
246
|
+
});
|
247
|
+
|
248
|
+
describe('extractHttpsLinks', () => {
|
249
|
+
it('should extract HTTPS links from text', () => {
|
250
|
+
const text = 'Text with https://example.com and https://test.com/image.jpg links';
|
251
|
+
// @ts-ignore - accessing private method for testing
|
252
|
+
const result = service.extractHttpsLinks(text);
|
253
|
+
expect(result).toEqual(['https://example.com', 'https://test.com/image.jpg']);
|
254
|
+
});
|
255
|
+
});
|
256
|
+
|
257
|
+
describe('cdnInit', () => {
|
258
|
+
it('should initialize CDN URLs if docCdnPrefix is set', async () => {
|
259
|
+
// 设置环境变量
|
260
|
+
process.env.DOC_S3_PUBLIC_DOMAIN = 'https://cdn.example.com';
|
261
|
+
|
262
|
+
// 重新导入模块以确保环境变量生效
|
263
|
+
const { ChangelogService } = await import('./index');
|
264
|
+
const service = new ChangelogService();
|
265
|
+
|
266
|
+
const mockData = { 'https://example.com/image.jpg': 'image-hash.jpg' };
|
267
|
+
const mockResponse = {
|
268
|
+
json: vi.fn().mockResolvedValue(mockData),
|
269
|
+
};
|
270
|
+
global.fetch = vi.fn().mockResolvedValue(mockResponse);
|
271
|
+
|
272
|
+
// @ts-ignore - accessing private method for testing
|
273
|
+
await service.cdnInit();
|
274
|
+
|
275
|
+
expect(service.cdnUrls).toEqual(mockData);
|
276
|
+
});
|
277
|
+
});
|
278
|
+
|
279
|
+
describe('replaceCdnUrl', () => {
|
280
|
+
it('should replace URL with CDN URL if available', async () => {
|
281
|
+
// 设置环境变量
|
282
|
+
process.env.DOC_S3_PUBLIC_DOMAIN = 'https://cdn.example.com';
|
283
|
+
|
284
|
+
// 重新导入模块以确保环境变量生效
|
285
|
+
const { ChangelogService } = await import('./index');
|
286
|
+
const service = new ChangelogService();
|
287
|
+
|
288
|
+
service.cdnUrls = { 'https://example.com/image.jpg': 'image-hash.jpg' };
|
289
|
+
|
290
|
+
// @ts-ignore - accessing private method for testing
|
291
|
+
const result = service.replaceCdnUrl('https://example.com/image.jpg');
|
292
|
+
|
293
|
+
expect(result).toBe('https://cdn.example.com/image-hash.jpg');
|
294
|
+
});
|
295
|
+
|
296
|
+
it('should return original URL if CDN URL is not available', () => {
|
297
|
+
const originalDocCdnPrefix = process.env.DOC_S3_PUBLIC_DOMAIN;
|
298
|
+
process.env.DOC_S3_PUBLIC_DOMAIN = 'https://cdn.example.com';
|
299
|
+
service.cdnUrls = {};
|
300
|
+
|
301
|
+
// @ts-ignore - accessing private method for testing
|
302
|
+
const result = service.replaceCdnUrl('https://example.com/image.jpg');
|
303
|
+
expect(result).toBe('https://example.com/image.jpg');
|
304
|
+
|
305
|
+
// Restore original value
|
306
|
+
process.env.DOC_S3_PUBLIC_DOMAIN = originalDocCdnPrefix;
|
307
|
+
});
|
308
|
+
});
|
309
|
+
});
|
310
|
+
});
|