@lobehub/chat 1.1.9 → 1.1.11

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 CHANGED
@@ -2,6 +2,48 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ### [Version 1.1.11](https://github.com/lobehub/lobe-chat/compare/v1.1.10...v1.1.11)
6
+
7
+ <sup>Released on **2024-06-25**</sup>
8
+
9
+ #### ♻ Code Refactoring
10
+
11
+ - **misc**: Refactor format utils.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### Code refactoring
19
+
20
+ - **misc**: Refactor format utils, closes [#3034](https://github.com/lobehub/lobe-chat/issues/3034) ([8e54ca0](https://github.com/lobehub/lobe-chat/commit/8e54ca0))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
30
+ ### [Version 1.1.10](https://github.com/lobehub/lobe-chat/compare/v1.1.9...v1.1.10)
31
+
32
+ <sup>Released on **2024-06-24**</sup>
33
+
34
+ <br/>
35
+
36
+ <details>
37
+ <summary><kbd>Improvements and Fixes</kbd></summary>
38
+
39
+ </details>
40
+
41
+ <div align="right">
42
+
43
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
44
+
45
+ </div>
46
+
5
47
  ### [Version 1.1.9](https://github.com/lobehub/lobe-chat/compare/v1.1.8...v1.1.9)
6
48
 
7
49
  <sup>Released on **2024-06-24**</sup>
package/README.md CHANGED
@@ -39,7 +39,7 @@ One-click **FREE** deployment of your private OpenAI ChatGPT/Claude/Gemini/Groq/
39
39
 
40
40
  <sup>Pioneering the new age of thinking and creating. Built for you, the Super Individual.</sup>
41
41
 
42
- [![][github-trending-shield]][github-trending-url]
42
+ [![][github-trending-shield]][github-trending-url] <a href="https://hellogithub.com/repository/39701baf5a734cb894ec812248a5655a" target="_blank"><img src="https://abroad.hellogithub.com/v1/widgets/recommend.svg?rid=39701baf5a734cb894ec812248a5655a&claim_uid=HxYvFN34htJzGCD" alt="Featured|HelloGitHub" style="width: 250px; height: 54px;" width="250" height="54" /></a>
43
43
 
44
44
  [![][image-overview]][vercel-link]
45
45
 
package/README.zh-CN.md CHANGED
@@ -39,6 +39,7 @@
39
39
  <sup>探索私人生产力的未来。在个体崛起的时代中为你打造.</sup>
40
40
 
41
41
  [![][github-trending-shield]][github-trending-url]
42
+ [![][github-hello-shield]][github-hello-url]
42
43
 
43
44
  [![][image-overview]][vercel-link]
44
45
 
@@ -752,6 +753,8 @@ This project is [Apache 2.0](./LICENSE) licensed.
752
753
  [github-contributors-shield]: https://img.shields.io/github/contributors/lobehub/lobe-chat?color=c4f042&labelColor=black&style=flat-square
753
754
  [github-forks-link]: https://github.com/lobehub/lobe-chat/network/members
754
755
  [github-forks-shield]: https://img.shields.io/github/forks/lobehub/lobe-chat?color=8ae8ff&labelColor=black&style=flat-square
756
+ [github-hello-shield]: https://abroad.hellogithub.com/v1/widgets/recommend.svg?rid=39701baf5a734cb894ec812248a5655a&claim_uid=HxYvFN34htJzGCD&theme=dark&theme=neutral&theme=dark&theme=neutral
757
+ [github-hello-url]: https://hellogithub.com/repository/39701baf5a734cb894ec812248a5655a
755
758
  [github-issues-link]: https://github.com/lobehub/lobe-chat/issues
756
759
  [github-issues-shield]: https://img.shields.io/github/issues/lobehub/lobe-chat?color=ff80eb&labelColor=black&style=flat-square
757
760
  [github-license-link]: https://github.com/lobehub/lobe-chat/blob/main/LICENSE
@@ -129,6 +129,7 @@
129
129
  },
130
130
  "layoutInitializing": "جاري تحميل التخطيط...",
131
131
  "legal": "بيان قانوني",
132
+ "loading": "جارِ التحميل...",
132
133
  "mail": {
133
134
  "business": "شراكات تجارية",
134
135
  "support": "الدعم عبر البريد الإلكتروني"
@@ -129,6 +129,7 @@
129
129
  },
130
130
  "layoutInitializing": "Инициализиране на оформлението...",
