@lobehub/chat 1.1.10 → 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,31 @@
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
+
5
30
  ### [Version 1.1.10](https://github.com/lobehub/lobe-chat/compare/v1.1.9...v1.1.10)
6
31
 
7
32
  <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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "1.1.10",
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",
@@ -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>
@@ -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
- };