@lobehub/chat 1.48.2 → 1.48.3
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 +25 -0
- package/changelog/v1.json +9 -0
- package/package.json +1 -1
- package/src/app/(main)/settings/provider/features/ModelList/ModelItem.tsx +31 -36
- package/src/const/currency.ts +2 -0
- package/src/const/discover.ts +0 -2
- package/src/utils/format.test.ts +2 -9
- package/src/utils/format.ts +2 -2
package/CHANGELOG.md
CHANGED
@@ -2,6 +2,31 @@
|
|
2
2
|
|
3
3
|
# Changelog
|
4
4
|
|
5
|
+
### [Version 1.48.3](https://github.com/lobehub/lobe-chat/compare/v1.48.2...v1.48.3)
|
6
|
+
|
7
|
+
<sup>Released on **2025-01-26**</sup>
|
8
|
+
|
9
|
+
#### 💄 Styles
|
10
|
+
|
11
|
+
- **misc**: Improve model pricing with CNY.
|
12
|
+
|
13
|
+
<br/>
|
14
|
+
|
15
|
+
<details>
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
17
|
+
|
18
|
+
#### Styles
|
19
|
+
|
20
|
+
- **misc**: Improve model pricing with CNY, closes [#5599](https://github.com/lobehub/lobe-chat/issues/5599) ([6d91457](https://github.com/lobehub/lobe-chat/commit/6d91457))
|
21
|
+
|
22
|
+
</details>
|
23
|
+
|
24
|
+
<div align="right">
|
25
|
+
|
26
|
+
[](#readme-top)
|
27
|
+
|
28
|
+
</div>
|
29
|
+
|
5
30
|
### [Version 1.48.2](https://github.com/lobehub/lobe-chat/compare/v1.48.1...v1.48.2)
|
6
31
|
|
7
32
|
<sup>Released on **2025-01-25**</sup>
|
package/changelog/v1.json
CHANGED
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@lobehub/chat",
|
3
|
-
"version": "1.48.
|
3
|
+
"version": "1.48.3",
|
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,10 @@ import { ModelInfoTags } from '@/components/ModelSelect';
|
|
11
11
|
import { useIsMobile } from '@/hooks/useIsMobile';
|
12
12
|
import { aiModelSelectors, useAiInfraStore } from '@/store/aiInfra';
|
13
13
|
import { AiModelSourceEnum, AiProviderModelListItem, ChatModelPricing } from '@/types/aiModel';
|
14
|
+
import { formatPriceByCurrency } from '@/utils/format';
|
14
15
|
|
15
16
|
import ModelConfigModal from './ModelConfigModal';
|
16
17
|
|
17
|
-
const f = (number: number | undefined, text: string) =>
|
18
|
-
typeof number !== 'undefined' ? text : undefined;
|
19
|
-
|
20
18
|
export const useStyles = createStyles(({ css, token, cx }) => {
|
21
19
|
const config = css`
|
22
20
|
opacity: 0;
|
@@ -74,7 +72,7 @@ const ModelItem = memo<ModelItemProps>(
|
|
74
72
|
type,
|
75
73
|
}) => {
|
76
74
|
const { styles } = useStyles();
|
77
|
-
const { t } = useTranslation(['modelProvider', 'components', 'models']);
|
75
|
+
const { t } = useTranslation(['modelProvider', 'components', 'models', 'common']);
|
78
76
|
const theme = useTheme();
|
79
77
|
|
80
78
|
const [activeAiProvider, isModelLoading, toggleModelEnabled, removeAiModel] = useAiInfraStore(
|
@@ -90,41 +88,43 @@ const ModelItem = memo<ModelItemProps>(
|
|
90
88
|
const [showConfig, setShowConfig] = useState(false);
|
91
89
|
|
92
90
|
const formatPricing = (): string[] => {
|
91
|
+
if (!pricing) return [];
|
92
|
+
|
93
93
|
switch (type) {
|
94
94
|
case 'chat': {
|
95
95
|
return [
|
96
|
-
|
97
|
-
pricing
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
pricing
|
102
|
-
|
103
|
-
|
96
|
+
typeof pricing.input === 'number' &&
|
97
|
+
t('providerModels.item.pricing.inputTokens', {
|
98
|
+
amount: formatPriceByCurrency(pricing.input, pricing?.currency),
|
99
|
+
}),
|
100
|
+
typeof pricing.output === 'number' &&
|
101
|
+
t('providerModels.item.pricing.outputTokens', {
|
102
|
+
amount: formatPriceByCurrency(pricing.output, pricing?.currency),
|
103
|
+
}),
|
104
104
|
].filter(Boolean) as string[];
|
105
105
|
}
|
106
106
|
case 'embedding': {
|
107
107
|
return [
|
108
|
-
|
109
|
-
pricing
|
110
|
-
|
111
|
-
|
108
|
+
typeof pricing.input === 'number' &&
|
109
|
+
t('providerModels.item.pricing.inputTokens', {
|
110
|
+
amount: formatPriceByCurrency(pricing.input, pricing?.currency),
|
111
|
+
}),
|
112
112
|
].filter(Boolean) as string[];
|
113
113
|
}
|
114
114
|
case 'tts': {
|
115
115
|
return [
|
116
|
-
|
117
|
-
pricing
|
118
|
-
|
119
|
-
|
116
|
+
typeof pricing.input === 'number' &&
|
117
|
+
t('providerModels.item.pricing.inputCharts', {
|
118
|
+
amount: formatPriceByCurrency(pricing.input, pricing?.currency),
|
119
|
+
}),
|
120
120
|
].filter(Boolean) as string[];
|
121
121
|
}
|
122
122
|
case 'stt': {
|
123
123
|
return [
|
124
|
-
|
125
|
-
pricing
|
126
|
-
|
127
|
-
|
124
|
+
typeof pricing.input === 'number' &&
|
125
|
+
t('providerModels.item.pricing.inputMinutes', {
|
126
|
+
amount: formatPriceByCurrency(pricing.input, pricing?.currency),
|
127
|
+
}),
|
128
128
|
].filter(Boolean) as string[];
|
129
129
|
}
|
130
130
|
|
@@ -142,7 +142,12 @@ const ModelItem = memo<ModelItemProps>(
|
|
142
142
|
releasedAt && t('providerModels.item.releasedAt', { releasedAt }),
|
143
143
|
...formatPricing(),
|
144
144
|
].filter(Boolean) as string[];
|
145
|
+
|
145
146
|
const { message, modal } = App.useApp();
|
147
|
+
const copyModelId = async () => {
|
148
|
+
await copyToClipboard(id);
|
149
|
+
message.success({ content: t('copySuccess', { ns: 'common' }) });
|
150
|
+
};
|
146
151
|
|
147
152
|
const isMobile = useIsMobile();
|
148
153
|
|
@@ -179,12 +184,7 @@ const ModelItem = memo<ModelItemProps>(
|
|
179
184
|
</Flexbox>
|
180
185
|
</Flexbox>
|
181
186
|
<div>
|
182
|
-
<Tag
|
183
|
-
onClick={() => {
|
184
|
-
copyToClipboard(id);
|
185
|
-
}}
|
186
|
-
style={{ cursor: 'pointer', marginRight: 0 }}
|
187
|
-
>
|
187
|
+
<Tag onClick={copyModelId} style={{ cursor: 'pointer', marginRight: 0 }}>
|
188
188
|
{id}
|
189
189
|
</Tag>
|
190
190
|
</div>
|
@@ -251,12 +251,7 @@ const ModelItem = memo<ModelItemProps>(
|
|
251
251
|
<Flexbox flex={1} gap={2} style={{ minWidth: 0 }}>
|
252
252
|
<Flexbox align={'center'} gap={8} horizontal>
|
253
253
|
{displayName || id}
|
254
|
-
<Tag
|
255
|
-
onClick={() => {
|
256
|
-
copyToClipboard(id);
|
257
|
-
}}
|
258
|
-
style={{ cursor: 'pointer', marginRight: 0 }}
|
259
|
-
>
|
254
|
+
<Tag onClick={copyModelId} style={{ cursor: 'pointer', marginRight: 0 }}>
|
260
255
|
{id}
|
261
256
|
</Tag>
|
262
257
|
<Flexbox className={styles.config} horizontal>
|
package/src/const/discover.ts
CHANGED
package/src/utils/format.test.ts
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
import dayjs from 'dayjs';
|
2
2
|
import { describe, expect, it } from 'vitest';
|
3
3
|
|
4
|
-
import {
|
4
|
+
import { USD_TO_CNY } from '@/const/currency';
|
5
5
|
|
6
6
|
import {
|
7
7
|
formatDate,
|
@@ -194,16 +194,9 @@ describe('format', () => {
|
|
194
194
|
expect(formatPriceByCurrency(1234.56, 'USD')).toBe('1,234.56');
|
195
195
|
});
|
196
196
|
|
197
|
-
it('should format CNY prices correctly', () => {
|
198
|
-
// Assuming CNY_TO_USD is 6.5
|
199
|
-
const CNY_TO_USD = 6.5;
|
200
|
-
expect(formatPriceByCurrency(1000, 'CNY')).toBe('140.06');
|
201
|
-
expect(formatPriceByCurrency(6500, 'CNY')).toBe('910.36');
|
202
|
-
});
|
203
|
-
|
204
197
|
it('should use the correct CNY_TO_USD conversion rate', () => {
|
205
198
|
const price = 1000;
|
206
|
-
const expectedCNY = formatPrice(price /
|
199
|
+
const expectedCNY = formatPrice(price / USD_TO_CNY);
|
207
200
|
expect(formatPriceByCurrency(price, 'CNY')).toBe(expectedCNY);
|
208
201
|
});
|
209
202
|
});
|
package/src/utils/format.ts
CHANGED
@@ -2,7 +2,7 @@ import dayjs from 'dayjs';
|
|
2
2
|
import { isNumber } from 'lodash-es';
|
3
3
|
import numeral from 'numeral';
|
4
4
|
|
5
|
-
import {
|
5
|
+
import { USD_TO_CNY } from '@/const/currency';
|
6
6
|
import { ModelPriceCurrency } from '@/types/llm';
|
7
7
|
|
8
8
|
export const formatSize = (bytes: number, fractionDigits: number = 1): string => {
|
@@ -118,7 +118,7 @@ export const formatPrice = (price: number, fractionDigits: number = 2) => {
|
|
118
118
|
|
119
119
|
export const formatPriceByCurrency = (price: number, currency?: ModelPriceCurrency) => {
|
120
120
|
if (currency === 'CNY') {
|
121
|
-
return formatPrice(price /
|
121
|
+
return formatPrice(price / USD_TO_CNY);
|
122
122
|
}
|
123
123
|
return formatPrice(price);
|
124
124
|
};
|