@lobehub/chat 1.76.1 → 1.77.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/CHANGELOG.md +25 -0
- package/changelog/v1.json +9 -0
- package/locales/ar/common.json +12 -1
- package/locales/ar/error.json +10 -0
- package/locales/ar/models.json +9 -6
- package/locales/ar/setting.json +28 -0
- package/locales/bg-BG/common.json +12 -1
- package/locales/bg-BG/error.json +10 -0
- package/locales/bg-BG/models.json +9 -6
- package/locales/bg-BG/setting.json +28 -0
- package/locales/de-DE/common.json +12 -1
- package/locales/de-DE/error.json +10 -0
- package/locales/de-DE/models.json +9 -6
- package/locales/de-DE/setting.json +28 -0
- package/locales/en-US/common.json +12 -1
- package/locales/en-US/error.json +10 -0
- package/locales/en-US/models.json +9 -6
- package/locales/en-US/setting.json +28 -0
- package/locales/es-ES/common.json +12 -1
- package/locales/es-ES/error.json +10 -0
- package/locales/es-ES/models.json +9 -6
- package/locales/es-ES/setting.json +28 -0
- package/locales/fa-IR/common.json +12 -1
- package/locales/fa-IR/error.json +10 -0
- package/locales/fa-IR/models.json +9 -6
- package/locales/fa-IR/setting.json +28 -0
- package/locales/fr-FR/common.json +12 -1
- package/locales/fr-FR/error.json +10 -0
- package/locales/fr-FR/models.json +9 -6
- package/locales/fr-FR/setting.json +28 -0
- package/locales/it-IT/common.json +12 -1
- package/locales/it-IT/error.json +10 -0
- package/locales/it-IT/models.json +9 -6
- package/locales/it-IT/setting.json +28 -0
- package/locales/ja-JP/common.json +12 -1
- package/locales/ja-JP/error.json +10 -0
- package/locales/ja-JP/models.json +9 -6
- package/locales/ja-JP/setting.json +28 -0
- package/locales/ko-KR/common.json +12 -1
- package/locales/ko-KR/error.json +10 -0
- package/locales/ko-KR/models.json +9 -6
- package/locales/ko-KR/setting.json +28 -0
- package/locales/nl-NL/common.json +12 -1
- package/locales/nl-NL/error.json +10 -0
- package/locales/nl-NL/models.json +9 -6
- package/locales/nl-NL/setting.json +28 -0
- package/locales/pl-PL/common.json +12 -1
- package/locales/pl-PL/error.json +10 -0
- package/locales/pl-PL/models.json +9 -6
- package/locales/pl-PL/setting.json +28 -0
- package/locales/pt-BR/common.json +12 -1
- package/locales/pt-BR/error.json +10 -0
- package/locales/pt-BR/models.json +9 -6
- package/locales/pt-BR/setting.json +28 -0
- package/locales/ru-RU/common.json +12 -1
- package/locales/ru-RU/error.json +10 -0
- package/locales/ru-RU/models.json +9 -6
- package/locales/ru-RU/setting.json +28 -0
- package/locales/tr-TR/common.json +12 -1
- package/locales/tr-TR/error.json +10 -0
- package/locales/tr-TR/models.json +9 -6
- package/locales/tr-TR/setting.json +28 -0
- package/locales/vi-VN/common.json +12 -1
- package/locales/vi-VN/error.json +10 -0
- package/locales/vi-VN/models.json +9 -6
- package/locales/vi-VN/setting.json +28 -0
- package/locales/zh-CN/common.json +12 -1
- package/locales/zh-CN/error.json +10 -0
- package/locales/zh-CN/models.json +9 -6
- package/locales/zh-CN/setting.json +28 -0
- package/locales/zh-TW/common.json +12 -1
- package/locales/zh-TW/error.json +10 -0
- package/locales/zh-TW/models.json +9 -6
- package/locales/zh-TW/setting.json +28 -0
- package/package.json +1 -1
- package/src/app/[variants]/(main)/(mobile)/me/data/features/Category.tsx +1 -1
- package/src/app/[variants]/(main)/chat/features/Migration/UpgradeButton.tsx +2 -1
- package/src/app/[variants]/(main)/settings/common/features/Common.tsx +0 -44
- package/src/app/[variants]/(main)/settings/hooks/useCategory.tsx +40 -14
- package/src/app/[variants]/(main)/settings/storage/Advanced.tsx +133 -0
- package/src/app/[variants]/(main)/settings/storage/IndexedDBStorage.tsx +55 -0
- package/src/app/[variants]/(main)/settings/storage/page.tsx +17 -0
- package/src/components/GroupIcon/index.tsx +25 -0
- package/src/components/IndexCard/index.tsx +143 -0
- package/src/components/ProgressItem/index.tsx +75 -0
- package/src/database/repositories/dataExporter/index.test.ts +330 -0
- package/src/database/repositories/dataExporter/index.ts +216 -0
- package/src/database/repositories/dataImporter/__tests__/fixtures/agents.json +65 -0
- package/src/database/repositories/dataImporter/__tests__/fixtures/agentsToSessions.json +541 -0
- package/src/database/repositories/dataImporter/__tests__/fixtures/topic.json +269 -0
- package/src/database/repositories/dataImporter/__tests__/fixtures/userSettings.json +18 -0
- package/src/database/repositories/dataImporter/__tests__/fixtures/with-client-id.json +778 -0
- package/src/database/repositories/dataImporter/__tests__/index.test.ts +120 -880
- package/src/database/repositories/dataImporter/deprecated/__tests__/index.test.ts +940 -0
- package/src/database/repositories/dataImporter/deprecated/index.ts +326 -0
- package/src/database/repositories/dataImporter/index.ts +684 -289
- package/src/features/DataImporter/ImportDetail.tsx +203 -0
- package/src/features/DataImporter/SuccessResult.tsx +22 -6
- package/src/features/DataImporter/_deprecated.ts +43 -0
- package/src/features/DataImporter/config.ts +21 -0
- package/src/features/DataImporter/index.tsx +112 -31
- package/src/features/DevPanel/PostgresViewer/DataTable/index.tsx +6 -0
- package/src/features/User/UserPanel/useMenu.tsx +0 -35
- package/src/features/User/__tests__/useMenu.test.tsx +0 -2
- package/src/locales/default/common.ts +11 -0
- package/src/locales/default/error.ts +10 -0
- package/src/locales/default/setting.ts +28 -0
- package/src/server/routers/lambda/exporter.ts +25 -0
- package/src/server/routers/lambda/importer.ts +19 -3
- package/src/server/routers/lambda/index.ts +2 -0
- package/src/services/config.ts +80 -135
- package/src/services/export/_deprecated.ts +155 -0
- package/src/services/export/client.ts +15 -0
- package/src/services/export/index.ts +6 -0
- package/src/services/export/server.ts +9 -0
- package/src/services/export/type.ts +5 -0
- package/src/services/import/_deprecated.ts +42 -1
- package/src/services/import/client.test.ts +1 -1
- package/src/services/import/client.ts +30 -1
- package/src/services/import/server.ts +70 -2
- package/src/services/import/type.ts +10 -0
- package/src/store/global/initialState.ts +1 -0
- package/src/types/export.ts +11 -0
- package/src/types/exportConfig.ts +2 -0
- package/src/types/importer.ts +15 -0
- package/src/utils/client/exportFile.ts +21 -0
- package/vitest.config.ts +1 -1
- package/src/utils/config.ts +0 -109
- /package/src/database/repositories/dataImporter/{__tests__ → deprecated/__tests__}/fixtures/messages.json +0 -0
@@ -346,6 +346,33 @@
|
|
346
346
|
},
|
347
347
|
"title": "主題設定"
|
348
348
|
},
|
349
|
+
"storage": {
|
350
|
+
"actions": {
|
351
|
+
"export": {
|
352
|
+
"button": "匯出",
|
353
|
+
"exportType": {
|
354
|
+
"agent": "匯出助手設定",
|
355
|
+
"agentWithMessage": "匯出助手和訊息",
|
356
|
+
"all": "匯出全域設定和所有助手資料",
|
357
|
+
"allAgent": "匯出所有助手設定",
|
358
|
+
"allAgentWithMessage": "匯出所有助手和訊息",
|
359
|
+
"globalSetting": "匯出全域設定"
|
360
|
+
},
|
361
|
+
"title": "匯出資料"
|
362
|
+
},
|
363
|
+
"import": {
|
364
|
+
"button": "匯入",
|
365
|
+
"title": "匯入資料"
|
366
|
+
},
|
367
|
+
"title": "進階操作"
|
368
|
+
},
|
369
|
+
"desc": "當前瀏覽器中的儲存用量",
|
370
|
+
"embeddings": {
|
371
|
+
"used": "向量儲存"
|
372
|
+
},
|
373
|
+
"title": "資料儲存",
|
374
|
+
"used": "儲存用量"
|
375
|
+
},
|
349
376
|
"submitAgentModal": {
|
350
377
|
"button": "提交助手",
|
351
378
|
"identifier": "助手標識符",
|
@@ -439,6 +466,7 @@
|
|
439
466
|
"hotkey": "快速鍵",
|
440
467
|
"llm": "語言模型",
|
441
468
|
"provider": "AI 服務商",
|
469
|
+
"storage": "資料儲存",
|
442
470
|
"sync": "雲端同步",
|
443
471
|
"system-agent": "系統助手",
|
444
472
|
"tts": "語音服務"
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@lobehub/chat",
|
3
|
-
"version": "1.
|
3
|
+
"version": "1.77.0",
|
4
4
|
"description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
|
5
5
|
"keywords": [
|
6
6
|
"framework",
|
@@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next';
|
|
5
5
|
|
6
6
|
import Cell, { CellProps } from '@/components/Cell';
|
7
7
|
import DataImporter from '@/features/DataImporter';
|
8
|
-
import { configService } from '@/services/
|
8
|
+
import { configService } from '@/services/export/_deprecated';
|
9
9
|
|
10
10
|
const Category = memo(() => {
|
11
11
|
const { t } = useTranslation('common');
|
@@ -2,7 +2,7 @@ import { Button } from 'antd';
|
|
2
2
|
import { ReactNode, memo } from 'react';
|
3
3
|
import { useTranslation } from 'react-i18next';
|
4
4
|
|
5
|
-
import {
|
5
|
+
import { ClientService } from '@/services/import/_deprecated';
|
6
6
|
import { useChatStore } from '@/store/chat';
|
7
7
|
import { useSessionStore } from '@/store/session';
|
8
8
|
|
@@ -31,6 +31,7 @@ const UpgradeButton = memo<UpgradeButtonProps>(
|
|
31
31
|
try {
|
32
32
|
setUpgradeStatus(UpgradeStatus.UPGRADING);
|
33
33
|
|
34
|
+
const configService = new ClientService();
|
34
35
|
await configService.importConfigState({
|
35
36
|
exportType: 'sessions',
|
36
37
|
state: state,
|
@@ -9,12 +9,8 @@ import { useTranslation } from 'react-i18next';
|
|
9
9
|
import { useSyncSettings } from '@/app/[variants]/(main)/settings/hooks/useSyncSettings';
|
10
10
|
import { FORM_STYLE } from '@/const/layoutTokens';
|
11
11
|
import { DEFAULT_SETTINGS } from '@/const/settings';
|
12
|
-
import { useChatStore } from '@/store/chat';
|
13
|
-
import { useFileStore } from '@/store/file';
|
14
12
|
import { useServerConfigStore } from '@/store/serverConfig';
|
15
13
|
import { serverConfigSelectors } from '@/store/serverConfig/selectors';
|
16
|
-
import { useSessionStore } from '@/store/session';
|
17
|
-
import { useToolStore } from '@/store/tool';
|
18
14
|
import { useUserStore } from '@/store/user';
|
19
15
|
import { settingsSelectors } from '@/store/user/selectors';
|
20
16
|
|
@@ -26,16 +22,6 @@ const Common = memo(() => {
|
|
26
22
|
|
27
23
|
const showAccessCodeConfig = useServerConfigStore(serverConfigSelectors.enabledAccessCode);
|
28
24
|
|
29
|
-
const [clearSessions, clearSessionGroups] = useSessionStore((s) => [
|
30
|
-
s.clearSessions,
|
31
|
-
s.clearSessionGroups,
|
32
|
-
]);
|
33
|
-
const [clearTopics, clearAllMessages] = useChatStore((s) => [
|
34
|
-
s.removeAllTopics,
|
35
|
-
s.clearAllMessages,
|
36
|
-
]);
|
37
|
-
const [removeAllFiles] = useFileStore((s) => [s.removeAllFiles]);
|
38
|
-
const removeAllPlugins = useToolStore((s) => s.removeAllPlugins);
|
39
25
|
const settings = useUserStore(settingsSelectors.currentSettings, isEqual);
|
40
26
|
const [setSettings, resetSettings] = useUserStore((s) => [s.setSettings, s.resetSettings]);
|
41
27
|
|
@@ -54,26 +40,6 @@ const Common = memo(() => {
|
|
54
40
|
});
|
55
41
|
}, []);
|
56
42
|
|
57
|
-
const handleClear = useCallback(() => {
|
58
|
-
modal.confirm({
|
59
|
-
centered: true,
|
60
|
-
okButtonProps: {
|
61
|
-
danger: true,
|
62
|
-
},
|
63
|
-
onOk: async () => {
|
64
|
-
await clearSessions();
|
65
|
-
await removeAllPlugins();
|
66
|
-
await clearTopics();
|
67
|
-
await removeAllFiles();
|
68
|
-
await clearAllMessages();
|
69
|
-
await clearSessionGroups();
|
70
|
-
|
71
|
-
message.success(t('danger.clear.success'));
|
72
|
-
},
|
73
|
-
title: t('danger.clear.confirm'),
|
74
|
-
});
|
75
|
-
}, []);
|
76
|
-
|
77
43
|
const system: SettingItemGroup = {
|
78
44
|
children: [
|
79
45
|
{
|
@@ -98,16 +64,6 @@ const Common = memo(() => {
|
|
98
64
|
label: t('danger.reset.title'),
|
99
65
|
minWidth: undefined,
|
100
66
|
},
|
101
|
-
{
|
102
|
-
children: (
|
103
|
-
<Button danger onClick={handleClear} type="primary">
|
104
|
-
{t('danger.clear.action')}
|
105
|
-
</Button>
|
106
|
-
),
|
107
|
-
desc: t('danger.clear.desc'),
|
108
|
-
label: t('danger.clear.title'),
|
109
|
-
minWidth: undefined,
|
110
|
-
},
|
111
67
|
],
|
112
68
|
title: t('settingSystem.title'),
|
113
69
|
};
|
@@ -1,6 +1,16 @@
|
|
1
1
|
import { Icon } from '@lobehub/ui';
|
2
2
|
import { Tag } from 'antd';
|
3
|
-
import {
|
3
|
+
import {
|
4
|
+
Bot,
|
5
|
+
Brain,
|
6
|
+
Cloudy,
|
7
|
+
Database,
|
8
|
+
Info,
|
9
|
+
KeyboardIcon,
|
10
|
+
Mic2,
|
11
|
+
Settings2,
|
12
|
+
Sparkles,
|
13
|
+
} from 'lucide-react';
|
4
14
|
import Link from 'next/link';
|
5
15
|
import { useMemo } from 'react';
|
6
16
|
import { useTranslation } from 'react-i18next';
|
@@ -30,14 +40,15 @@ export const useCategory = () => {
|
|
30
40
|
),
|
31
41
|
},
|
32
42
|
{
|
33
|
-
icon: <Icon icon={
|
34
|
-
key: SettingsTabs.
|
43
|
+
icon: <Icon icon={Bot} />,
|
44
|
+
key: SettingsTabs.Agent,
|
35
45
|
label: (
|
36
|
-
<Link href={'/settings/
|
37
|
-
{t('tab.
|
46
|
+
<Link href={'/settings/agent'} onClick={(e) => e.preventDefault()}>
|
47
|
+
{t('tab.agent')}
|
38
48
|
</Link>
|
39
49
|
),
|
40
50
|
},
|
51
|
+
// TODO: remove in V2
|
41
52
|
enableWebrtc && {
|
42
53
|
icon: <Icon icon={Cloudy} />,
|
43
54
|
key: SettingsTabs.Sync,
|
@@ -52,6 +63,18 @@ export const useCategory = () => {
|
|
52
63
|
</Link>
|
53
64
|
),
|
54
65
|
},
|
66
|
+
!mobile && {
|
67
|
+
icon: <Icon icon={KeyboardIcon} />,
|
68
|
+
key: SettingsTabs.Hotkey,
|
69
|
+
label: (
|
70
|
+
<Link href={'/settings/hotkey'} onClick={(e) => e.preventDefault()}>
|
71
|
+
{t('tab.hotkey')}
|
72
|
+
</Link>
|
73
|
+
),
|
74
|
+
},
|
75
|
+
{
|
76
|
+
type: 'divider',
|
77
|
+
},
|
55
78
|
showLLM &&
|
56
79
|
// TODO: Remove /llm when v2.0
|
57
80
|
(isDeprecatedEdition
|
@@ -84,20 +107,23 @@ export const useCategory = () => {
|
|
84
107
|
),
|
85
108
|
},
|
86
109
|
{
|
87
|
-
icon: <Icon icon={
|
88
|
-
key: SettingsTabs.
|
110
|
+
icon: <Icon icon={Sparkles} />,
|
111
|
+
key: SettingsTabs.SystemAgent,
|
89
112
|
label: (
|
90
|
-
<Link href={'/settings/agent'} onClick={(e) => e.preventDefault()}>
|
91
|
-
{t('tab.agent')}
|
113
|
+
<Link href={'/settings/system-agent'} onClick={(e) => e.preventDefault()}>
|
114
|
+
{t('tab.system-agent')}
|
92
115
|
</Link>
|
93
116
|
),
|
94
117
|
},
|
95
|
-
|
96
|
-
|
97
|
-
|
118
|
+
{
|
119
|
+
type: 'divider',
|
120
|
+
},
|
121
|
+
{
|
122
|
+
icon: <Icon icon={Database} />,
|
123
|
+
key: SettingsTabs.Storage,
|
98
124
|
label: (
|
99
|
-
<Link href={'/settings/
|
100
|
-
{t('tab.
|
125
|
+
<Link href={'/settings/storage'} onClick={(e) => e.preventDefault()}>
|
126
|
+
{t('tab.storage')}
|
101
127
|
</Link>
|
102
128
|
),
|
103
129
|
},
|
@@ -0,0 +1,133 @@
|
|
1
|
+
'use client';
|
2
|
+
|
3
|
+
import { Form, Icon, type ItemGroup } from '@lobehub/ui';
|
4
|
+
import { App, Button, Dropdown, Space } from 'antd';
|
5
|
+
import isEqual from 'fast-deep-equal';
|
6
|
+
import { ChevronDown, HardDriveDownload, HardDriveUpload } from 'lucide-react';
|
7
|
+
import { useCallback } from 'react';
|
8
|
+
import { useTranslation } from 'react-i18next';
|
9
|
+
|
10
|
+
import { FORM_STYLE } from '@/const/layoutTokens';
|
11
|
+
import DataImporter from '@/features/DataImporter';
|
12
|
+
import { configService } from '@/services/config';
|
13
|
+
import { useChatStore } from '@/store/chat';
|
14
|
+
import { useFileStore } from '@/store/file';
|
15
|
+
import { useSessionStore } from '@/store/session';
|
16
|
+
import { useToolStore } from '@/store/tool';
|
17
|
+
import { useUserStore } from '@/store/user';
|
18
|
+
import { settingsSelectors } from '@/store/user/selectors';
|
19
|
+
|
20
|
+
const AdvancedActions = () => {
|
21
|
+
const { t } = useTranslation('setting');
|
22
|
+
const [form] = Form.useForm();
|
23
|
+
const { message, modal } = App.useApp();
|
24
|
+
const [clearSessions, clearSessionGroups] = useSessionStore((s) => [
|
25
|
+
s.clearSessions,
|
26
|
+
s.clearSessionGroups,
|
27
|
+
]);
|
28
|
+
const [clearTopics, clearAllMessages] = useChatStore((s) => [
|
29
|
+
s.removeAllTopics,
|
30
|
+
s.clearAllMessages,
|
31
|
+
]);
|
32
|
+
const [removeAllFiles] = useFileStore((s) => [s.removeAllFiles]);
|
33
|
+
const removeAllPlugins = useToolStore((s) => s.removeAllPlugins);
|
34
|
+
const settings = useUserStore(settingsSelectors.currentSettings, isEqual);
|
35
|
+
|
36
|
+
const handleClear = useCallback(() => {
|
37
|
+
modal.confirm({
|
38
|
+
centered: true,
|
39
|
+
okButtonProps: {
|
40
|
+
danger: true,
|
41
|
+
},
|
42
|
+
onOk: async () => {
|
43
|
+
await clearSessions();
|
44
|
+
await removeAllPlugins();
|
45
|
+
await clearTopics();
|
46
|
+
await removeAllFiles();
|
47
|
+
await clearAllMessages();
|
48
|
+
await clearSessionGroups();
|
49
|
+
|
50
|
+
message.success(t('danger.clear.success'));
|
51
|
+
},
|
52
|
+
title: t('danger.clear.confirm'),
|
53
|
+
});
|
54
|
+
}, []);
|
55
|
+
|
56
|
+
const system: ItemGroup = {
|
57
|
+
children: [
|
58
|
+
{
|
59
|
+
children: (
|
60
|
+
<DataImporter>
|
61
|
+
<Button icon={<Icon icon={HardDriveDownload} />}>
|
62
|
+
{t('storage.actions.import.button')}
|
63
|
+
</Button>
|
64
|
+
</DataImporter>
|
65
|
+
),
|
66
|
+
label: t('storage.actions.import.title'),
|
67
|
+
minWidth: undefined,
|
68
|
+
},
|
69
|
+
{
|
70
|
+
children: (
|
71
|
+
<Space.Compact>
|
72
|
+
<Button
|
73
|
+
icon={<Icon icon={HardDriveUpload} />}
|
74
|
+
onClick={() => {
|
75
|
+
configService.exportAll();
|
76
|
+
}}
|
77
|
+
>
|
78
|
+
{t('storage.actions.export.button')}
|
79
|
+
</Button>
|
80
|
+
<Dropdown
|
81
|
+
menu={{
|
82
|
+
items: [
|
83
|
+
{
|
84
|
+
key: 'allAgent',
|
85
|
+
label: t('storage.actions.export.exportType.allAgent'),
|
86
|
+
onClick: configService.exportAgents,
|
87
|
+
},
|
88
|
+
{
|
89
|
+
key: 'allAgentWithMessage',
|
90
|
+
label: t('storage.actions.export.exportType.allAgentWithMessage'),
|
91
|
+
onClick: configService.exportSessions,
|
92
|
+
},
|
93
|
+
{
|
94
|
+
key: 'globalSetting',
|
95
|
+
label: t('storage.actions.export.exportType.globalSetting'),
|
96
|
+
onClick: configService.exportSettings,
|
97
|
+
},
|
98
|
+
],
|
99
|
+
}}
|
100
|
+
>
|
101
|
+
<Button icon={<Icon icon={ChevronDown} />} />
|
102
|
+
</Dropdown>
|
103
|
+
</Space.Compact>
|
104
|
+
),
|
105
|
+
label: t('storage.actions.export.title'),
|
106
|
+
minWidth: undefined,
|
107
|
+
},
|
108
|
+
{
|
109
|
+
children: (
|
110
|
+
<Button danger onClick={handleClear} type="primary">
|
111
|
+
{t('danger.clear.action')}
|
112
|
+
</Button>
|
113
|
+
),
|
114
|
+
desc: t('danger.clear.desc'),
|
115
|
+
label: t('danger.clear.title'),
|
116
|
+
minWidth: undefined,
|
117
|
+
},
|
118
|
+
],
|
119
|
+
title: t('storage.actions.title'),
|
120
|
+
};
|
121
|
+
return (
|
122
|
+
<Form
|
123
|
+
form={form}
|
124
|
+
initialValues={settings}
|
125
|
+
items={[system]}
|
126
|
+
itemsType={'group'}
|
127
|
+
variant={'pure'}
|
128
|
+
{...FORM_STYLE}
|
129
|
+
/>
|
130
|
+
);
|
131
|
+
};
|
132
|
+
|
133
|
+
export default AdvancedActions;
|
@@ -0,0 +1,55 @@
|
|
1
|
+
'use client';
|
2
|
+
|
3
|
+
import { Skeleton } from 'antd';
|
4
|
+
import { DatabaseIcon } from 'lucide-react';
|
5
|
+
import { memo } from 'react';
|
6
|
+
import { useTranslation } from 'react-i18next';
|
7
|
+
import { Flexbox } from 'react-layout-kit';
|
8
|
+
import useSWR from 'swr';
|
9
|
+
|
10
|
+
import GroupIcon from '@/components/GroupIcon';
|
11
|
+
import IndexCard from '@/components/IndexCard';
|
12
|
+
import ProgressItem from '@/components/ProgressItem';
|
13
|
+
import { formatSize } from '@/utils/format';
|
14
|
+
|
15
|
+
const IndexedDBStorage = memo(() => {
|
16
|
+
const { t } = useTranslation('setting');
|
17
|
+
const { data, isLoading } = useSWR('fetch-client-usage', async () => {
|
18
|
+
const estimate = await navigator.storage.estimate();
|
19
|
+
const quota = estimate.quota || 0;
|
20
|
+
const usage = estimate.usage || 0;
|
21
|
+
|
22
|
+
const percent = (usage / quota) * 100;
|
23
|
+
|
24
|
+
return { percent: percent < 1 ? 1 : percent, total: quota, used: usage };
|
25
|
+
});
|
26
|
+
|
27
|
+
return (
|
28
|
+
<IndexCard
|
29
|
+
desc={t('storage.desc', { day: 15 })}
|
30
|
+
icon={<GroupIcon icon={DatabaseIcon} />}
|
31
|
+
padding={0}
|
32
|
+
title={t('storage.title')}
|
33
|
+
>
|
34
|
+
{isLoading ? (
|
35
|
+
<Flexbox padding={16}>
|
36
|
+
<Skeleton active paragraph={{ rows: 1, width: '100%' }} title={false} />
|
37
|
+
<Skeleton.Button active style={{ height: 48, width: '100%' }} />
|
38
|
+
</Flexbox>
|
39
|
+
) : (
|
40
|
+
<Flexbox gap={16} paddingBlock={16}>
|
41
|
+
<ProgressItem
|
42
|
+
percent={data?.percent || 0}
|
43
|
+
title={t('storage.used')}
|
44
|
+
usage={{
|
45
|
+
total: data ? formatSize(data.total) : '-',
|
46
|
+
used: data ? formatSize(data.used) : '-',
|
47
|
+
}}
|
48
|
+
/>
|
49
|
+
</Flexbox>
|
50
|
+
)}
|
51
|
+
</IndexCard>
|
52
|
+
);
|
53
|
+
});
|
54
|
+
|
55
|
+
export default IndexedDBStorage;
|
@@ -0,0 +1,17 @@
|
|
1
|
+
'use client';
|
2
|
+
|
3
|
+
import { isServerMode } from '@/const/version';
|
4
|
+
|
5
|
+
import Advanced from './Advanced';
|
6
|
+
import IndexedDBStorage from './IndexedDBStorage';
|
7
|
+
|
8
|
+
const StorageEstimate = () => {
|
9
|
+
return (
|
10
|
+
<>
|
11
|
+
{!isServerMode && <IndexedDBStorage />}
|
12
|
+
<Advanced />
|
13
|
+
</>
|
14
|
+
);
|
15
|
+
};
|
16
|
+
|
17
|
+
export default StorageEstimate;
|
@@ -0,0 +1,25 @@
|
|
1
|
+
import { Icon } from '@lobehub/ui';
|
2
|
+
import { createStyles } from 'antd-style';
|
3
|
+
import { LucideIcon } from 'lucide-react';
|
4
|
+
import { memo } from 'react';
|
5
|
+
import { Center } from 'react-layout-kit';
|
6
|
+
|
7
|
+
const useStyles = createStyles(({ css, token }) => ({
|
8
|
+
icon: css`
|
9
|
+
border: 1px solid ${token.colorBorderSecondary};
|
10
|
+
border-radius: 8px;
|
11
|
+
background: ${token.colorBgElevated};
|
12
|
+
`,
|
13
|
+
}));
|
14
|
+
|
15
|
+
const GroupIcon = memo<{ icon: LucideIcon }>(({ icon }) => {
|
16
|
+
const { styles } = useStyles();
|
17
|
+
|
18
|
+
return (
|
19
|
+
<Center className={styles.icon} flex={'none'} height={40} width={40}>
|
20
|
+
<Icon icon={icon} size={{ fontSize: 24 }} />
|
21
|
+
</Center>
|
22
|
+
);
|
23
|
+
});
|
24
|
+
|
25
|
+
export default GroupIcon;
|
@@ -0,0 +1,143 @@
|
|
1
|
+
import { ActionIcon } from '@lobehub/ui';
|
2
|
+
import { createStyles } from 'antd-style';
|
3
|
+
import { ChevronDown, ChevronRight } from 'lucide-react';
|
4
|
+
import { ReactNode, memo } from 'react';
|
5
|
+
import { Center, Flexbox, FlexboxProps } from 'react-layout-kit';
|
6
|
+
|
7
|
+
const useStyles = createStyles(({ css, token, responsive }) => ({
|
8
|
+
card: css`
|
9
|
+
position: relative;
|
10
|
+
|
11
|
+
overflow: hidden;
|
12
|
+
|
13
|
+
border: 1px solid ${token.colorBorderSecondary};
|
14
|
+
border-radius: ${token.borderRadiusLG}px;
|
15
|
+
|
16
|
+
background: ${token.colorBgContainer};
|
17
|
+
`,
|
18
|
+
desc: css`
|
19
|
+
font-size: 14px;
|
20
|
+
line-height: 1.4;
|
21
|
+
color: ${token.colorTextDescription};
|
22
|
+
${responsive.mobile} {
|
23
|
+
font-size: 12px;
|
24
|
+
}
|
25
|
+
`,
|
26
|
+
expend: css`
|
27
|
+
position: absolute;
|
28
|
+
inset-block-end: -12px;
|
29
|
+
inset-inline-start: 50%;
|
30
|
+
transform: translateX(-50%);
|
31
|
+
|
32
|
+
border: 1px solid ${token.colorBorderSecondary};
|
33
|
+
border-radius: 50%;
|
34
|
+
|
35
|
+
background: ${token.colorBgContainer};
|
36
|
+
`,
|
37
|
+
header: css`
|
38
|
+
border-block-end: 1px solid ${token.colorBorderSecondary};
|
39
|
+
background: ${token.colorFillQuaternary};
|
40
|
+
`,
|
41
|
+
more: css`
|
42
|
+
border: 1px solid ${token.colorBorderSecondary};
|
43
|
+
`,
|
44
|
+
title: css`
|
45
|
+
font-size: 16px;
|
46
|
+
font-weight: bold;
|
47
|
+
line-height: 1.4;
|
48
|
+
${responsive.mobile} {
|
49
|
+
font-size: 14px;
|
50
|
+
}
|
51
|
+
`,
|
52
|
+
}));
|
53
|
+
|
54
|
+
interface IndexCardProps extends Omit<FlexboxProps, 'title'> {
|
55
|
+
desc?: ReactNode;
|
56
|
+
expand?: boolean;
|
57
|
+
extra?: ReactNode;
|
58
|
+
icon?: ReactNode;
|
59
|
+
moreTooltip?: string;
|
60
|
+
onExpand?: () => void;
|
61
|
+
onMoreClick?: () => void;
|
62
|
+
title?: ReactNode;
|
63
|
+
}
|
64
|
+
|
65
|
+
const IndexCard = memo<IndexCardProps>(
|
66
|
+
({
|
67
|
+
expand = true,
|
68
|
+
onExpand,
|
69
|
+
icon,
|
70
|
+
className,
|
71
|
+
onMoreClick,
|
72
|
+
title,
|
73
|
+
extra,
|
74
|
+
moreTooltip,
|
75
|
+
desc,
|
76
|
+
children,
|
77
|
+
...rest
|
78
|
+
}) => {
|
79
|
+
const { styles } = useStyles();
|
80
|
+
return (
|
81
|
+
<Flexbox
|
82
|
+
style={{
|
83
|
+
marginBottom: !expand ? 12 : undefined,
|
84
|
+
position: 'relative',
|
85
|
+
}}
|
86
|
+
>
|
87
|
+
<Flexbox
|
88
|
+
className={styles.card}
|
89
|
+
style={{
|
90
|
+
paddingBottom: !expand ? 12 : undefined,
|
91
|
+
}}
|
92
|
+
>
|
93
|
+
{title && (
|
94
|
+
<Flexbox
|
95
|
+
align={'center'}
|
96
|
+
className={styles.header}
|
97
|
+
gap={16}
|
98
|
+
horizontal
|
99
|
+
justify={'space-between'}
|
100
|
+
padding={16}
|
101
|
+
>
|
102
|
+
<Flexbox align={'center'} gap={12} horizontal>
|
103
|
+
{icon}
|
104
|
+
<Flexbox>
|
105
|
+
<div className={styles.title}>{title}</div>
|
106
|
+
{desc && <div className={styles.desc}>{desc}</div>}
|
107
|
+
</Flexbox>
|
108
|
+
</Flexbox>
|
109
|
+
<Flexbox align={'center'} gap={8} horizontal>
|
110
|
+
{extra}
|
111
|
+
{onMoreClick && (
|
112
|
+
<ActionIcon
|
113
|
+
className={styles.more}
|
114
|
+
icon={ChevronRight}
|
115
|
+
onClick={onMoreClick}
|
116
|
+
size={{ blockSize: 32, borderRadius: '50%', fontSize: 16 }}
|
117
|
+
title={moreTooltip}
|
118
|
+
/>
|
119
|
+
)}
|
120
|
+
</Flexbox>
|
121
|
+
</Flexbox>
|
122
|
+
)}
|
123
|
+
<Flexbox className={className} gap={16} padding={16} width={'100%'} {...rest}>
|
124
|
+
{children}
|
125
|
+
</Flexbox>
|
126
|
+
</Flexbox>
|
127
|
+
{!expand && (
|
128
|
+
<Center className={styles.expend} height={24} width={24}>
|
129
|
+
<ActionIcon
|
130
|
+
icon={ChevronDown}
|
131
|
+
onClick={onExpand}
|
132
|
+
size={{ blockSize: 24, borderRadius: '50%', fontSize: 16 }}
|
133
|
+
/>
|
134
|
+
</Center>
|
135
|
+
)}
|
136
|
+
</Flexbox>
|
137
|
+
);
|
138
|
+
},
|
139
|
+
);
|
140
|
+
|
141
|
+
IndexCard.displayName = 'IndexCard';
|
142
|
+
|
143
|
+
export default IndexCard;
|
@@ -0,0 +1,75 @@
|
|
1
|
+
import { Progress } from 'antd';
|
2
|
+
import { createStyles, useResponsive } from 'antd-style';
|
3
|
+
import { CSSProperties, memo } from 'react';
|
4
|
+
import { Flexbox } from 'react-layout-kit';
|
5
|
+
|
6
|
+
const useStyles = createStyles(({ css, token }) => ({
|
7
|
+
desc: css`
|
8
|
+
height: 20px;
|
9
|
+
font-size: 12px;
|
10
|
+
line-height: 20px;
|
11
|
+
color: ${token.colorTextTertiary};
|
12
|
+
`,
|
13
|
+
title: css`
|
14
|
+
font-size: 15px;
|
15
|
+
font-weight: bold;
|
16
|
+
color: ${token.colorTextSecondary};
|
17
|
+
`,
|
18
|
+
}));
|
19
|
+
|
20
|
+
interface ProgressItemProps {
|
21
|
+
className?: string;
|
22
|
+
desc?: string;
|
23
|
+
legend?: string;
|
24
|
+
padding?: number;
|
25
|
+
percent: number;
|
26
|
+
style?: CSSProperties;
|
27
|
+
title: string;
|
28
|
+
usage: {
|
29
|
+
total: string | number;
|
30
|
+
used: string | number;
|
31
|
+
};
|
32
|
+
}
|
33
|
+
|
34
|
+
const ProgressItem = memo<ProgressItemProps>(
|
35
|
+
({ legend, title, desc, usage, percent, style, className }) => {
|
36
|
+
const { mobile } = useResponsive();
|
37
|
+
const { styles, theme } = useStyles();
|
38
|
+
|
39
|
+
return (
|
40
|
+
<Flexbox className={className} paddingInline={16} style={style} width={'100%'}>
|
41
|
+
<Flexbox align={'center'} horizontal justify={'space-between'} width={'100%'}>
|
42
|
+
<Flexbox align={'center'} gap={8} horizontal>
|
43
|
+
{legend && (
|
44
|
+
<Flexbox
|
45
|
+
height={8}
|
46
|
+
style={{
|
47
|
+
background: theme.geekblue,
|
48
|
+
borderRadius: '50%',
|
49
|
+
flex: 'none',
|
50
|
+
}}
|
51
|
+
width={8}
|
52
|
+
/>
|
53
|
+
)}
|
54
|
+
<Flexbox align={'baseline'} gap={mobile ? 0 : 8} horizontal={!mobile}>
|
55
|
+
<div className={styles.title}>{title}</div>
|
56
|
+
{desc && <div className={styles.desc}>{desc}</div>}
|
57
|
+
</Flexbox>
|
58
|
+
</Flexbox>
|
59
|
+
<div>
|
60
|
+
<span style={{ fontWeight: 'bold' }}>{usage.used}</span>
|
61
|
+
{['', '/', usage.total].join(' ')}
|
62
|
+
</div>
|
63
|
+
</Flexbox>
|
64
|
+
<Progress
|
65
|
+
percent={percent}
|
66
|
+
showInfo={false}
|
67
|
+
size={'small'}
|
68
|
+
strokeColor={theme.colorPrimary}
|
69
|
+
/>
|
70
|
+
</Flexbox>
|
71
|
+
);
|
72
|
+
},
|
73
|
+
);
|
74
|
+
|
75
|
+
export default ProgressItem;
|