@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
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#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.14",
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={'lobe-mobile-scroll-container'}
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
- fullWidth?: boolean;
10
+
11
+ maxWidth?: number;
11
12
  }
12
13
  const SettingContainer = memo<PropsWithChildren<SettingContainerProps>>(
13
- ({ children, addonAfter, addonBefore, fullWidth }) => {
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: fullWidth ? undefined : 1024,
39
+ maxWidth,
29
40
  }}
30
41
  width={'100%'}
31
42
  >
@@ -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(null);
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(null)).toBe(undefined);
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
  });
@@ -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
- * format number with comma
75
- * @param num
76
- */
77
- export const formatNumber = (num: any) => {
78
- if (!num) return;
79
- return new Intl.NumberFormat('en-US').format(num);
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
- const [a, b] = price.toFixed(2).split('.');
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
+ };