diff --git a/api/models/tx.js b/api/models/tx.js index 328f2c2d4d..4ea93e1a1e 100644 --- a/api/models/tx.js +++ b/api/models/tx.js @@ -173,6 +173,9 @@ const tokenValues = Object.assign( 'grok-3-mini': { prompt: 0.3, completion: 0.5 }, 'grok-3-mini-fast': { prompt: 0.6, completion: 4 }, 'grok-4': { prompt: 3.0, completion: 15.0 }, + 'grok-4-fast': { prompt: 0.2, completion: 0.5 }, + 'grok-4-1-fast': { prompt: 0.2, completion: 0.5 }, // covers reasoning & non-reasoning variants + 'grok-code-fast': { prompt: 0.2, completion: 1.5 }, codestral: { prompt: 0.3, completion: 0.9 }, 'ministral-3b': { prompt: 0.04, completion: 0.04 }, 'ministral-8b': { prompt: 0.1, completion: 0.1 }, diff --git a/api/models/tx.spec.js b/api/models/tx.spec.js index b70f9572d0..75a8fa1922 100644 --- a/api/models/tx.spec.js +++ b/api/models/tx.spec.js @@ -1205,6 +1205,39 @@ describe('Grok Model Tests - Pricing', () => { ); }); + test('should return correct prompt and completion rates for Grok 4 Fast model', () => { + expect(getMultiplier({ model: 'grok-4-fast', tokenType: 'prompt' })).toBe( + tokenValues['grok-4-fast'].prompt, + ); + expect(getMultiplier({ model: 'grok-4-fast', tokenType: 'completion' })).toBe( + tokenValues['grok-4-fast'].completion, + ); + }); + + test('should return correct prompt and completion rates for Grok 4.1 Fast models', () => { + expect(getMultiplier({ model: 'grok-4-1-fast-reasoning', tokenType: 'prompt' })).toBe( + tokenValues['grok-4-1-fast'].prompt, + ); + expect(getMultiplier({ model: 'grok-4-1-fast-reasoning', tokenType: 'completion' })).toBe( + tokenValues['grok-4-1-fast'].completion, + ); + expect(getMultiplier({ model: 'grok-4-1-fast-non-reasoning', tokenType: 'prompt' })).toBe( + tokenValues['grok-4-1-fast'].prompt, + ); + expect(getMultiplier({ model: 'grok-4-1-fast-non-reasoning', tokenType: 'completion' })).toBe( + tokenValues['grok-4-1-fast'].completion, + ); + }); + + test('should return correct prompt and completion rates for Grok Code Fast model', () => { + expect(getMultiplier({ model: 'grok-code-fast-1', tokenType: 'prompt' })).toBe( + tokenValues['grok-code-fast'].prompt, + ); + expect(getMultiplier({ model: 'grok-code-fast-1', tokenType: 'completion' })).toBe( + tokenValues['grok-code-fast'].completion, + ); + }); + test('should return correct prompt and completion rates for Grok 3 models with prefixes', () => { expect(getMultiplier({ model: 'xai/grok-3', tokenType: 'prompt' })).toBe( tokenValues['grok-3'].prompt, @@ -1240,6 +1273,39 @@ describe('Grok Model Tests - Pricing', () => { tokenValues['grok-4'].completion, ); }); + + test('should return correct prompt and completion rates for Grok 4 Fast model with prefixes', () => { + expect(getMultiplier({ model: 'xai/grok-4-fast', tokenType: 'prompt' })).toBe( + tokenValues['grok-4-fast'].prompt, + ); + expect(getMultiplier({ model: 'xai/grok-4-fast', tokenType: 'completion' })).toBe( + tokenValues['grok-4-fast'].completion, + ); + }); + + test('should return correct prompt and completion rates for Grok 4.1 Fast models with prefixes', () => { + expect(getMultiplier({ model: 'xai/grok-4-1-fast-reasoning', tokenType: 'prompt' })).toBe( + tokenValues['grok-4-1-fast'].prompt, + ); + expect(getMultiplier({ model: 'xai/grok-4-1-fast-reasoning', tokenType: 'completion' })).toBe( + tokenValues['grok-4-1-fast'].completion, + ); + expect(getMultiplier({ model: 'xai/grok-4-1-fast-non-reasoning', tokenType: 'prompt' })).toBe( + tokenValues['grok-4-1-fast'].prompt, + ); + expect( + getMultiplier({ model: 'xai/grok-4-1-fast-non-reasoning', tokenType: 'completion' }), + ).toBe(tokenValues['grok-4-1-fast'].completion); + }); + + test('should return correct prompt and completion rates for Grok Code Fast model with prefixes', () => { + expect(getMultiplier({ model: 'xai/grok-code-fast-1', tokenType: 'prompt' })).toBe( + tokenValues['grok-code-fast'].prompt, + ); + expect(getMultiplier({ model: 'xai/grok-code-fast-1', tokenType: 'completion' })).toBe( + tokenValues['grok-code-fast'].completion, + ); + }); }); }); diff --git a/api/utils/tokens.spec.js b/api/utils/tokens.spec.js index c4589c610e..a169c31622 100644 --- a/api/utils/tokens.spec.js +++ b/api/utils/tokens.spec.js @@ -778,6 +778,16 @@ describe('Grok Model Tests - Tokens', () => { expect(getModelMaxTokens('grok-4-0709')).toBe(256000); }); + test('should return correct tokens for Grok 4 Fast and Grok 4.1 Fast models', () => { + expect(getModelMaxTokens('grok-4-fast')).toBe(2000000); + expect(getModelMaxTokens('grok-4-1-fast-reasoning')).toBe(2000000); + expect(getModelMaxTokens('grok-4-1-fast-non-reasoning')).toBe(2000000); + }); + + test('should return correct tokens for Grok Code Fast model', () => { + expect(getModelMaxTokens('grok-code-fast-1')).toBe(256000); + }); + test('should handle partial matches for Grok models with prefixes', () => { // Vision models should match before general models expect(getModelMaxTokens('xai/grok-2-vision-1212')).toBe(32768); @@ -797,6 +807,12 @@ describe('Grok Model Tests - Tokens', () => { expect(getModelMaxTokens('xai/grok-3-mini-fast')).toBe(131072); // Grok 4 model expect(getModelMaxTokens('xai/grok-4-0709')).toBe(256000); + // Grok 4 Fast and 4.1 Fast models + expect(getModelMaxTokens('xai/grok-4-fast')).toBe(2000000); + expect(getModelMaxTokens('xai/grok-4-1-fast-reasoning')).toBe(2000000); + expect(getModelMaxTokens('xai/grok-4-1-fast-non-reasoning')).toBe(2000000); + // Grok Code Fast model + expect(getModelMaxTokens('xai/grok-code-fast-1')).toBe(256000); }); }); @@ -820,6 +836,12 @@ describe('Grok Model Tests - Tokens', () => { expect(matchModelName('grok-3-mini-fast')).toBe('grok-3-mini-fast'); // Grok 4 model expect(matchModelName('grok-4-0709')).toBe('grok-4'); + // Grok 4 Fast and 4.1 Fast models + expect(matchModelName('grok-4-fast')).toBe('grok-4-fast'); + expect(matchModelName('grok-4-1-fast-reasoning')).toBe('grok-4-1-fast'); + expect(matchModelName('grok-4-1-fast-non-reasoning')).toBe('grok-4-1-fast'); + // Grok Code Fast model + expect(matchModelName('grok-code-fast-1')).toBe('grok-code-fast'); }); test('should match Grok model variations with prefixes', () => { @@ -841,6 +863,12 @@ describe('Grok Model Tests - Tokens', () => { expect(matchModelName('xai/grok-3-mini-fast')).toBe('grok-3-mini-fast'); // Grok 4 model expect(matchModelName('xai/grok-4-0709')).toBe('grok-4'); + // Grok 4 Fast and 4.1 Fast models + expect(matchModelName('xai/grok-4-fast')).toBe('grok-4-fast'); + expect(matchModelName('xai/grok-4-1-fast-reasoning')).toBe('grok-4-1-fast'); + expect(matchModelName('xai/grok-4-1-fast-non-reasoning')).toBe('grok-4-1-fast'); + // Grok Code Fast model + expect(matchModelName('xai/grok-code-fast-1')).toBe('grok-code-fast'); }); }); }); diff --git a/client/src/hooks/Conversations/useExportConversation.ts b/client/src/hooks/Conversations/useExportConversation.ts index 25758f54b0..6b5d53e65e 100644 --- a/client/src/hooks/Conversations/useExportConversation.ts +++ b/client/src/hooks/Conversations/useExportConversation.ts @@ -366,7 +366,9 @@ export default function useExportConversation({ } /** Use JSON.stringify without indentation to minimize file size for deeply nested recursive exports */ - download(JSON.stringify(data), `${filename}.json`, 'application/json'); + const jsonString = JSON.stringify(data); + const blob = new Blob([jsonString], { type: 'application/json;charset=utf-8' }); + download(blob, `${filename}.json`, 'application/json'); }; const exportConversation = () => { diff --git a/packages/api/src/utils/tokens.ts b/packages/api/src/utils/tokens.ts index 5271d69f1d..7dda247cc1 100644 --- a/packages/api/src/utils/tokens.ts +++ b/packages/api/src/utils/tokens.ts @@ -280,6 +280,9 @@ const xAIModels = { 'grok-3-mini': 131072, 'grok-3-mini-fast': 131072, 'grok-4': 256000, // 256K context + 'grok-4-fast': 2000000, // 2M context + 'grok-4-1-fast': 2000000, // 2M context (covers reasoning & non-reasoning variants) + 'grok-code-fast': 256000, // 256K context }; const aggregateModels = {