@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 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
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#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
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#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
- 版)的部署基本原理和流程,因此只包含核心环境变量配置的内容。如果你还不了解 LobeChat DB
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.13",
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.2.33",
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={'lobe-mobile-scroll-container'}
20
+ id={id}
20
21
  style={{
21
22
  overflowX: 'hidden',
22
23
  overflowY: 'auto',
@@ -46,7 +46,6 @@ const Mistral: ModelProviderCard = {
46
46
  {
47
47
  description: 'Codestral是专注于代码生成的尖端生成模型,优化了中间填充和代码补全任务。',
48
48
  displayName: 'Codestral',
49
- enabled: true,
50
49
  id: 'codestral-latest',
51
50
  pricing: {
52
51
  input: 0.2,
@@ -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
- 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
+ };