@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 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
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#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
@@ -1,4 +1,13 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "improvements": [
5
+ "Improve model pricing with CNY."
6
+ ]
7
+ },
8
+ "date": "2025-01-26",
9
+ "version": "1.48.3"
10
+ },
2
11
  {
3
12
  "children": {
4
13
  "improvements": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "1.48.2",
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
- f(
97
- pricing?.input,
98
- t('providerModels.item.pricing.inputTokens', { amount: pricing?.input }),
99
- ),
100
- f(
101
- pricing?.output,
102
- t('providerModels.item.pricing.outputTokens', { amount: pricing?.output }),
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
- f(
109
- pricing?.input,
110
- t('providerModels.item.pricing.inputTokens', { amount: pricing?.input }),
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
- f(
117
- pricing?.input,
118
- t('providerModels.item.pricing.inputCharts', { amount: pricing?.input }),
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
- f(
125
- pricing?.input,
126
- t('providerModels.item.pricing.inputMinutes', { amount: pricing?.input }),
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>
@@ -0,0 +1,2 @@
1
+ // in 2025.01.26
2
+ export const USD_TO_CNY = 7.24;
@@ -6,8 +6,6 @@ import {
6
6
  DiscoverProviderItem,
7
7
  } from '@/types/discover';
8
8
 
9
- export const CNY_TO_USD = 7.14;
10
-
11
9
  const DEFAULT_CREATED_AT = new Date().toISOString();
12
10
 
13
11
  export const DEFAULT_DISCOVER_ASSISTANT_ITEM: Partial<DiscoverAssistantItem> = {
@@ -1,7 +1,7 @@
1
1
  import dayjs from 'dayjs';
2
2
  import { describe, expect, it } from 'vitest';
3
3
 
4
- import { CNY_TO_USD } from '@/const/discover';
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 / CNY_TO_USD);
199
+ const expectedCNY = formatPrice(price / USD_TO_CNY);
207
200
  expect(formatPriceByCurrency(price, 'CNY')).toBe(expectedCNY);
208
201
  });
209
202
  });
@@ -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 { CNY_TO_USD } from '@/const/discover';
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 / CNY_TO_USD);
121
+ return formatPrice(price / USD_TO_CNY);
122
122
  }
123
123
  return formatPrice(price);
124
124
  };