@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.
Files changed (129) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/changelog/v1.json +9 -0
  3. package/locales/ar/common.json +12 -1
  4. package/locales/ar/error.json +10 -0
  5. package/locales/ar/models.json +9 -6
  6. package/locales/ar/setting.json +28 -0
  7. package/locales/bg-BG/common.json +12 -1
  8. package/locales/bg-BG/error.json +10 -0
  9. package/locales/bg-BG/models.json +9 -6
  10. package/locales/bg-BG/setting.json +28 -0
  11. package/locales/de-DE/common.json +12 -1
  12. package/locales/de-DE/error.json +10 -0
  13. package/locales/de-DE/models.json +9 -6
  14. package/locales/de-DE/setting.json +28 -0
  15. package/locales/en-US/common.json +12 -1
  16. package/locales/en-US/error.json +10 -0
  17. package/locales/en-US/models.json +9 -6
  18. package/locales/en-US/setting.json +28 -0
  19. package/locales/es-ES/common.json +12 -1
  20. package/locales/es-ES/error.json +10 -0
  21. package/locales/es-ES/models.json +9 -6
  22. package/locales/es-ES/setting.json +28 -0
  23. package/locales/fa-IR/common.json +12 -1
  24. package/locales/fa-IR/error.json +10 -0
  25. package/locales/fa-IR/models.json +9 -6
  26. package/locales/fa-IR/setting.json +28 -0
  27. package/locales/fr-FR/common.json +12 -1
  28. package/locales/fr-FR/error.json +10 -0
  29. package/locales/fr-FR/models.json +9 -6
  30. package/locales/fr-FR/setting.json +28 -0
  31. package/locales/it-IT/common.json +12 -1
  32. package/locales/it-IT/error.json +10 -0
  33. package/locales/it-IT/models.json +9 -6
  34. package/locales/it-IT/setting.json +28 -0
  35. package/locales/ja-JP/common.json +12 -1
  36. package/locales/ja-JP/error.json +10 -0
  37. package/locales/ja-JP/models.json +9 -6
  38. package/locales/ja-JP/setting.json +28 -0
  39. package/locales/ko-KR/common.json +12 -1
  40. package/locales/ko-KR/error.json +10 -0
  41. package/locales/ko-KR/models.json +9 -6
  42. package/locales/ko-KR/setting.json +28 -0
  43. package/locales/nl-NL/common.json +12 -1
  44. package/locales/nl-NL/error.json +10 -0
  45. package/locales/nl-NL/models.json +9 -6
  46. package/locales/nl-NL/setting.json +28 -0
  47. package/locales/pl-PL/common.json +12 -1
  48. package/locales/pl-PL/error.json +10 -0
  49. package/locales/pl-PL/models.json +9 -6
  50. package/locales/pl-PL/setting.json +28 -0
  51. package/locales/pt-BR/common.json +12 -1
  52. package/locales/pt-BR/error.json +10 -0
  53. package/locales/pt-BR/models.json +9 -6
  54. package/locales/pt-BR/setting.json +28 -0
  55. package/locales/ru-RU/common.json +12 -1
  56. package/locales/ru-RU/error.json +10 -0
  57. package/locales/ru-RU/models.json +9 -6
  58. package/locales/ru-RU/setting.json +28 -0
  59. package/locales/tr-TR/common.json +12 -1
  60. package/locales/tr-TR/error.json +10 -0
  61. package/locales/tr-TR/models.json +9 -6
  62. package/locales/tr-TR/setting.json +28 -0
  63. package/locales/vi-VN/common.json +12 -1
  64. package/locales/vi-VN/error.json +10 -0
  65. package/locales/vi-VN/models.json +9 -6
  66. package/locales/vi-VN/setting.json +28 -0
  67. package/locales/zh-CN/common.json +12 -1
  68. package/locales/zh-CN/error.json +10 -0
  69. package/locales/zh-CN/models.json +9 -6
  70. package/locales/zh-CN/setting.json +28 -0
  71. package/locales/zh-TW/common.json +12 -1
  72. package/locales/zh-TW/error.json +10 -0
  73. package/locales/zh-TW/models.json +9 -6
  74. package/locales/zh-TW/setting.json +28 -0
  75. package/package.json +1 -1
  76. package/src/app/[variants]/(main)/(mobile)/me/data/features/Category.tsx +1 -1
  77. package/src/app/[variants]/(main)/chat/features/Migration/UpgradeButton.tsx +2 -1
  78. package/src/app/[variants]/(main)/settings/common/features/Common.tsx +0 -44
  79. package/src/app/[variants]/(main)/settings/hooks/useCategory.tsx +40 -14
  80. package/src/app/[variants]/(main)/settings/storage/Advanced.tsx +133 -0
  81. package/src/app/[variants]/(main)/settings/storage/IndexedDBStorage.tsx +55 -0
  82. package/src/app/[variants]/(main)/settings/storage/page.tsx +17 -0
  83. package/src/components/GroupIcon/index.tsx +25 -0
  84. package/src/components/IndexCard/index.tsx +143 -0
  85. package/src/components/ProgressItem/index.tsx +75 -0
  86. package/src/database/repositories/dataExporter/index.test.ts +330 -0
  87. package/src/database/repositories/dataExporter/index.ts +216 -0
  88. package/src/database/repositories/dataImporter/__tests__/fixtures/agents.json +65 -0
  89. package/src/database/repositories/dataImporter/__tests__/fixtures/agentsToSessions.json +541 -0
  90. package/src/database/repositories/dataImporter/__tests__/fixtures/topic.json +269 -0
  91. package/src/database/repositories/dataImporter/__tests__/fixtures/userSettings.json +18 -0
  92. package/src/database/repositories/dataImporter/__tests__/fixtures/with-client-id.json +778 -0
  93. package/src/database/repositories/dataImporter/__tests__/index.test.ts +120 -880
  94. package/src/database/repositories/dataImporter/deprecated/__tests__/index.test.ts +940 -0
  95. package/src/database/repositories/dataImporter/deprecated/index.ts +326 -0
  96. package/src/database/repositories/dataImporter/index.ts +684 -289
  97. package/src/features/DataImporter/ImportDetail.tsx +203 -0
  98. package/src/features/DataImporter/SuccessResult.tsx +22 -6
  99. package/src/features/DataImporter/_deprecated.ts +43 -0
  100. package/src/features/DataImporter/config.ts +21 -0
  101. package/src/features/DataImporter/index.tsx +112 -31
  102. package/src/features/DevPanel/PostgresViewer/DataTable/index.tsx +6 -0
  103. package/src/features/User/UserPanel/useMenu.tsx +0 -35
  104. package/src/features/User/__tests__/useMenu.test.tsx +0 -2
  105. package/src/locales/default/common.ts +11 -0
  106. package/src/locales/default/error.ts +10 -0
  107. package/src/locales/default/setting.ts +28 -0
  108. package/src/server/routers/lambda/exporter.ts +25 -0
  109. package/src/server/routers/lambda/importer.ts +19 -3
  110. package/src/server/routers/lambda/index.ts +2 -0
  111. package/src/services/config.ts +80 -135
  112. package/src/services/export/_deprecated.ts +155 -0
  113. package/src/services/export/client.ts +15 -0
  114. package/src/services/export/index.ts +6 -0
  115. package/src/services/export/server.ts +9 -0
  116. package/src/services/export/type.ts +5 -0
  117. package/src/services/import/_deprecated.ts +42 -1
  118. package/src/services/import/client.test.ts +1 -1
  119. package/src/services/import/client.ts +30 -1
  120. package/src/services/import/server.ts +70 -2
  121. package/src/services/import/type.ts +10 -0
  122. package/src/store/global/initialState.ts +1 -0
  123. package/src/types/export.ts +11 -0
  124. package/src/types/exportConfig.ts +2 -0
  125. package/src/types/importer.ts +15 -0
  126. package/src/utils/client/exportFile.ts +21 -0
  127. package/vitest.config.ts +1 -1
  128. package/src/utils/config.ts +0 -109
  129. /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.76.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/config';
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 { configService } from '@/services/config';
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 { Bot, Brain, Cloudy, Info, KeyboardIcon, Mic2, Settings2, Sparkles } from 'lucide-react';
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={Sparkles} />,
34
- key: SettingsTabs.SystemAgent,
43
+ icon: <Icon icon={Bot} />,
44
+ key: SettingsTabs.Agent,
35
45
  label: (
36
- <Link href={'/settings/system-agent'} onClick={(e) => e.preventDefault()}>
37
- {t('tab.system-agent')}
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={Bot} />,
88
- key: SettingsTabs.Agent,
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
- !mobile && {
96
- icon: <Icon icon={KeyboardIcon} />,
97
- key: SettingsTabs.Hotkey,
118
+ {
119
+ type: 'divider',
120
+ },
121
+ {
122
+ icon: <Icon icon={Database} />,
123
+ key: SettingsTabs.Storage,
98
124
  label: (
99
- <Link href={'/settings/hotkey'} onClick={(e) => e.preventDefault()}>
100
- {t('tab.hotkey')}
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;