@lobehub/chat 0.154.7 → 0.155.1
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 +50 -0
- package/locales/ar/common.json +6 -0
- package/locales/bg-BG/common.json +6 -0
- package/locales/de-DE/common.json +6 -0
- package/locales/en-US/common.json +6 -0
- package/locales/es-ES/common.json +6 -0
- package/locales/fr-FR/common.json +6 -0
- package/locales/it-IT/common.json +6 -0
- package/locales/ja-JP/common.json +6 -0
- package/locales/ko-KR/common.json +6 -0
- package/locales/nl-NL/common.json +6 -0
- package/locales/pl-PL/common.json +6 -0
- package/locales/pt-BR/common.json +6 -0
- package/locales/ru-RU/common.json +6 -0
- package/locales/tr-TR/common.json +6 -0
- package/locales/vi-VN/common.json +6 -0
- package/locales/zh-CN/common.json +6 -0
- package/locales/zh-TW/common.json +6 -0
- package/package.json +1 -1
- package/src/app/(main)/(mobile)/me/features/AvatarBanner.tsx +2 -3
- package/src/app/(main)/(mobile)/me/loading.tsx +19 -2
- package/src/app/(main)/(mobile)/me/page.tsx +6 -1
- package/src/app/(main)/settings/llm/index.tsx +1 -1
- package/src/database/client/models/topic.ts +4 -0
- package/src/features/User/DataStatistics.tsx +153 -0
- package/src/features/User/UserPanel/PanelContent.tsx +2 -0
- package/src/locales/default/common.ts +6 -1
- package/src/services/message/client.ts +9 -0
- package/src/services/session/client.ts +1 -0
- package/src/services/topic/client.test.ts +28 -0
- package/src/services/topic/client.ts +4 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,56 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
+
### [Version 0.155.1](https://github.com/lobehub/lobe-chat/compare/v0.155.0...v0.155.1)
|
|
6
|
+
|
|
7
|
+
<sup>Released on **2024-05-07**</sup>
|
|
8
|
+
|
|
9
|
+
#### 💄 Styles
|
|
10
|
+
|
|
11
|
+
- **misc**: Improve llm list when all closed.
|
|
12
|
+
|
|
13
|
+
<br/>
|
|
14
|
+
|
|
15
|
+
<details>
|
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
17
|
+
|
|
18
|
+
#### Styles
|
|
19
|
+
|
|
20
|
+
- **misc**: Improve llm list when all closed, closes [#2409](https://github.com/lobehub/lobe-chat/issues/2409) ([1eb20c7](https://github.com/lobehub/lobe-chat/commit/1eb20c7))
|
|
21
|
+
|
|
22
|
+
</details>
|
|
23
|
+
|
|
24
|
+
<div align="right">
|
|
25
|
+
|
|
26
|
+
[](#readme-top)
|
|
27
|
+
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
## [Version 0.155.0](https://github.com/lobehub/lobe-chat/compare/v0.154.7...v0.155.0)
|
|
31
|
+
|
|
32
|
+
<sup>Released on **2024-05-07**</sup>
|
|
33
|
+
|
|
34
|
+
#### ✨ Features
|
|
35
|
+
|
|
36
|
+
- **misc**: Add DataStatistics.
|
|
37
|
+
|
|
38
|
+
<br/>
|
|
39
|
+
|
|
40
|
+
<details>
|
|
41
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
42
|
+
|
|
43
|
+
#### What's improved
|
|
44
|
+
|
|
45
|
+
- **misc**: Add DataStatistics ([cf474bb](https://github.com/lobehub/lobe-chat/commit/cf474bb))
|
|
46
|
+
|
|
47
|
+
</details>
|
|
48
|
+
|
|
49
|
+
<div align="right">
|
|
50
|
+
|
|
51
|
+
[](#readme-top)
|
|
52
|
+
|
|
53
|
+
</div>
|
|
54
|
+
|
|
5
55
|
### [Version 0.154.7](https://github.com/lobehub/lobe-chat/compare/v0.154.6...v0.154.7)
|
|
6
56
|
|
|
7
57
|
<sup>Released on **2024-05-07**</sup>
|
package/locales/ar/common.json
CHANGED
|
@@ -12,6 +12,12 @@
|
|
|
12
12
|
"copy": "نسخ",
|
|
13
13
|
"copyFail": "فشل في النسخ",
|
|
14
14
|
"copySuccess": "تم النسخ بنجاح",
|
|
15
|
+
"dataStatistics": {
|
|
16
|
+
"messages": "رسائل",
|
|
17
|
+
"sessions": "جلسات",
|
|
18
|
+
"today": "اليوم",
|
|
19
|
+
"topics": "مواضيع"
|
|
20
|
+
},
|
|
15
21
|
"defaultAgent": "مساعد افتراضي",
|
|
16
22
|
"defaultSession": "جلسة افتراضية",
|
|
17
23
|
"delete": "حذف",
|
|
@@ -12,6 +12,12 @@
|
|
|
12
12
|
"copy": "Копирай",
|
|
13
13
|
"copyFail": "Копирането не е успешно",
|
|
14
14
|
"copySuccess": "Копирано успешно",
|
|
15
|
+
"dataStatistics": {
|
|
16
|
+
"messages": "Съобщения",
|
|
17
|
+
"sessions": "Сесии",
|
|
18
|
+
"today": "Днес",
|
|
19
|
+
"topics": "Теми"
|
|
20
|
+
},
|
|
15
21
|
"defaultAgent": "Агент по подразбиране",
|
|
16
22
|
"defaultSession": "Агент по подразбиране",
|
|
17
23
|
"delete": "Изтрий",
|
|
@@ -12,6 +12,12 @@
|
|
|
12
12
|
"copy": "Kopieren",
|
|
13
13
|
"copyFail": "Kopieren fehlgeschlagen",
|
|
14
14
|
"copySuccess": "Kopieren erfolgreich",
|
|
15
|
+
"dataStatistics": {
|
|
16
|
+
"messages": "Nachrichten",
|
|
17
|
+
"sessions": "Sitzungen",
|
|
18
|
+
"today": "Heute",
|
|
19
|
+
"topics": "Themen"
|
|
20
|
+
},
|
|
15
21
|
"defaultAgent": "Standardassistent",
|
|
16
22
|
"defaultSession": "Standardassistent",
|
|
17
23
|
"delete": "Löschen",
|
|
@@ -12,6 +12,12 @@
|
|
|
12
12
|
"copy": "Copy",
|
|
13
13
|
"copyFail": "Copy failed",
|
|
14
14
|
"copySuccess": "Copied successfully",
|
|
15
|
+
"dataStatistics": {
|
|
16
|
+
"messages": "Messages",
|
|
17
|
+
"sessions": "Assistants",
|
|
18
|
+
"today": "Today's New",
|
|
19
|
+
"topics": "Topics"
|
|
20
|
+
},
|
|
15
21
|
"defaultAgent": "Default Agent",
|
|
16
22
|
"defaultSession": "Default Agent",
|
|
17
23
|
"delete": "Delete",
|
|
@@ -12,6 +12,12 @@
|
|
|
12
12
|
"copy": "Copiar",
|
|
13
13
|
"copyFail": "Fallo al copiar",
|
|
14
14
|
"copySuccess": "¡Copia exitosa!",
|
|
15
|
+
"dataStatistics": {
|
|
16
|
+
"messages": "Mensajes",
|
|
17
|
+
"sessions": "Sesiones",
|
|
18
|
+
"today": "Hoy",
|
|
19
|
+
"topics": "Temas"
|
|
20
|
+
},
|
|
15
21
|
"defaultAgent": "Asistente predeterminado",
|
|
16
22
|
"defaultSession": "Sesión predeterminada",
|
|
17
23
|
"delete": "Eliminar",
|
|
@@ -12,6 +12,12 @@
|
|
|
12
12
|
"copy": "Copier",
|
|
13
13
|
"copyFail": "Échec de la copie",
|
|
14
14
|
"copySuccess": "Copie réussie",
|
|
15
|
+
"dataStatistics": {
|
|
16
|
+
"messages": "Messages",
|
|
17
|
+
"sessions": "Sessions",
|
|
18
|
+
"today": "Aujourd'hui",
|
|
19
|
+
"topics": "Sujets"
|
|
20
|
+
},
|
|
15
21
|
"defaultAgent": "Agent par défaut",
|
|
16
22
|
"defaultSession": "Session par défaut",
|
|
17
23
|
"delete": "Supprimer",
|
|
@@ -12,6 +12,12 @@
|
|
|
12
12
|
"copy": "Copia",
|
|
13
13
|
"copyFail": "Copia non riuscita",
|
|
14
14
|
"copySuccess": "Copia riuscita",
|
|
15
|
+
"dataStatistics": {
|
|
16
|
+
"messages": "Messaggi",
|
|
17
|
+
"sessions": "Sessioni",
|
|
18
|
+
"today": "Oggi",
|
|
19
|
+
"topics": "Argomenti"
|
|
20
|
+
},
|
|
15
21
|
"defaultAgent": "Assistente predefinito",
|
|
16
22
|
"defaultSession": "Sessione predefinita",
|
|
17
23
|
"delete": "Elimina",
|
|
@@ -12,6 +12,12 @@
|
|
|
12
12
|
"copy": "コピー",
|
|
13
13
|
"copyFail": "コピーに失敗しました",
|
|
14
14
|
"copySuccess": "コピーが成功しました",
|
|
15
|
+
"dataStatistics": {
|
|
16
|
+
"messages": "メッセージ",
|
|
17
|
+
"sessions": "セッション",
|
|
18
|
+
"today": "今日の追加",
|
|
19
|
+
"topics": "トピック"
|
|
20
|
+
},
|
|
15
21
|
"defaultAgent": "デフォルトエージェント",
|
|
16
22
|
"defaultSession": "デフォルトセッション",
|
|
17
23
|
"delete": "削除",
|
|
@@ -12,6 +12,12 @@
|
|
|
12
12
|
"copy": "복사",
|
|
13
13
|
"copyFail": "복사 실패",
|
|
14
14
|
"copySuccess": "복사 성공",
|
|
15
|
+
"dataStatistics": {
|
|
16
|
+
"messages": "메시지",
|
|
17
|
+
"sessions": "세션",
|
|
18
|
+
"today": "오늘",
|
|
19
|
+
"topics": "주제"
|
|
20
|
+
},
|
|
15
21
|
"defaultAgent": "기본 에이전트",
|
|
16
22
|
"defaultSession": "기본 세션",
|
|
17
23
|
"delete": "삭제",
|
|
@@ -12,6 +12,12 @@
|
|
|
12
12
|
"copy": "Kopiëren",
|
|
13
13
|
"copyFail": "Kopiëren mislukt",
|
|
14
14
|
"copySuccess": "Kopiëren gelukt",
|
|
15
|
+
"dataStatistics": {
|
|
16
|
+
"messages": "Berichten",
|
|
17
|
+
"sessions": "Sessies",
|
|
18
|
+
"today": "Vandaag",
|
|
19
|
+
"topics": "Onderwerpen"
|
|
20
|
+
},
|
|
15
21
|
"defaultAgent": "Standaard assistent",
|
|
16
22
|
"defaultSession": "Standaard assistent",
|
|
17
23
|
"delete": "Verwijderen",
|
|
@@ -12,6 +12,12 @@
|
|
|
12
12
|
"copy": "Kopiuj",
|
|
13
13
|
"copyFail": "Nie udało się skopiować",
|
|
14
14
|
"copySuccess": "Skopiowano pomyślnie",
|
|
15
|
+
"dataStatistics": {
|
|
16
|
+
"messages": "Wiadomości",
|
|
17
|
+
"sessions": "Sesje",
|
|
18
|
+
"today": "Dzisiaj",
|
|
19
|
+
"topics": "Tematy"
|
|
20
|
+
},
|
|
15
21
|
"defaultAgent": "Domyślny asystent",
|
|
16
22
|
"defaultSession": "Domyślna sesja",
|
|
17
23
|
"delete": "Usuń",
|
|
@@ -12,6 +12,12 @@
|
|
|
12
12
|
"copy": "Copiar",
|
|
13
13
|
"copyFail": "Falha ao copiar",
|
|
14
14
|
"copySuccess": "Cópia bem-sucedida",
|
|
15
|
+
"dataStatistics": {
|
|
16
|
+
"messages": "Mensagens",
|
|
17
|
+
"sessions": "Sessões",
|
|
18
|
+
"today": "Hoje",
|
|
19
|
+
"topics": "Tópicos"
|
|
20
|
+
},
|
|
15
21
|
"defaultAgent": "Assistente padrão",
|
|
16
22
|
"defaultSession": "Sessão padrão",
|
|
17
23
|
"delete": "Excluir",
|
|
@@ -12,6 +12,12 @@
|
|
|
12
12
|
"copy": "Копировать",
|
|
13
13
|
"copyFail": "Не удалось скопировать",
|
|
14
14
|
"copySuccess": "Успешно скопировано",
|
|
15
|
+
"dataStatistics": {
|
|
16
|
+
"messages": "Сообщения",
|
|
17
|
+
"sessions": "Сессии",
|
|
18
|
+
"today": "Сегодня",
|
|
19
|
+
"topics": "Темы"
|
|
20
|
+
},
|
|
15
21
|
"defaultAgent": "Пользовательский агент",
|
|
16
22
|
"defaultSession": "Пользовательский агент",
|
|
17
23
|
"delete": "Удалить",
|
|
@@ -12,6 +12,12 @@
|
|
|
12
12
|
"copy": "Kopyala",
|
|
13
13
|
"copyFail": "Kopyalama başarısız oldu",
|
|
14
14
|
"copySuccess": "Kopyalama Başarılı",
|
|
15
|
+
"dataStatistics": {
|
|
16
|
+
"messages": "Mesajlar",
|
|
17
|
+
"sessions": "Oturumlar",
|
|
18
|
+
"today": "Bugün",
|
|
19
|
+
"topics": "Konular"
|
|
20
|
+
},
|
|
15
21
|
"defaultAgent": "Varsayılan Asistan",
|
|
16
22
|
"defaultSession": "Varsayılan Asistan",
|
|
17
23
|
"delete": "Sil",
|
|
@@ -12,6 +12,12 @@
|
|
|
12
12
|
"copy": "Sao chép",
|
|
13
13
|
"copyFail": "Sao chép thất bại",
|
|
14
14
|
"copySuccess": "Sao chép thành công",
|
|
15
|
+
"dataStatistics": {
|
|
16
|
+
"messages": "Tin nhắn",
|
|
17
|
+
"sessions": "Phiên làm việc",
|
|
18
|
+
"today": "Hôm nay",
|
|
19
|
+
"topics": "Chủ đề"
|
|
20
|
+
},
|
|
15
21
|
"defaultAgent": "Trợ lý mặc định",
|
|
16
22
|
"defaultSession": "Phiên mặc định",
|
|
17
23
|
"delete": "Xóa",
|
|
@@ -12,6 +12,12 @@
|
|
|
12
12
|
"copy": "复制",
|
|
13
13
|
"copyFail": "复制失败",
|
|
14
14
|
"copySuccess": "复制成功",
|
|
15
|
+
"dataStatistics": {
|
|
16
|
+
"messages": "消息",
|
|
17
|
+
"sessions": "助手",
|
|
18
|
+
"today": "今日新增",
|
|
19
|
+
"topics": "话题"
|
|
20
|
+
},
|
|
15
21
|
"defaultAgent": "自定义助手",
|
|
16
22
|
"defaultSession": "自定义助手",
|
|
17
23
|
"delete": "删除",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobehub/chat",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.155.1",
|
|
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",
|
|
@@ -4,13 +4,12 @@ import { PropsWithChildren, memo } from 'react';
|
|
|
4
4
|
import { Flexbox } from 'react-layout-kit';
|
|
5
5
|
|
|
6
6
|
import UserAvatar from '@/features/User/UserAvatar';
|
|
7
|
-
import UserInfo from '@/features/User/UserInfo';
|
|
8
7
|
|
|
9
8
|
import { useStyles } from './style';
|
|
10
9
|
|
|
11
10
|
export const AVATAR_SIZE = 80;
|
|
12
11
|
|
|
13
|
-
const AvatarBanner = memo<PropsWithChildren>(() => {
|
|
12
|
+
const AvatarBanner = memo<PropsWithChildren>(({ children }) => {
|
|
14
13
|
const { styles } = useStyles();
|
|
15
14
|
|
|
16
15
|
return (
|
|
@@ -20,7 +19,7 @@ const AvatarBanner = memo<PropsWithChildren>(() => {
|
|
|
20
19
|
<UserAvatar shape={'square'} size={AVATAR_SIZE} />
|
|
21
20
|
</div>
|
|
22
21
|
</Flexbox>
|
|
23
|
-
<
|
|
22
|
+
<Flexbox className={styles.info}>{children}</Flexbox>
|
|
24
23
|
</>
|
|
25
24
|
);
|
|
26
25
|
});
|
|
@@ -4,6 +4,7 @@ import { Skeleton } from 'antd';
|
|
|
4
4
|
import { memo } from 'react';
|
|
5
5
|
import { Flexbox } from 'react-layout-kit';
|
|
6
6
|
|
|
7
|
+
import Divider from '@/components/Cell/Divider';
|
|
7
8
|
import SkeletonLoading from '@/components/SkeletonLoading';
|
|
8
9
|
|
|
9
10
|
import { useStyles } from './features/style';
|
|
@@ -22,9 +23,25 @@ const Loading = memo(() => {
|
|
|
22
23
|
paddingInline={12}
|
|
23
24
|
>
|
|
24
25
|
<Skeleton.Avatar active shape={'circle'} size={48} />
|
|
25
|
-
<Skeleton active
|
|
26
|
+
<Skeleton.Button active block />
|
|
26
27
|
</Flexbox>
|
|
27
|
-
<
|
|
28
|
+
<Flexbox gap={4} horizontal paddingBlock={12} paddingInline={16}>
|
|
29
|
+
<Skeleton.Button active block />
|
|
30
|
+
<Skeleton.Button active block />
|
|
31
|
+
<Skeleton.Button active block />
|
|
32
|
+
</Flexbox>
|
|
33
|
+
<Divider />
|
|
34
|
+
<SkeletonLoading
|
|
35
|
+
active
|
|
36
|
+
paragraph={{ rows: 6, style: { marginBottom: 0 }, width: '100%' }}
|
|
37
|
+
title={false}
|
|
38
|
+
/>
|
|
39
|
+
<Divider />
|
|
40
|
+
<SkeletonLoading
|
|
41
|
+
active
|
|
42
|
+
paragraph={{ rows: 3, style: { marginBottom: 0 }, width: '100%' }}
|
|
43
|
+
title={false}
|
|
44
|
+
/>
|
|
28
45
|
</>
|
|
29
46
|
);
|
|
30
47
|
});
|
|
@@ -3,6 +3,8 @@ import { Center } from 'react-layout-kit';
|
|
|
3
3
|
|
|
4
4
|
import BrandWatermark from '@/components/BrandWatermark';
|
|
5
5
|
import Divider from '@/components/Cell/Divider';
|
|
6
|
+
import DataStatistics from '@/features/User/DataStatistics';
|
|
7
|
+
import UserInfo from '@/features/User/UserInfo';
|
|
6
8
|
import { isMobileDevice } from '@/utils/responsive';
|
|
7
9
|
|
|
8
10
|
import AvatarBanner from './features/AvatarBanner';
|
|
@@ -16,7 +18,10 @@ const Page = () => {
|
|
|
16
18
|
|
|
17
19
|
return (
|
|
18
20
|
<>
|
|
19
|
-
<AvatarBanner
|
|
21
|
+
<AvatarBanner>
|
|
22
|
+
<UserInfo />
|
|
23
|
+
<DataStatistics paddingInline={16} />
|
|
24
|
+
</AvatarBanner>
|
|
20
25
|
<Divider />
|
|
21
26
|
<Cate />
|
|
22
27
|
<ExtraCate />
|
|
@@ -118,6 +118,10 @@ class _TopicModel extends BaseModel {
|
|
|
118
118
|
return this.table.get(id);
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
+
async count() {
|
|
122
|
+
return this.table.count();
|
|
123
|
+
}
|
|
124
|
+
|
|
121
125
|
// **************** Create *************** //
|
|
122
126
|
|
|
123
127
|
async create({ title, favorite, sessionId, messages }: CreateTopicParams, id = nanoid()) {
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Icon, Tooltip } from '@lobehub/ui';
|
|
4
|
+
import { Badge } from 'antd';
|
|
5
|
+
import { createStyles } from 'antd-style';
|
|
6
|
+
import { isNumber } from 'lodash-es';
|
|
7
|
+
import { LoaderCircle } from 'lucide-react';
|
|
8
|
+
import { memo, useMemo } from 'react';
|
|
9
|
+
import { useTranslation } from 'react-i18next';
|
|
10
|
+
import { Flexbox, FlexboxProps } from 'react-layout-kit';
|
|
11
|
+
import useSWR from 'swr';
|
|
12
|
+
|
|
13
|
+
import { messageService } from '@/services/message';
|
|
14
|
+
import { sessionService } from '@/services/session';
|
|
15
|
+
import { topicService } from '@/services/topic';
|
|
16
|
+
|
|
17
|
+
const useStyles = createStyles(({ css, token }) => ({
|
|
18
|
+
card: css`
|
|
19
|
+
padding: 6px 8px;
|
|
20
|
+
background: ${token.colorFillTertiary};
|
|
21
|
+
border-radius: ${token.borderRadius}px;
|
|
22
|
+
`,
|
|
23
|
+
count: css`
|
|
24
|
+
font-size: 16px;
|
|
25
|
+
font-weight: bold;
|
|
26
|
+
line-height: 1.2;
|
|
27
|
+
`,
|
|
28
|
+
title: css`
|
|
29
|
+
font-size: 12px;
|
|
30
|
+
line-height: 1.2;
|
|
31
|
+
color: ${token.colorTextDescription};
|
|
32
|
+
`,
|
|
33
|
+
today: css`
|
|
34
|
+
font-size: 12px;
|
|
35
|
+
`,
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
const formatNumber = (num: any) => {
|
|
39
|
+
if (!isNumber(num)) return num;
|
|
40
|
+
// 使用Intl.NumberFormat来添加千分号
|
|
41
|
+
const formattedWithComma = new Intl.NumberFormat('en-US').format(num);
|
|
42
|
+
|
|
43
|
+
// 格式化为 K 或 M
|
|
44
|
+
if (num >= 10_000_000) {
|
|
45
|
+
return (num / 1_000_000).toFixed(1) + 'M';
|
|
46
|
+
} else if (num >= 10_000) {
|
|
47
|
+
return (num / 1000).toFixed(1) + 'K';
|
|
48
|
+
} else if (num === 0) {
|
|
49
|
+
return 0;
|
|
50
|
+
} else {
|
|
51
|
+
return formattedWithComma;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const DataStatistics = memo<Omit<FlexboxProps, 'children'>>(({ style, ...rest }) => {
|
|
56
|
+
// sessions
|
|
57
|
+
const { data: sessions, isLoading: sessionsLoading } = useSWR(
|
|
58
|
+
'count-sessions',
|
|
59
|
+
sessionService.countSessions,
|
|
60
|
+
);
|
|
61
|
+
// topics
|
|
62
|
+
const { data: topics, isLoading: topicsLoading } = useSWR(
|
|
63
|
+
'count-topics',
|
|
64
|
+
topicService.countTopics,
|
|
65
|
+
);
|
|
66
|
+
// messages
|
|
67
|
+
const { data: messages, isLoading: messagesLoading } = useSWR(
|
|
68
|
+
'count-messages',
|
|
69
|
+
messageService.countMessages,
|
|
70
|
+
);
|
|
71
|
+
const { data: messagesToday } = useSWR('today-messages', messageService.countTodayMessages);
|
|
72
|
+
|
|
73
|
+
const { styles, theme } = useStyles();
|
|
74
|
+
const { t } = useTranslation('common');
|
|
75
|
+
|
|
76
|
+
const loading = useMemo(() => <Icon icon={LoaderCircle} spin />, []);
|
|
77
|
+
|
|
78
|
+
const items = [
|
|
79
|
+
{
|
|
80
|
+
count: sessionsLoading ? loading : sessions,
|
|
81
|
+
key: 'sessions',
|
|
82
|
+
title: t('dataStatistics.sessions'),
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
count: topicsLoading ? loading : topics,
|
|
86
|
+
key: 'topics',
|
|
87
|
+
title: t('dataStatistics.topics'),
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
count: messagesLoading ? loading : messages,
|
|
91
|
+
countToady: messagesToday,
|
|
92
|
+
key: 'messages',
|
|
93
|
+
title: t('dataStatistics.messages'),
|
|
94
|
+
},
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<Flexbox
|
|
99
|
+
align={'center'}
|
|
100
|
+
gap={4}
|
|
101
|
+
horizontal
|
|
102
|
+
paddingInline={8}
|
|
103
|
+
style={{ marginBottom: 8, ...style }}
|
|
104
|
+
width={'100%'}
|
|
105
|
+
{...rest}
|
|
106
|
+
>
|
|
107
|
+
{items.map((item) => {
|
|
108
|
+
if (item.key === 'messages') {
|
|
109
|
+
const showBadge = Boolean(item.countToady && item.countToady > 0);
|
|
110
|
+
return (
|
|
111
|
+
<Flexbox
|
|
112
|
+
align={'center'}
|
|
113
|
+
className={styles.card}
|
|
114
|
+
flex={showBadge ? 2 : 1}
|
|
115
|
+
gap={4}
|
|
116
|
+
horizontal
|
|
117
|
+
justify={'space-between'}
|
|
118
|
+
key={item.key}
|
|
119
|
+
>
|
|
120
|
+
<Flexbox gap={2}>
|
|
121
|
+
<div className={styles.count}>{formatNumber(item.count)}</div>
|
|
122
|
+
<div className={styles.title}>{item.title}</div>
|
|
123
|
+
</Flexbox>
|
|
124
|
+
{showBadge && (
|
|
125
|
+
<Tooltip title={t('dataStatistics.today')}>
|
|
126
|
+
<Badge
|
|
127
|
+
count={`+${item.countToady}`}
|
|
128
|
+
style={{
|
|
129
|
+
background: theme.colorSuccess,
|
|
130
|
+
color: theme.colorSuccessBg,
|
|
131
|
+
cursor: 'pointer',
|
|
132
|
+
}}
|
|
133
|
+
/>
|
|
134
|
+
</Tooltip>
|
|
135
|
+
)}
|
|
136
|
+
</Flexbox>
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return (
|
|
141
|
+
<Flexbox className={styles.card} flex={1} gap={2} key={item.key}>
|
|
142
|
+
<Flexbox horizontal>
|
|
143
|
+
<div className={styles.count}>{formatNumber(item.count)}</div>
|
|
144
|
+
</Flexbox>
|
|
145
|
+
<div className={styles.title}>{item.title}</div>
|
|
146
|
+
</Flexbox>
|
|
147
|
+
);
|
|
148
|
+
})}
|
|
149
|
+
</Flexbox>
|
|
150
|
+
);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
export default DataStatistics;
|
|
@@ -8,6 +8,7 @@ import { enableAuth } from '@/const/auth';
|
|
|
8
8
|
import { useUserStore } from '@/store/user';
|
|
9
9
|
import { authSelectors } from '@/store/user/selectors';
|
|
10
10
|
|
|
11
|
+
import DataStatistics from '../DataStatistics';
|
|
11
12
|
import UserInfo from '../UserInfo';
|
|
12
13
|
import UserLoginOrSignup from '../UserLoginOrSignup';
|
|
13
14
|
import LangButton from './LangButton';
|
|
@@ -50,6 +51,7 @@ const PanelContent = memo<{ closePopover: () => void }>(({ closePopover }) => {
|
|
|
50
51
|
) : (
|
|
51
52
|
<UserLoginOrSignup onClick={handleSignIn} />
|
|
52
53
|
)}
|
|
54
|
+
<DataStatistics />
|
|
53
55
|
<Menu items={mainItems} onClick={closePopover} />
|
|
54
56
|
<Flexbox
|
|
55
57
|
align={'center'}
|
|
@@ -11,8 +11,13 @@ export default {
|
|
|
11
11
|
close: '关闭',
|
|
12
12
|
copy: '复制',
|
|
13
13
|
copyFail: '复制失败',
|
|
14
|
-
|
|
15
14
|
copySuccess: '复制成功',
|
|
15
|
+
dataStatistics: {
|
|
16
|
+
messages: '消息',
|
|
17
|
+
sessions: '助手',
|
|
18
|
+
today: '今日新增',
|
|
19
|
+
topics: '话题',
|
|
20
|
+
},
|
|
16
21
|
defaultAgent: '自定义助手',
|
|
17
22
|
defaultSession: '自定义助手',
|
|
18
23
|
delete: '删除',
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import dayjs from 'dayjs';
|
|
2
|
+
|
|
1
3
|
import { MessageModel } from '@/database/client/models/message';
|
|
2
4
|
import { DB_Message } from '@/database/client/schemas/message';
|
|
3
5
|
import { ChatMessage, ChatMessageError, ChatPluginPayload } from '@/types/message';
|
|
@@ -27,6 +29,13 @@ export class ClientService implements IMessageService {
|
|
|
27
29
|
return MessageModel.count();
|
|
28
30
|
}
|
|
29
31
|
|
|
32
|
+
async countTodayMessages() {
|
|
33
|
+
const topics = await MessageModel.queryAll();
|
|
34
|
+
return topics.filter(
|
|
35
|
+
(item) => dayjs(item.createdAt).format('YYYY-MM-DD') === dayjs().format('YYYY-MM-DD'),
|
|
36
|
+
).length;
|
|
37
|
+
}
|
|
38
|
+
|
|
30
39
|
async getAllMessagesInSession(sessionId: string) {
|
|
31
40
|
return MessageModel.queryBySessionId(sessionId);
|
|
32
41
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Mock, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
2
|
|
|
3
|
+
import { SessionModel } from '@/database/client/models/session';
|
|
3
4
|
import { CreateTopicParams, TopicModel } from '@/database/client/models/topic';
|
|
4
5
|
import { ChatTopic } from '@/types/topic';
|
|
5
6
|
|
|
@@ -13,6 +14,7 @@ vi.mock('@/database/client/models/topic', () => {
|
|
|
13
14
|
create: vi.fn(),
|
|
14
15
|
query: vi.fn(),
|
|
15
16
|
delete: vi.fn(),
|
|
17
|
+
count: vi.fn(),
|
|
16
18
|
batchDeleteBySessionId: vi.fn(),
|
|
17
19
|
batchDelete: vi.fn(),
|
|
18
20
|
clearTable: vi.fn(),
|
|
@@ -214,4 +216,30 @@ describe('TopicService', () => {
|
|
|
214
216
|
expect(result).toBe(mockTopics);
|
|
215
217
|
});
|
|
216
218
|
});
|
|
219
|
+
|
|
220
|
+
describe('countTopics', () => {
|
|
221
|
+
it('should return false if no topics exist', async () => {
|
|
222
|
+
// Setup
|
|
223
|
+
(TopicModel.count as Mock).mockResolvedValue(0);
|
|
224
|
+
|
|
225
|
+
// Execute
|
|
226
|
+
const result = await topicService.countTopics();
|
|
227
|
+
|
|
228
|
+
// Assert
|
|
229
|
+
expect(TopicModel.count).toHaveBeenCalled();
|
|
230
|
+
expect(result).toBe(0);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('should return true if topics exist', async () => {
|
|
234
|
+
// Setup
|
|
235
|
+
(TopicModel.count as Mock).mockResolvedValue(1);
|
|
236
|
+
|
|
237
|
+
// Execute
|
|
238
|
+
const result = await topicService.countTopics();
|
|
239
|
+
|
|
240
|
+
// Assert
|
|
241
|
+
expect(TopicModel.count).toHaveBeenCalled();
|
|
242
|
+
expect(result).toBe(1);
|
|
243
|
+
});
|
|
244
|
+
});
|
|
217
245
|
});
|
|
@@ -34,6 +34,10 @@ export class ClientService implements ITopicService {
|
|
|
34
34
|
return TopicModel.queryAll();
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
async countTopics() {
|
|
38
|
+
return TopicModel.count();
|
|
39
|
+
}
|
|
40
|
+
|
|
37
41
|
async updateTopicFavorite(id: string, favorite?: boolean) {
|
|
38
42
|
return this.updateTopic(id, { favorite });
|
|
39
43
|
}
|