131
131
  "legal": "Правно уведомление",
132
+ "loading": "Зареждане...",
132
133
  "mail": {
133
134
  "business": "Бизнес сътрудничество",
134
135
  "support": "Поддръжка по имейл"
@@ -129,6 +129,7 @@
129
129
  },
130
130
  "layoutInitializing": "Layout wird geladen...",
131
131
  "legal": "Rechtliches",
132
+ "loading": "Laden...",
132
133
  "mail": {
133
134
  "business": "Geschäftliche Zusammenarbeit",
134
135
  "support": "E-Mail-Support"
@@ -129,6 +129,7 @@
129
129
  },
130
130
  "layoutInitializing": "Initializing layout...",
131
131
  "legal": "Legal Disclaimer",
132
+ "loading": "Loading...",
132
133
  "mail": {
133
134
  "business": "Business Cooperation",
134
135
  "support": "Email Support"
@@ -129,6 +129,7 @@
129
129
  },
130
130
  "layoutInitializing": "Inicializando diseño...",
131
131
  "legal": "Aviso Legal",
132
+ "loading": "Cargando...",
132
133
  "mail": {
133
134
  "business": "Colaboración Comercial",
134
135
  "support": "Soporte por Correo"
@@ -129,6 +129,7 @@
129
129
  },
130
130
  "layoutInitializing": "Initialisation de la mise en page en cours...",
131
131
  "legal": "Mentions légales",
132
+ "loading": "Chargement en cours...",
132
133
  "mail": {
133
134
  "business": "Partenariats commerciaux",
134
135
  "support": "Support par e-mail"
@@ -129,6 +129,7 @@
129
129
  },
130
130
  "layoutInitializing": "Inizializzazione layout in corso...",
131
131
  "legal": "Avviso legale",
132
+ "loading": "Caricamento in corso...",
132
133
  "mail": {
133
134
  "business": "Collaborazioni commerciali",
134
135
  "support": "Supporto via email"
@@ -129,6 +129,7 @@
129
129
  },
130
130
  "layoutInitializing": "レイアウトを初期化中...",
131
131
  "legal": "法的声明",
132
+ "loading": "読み込み中...",
132
133
  "mail": {
133
134
  "business": "ビジネス提携",
134
135
  "support": "メールサポート"
@@ -129,6 +129,7 @@
129
129
  },
130
130
  "layoutInitializing": "레이아웃을 불러오는 중...",
131
131
  "legal": "법적 고지",
132
+ "loading": "로딩 중...",
132
133
  "mail": {
133
134
  "business": "비즈니스 협력",
134
135
  "support": "이메일 지원"
@@ -129,6 +129,7 @@
129
129
  },
130
130
  "layoutInitializing": "Lay-out wordt geladen...",
131
131
  "legal": "Juridisch",
132
+ "loading": "Laden...",
132
133
  "mail": {
133
134
  "business": "Zakelijke samenwerking",
134
135
  "support": "E-mailondersteuning"
@@ -129,6 +129,7 @@
129
129
  },
130
130
  "layoutInitializing": "Inicjowanie układu...",
131
131
  "legal": "Oświadczenie prawne",
132
+ "loading": "Ładowanie...",
132
133
  "mail": {
133
134
  "business": "Współpraca biznesowa",
134
135
  "support": "Wsparcie mailowe"
@@ -129,6 +129,7 @@
129
129
  },
130
130
  "layoutInitializing": "Inicializando layout...",
131
131
  "legal": "Aviso Legal",
132
+ "loading": "Carregando...",
132
133
  "mail": {
133
134
  "business": "Parcerias Comerciais",
134
135
  "support": "Suporte por E-mail"
@@ -129,6 +129,7 @@
129
129
  },
130
130
  "layoutInitializing": "Инициализация макета...",
131
131
  "legal": "Юридическое уведомление",
132
+ "loading": "Загрузка...",
132
133
  "mail": {
133
134
  "business": "Деловое сотрудничество",
134
135
  "support": "Поддержка по электронной почте"
@@ -129,6 +129,7 @@
129
129
  },
130
130
  "layoutInitializing": "Başlatılıyor...",
131
131
  "legal": "Hukuki Bildirim",
132
+ "loading": "Yükleniyor...",
132
133
  "mail": {
133
134
  "business": "İşbirliği",
134
135
  "support": "E-posta Desteği"
@@ -129,6 +129,7 @@
129
129
  },
130
130
  "layoutInitializing": "Đang tải bố cục...",
