@lobehub/chat 1.22.13 → 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 +50 -0
- package/docs/self-hosting/server-database/docker.zh-CN.mdx +3 -4
- package/package.json +2 -2
- package/src/components/server/MobileNavLayout.tsx +2 -1
- package/src/config/modelProviders/mistral.ts +0 -1
- package/src/config/modelProviders/wenxin.ts +1 -1
- package/src/features/Setting/SettingContainer.tsx +17 -6
- package/src/utils/format.test.ts +66 -2
- package/src/utils/format.ts +35 -10
package/CHANGELOG.md
CHANGED
@@ -2,6 +2,56 @@
|
|
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
|
+
|
30
|
+
### [Version 1.22.14](https://github.com/lobehub/lobe-chat/compare/v1.22.13...v1.22.14)
|
31
|
+
|
32
|
+
<sup>Released on **2024-10-20**</sup>
|
33
|
+
|
34
|
+
#### 💄 Styles
|
35
|
+
|
36
|
+
- **misc**: Update wenxin 4.0 turbo model to latest.
|
37
|
+
|
38
|
+
<br/>
|
39
|
+
|
40
|
+
<details>
|
41
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
42
|
+
|
43
|
+
#### Styles
|
44
|
+
|
45
|
+
- **misc**: Update wenxin 4.0 turbo model to latest, closes [#4428](https://github.com/lobehub/lobe-chat/issues/4428) ([3389fbb](https://github.com/lobehub/lobe-chat/commit/3389fbb))
|
46
|
+
|
47
|
+
</details>
|
48
|
+
|
49
|
+
<div align="right">
|
50
|
+
|
51
|
+
[](#readme-top)
|
52
|
+
|
53
|
+
</div>
|
54
|
+
|
5
55
|
### [Version 1.22.13](https://github.com/lobehub/lobe-chat/compare/v1.22.12...v1.22.13)
|
6
56
|
|
7
57
|
<sup>Released on **2024-10-20**</sup>
|
@@ -20,10 +20,8 @@ tags:
|
|
20
20
|
</div>
|
21
21
|
|
22
22
|
<Callout type="info">
|
23
|
-
本文已经假定你了解了 LobeChat 服务端数据库版本(下简称 DB
|
24
|
-
|
25
|
-
版的部署原理,请先查阅 [使用服务端数据库部署](https://lobehub.com/zh/docs/self-hosting/advanced/s3/tencent-cloud) 。
|
26
|
-
此外,针对国内的腾讯云储存桶用户,可查询[配置腾讯云 COS 存储服务](https://lobehub.com/zh/docs/self-hosting/advanced/s3/tencent-cloud)。
|
23
|
+
本文已经假定你了解了 LobeChat 服务端数据库版本(下简称 DB 版)的部署基本原理和流程,因此只包含核心环境变量配置的内容。如果你还不了解 LobeChat DB 版的部署原理,请先查阅 [使用服务端数据库部署](/zh/docs/self-hosting/server-database) 。
|
24
|
+
此外,针对国内的腾讯云储存桶用户,可查询[配置腾讯云 COS 存储服务](/zh/docs/self-hosting/advanced/s3/tencent-cloud)。
|
27
25
|
</Callout>
|
28
26
|
|
29
27
|
<Callout type="warning">
|
@@ -165,6 +163,7 @@ $ docker run -it -d --name lobe-chat-database -p 3210:3210 \
|
|
165
163
|
,将会走到自身容器的 `localhost`,此时请尝试用 `host.docker.internal` 替代 `localhost`
|
166
164
|
</Callout>
|
167
165
|
|
166
|
+
|
168
167
|
[docker-pulls-link]: https://hub.docker.com/r/lobehub/lobe-chat-database
|
169
168
|
[docker-pulls-shield]: https://img.shields.io/docker/pulls/lobehub/lobe-chat-database?color=45cc11&labelColor=black&style=flat-square
|
170
169
|
[docker-release-link]: https://hub.docker.com/r/lobehub/lobe-chat-database
|
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",
|
@@ -119,7 +119,7 @@
|
|
119
119
|
"@huggingface/inference": "^2.8.1",
|
120
120
|
"@icons-pack/react-simple-icons": "9.6.0",
|
121
121
|
"@khmyznikov/pwa-install": "^0.3.9",
|
122
|
-
"@langchain/community": "^0.
|
122
|
+
"@langchain/community": "^0.3.0",
|
123
123
|
"@lobehub/chat-plugin-sdk": "^1.32.4",
|
124
124
|
"@lobehub/chat-plugins-gateway": "^1.9.0",
|
125
125
|
"@lobehub/icons": "^1.35.4",
|
@@ -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',
|
@@ -71,7 +71,7 @@ const BaiduWenxin: ModelProviderCard = {
|
|
71
71
|
'百度自研的旗舰级超大规模⼤语⾔模型,综合效果表现出色,广泛适用于各领域复杂任务场景;支持自动对接百度搜索插件,保障问答信息时效。相较于ERNIE 4.0在性能表现上更优秀',
|
72
72
|
displayName: 'ERNIE 4.0 Turbo 8K',
|
73
73
|
enabled: true,
|
74
|
-
id: 'ERNIE-4.0-Turbo-8K',
|
74
|
+
id: 'ERNIE-4.0-Turbo-8K-Latest',
|
75
75
|
pricing: {
|
76
76
|
currency: 'CNY',
|
77
77
|
input: 20,
|
@@ -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
|
+
};
|