@lobehub/chat 1.111.0 → 1.111.2

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.
Files changed (101) hide show
  1. package/.cursor/rules/code-review.mdc +2 -19
  2. package/.cursor/rules/cursor-ux.mdc +0 -72
  3. package/.cursor/rules/project-introduce.mdc +5 -5
  4. package/.cursor/rules/react-component.mdc +92 -73
  5. package/.cursor/rules/rules-attach.mdc +28 -61
  6. package/.cursor/rules/system-role.mdc +8 -20
  7. package/.cursor/rules/typescript.mdc +55 -14
  8. package/CHANGELOG.md +50 -0
  9. package/changelog/v1.json +14 -0
  10. package/package.json +1 -1
  11. package/packages/types/src/aiModel.ts +67 -46
  12. package/packages/types/src/llm.ts +3 -3
  13. package/src/app/[variants]/(main)/discover/(detail)/model/[...slugs]/features/Details/Overview/ProviderList/index.tsx +23 -12
  14. package/src/app/[variants]/(main)/discover/(detail)/provider/[...slugs]/features/Details/Overview/ModelList/index.tsx +23 -10
  15. package/src/app/[variants]/(main)/settings/provider/features/ModelList/ModelItem.tsx +21 -12
  16. package/src/config/aiModels/ai21.ts +8 -4
  17. package/src/config/aiModels/ai360.ts +28 -14
  18. package/src/config/aiModels/aihubmix.ts +174 -86
  19. package/src/config/aiModels/anthropic.ts +97 -38
  20. package/src/config/aiModels/azure.ts +54 -32
  21. package/src/config/aiModels/azureai.ts +63 -37
  22. package/src/config/aiModels/baichuan.ts +24 -12
  23. package/src/config/aiModels/bedrock.ts +60 -30
  24. package/src/config/aiModels/cohere.ts +60 -30
  25. package/src/config/aiModels/deepseek.ts +10 -6
  26. package/src/config/aiModels/fal.ts +43 -6
  27. package/src/config/aiModels/fireworksai.ts +88 -44
  28. package/src/config/aiModels/giteeai.ts +1 -1
  29. package/src/config/aiModels/github.ts +44 -26
  30. package/src/config/aiModels/google.ts +119 -68
  31. package/src/config/aiModels/groq.ts +48 -24
  32. package/src/config/aiModels/higress.ts +617 -310
  33. package/src/config/aiModels/hunyuan.ts +105 -54
  34. package/src/config/aiModels/infiniai.ts +104 -52
  35. package/src/config/aiModels/internlm.ts +16 -8
  36. package/src/config/aiModels/jina.ts +4 -2
  37. package/src/config/aiModels/minimax.ts +11 -10
  38. package/src/config/aiModels/mistral.ts +40 -20
  39. package/src/config/aiModels/moonshot.ts +42 -22
  40. package/src/config/aiModels/novita.ts +196 -98
  41. package/src/config/aiModels/openai.ts +270 -137
  42. package/src/config/aiModels/openrouter.ts +205 -100
  43. package/src/config/aiModels/perplexity.ts +36 -6
  44. package/src/config/aiModels/ppio.ts +76 -38
  45. package/src/config/aiModels/qwen.ts +257 -133
  46. package/src/config/aiModels/sambanova.ts +56 -28
  47. package/src/config/aiModels/sensenova.ts +100 -50
  48. package/src/config/aiModels/siliconcloud.ts +224 -112
  49. package/src/config/aiModels/stepfun.ts +44 -22
  50. package/src/config/aiModels/taichu.ts +8 -4
  51. package/src/config/aiModels/tencentcloud.ts +12 -6
  52. package/src/config/aiModels/upstage.ts +8 -4
  53. package/src/config/aiModels/v0.ts +15 -12
  54. package/src/config/aiModels/vertexai.ts +49 -27
  55. package/src/config/aiModels/volcengine.ts +110 -51
  56. package/src/config/aiModels/wenxin.ts +179 -73
  57. package/src/config/aiModels/xai.ts +33 -19
  58. package/src/config/aiModels/zeroone.ts +48 -24
  59. package/src/config/aiModels/zhipu.ts +118 -69
  60. package/src/config/modelProviders/ai21.ts +0 -8
  61. package/src/config/modelProviders/ai360.ts +0 -20
  62. package/src/config/modelProviders/anthropic.ts +0 -56
  63. package/src/config/modelProviders/baichuan.ts +0 -30
  64. package/src/config/modelProviders/bedrock.ts +0 -74
  65. package/src/config/modelProviders/deepseek.ts +0 -13
  66. package/src/config/modelProviders/fireworksai.ts +0 -88
  67. package/src/config/modelProviders/google.ts +0 -59
  68. package/src/config/modelProviders/groq.ts +0 -48
  69. package/src/config/modelProviders/higress.ts +0 -727
  70. package/src/config/modelProviders/hunyuan.ts +0 -45
  71. package/src/config/modelProviders/infiniai.ts +0 -60
  72. package/src/config/modelProviders/internlm.ts +0 -8
  73. package/src/config/modelProviders/mistral.ts +0 -48
  74. package/src/config/modelProviders/modelscope.ts +2 -1
  75. package/src/config/modelProviders/openai.ts +5 -100
  76. package/src/config/modelProviders/openrouter.ts +0 -77
  77. package/src/config/modelProviders/ppio.ts +0 -95
  78. package/src/config/modelProviders/qwen.ts +0 -165
  79. package/src/config/modelProviders/sensenova.ts +0 -45
  80. package/src/config/modelProviders/siliconcloud.ts +0 -266
  81. package/src/config/modelProviders/stepfun.ts +0 -60
  82. package/src/config/modelProviders/taichu.ts +0 -10
  83. package/src/config/modelProviders/wenxin.ts +0 -90
  84. package/src/config/modelProviders/xai.ts +0 -16
  85. package/src/config/modelProviders/zeroone.ts +0 -60
  86. package/src/config/modelProviders/zhipu.ts +0 -80
  87. package/src/features/Conversation/Extras/Usage/UsageDetail/ModelCard.tsx +4 -3
  88. package/src/features/Conversation/Extras/Usage/UsageDetail/pricing.ts +25 -15
  89. package/src/features/Conversation/Extras/Usage/UsageDetail/tokens.test.ts +7 -5
  90. package/src/features/Conversation/Extras/Usage/UsageDetail/tokens.ts +6 -5
  91. package/src/libs/model-runtime/fal/index.ts +8 -2
  92. package/src/libs/model-runtime/utils/openaiCompatibleFactory/index.test.ts +54 -8
  93. package/src/server/routers/lambda/agent.ts +2 -2
  94. package/src/server/routers/lambda/config/__snapshots__/index.test.ts.snap +0 -28
  95. package/src/server/services/discover/index.ts +7 -6
  96. package/src/server/services/user/index.ts +1 -2
  97. package/src/utils/__snapshots__/parseModels.test.ts.snap +28 -4
  98. package/src/utils/_deprecated/__snapshots__/parseModels.test.ts.snap +0 -8
  99. package/src/utils/parseModels.test.ts +60 -9
  100. package/src/utils/pricing.test.ts +183 -0
  101. package/src/utils/pricing.ts +90 -0