131
131
  "legal": "Tuyên bố về pháp lý",
132
+ "loading": "Đang tải...",
132
133
  "mail": {
133
134
  "business": "Hợp tác kinh doanh",
134
135
  "support": "Hỗ trợ qua email"
@@ -129,6 +129,7 @@
129
129
  },
130
130
  "layoutInitializing": "正在加载布局...",
131
131
  "legal": "法律声明",
132
+ "loading": "加载中...",
132
133
  "mail": {
133
134
  "business": "商务合作",
134
135
  "support": "邮件支持"
@@ -129,6 +129,7 @@
129
129
  },
130
130
  "layoutInitializing": "正在載入版面配置...",
131
131
  "legal": "法律聲明",
132
+ "loading": "載入中...",
132
133
  "mail": {
133
134
  "business": "商務合作",
134
135
  "support": "郵件支持"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "1.1.9",
3
+ "version": "1.1.11",
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",
@@ -0,0 +1,21 @@
1
+ 'use client';
2
+
3
+ import { Icon } from '@lobehub/ui';
4
+ import { Typography } from 'antd';
5
+ import { LoaderCircle } from 'lucide-react';
6
+ import { useTranslation } from 'react-i18next';
7
+ import { Center, Flexbox } from 'react-layout-kit';
8
+
9
+ export default () => {
10
+ const { t } = useTranslation('common');
11
+ return (
12
+ <Center height={'100%'} width={'100%'}>
13
+ <Flexbox align={'center'} gap={8}>
14
+ <div>
15
+ <Icon icon={LoaderCircle} size={'large'} spin />
16
+ </div>
17
+ <Typography.Text type={'secondary'}>{t('loading')}</Typography.Text>
18
+ </Flexbox>
19
+ </Center>
20
+ );
21
+ };
@@ -8,9 +8,10 @@ import useSWR from 'swr';
8
8
 
9
9
  import { ollamaService } from '@/services/ollama';
10
10
  import { useChatStore } from '@/store/chat';
11
+ import { formatSize } from '@/utils/format';
11
12
 
12
13
  import { ErrorActionContainer, FormAction } from '../../style';
13
- import { formatSize, useDownloadMonitor } from './useDownloadMonitor';
14
+ import { useDownloadMonitor } from './useDownloadMonitor';
14
15
 
15
16
  interface OllamaModelFormProps {
16
17
  id: string;
@@ -1,23 +1,6 @@
1
1
  import { useEffect, useRef, useState } from 'react';
2
2
 
3
- import { formatTime } from '@/utils/speed';
4
-
5
- export const formatSize = (bytes: number): string => {
6
- const kbSize = bytes / 1024;
7
- if (kbSize < 1024) {
8
- return `${kbSize.toFixed(1)} KB`;
9
- } else if (kbSize < 1_048_576) {
10
- const mbSize = kbSize / 1024;
11
- return `${mbSize.toFixed(1)} MB`;
12
- } else {
13
- const gbSize = kbSize / 1_048_576;
14
- return `${gbSize.toFixed(1)} GB`;
15
- }
16
- };
17
-
18
- const formatSpeed = (speed: number): string => {
19
- return `${formatSize(speed)}/s`;
20
- };
3
+ import { formatSpeed, formatTime } from '@/utils/format';
21
4
 
22
5
  export const useDownloadMonitor = (totalSize: number, completedSize: number) => {
23
6
  const [downloadSpeed, setDownloadSpeed] = useState<string>('0 KB/s');
@@ -4,7 +4,7 @@ import React, { memo } from 'react';
4
4
  import { useTranslation } from 'react-i18next';
5
5
  import { Flexbox } from 'react-layout-kit';
6
6
 
7
- import { formatSpeed, formatTime } from '@/utils/speed';
7
+ import { formatSpeed, formatTime } from '@/utils/format';
8
8
 
9
9
  import DataLoading from './Loading';
10
10
 
@@ -40,7 +40,7 @@ export const FileUploading = memo<FileUploadingProps>(({ progress = 0, speed = 0
40
40
  {t('importModal.uploading.restTime')}: {restTime ? formatTime(restTime) : '-'}
41
41
  </span>
42
42
  <span>
43
- {t('importModal.uploading.speed')}: {formatSpeed(speed)}
43
+ {t('importModal.uploading.speed')}: {formatSpeed(speed * 1024)}
44
44
  </span>
45
45
  </Flexbox>
46
46
  </Flexbox>
@@ -129,6 +129,7 @@ export default {
129
129
  },
130
130
  layoutInitializing: '正在加载布局...',
131
131
  legal: '法律声明',
132
+ loading: '加载中...',
132
133
  mail: {
133
134
  business: '商务合作',
134
135
  support: '邮件支持',
@@ -0,0 +1,75 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { formatSize, formatSpeed, formatTime } from './format';
4
+
5
+ describe('formatSize', () => {
6
+ it('should format bytes to KB correctly', () => {
7
+ expect(formatSize(1024)).toBe('1.0 KB');
8
+ expect(formatSize(2048)).toBe('2.0 KB');
9
+ expect(formatSize(1536)).toBe('1.5 KB');
10
+ });
11
+
12
+ it('should format bytes to MB correctly', () => {
13
+ expect(formatSize(1048576)).toBe('1.0 MB');
14
+ expect(formatSize(2097152)).toBe('2.0 MB');
15
+ expect(formatSize(1572864)).toBe('1.5 MB');
16
+ });
17
+
18
+ it('should format bytes to GB correctly', () => {
19
+ expect(formatSize(1073741824)).toBe('1.0 GB');
20
+ expect(formatSize(2147483648)).toBe('2.0 GB');
21
+ expect(formatSize(1610612736)).toBe('1.5 GB');
22
+ });
23
+
24
+ it('should handle edge cases', () => {
25
+ expect(formatSize(0)).toBe('0.0 KB');
26
+ expect(formatSize(1023)).toBe('1.0 KB');
27
+ expect(formatSize(1073741823)).toBe('1024.0 MB');
28
+ });
29
+ });
30
+
31
+ describe('formatSpeed', () => {
32
+ it('should format speed in KB/s correctly', () => {
33
+ expect(formatSpeed(10 * 1024)).toBe('10.00 KB/s');
34
+ expect(formatSpeed(999.99 * 1024)).toBe('999.99 KB/s');
35
+ });
36
+
37
+ it('should format speed in MB/s correctly', () => {
38
+ expect(formatSpeed(1024 * 1024)).toBe('1.00 MB/s');
39
+ expect(formatSpeed(10240 * 1024)).toBe('10.00 MB/s');
40
+ });
41
+
42
+ it('should format speed in GB/s correctly', () => {
43
+ expect(formatSpeed(1048576 * 1024)).toBe('1.00 GB/s');
44
+ expect(formatSpeed(10485760 * 1024)).toBe('10.00 GB/s');
45
+ });
46
+
47
+ it('should handle edge cases', () => {
48
+ expect(formatSpeed(0)).toBe('0.00 Byte/s');
49
+ expect(formatSpeed(1000 * 1024)).toBe('1000.00 KB/s');
50
+ expect(formatSpeed(1000.01 * 1024)).toBe('0.98 MB/s');
51
+ });
52
+ });
53
+
54
+ describe('formatTime', () => {
55
+ it('should format time in seconds correctly', () => {
56
+ expect(formatTime(30)).toBe('30.0 s');
57
+ expect(formatTime(59.9)).toBe('59.9 s');
58
+ });
59
+
60
+ it('should format time in minutes correctly', () => {
61
+ expect(formatTime(60)).toBe('1.0 min');
62
+ expect(formatTime(3599)).toBe('60.0 min');
63
+ });
64
+
65
+ it('should format time in hours correctly', () => {
66
+ expect(formatTime(3600)).toBe('1.00 h');
67
+ expect(formatTime(7200)).toBe('2.00 h');
68
+ });
69
+
70
+ it('should handle edge cases', () => {
71
+ expect(formatTime(0)).toBe('0.0 s');
72
+ expect(formatTime(59.99)).toBe('60.0 s');
73
+ expect(formatTime(3599.99)).toBe('60.0 min');
74
+ });
75
+ });
@@ -0,0 +1,48 @@
1
+ export const formatSize = (bytes: number, fractionDigits = 1): string => {
2
+ const kbSize = bytes / 1024;
3
+ if (kbSize < 1024) {
4
+ return `${kbSize.toFixed(fractionDigits)} KB`;
5
+ } else if (kbSize < 1_048_576) {
6
+ const mbSize = kbSize / 1024;
7
+ return `${mbSize.toFixed(fractionDigits)} MB`;
8
+ } else {
9
+ const gbSize = kbSize / 1_048_576;
10
+ return `${gbSize.toFixed(fractionDigits)} GB`;
11
+ }
12
+ };
13
+
14
+ /**
15
+ * format speed from Byte number to string like KB/s, MB/s or GB/s
16
+ */
17
+ export const formatSpeed = (byte: number, fractionDigits = 2) => {
18
+ let word = '';
19
+
20
+ // Byte
21
+ if (byte <= 1000) {
22
+ word = byte.toFixed(fractionDigits) + ' Byte/s';
23
+ }
24
+ // KB
25
+ else if (byte / 1024 <= 1000) {
26
+ word = (byte / 1024).toFixed(fractionDigits) + ' KB/s';
27
+ }
28
+ // MB
29
+ else if (byte / 1024 / 1024 <= 1000) {
30
+ word = (byte / 1024 / 1024).toFixed(fractionDigits) + ' MB/s';
31
+ }
32
+ // GB
33
+ else {
34
+ word = (byte / 1024 / 1024 / 1024).toFixed(fractionDigits) + ' GB/s';
35
+ }
36
+
37
+ return word;
38
+ };
39
+
40
+ export const formatTime = (timeInSeconds: number): string => {
41
+ if (timeInSeconds < 60) {
42
+ return `${timeInSeconds.toFixed(1)} s`;
43
+ } else if (timeInSeconds < 3600) {
44
+ return `${(timeInSeconds / 60).toFixed(1)} min`;
45
+ } else {
46
+ return `${(timeInSeconds / 3600).toFixed(2)} h`;
47
+ }
48
+ };
@@ -1,50 +0,0 @@
1
- import { useEffect, useState } from 'react';
2
-
3
- import { formatSpeed } from '@/utils/speed';
4
-
5
- // 用于测试的文件路径和尺寸
6
- const targetFile = { size: 15.4, url: '/favicon-32x32.ico' };
7
-
8
- const testDownloadSpeed = (url: string, size: number) =>
9
- new Promise<{ costTime: number; speed: number }>((resolve, reject) => {
10
- const img = new Image();
11
-
12
- img.src = `${url}?_t=${Math.random()}`; // 加个时间戳以避免浏览器只发起一次请求
13
-
14
- const startTime = Date.now();
15
-
16
- // eslint-disable-next-line unicorn/prefer-add-event-listener
17
- img.onload = function () {
18
- const fileSize = size; // 单位是 kb
19
- const endTime = Date.now();
20
- const costTime = endTime - startTime;
21
- const speed = (fileSize / (endTime - startTime)) * 1000; // 单位是 kb/s
22
- resolve({ costTime, speed });
23
- };
24
-
25
- // eslint-disable-next-line unicorn/prefer-add-event-listener
26
- img.onerror = reject;
27
- });
28
-
29
- /**
30
- * return the download speed of the network
31
- */
32
- export const useDownloadSpeed = () => {
33
- const [speed, setSpeed] = useState('-');
34
-
35
- useEffect(() => {
36
- const handleSpeed = async () => {
37
- let { speed } = await testDownloadSpeed(targetFile.url, targetFile.size);
38
-
39
- setSpeed(formatSpeed(speed));
40
- };
41
-
42
- const interval = setInterval(handleSpeed, 3000);
43
-
44
- return () => {
45
- clearInterval(interval);
46
- };
47
- }, []);
48
-
49
- return speed;
50
- };
@@ -1,32 +0,0 @@
1
- /**
2
- * format speed from KB number to string like KB/s, MB/s or GB/s
3
- * @param speed
4
- */
5
- export const formatSpeed = (speed: number) => {
6
- let word = '';
7
-
8
- // KB
9
- if (speed <= 1000) {
10
- word = speed.toFixed(2) + 'KB/s';
11
- }
12
- // MB
13
- else if (speed / 1024 <= 1000) {
14
- word = (speed / 1024).toFixed(2) + 'MB/s';
15
- }
16
- // GB
17
- else {
18
- word = (speed / 1024 / 1024).toFixed(2) + 'GB/s';
19
- }
20
-
21
- return word;
22
- };
23
-
24
- export const formatTime = (timeInSeconds: number): string => {
25
- if (timeInSeconds < 60) {
26
- return `${timeInSeconds.toFixed(1)} s`;
27
- } else if (timeInSeconds < 3600) {
28
- return `${(timeInSeconds / 60).toFixed(1)} min`;
29
- } else {
30
- return `${(timeInSeconds / 3600).toFixed(2)} h`;
31
- }
32
- };