feat: add MiniMax provider support (#224)
Add MiniMax as a new LLM provider using the Anthropic-compatible API. Changes: - Add MiniMax to LLMProvider type and MiniMaxConfig interface - Add MiniMax chat model creation via ChatAnthropic with custom base URL - Add MiniMax settings persistence and model list (MiniMax-M2.5, MiniMax-M2.5-highspeed) - Add MiniMax provider UI in SettingsPanel with API key and model selection
This commit is contained in:
@@ -281,7 +281,7 @@ export const SettingsPanel = ({ isOpen, onClose, onSettingsSaved, backendUrl, is
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
const providers: LLMProvider[] = ['openai', 'gemini', 'anthropic', 'azure-openai', 'ollama', 'openrouter'];
|
||||
const providers: LLMProvider[] = ['openai', 'gemini', 'anthropic', 'azure-openai', 'ollama', 'openrouter', 'minimax'];
|
||||
|
||||
|
||||
return (
|
||||
@@ -366,7 +366,7 @@ export const SettingsPanel = ({ isOpen, onClose, onSettingsSaved, backendUrl, is
|
||||
w-8 h-8 rounded-lg flex items-center justify-center text-lg
|
||||
${settings.activeProvider === provider ? 'bg-accent/20' : 'bg-surface'}
|
||||
`}>
|
||||
{provider === 'openai' ? '🤖' : provider === 'gemini' ? '💎' : provider === 'anthropic' ? '🧠' : provider === 'ollama' ? '🦙' : provider === 'openrouter' ? '🌐' : '☁️'}
|
||||
{provider === 'openai' ? '🤖' : provider === 'gemini' ? '💎' : provider === 'anthropic' ? '🧠' : provider === 'ollama' ? '🦙' : provider === 'openrouter' ? '🌐' : provider === 'minimax' ? '⚡' : '☁️'}
|
||||
</div>
|
||||
<span className="font-medium">{getProviderDisplayName(provider)}</span>
|
||||
</button>
|
||||
@@ -814,7 +814,64 @@ export const SettingsPanel = ({ isOpen, onClose, onSettingsSaved, backendUrl, is
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* MiniMax Settings */}
|
||||
{settings.activeProvider === 'minimax' && (
|
||||
<div className="space-y-4 animate-fade-in">
|
||||
<div className="space-y-2">
|
||||
<label className="flex items-center gap-2 text-sm font-medium text-text-secondary">
|
||||
<Key className="w-4 h-4" />
|
||||
API Key
|
||||
</label>
|
||||
<div className="relative">
|
||||
<input
|
||||
type={showApiKey['minimax'] ? 'text' : 'password'}
|
||||
value={settings.minimax?.apiKey ?? ''}
|
||||
onChange={e => setSettings(prev => ({
|
||||
...prev,
|
||||
minimax: { ...prev.minimax!, apiKey: e.target.value }
|
||||
}))}
|
||||
placeholder="Enter your MiniMax API key"
|
||||
className="w-full px-4 py-3 pr-12 bg-elevated border border-border-subtle rounded-xl text-text-primary placeholder:text-text-muted focus:border-accent focus:ring-2 focus:ring-accent/20 outline-none transition-all"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => toggleApiKeyVisibility('minimax')}
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 p-1 text-text-muted hover:text-text-primary transition-colors"
|
||||
>
|
||||
{showApiKey['minimax'] ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-xs text-text-muted">
|
||||
Get your API key from{' '}
|
||||
<a
|
||||
href="https://platform.minimax.io"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-accent hover:underline"
|
||||
>
|
||||
MiniMax Platform
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-text-secondary">Model</label>
|
||||
<input
|
||||
type="text"
|
||||
value={settings.minimax?.model ?? 'MiniMax-M2.5'}
|
||||
onChange={e => setSettings(prev => ({
|
||||
...prev,
|
||||
minimax: { ...prev.minimax!, model: e.target.value }
|
||||
}))}
|
||||
placeholder="e.g., MiniMax-M2.5, MiniMax-M2.5-highspeed"
|
||||
className="w-full px-4 py-3 bg-elevated border border-border-subtle rounded-xl text-text-primary placeholder:text-text-muted focus:border-accent focus:ring-2 focus:ring-accent/20 outline-none transition-all font-mono text-sm"
|
||||
/>
|
||||
<p className="text-xs text-text-muted">
|
||||
Available models: MiniMax-M2.5 (default), MiniMax-M2.5-highspeed (faster)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Privacy Note */}
|
||||
<div className="p-4 bg-elevated/50 border border-border-subtle rounded-xl">
|
||||
|
||||
@@ -13,14 +13,15 @@ import { ChatAnthropic } from '@langchain/anthropic';
|
||||
import { ChatOllama } from '@langchain/ollama';
|
||||
import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
||||
import { createGraphRAGTools } from './tools';
|
||||
import type {
|
||||
ProviderConfig,
|
||||
import type {
|
||||
ProviderConfig,
|
||||
OpenAIConfig,
|
||||
AzureOpenAIConfig,
|
||||
AzureOpenAIConfig,
|
||||
GeminiConfig,
|
||||
AnthropicConfig,
|
||||
OllamaConfig,
|
||||
OpenRouterConfig,
|
||||
MiniMaxConfig,
|
||||
AgentStreamChunk,
|
||||
} from './types';
|
||||
import {
|
||||
@@ -197,7 +198,7 @@ export const createChatModel = (config: ProviderConfig): BaseChatModel => {
|
||||
|
||||
case 'openrouter': {
|
||||
const openRouterConfig = config as OpenRouterConfig;
|
||||
|
||||
|
||||
// Debug logging
|
||||
if (import.meta.env.DEV) {
|
||||
console.log('🌐 OpenRouter config:', {
|
||||
@@ -207,11 +208,11 @@ export const createChatModel = (config: ProviderConfig): BaseChatModel => {
|
||||
baseUrl: openRouterConfig.baseUrl,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
if (!openRouterConfig.apiKey || openRouterConfig.apiKey.trim() === '') {
|
||||
throw new Error('OpenRouter API key is required but was not provided');
|
||||
}
|
||||
|
||||
|
||||
return new ChatOpenAI({
|
||||
openAIApiKey: openRouterConfig.apiKey,
|
||||
apiKey: openRouterConfig.apiKey, // Fallback for some versions
|
||||
@@ -225,7 +226,26 @@ export const createChatModel = (config: ProviderConfig): BaseChatModel => {
|
||||
streaming: true,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
case 'minimax': {
|
||||
const minimaxConfig = config as MiniMaxConfig;
|
||||
|
||||
if (!minimaxConfig.apiKey || minimaxConfig.apiKey.trim() === '') {
|
||||
throw new Error('MiniMax API key is required but was not provided');
|
||||
}
|
||||
|
||||
return new ChatAnthropic({
|
||||
anthropicApiKey: minimaxConfig.apiKey,
|
||||
model: minimaxConfig.model,
|
||||
temperature: minimaxConfig.temperature ?? 0.1,
|
||||
maxTokens: minimaxConfig.maxTokens ?? 8192,
|
||||
streaming: true,
|
||||
clientOptions: {
|
||||
baseURL: 'https://api.minimax.io/anthropic',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
default:
|
||||
throw new Error(`Unsupported provider: ${(config as any).provider}`);
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
* All API keys are stored locally - never sent to any server except the LLM provider.
|
||||
*/
|
||||
|
||||
import {
|
||||
LLMSettings,
|
||||
DEFAULT_LLM_SETTINGS,
|
||||
import {
|
||||
LLMSettings,
|
||||
DEFAULT_LLM_SETTINGS,
|
||||
LLMProvider,
|
||||
OpenAIConfig,
|
||||
AzureOpenAIConfig,
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
AnthropicConfig,
|
||||
OllamaConfig,
|
||||
OpenRouterConfig,
|
||||
MiniMaxConfig,
|
||||
ProviderConfig,
|
||||
} from './types';
|
||||
|
||||
@@ -60,6 +61,10 @@ export const loadSettings = (): LLMSettings => {
|
||||
...DEFAULT_LLM_SETTINGS.openrouter,
|
||||
...parsed.openrouter,
|
||||
},
|
||||
minimax: {
|
||||
...DEFAULT_LLM_SETTINGS.minimax,
|
||||
...parsed.minimax,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
console.warn('Failed to load LLM settings:', error);
|
||||
@@ -89,6 +94,7 @@ export const updateProviderSettings = <T extends LLMProvider>(
|
||||
T extends 'gemini' ? Partial<Omit<GeminiConfig, 'provider'>> :
|
||||
T extends 'anthropic' ? Partial<Omit<AnthropicConfig, 'provider'>> :
|
||||
T extends 'ollama' ? Partial<Omit<OllamaConfig, 'provider'>> :
|
||||
T extends 'minimax' ? Partial<Omit<MiniMaxConfig, 'provider'>> :
|
||||
never
|
||||
>
|
||||
): LLMSettings => {
|
||||
@@ -162,6 +168,17 @@ export const updateProviderSettings = <T extends LLMProvider>(
|
||||
saveSettings(updated);
|
||||
return updated;
|
||||
}
|
||||
case 'minimax': {
|
||||
const updated: LLMSettings = {
|
||||
...current,
|
||||
minimax: {
|
||||
...(current.minimax ?? {}),
|
||||
...(updates as Partial<Omit<MiniMaxConfig, 'provider'>>),
|
||||
},
|
||||
};
|
||||
saveSettings(updated);
|
||||
return updated;
|
||||
}
|
||||
default: {
|
||||
// Should be unreachable due to T extends LLMProvider, but keep a safe fallback
|
||||
const updated: LLMSettings = { ...current };
|
||||
@@ -245,7 +262,16 @@ export const getActiveProviderConfig = (): ProviderConfig | null => {
|
||||
temperature: settings.openrouter.temperature,
|
||||
maxTokens: settings.openrouter.maxTokens,
|
||||
} as OpenRouterConfig;
|
||||
|
||||
|
||||
case 'minimax':
|
||||
if (!settings.minimax?.apiKey) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
provider: 'minimax',
|
||||
...settings.minimax,
|
||||
} as MiniMaxConfig;
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
@@ -282,6 +308,8 @@ export const getProviderDisplayName = (provider: LLMProvider): string => {
|
||||
return 'Ollama (Local)';
|
||||
case 'openrouter':
|
||||
return 'OpenRouter';
|
||||
case 'minimax':
|
||||
return 'MiniMax';
|
||||
default:
|
||||
return provider;
|
||||
}
|
||||
@@ -303,6 +331,8 @@ export const getAvailableModels = (provider: LLMProvider): string[] => {
|
||||
return ['claude-sonnet-4-20250514', 'claude-3-5-sonnet-20241022', 'claude-3-5-haiku-20241022', 'claude-3-opus-20240229'];
|
||||
case 'ollama':
|
||||
return ['llama3.2', 'llama3.1', 'mistral', 'codellama', 'deepseek-coder'];
|
||||
case 'minimax':
|
||||
return ['MiniMax-M2.5', 'MiniMax-M2.5-highspeed'];
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
/**
|
||||
* Supported LLM providers
|
||||
*/
|
||||
export type LLMProvider = 'openai' | 'azure-openai' | 'gemini' | 'anthropic' | 'ollama' | 'openrouter';
|
||||
export type LLMProvider = 'openai' | 'azure-openai' | 'gemini' | 'anthropic' | 'ollama' | 'openrouter' | 'minimax';
|
||||
|
||||
/**
|
||||
* Base configuration shared by all providers
|
||||
@@ -78,10 +78,19 @@ export interface OpenRouterConfig extends BaseProviderConfig {
|
||||
baseUrl?: string; // defaults to https://openrouter.ai/api/v1
|
||||
}
|
||||
|
||||
/**
|
||||
* MiniMax configuration (Anthropic-compatible API)
|
||||
*/
|
||||
export interface MiniMaxConfig extends BaseProviderConfig {
|
||||
provider: 'minimax';
|
||||
apiKey: string;
|
||||
model: string; // e.g., 'MiniMax-M2.5', 'MiniMax-M2.5-highspeed'
|
||||
}
|
||||
|
||||
/**
|
||||
* Union type for all provider configurations
|
||||
*/
|
||||
export type ProviderConfig = OpenAIConfig | AzureOpenAIConfig | GeminiConfig | AnthropicConfig | OllamaConfig | OpenRouterConfig;
|
||||
export type ProviderConfig = OpenAIConfig | AzureOpenAIConfig | GeminiConfig | AnthropicConfig | OllamaConfig | OpenRouterConfig | MiniMaxConfig;
|
||||
|
||||
/**
|
||||
* Stored settings (what goes to localStorage)
|
||||
@@ -98,6 +107,7 @@ export interface LLMSettings {
|
||||
anthropic?: Partial<Omit<AnthropicConfig, 'provider'>>;
|
||||
ollama?: Partial<Omit<OllamaConfig, 'provider'>>;
|
||||
openrouter?: Partial<Omit<OpenRouterConfig, 'provider'>>;
|
||||
minimax?: Partial<Omit<MiniMaxConfig, 'provider'>>;
|
||||
|
||||
// Intelligent Clustering Settings
|
||||
intelligentClustering: boolean;
|
||||
@@ -148,6 +158,11 @@ export const DEFAULT_LLM_SETTINGS: LLMSettings = {
|
||||
baseUrl: 'https://openrouter.ai/api/v1',
|
||||
temperature: 0.1,
|
||||
},
|
||||
minimax: {
|
||||
apiKey: '',
|
||||
model: 'MiniMax-M2.5',
|
||||
temperature: 0.1,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user