@@ -732,9 +732,26 @@ describe('transformToChatModelCards', () => {
732
732
  id: 'gpt-4o',
733
733
  maxOutput: 4096,
734
734
  pricing: {
735
- cachedInput: 1.25,
736
- input: 2.5,
737
- output: 10,
735
+ units: [
736
+ {
737
+ name: 'textInput_cacheRead',
738
+ rate: 1.25,
739
+ strategy: 'fixed',
740
+ unit: 'millionTokens',
741
+ },
742
+ {
743
+ name: 'textInput',
744
+ rate: 2.5,
745
+ strategy: 'fixed',
746
+ unit: 'millionTokens',
747
+ },
748
+ {
749
+ name: 'textOutput',
750
+ rate: 10,
751
+ strategy: 'fixed',
752
+ unit: 'millionTokens',
753
+ },
754
+ ],
738
755
  },
739
756
  providerId: 'azure',
740
757
  releasedAt: '2024-05-13',
@@ -753,9 +770,26 @@ describe('transformToChatModelCards', () => {
753
770
  id: 'gpt-4o-mini',
754
771
  maxOutput: 4096,
755
772
  pricing: {
756
- cachedInput: 0.075,
757
- input: 0.15,
758
- output: 0.6,
773
+ units: [
774
+ {
775
+ name: 'textInput_cacheRead',
776
+ rate: 0.075,
777
+ strategy: 'fixed',
778
+ unit: 'millionTokens',
779
+ },
780
+ {
781
+ name: 'textInput',
782
+ rate: 0.15,
783
+ strategy: 'fixed',
784
+ unit: 'millionTokens',
785
+ },
786
+ {
787
+ name: 'textOutput',
788
+ rate: 0.6,
789
+ strategy: 'fixed',
790
+ unit: 'millionTokens',
791
+ },
792
+ ],
759
793
  },
760
794
  type: 'chat',
761
795
  },
@@ -772,9 +806,26 @@ describe('transformToChatModelCards', () => {
772
806
  id: 'o1-mini',
773
807
  maxOutput: 65536,
774
808
  pricing: {
775
- cachedInput: 0.55,
776
- input: 1.1,
777
- output: 4.4,
809
+ units: [
810
+ {
811
+ name: 'textInput_cacheRead',
812
+ rate: 0.55,
813
+ strategy: 'fixed',
814
+ unit: 'millionTokens',
815
+ },
816
+ {
817
+ name: 'textInput',
818
+ rate: 1.1,
819
+ strategy: 'fixed',
820
+ unit: 'millionTokens',
821
+ },
822
+ {
823
+ name: 'textOutput',
824
+ rate: 4.4,
825
+ strategy: 'fixed',
826
+ unit: 'millionTokens',
827
+ },
828
+ ],
778
829
  },
779
830
  releasedAt: '2024-09-12',
780
831
  type: 'chat',
@@ -0,0 +1,183 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { Pricing } from '@/types/aiModel';
4
+
5
+ import {
6
+ getAudioInputUnitRate,
7
+ getAudioOutputUnitRate,
8
+ getCachedAudioInputUnitRate,
9
+ getCachedTextInputUnitRate,
10
+ getTextInputUnitRate,
11
+ getTextOutputUnitRate,
12
+ getUnitRateByName,
13
+ getWriteCacheInputUnitRate,
14
+ } from './pricing';
15
+
16
+ describe('pricing utilities (new)', () => {
17
+ describe('getUnitRateByName', () => {
18
+ it('returns undefined when pricing or unitName is missing', () => {
19
+ expect(getUnitRateByName()).toBeUndefined();
20
+ const p = { units: [] } as Pricing;
21
+ expect(getUnitRateByName(p)).toBeUndefined();
22
+ });
23
+
24
+ it('returns undefined when unit not found', () => {
25
+ const pricing: Pricing = {
26
+ units: [{ name: 'textOutput', strategy: 'fixed', unit: 'millionTokens', rate: 0.002 }],
27
+ };
28
+ expect(getUnitRateByName(pricing, 'textInput')).toBeUndefined();
29
+ });
30
+
31
+ it('handles fixed strategy', () => {
32
+ const pricing: Pricing = {
33
+ units: [{ name: 'textInput', strategy: 'fixed', unit: 'millionTokens', rate: 0.001 }],
34
+ };
35
+ expect(getUnitRateByName(pricing, 'textInput')).toBe(0.001);
36
+ });
37
+
38
+ it('handles tiered strategy (first tier)', () => {
39
+ const pricing: Pricing = {
40
+ units: [
41
+ {
42
+ name: 'audioInput',
43
+ strategy: 'tiered',
44
+ unit: 'second',
45
+ tiers: [
46
+ { rate: 0.01, upTo: 3600 },
47
+ { rate: 0.008, upTo: 'infinity' },
48
+ ],
49
+ },
50
+ ],
51
+ };
52
+ expect(getUnitRateByName(pricing, 'audioInput')).toBe(0.01);
53
+ });
54
+
55
+ it('returns undefined when tiered.tiers is absent or empty', () => {
56
+ const noTiers: Pricing = {
57
+ units: [
58
+ { name: 'textInput', strategy: 'tiered', unit: 'millionTokens', tiers: undefined as any },
59
+ ],
60
+ };
61
+ const emptyTiers: Pricing = {
62
+ units: [{ name: 'textInput', strategy: 'tiered', unit: 'millionTokens', tiers: [] }],
63
+ };
64
+ expect(getUnitRateByName(noTiers, 'textInput')).toBeUndefined();
65
+ expect(getUnitRateByName(emptyTiers, 'textInput')).toBeUndefined();
66
+ });
67
+
68
+ it('handles lookup strategy (first price value)', () => {
69
+ const pricing: Pricing = {
70
+ units: [
71
+ {
72
+ name: 'textInput',
73
+ strategy: 'lookup',
74
+ unit: 'millionTokens',
75
+ lookup: {
76
+ pricingParams: ['model'],
77
+ prices: { a: 0.001, b: 0.03 },
78
+ },
79
+ },
80
+ ],
81
+ };
82
+ expect(getUnitRateByName(pricing, 'textInput')).toBe(0.001);
83
+ });
84
+
85
+ it('returns undefined when lookup missing or has no prices', () => {
86
+ const missingLookup: Pricing = {
87
+ units: [
88
+ {
89
+ name: 'textInput',
90
+ strategy: 'lookup',
91
+ unit: 'millionTokens',
92
+ lookup: undefined as any,
93
+ },
94
+ ],
95
+ } as any;
96
+ const emptyPrices: Pricing = {
97
+ units: [
98
+ {
99
+ name: 'textInput',
100
+ strategy: 'lookup',
101
+ unit: 'millionTokens',
102
+ lookup: { pricingParams: ['model'], prices: {} },
103
+ },
104
+ ],
105
+ };
106
+ expect(getUnitRateByName(missingLookup, 'textInput')).toBeUndefined();
107
+ expect(getUnitRateByName(emptyPrices, 'textInput')).toBeUndefined();
108
+ });
109
+
110
+ it('works with multiple units', () => {
111
+ const pricing: Pricing = {
112
+ units: [
113
+ { name: 'textInput', strategy: 'fixed', unit: 'millionTokens', rate: 0.001 },
114
+ { name: 'textOutput', strategy: 'fixed', unit: 'millionTokens', rate: 0.002 },
115
+ {
116
+ name: 'audioInput',
117
+ strategy: 'tiered',
118
+ unit: 'second',
119
+ tiers: [
120
+ { rate: 0.01, upTo: 3600 },
121
+ { rate: 0.008, upTo: 'infinity' },
122
+ ],
123
+ },
124
+ ],
125
+ };
126
+ expect(getUnitRateByName(pricing, 'textInput')).toBe(0.001);
127
+ expect(getUnitRateByName(pricing, 'textOutput')).toBe(0.002);
128
+ expect(getUnitRateByName(pricing, 'audioInput')).toBe(0.01);
129
+ });
130
+ });
131
+
132
+ describe('wrapper helpers', () => {
133
+ it('return the same values as getUnitRateByName for each unit', () => {
134
+ const pricing: Pricing = {
135
+ units: [
136
+ { name: 'textInput', strategy: 'fixed', unit: 'millionTokens', rate: 0.001 },
137
+ { name: 'textOutput', strategy: 'fixed', unit: 'millionTokens', rate: 0.002 },
138
+ {
139
+ name: 'audioInput',
140
+ strategy: 'tiered',
141
+ unit: 'second',
142
+ tiers: [
143
+ { rate: 0.01, upTo: 3600 },
144
+ { rate: 0.008, upTo: 'infinity' },
145
+ ],
146
+ },
147
+ { name: 'audioOutput', strategy: 'fixed', unit: 'second', rate: 0.015 },
148
+ { name: 'textInput_cacheRead', strategy: 'fixed', unit: 'millionTokens', rate: 0.0005 },
149
+ {
150
+ name: 'textInput_cacheWrite',
151
+ strategy: 'lookup',
152
+ unit: 'millionTokens',
153
+ lookup: { pricingParams: ['ttl'], prices: { '5': 0.2, '60': 0.6 } },
154
+ },
155
+ { name: 'audioInput_cacheRead', strategy: 'fixed', unit: 'second', rate: 0.005 },
156
+ ],
157
+ };
158
+
159
+ expect(getTextInputUnitRate(pricing)).toBe(getUnitRateByName(pricing, 'textInput'));
160
+ expect(getTextOutputUnitRate(pricing)).toBe(getUnitRateByName(pricing, 'textOutput'));
161
+ expect(getAudioInputUnitRate(pricing)).toBe(getUnitRateByName(pricing, 'audioInput'));
162
+ expect(getAudioOutputUnitRate(pricing)).toBe(getUnitRateByName(pricing, 'audioOutput'));
163
+ expect(getCachedTextInputUnitRate(pricing)).toBe(
164
+ getUnitRateByName(pricing, 'textInput_cacheRead'),
165
+ );
166
+ expect(getWriteCacheInputUnitRate(pricing)).toBe(
167
+ getUnitRateByName(pricing, 'textInput_cacheWrite'),
168
+ );
169
+ expect(getCachedAudioInputUnitRate(pricing)).toBe(
170
+ getUnitRateByName(pricing, 'audioInput_cacheRead'),
171
+ );
172
+
173
+ // also validate expected concrete values for clarity
174
+ expect(getTextInputUnitRate(pricing)).toBe(0.001);
175
+ expect(getTextOutputUnitRate(pricing)).toBe(0.002);
176
+ expect(getAudioInputUnitRate(pricing)).toBe(0.01);
177
+ expect(getAudioOutputUnitRate(pricing)).toBe(0.015);
178
+ expect(getCachedTextInputUnitRate(pricing)).toBe(0.0005);
179
+ expect(getWriteCacheInputUnitRate(pricing)).toBe(0.2);
180
+ expect(getCachedAudioInputUnitRate(pricing)).toBe(0.005);
181
+ });
182
+ });
183
+ });
@@ -0,0 +1,90 @@
1
+ import { Pricing, PricingUnit, PricingUnitName } from '@/types/aiModel';
2
+
3
+ /**
4
+ * Internal helper to extract the displayed unit rate from a pricing unit by strategy
5
+ * - fixed → rate
6
+ * - tiered → tiers[0].rate
7
+ * - lookup → first price value
8
+ */
9
+ const getRateFromUnit = (unit: PricingUnit): number | undefined => {
10
+ switch (unit.strategy) {
11
+ case 'fixed': {
12
+ return unit.rate;
13
+ }
14
+ case 'tiered': {
15
+ return unit.tiers?.[0]?.rate;
16
+ }
17
+ case 'lookup': {
18
+ const prices = Object.values(unit.lookup?.prices || {});
19
+ return prices[0];
20
+ }
21
+ default: {
22
+ return undefined;
23
+ }
24
+ }
25
+ };
26
+
27
+ /**
28
+ * Get unit rate by unit name, used to deduplicate logic across helpers
29
+ */
30
+ export const getUnitRateByName = (
31
+ pricing?: Pricing,
32
+ unitName?: PricingUnitName,
33
+ ): number | undefined => {
34
+ if (!pricing?.units || !unitName) return undefined;
35
+ const unit = pricing.units.find((u) => u.name === unitName);
36
+ if (!unit) return undefined;
37
+ return getRateFromUnit(unit);
38
+ };
39
+
40
+ /**
41
+ * Get text input unit rate from pricing
42
+ * - fixed → rate
43
+ * - tiered → tiers[0].rate
44
+ * - lookup → Object.values(lookup.prices)[0]
45
+ */
46
+ export function getTextInputUnitRate(pricing?: Pricing): number | undefined {
47
+ return getUnitRateByName(pricing, 'textInput');
48
+ }
49
+
50
+ /**
51
+ * Get text output unit rate from pricing
52
+ */
53
+ export function getTextOutputUnitRate(pricing?: Pricing): number | undefined {
54
+ return getUnitRateByName(pricing, 'textOutput');
55
+ }
56
+
57
+ /**
58
+ * Get audio input unit rate from pricing
59
+ */
60
+ export function getAudioInputUnitRate(pricing?: Pricing): number | undefined {
61
+ return getUnitRateByName(pricing, 'audioInput');
62
+ }
63
+
64
+ /**
65
+ * Get audio output unit rate from pricing
66
+ */
67
+ export function getAudioOutputUnitRate(pricing?: Pricing): number | undefined {
68
+ return getUnitRateByName(pricing, 'audioOutput');
69
+ }
70
+
71
+ /**
72
+ * Get cached text input unit rate from pricing
73
+ */
74
+ export function getCachedTextInputUnitRate(pricing?: Pricing): number | undefined {
75
+ return getUnitRateByName(pricing, 'textInput_cacheRead');
76
+ }
77
+
78
+ /**
79
+ * Get write cache input unit rate from pricing (TextInputCacheWrite)
80
+ */
81
+ export function getWriteCacheInputUnitRate(pricing?: Pricing): number | undefined {
82
+ return getUnitRateByName(pricing, 'textInput_cacheWrite');
83
+ }
84
+
85
+ /**
86
+ * Get cached audio input unit rate from pricing
87
+ */
88
+ export function getCachedAudioInputUnitRate(pricing?: Pricing): number | undefined {
89
+ return getUnitRateByName(pricing, 'audioInput_cacheRead');
90
+ }