@lobehub/chat 1.22.14 → 1.22.15
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.22.15](https://github.com/lobehub/lobe-chat/compare/v1.22.14...v1.22.15)
|
6
|
+
|
7
|
+
<sup>Released on **2024-10-21**</sup>
|
8
|
+
|
9
|
+
#### ♻ Code Refactoring
|
10
|
+
|
11
|
+
- **misc**: Update format utils and shared layout.
|
12
|
+
|
13
|
+
<br/>
|
14
|
+
|
15
|
+
<details>
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
17
|
+
|
18
|
+
#### Code refactoring
|
19
|
+
|
20
|
+
- **misc**: Update format utils and shared layout, closes [#4431](https://github.com/lobehub/lobe-chat/issues/4431) ([56ed073](https://github.com/lobehub/lobe-chat/commit/56ed073))
|
21
|
+
|
22
|
+
</details>
|
23
|
+
|
24
|
+
<div align="right">
|
25
|
+
|
26
|
+
[](#readme-top)
|
27
|
+
|
28
|
+
</div>
|
29
|
+
|
5
30
|
### [Version 1.22.14](https://github.com/lobehub/lobe-chat/compare/v1.22.13...v1.22.14)
|
6
31
|
|
7
32
|
<sup>Released on **2024-10-20**</sup>
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@lobehub/chat",
|
3
|
-
"version": "1.22.
|
3
|
+
"version": "1.22.15",
|
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",
|
@@ -11,12 +11,13 @@ const MobileContentLayout = ({
|
|
11
11
|
withNav,
|
12
12
|
style,
|
13
13
|
header,
|
14
|
+
id = 'lobe-mobile-scroll-container',
|
14
15
|
...rest
|
15
16
|
}: MobileContentLayoutProps) => {
|
16
17
|
const content = (
|
17
18
|
<Flexbox
|
18
19
|
height="100%"
|
19
|
-
id={
|
20
|
+
id={id}
|
20
21
|
style={{
|
21
22
|
overflowX: 'hidden',
|
22
23
|
overflowY: 'auto',
|
@@ -2,30 +2,41 @@
|
|
2
2
|
|
3
3
|
import { useResponsive } from 'antd-style';
|
4
4
|
import { PropsWithChildren, ReactNode, memo } from 'react';
|
5
|
-
import { Flexbox } from 'react-layout-kit';
|
5
|
+
import { Flexbox, FlexboxProps } from 'react-layout-kit';
|
6
6
|
|
7
|
-
interface SettingContainerProps {
|
7
|
+
interface SettingContainerProps extends FlexboxProps {
|
8
8
|
addonAfter?: ReactNode;
|
9
9
|
addonBefore?: ReactNode;
|
10
|
-
|
10
|
+
|
11
|
+
maxWidth?: number;
|
11
12
|
}
|
12
13
|
const SettingContainer = memo<PropsWithChildren<SettingContainerProps>>(
|
13
|
-
({
|
14
|
+
({
|
15
|
+
id = 'lobe-desktop-scroll-container',
|
16
|
+
maxWidth = 1024,
|
17
|
+
children,
|
18
|
+
addonAfter,
|
19
|
+
addonBefore,
|
20
|
+
style,
|
21
|
+
...rest
|
22
|
+
}) => {
|
14
23
|
const { mobile = false } = useResponsive();
|
15
24
|
return (
|
16
25
|
<Flexbox
|
17
26
|
align={'center'}
|
18
27
|
height={'100%'}
|
28
|
+
id={id}
|
19
29
|
paddingBlock={mobile ? undefined : 32}
|
20
|
-
style={{ overflowX: 'hidden', overflowY: 'auto' }}
|
30
|
+
style={{ overflowX: 'hidden', overflowY: 'auto', ...style }}
|
21
31
|
width={'100%'}
|
32
|
+
{...rest}
|
22
33
|
>
|
23
34
|
{addonBefore}
|
24
35
|
<Flexbox
|
25
36
|
gap={64}
|
26
37
|
paddingInline={mobile ? undefined : 24}
|
27
38
|
style={{
|
28
|
-
maxWidth
|
39
|
+
maxWidth,
|
29
40
|
}}
|
30
41
|
width={'100%'}
|
31
42
|
>
|
package/src/utils/format.test.ts
CHANGED
@@ -1,6 +1,11 @@
|
|
1
|
+
import dayjs from 'dayjs';
|
1
2
|
import { describe, expect, it } from 'vitest';
|
2
3
|
|
4
|
+
import { CNY_TO_USD } from '@/const/discover';
|
5
|
+
|
3
6
|
import {
|
7
|
+
formatDate,
|
8
|
+
formatIntergerNumber,
|
4
9
|
formatNumber,
|
5
10
|
formatPrice,
|
6
11
|
formatPriceByCurrency,
|
@@ -37,6 +42,15 @@ describe('format', () => {
|
|
37
42
|
expect(formatSize(1023)).toBe('1.0 KB');
|
38
43
|
expect(formatSize(1073741823)).toBe('1024.0 MB');
|
39
44
|
});
|
45
|
+
|
46
|
+
it('should handle undefined input', () => {
|
47
|
+
expect(formatSize(undefined as any)).toBe('--');
|
48
|
+
});
|
49
|
+
|
50
|
+
it('should use custom fraction digits', () => {
|
51
|
+
expect(formatSize(1536, 2)).toBe('1.50 KB');
|
52
|
+
expect(formatSize(1572864, 3)).toBe('1.500 MB');
|
53
|
+
});
|
40
54
|
});
|
41
55
|
|
42
56
|
describe('formatSpeed', () => {
|
@@ -60,6 +74,14 @@ describe('format', () => {
|
|
60
74
|
expect(formatSpeed(1000 * 1024)).toBe('1000.00 KB/s');
|
61
75
|
expect(formatSpeed(1000.01 * 1024)).toBe('0.98 MB/s');
|
62
76
|
});
|
77
|
+
|
78
|
+
it('should handle undefined input', () => {
|
79
|
+
expect(formatSpeed(undefined as any)).toBe('--');
|
80
|
+
});
|
81
|
+
|
82
|
+
it('should use custom fraction digits', () => {
|
83
|
+
expect(formatSpeed(1024, 3)).toBe('1.000 KB/s');
|
84
|
+
});
|
63
85
|
});
|
64
86
|
|
65
87
|
describe('formatTime', () => {
|
@@ -88,12 +110,16 @@ describe('format', () => {
|
|
88
110
|
expect(formatTime(59.99)).toBe('60.0 s');
|
89
111
|
expect(formatTime(3599.99)).toBe('60.0 min');
|
90
112
|
});
|
113
|
+
it('should handle non-number inputs', () => {
|
114
|
+
expect(formatTime('not a number' as any)).toBe('not a number');
|
115
|
+
expect(formatTime(undefined as any)).toBe('--');
|
116
|
+
});
|
91
117
|
});
|
92
118
|
|
93
119
|
describe('formatShortenNumber', () => {
|
94
120
|
it('should return the input if it is not a number', () => {
|
95
121
|
expect(formatShortenNumber('not a number')).toBe('not a number');
|
96
|
-
expect(formatShortenNumber(null)).toBe(
|
122
|
+
expect(formatShortenNumber(null)).toBe('--');
|
97
123
|
});
|
98
124
|
|
99
125
|
it('should format numbers less than 10,000 correctly', () => {
|
@@ -125,7 +151,22 @@ describe('format', () => {
|
|
125
151
|
it('should handle non-number inputs', () => {
|
126
152
|
expect(formatNumber('1000')).toBe('1,000');
|
127
153
|
expect(formatNumber('not a number')).toBe(Number.NaN.toString());
|
128
|
-
expect(formatNumber(
|
154
|
+
expect(formatNumber(0)).toBe('0');
|
155
|
+
expect(formatNumber(0, 1)).toBe('0.0');
|
156
|
+
expect(formatNumber(null)).toBe('--');
|
157
|
+
});
|
158
|
+
|
159
|
+
it('should handle fraction digits correctly', () => {
|
160
|
+
expect(formatNumber(1234.5678, 2)).toBe('1,234.57');
|
161
|
+
expect(formatNumber(1234.5678, 3)).toBe('1,234.568');
|
162
|
+
});
|
163
|
+
});
|
164
|
+
|
165
|
+
describe('formatIntergerNumber', () => {
|
166
|
+
it('should format numbers with commas correctly', () => {
|
167
|
+
expect(formatIntergerNumber(1000.12)).toBe('1,000');
|
168
|
+
expect(formatIntergerNumber(0)).toBe('0');
|
169
|
+
expect(formatIntergerNumber(null)).toBe('--');
|
129
170
|
});
|
130
171
|
});
|
131
172
|
|
@@ -150,6 +191,12 @@ describe('format', () => {
|
|
150
191
|
expect(formatPriceByCurrency(1000, 'CNY')).toBe('140.06');
|
151
192
|
expect(formatPriceByCurrency(6500, 'CNY')).toBe('910.36');
|
152
193
|
});
|
194
|
+
|
195
|
+
it('should use the correct CNY_TO_USD conversion rate', () => {
|
196
|
+
const price = 1000;
|
197
|
+
const expectedCNY = formatPrice(price / CNY_TO_USD);
|
198
|
+
expect(formatPriceByCurrency(price, 'CNY')).toBe(expectedCNY);
|
199
|
+
});
|
153
200
|
});
|
154
201
|
|
155
202
|
describe('formatTokenNumber', () => {
|
@@ -192,4 +239,21 @@ describe('format', () => {
|
|
192
239
|
expect(formatTokenNumber(2097152)).toBe('2M'); // Gemini Pro
|
193
240
|
});
|
194
241
|
});
|
242
|
+
|
243
|
+
describe('formatDate', () => {
|
244
|
+
it('should format date correctly', () => {
|
245
|
+
const date = new Date('2023-05-15T12:00:00Z');
|
246
|
+
expect(formatDate(date)).toBe('2023-05-15');
|
247
|
+
});
|
248
|
+
|
249
|
+
it('should handle undefined input', () => {
|
250
|
+
expect(formatDate(undefined)).toBe('--');
|
251
|
+
});
|
252
|
+
|
253
|
+
it('should use dayjs for formatting', () => {
|
254
|
+
const date = new Date('2023-05-15T12:00:00Z');
|
255
|
+
const expectedFormat = dayjs(date).format('YYYY-MM-DD');
|
256
|
+
expect(formatDate(date)).toBe(expectedFormat);
|
257
|
+
});
|
258
|
+
});
|
195
259
|
});
|
package/src/utils/format.ts
CHANGED
@@ -1,10 +1,13 @@
|
|
1
|
+
import dayjs from 'dayjs';
|
1
2
|
import { isNumber } from 'lodash-es';
|
2
3
|
import numeral from 'numeral';
|
3
4
|
|
4
5
|
import { CNY_TO_USD } from '@/const/discover';
|
5
6
|
import { ModelPriceCurrency } from '@/types/llm';
|
6
7
|
|
7
|
-
export const formatSize = (bytes: number, fractionDigits = 1): string => {
|
8
|
+
export const formatSize = (bytes: number, fractionDigits: number = 1): string => {
|
9
|
+
if (!bytes && bytes !== 0) return '--';
|
10
|
+
|
8
11
|
const kbSize = bytes / 1024;
|
9
12
|
if (kbSize < 1024) {
|
10
13
|
return `${kbSize.toFixed(fractionDigits)} KB`;
|
@@ -21,6 +24,8 @@ export const formatSize = (bytes: number, fractionDigits = 1): string => {
|
|
21
24
|
* format speed from Byte number to string like KB/s, MB/s or GB/s
|
22
25
|
*/
|
23
26
|
export const formatSpeed = (byte: number, fractionDigits = 2) => {
|
27
|
+
if (!byte && byte !== 0) return '--';
|
28
|
+
|
24
29
|
let word = '';
|
25
30
|
|
26
31
|
// Byte
|
@@ -44,6 +49,9 @@ export const formatSpeed = (byte: number, fractionDigits = 2) => {
|
|
44
49
|
};
|
45
50
|
|
46
51
|
export const formatTime = (timeInSeconds: number): string => {
|
52
|
+
if (!timeInSeconds && timeInSeconds !== 0) return '--';
|
53
|
+
if (!isNumber(timeInSeconds)) return timeInSeconds;
|
54
|
+
|
47
55
|
if (timeInSeconds < 60) {
|
48
56
|
return `${timeInSeconds.toFixed(1)} s`;
|
49
57
|
} else if (timeInSeconds < 3600) {
|
@@ -54,7 +62,9 @@ export const formatTime = (timeInSeconds: number): string => {
|
|
54
62
|
};
|
55
63
|
|
56
64
|
export const formatShortenNumber = (num: any) => {
|
65
|
+
if (!num && num !== 0) return '--';
|
57
66
|
if (!isNumber(num)) return num;
|
67
|
+
|
58
68
|
// 使用Intl.NumberFormat来添加千分号
|
59
69
|
const formattedWithComma = new Intl.NumberFormat('en-US').format(num);
|
60
70
|
|
@@ -70,16 +80,23 @@ export const formatShortenNumber = (num: any) => {
|
|
70
80
|
}
|
71
81
|
};
|
72
82
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
83
|
+
export const formatNumber = (num: any, fractionDigits?: number) => {
|
84
|
+
if (!num && num !== 0) return '--';
|
85
|
+
|
86
|
+
if (!fractionDigits) return new Intl.NumberFormat('en-US').format(num);
|
87
|
+
const [a, b] = num.toFixed(fractionDigits).split('.');
|
88
|
+
return `${numeral(a).format('0,0')}.${b}`;
|
89
|
+
};
|
90
|
+
|
91
|
+
export const formatIntergerNumber = (num: any) => {
|
92
|
+
if (!num && num !== 0) return '--';
|
93
|
+
|
94
|
+
return numeral(num).format('0,0');
|
80
95
|
};
|
81
96
|
|
82
97
|
export const formatTokenNumber = (num: number): string => {
|
98
|
+
if (!num && num !== 0) return '--';
|
99
|
+
|
83
100
|
if (num > 0 && num < 1024) return '1K';
|
84
101
|
|
85
102
|
let kiloToken = Math.floor(num / 1024);
|
@@ -90,8 +107,10 @@ export const formatTokenNumber = (num: number): string => {
|
|
90
107
|
return kiloToken < 1000 ? `${kiloToken}K` : `${Math.floor(kiloToken / 1000)}M`;
|
91
108
|
};
|
92
109
|
|
93
|
-
export const formatPrice = (price: number) => {
|
94
|
-
|
110
|
+
export const formatPrice = (price: number, fractionDigits: number = 2) => {
|
111
|
+
if (!price && price !== 0) return '--';
|
112
|
+
|
113
|
+
const [a, b] = price.toFixed(fractionDigits).split('.');
|
95
114
|
return `${numeral(a).format('0,0')}.${b}`;
|
96
115
|
};
|
97
116
|
|
@@ -101,3 +120,9 @@ export const formatPriceByCurrency = (price: number, currency?: ModelPriceCurren
|
|
101
120
|
}
|
102
121
|
return formatPrice(price);
|
103
122
|
};
|
123
|
+
|
124
|
+
export const formatDate = (date?: Date) => {
|
125
|
+
if (!date) return '--';
|
126
|
+
|
127
|
+
return dayjs(date).format('YYYY-MM-DD');
|
128
|
+
};
|