|
@@ -7,6 +7,7 @@ import {
|
|
|
LLMProviderEnum,
|
|
|
llmProviderModelNames,
|
|
|
} from '@extension/storage';
|
|
|
+import type { OpenRouterModel } from '@extension/storage';
|
|
|
|
|
|
export const ModelSettings = () => {
|
|
|
const [apiKeys, setApiKeys] = useState<Record<LLMProviderEnum, { apiKey: string; baseUrl?: string }>>(
|
|
@@ -18,6 +19,8 @@ export const ModelSettings = () => {
|
|
|
[AgentNameEnum.Planner]: '',
|
|
|
[AgentNameEnum.Validator]: '',
|
|
|
});
|
|
|
+ const [openRouterModels, setOpenRouterModels] = useState<OpenRouterModel[]>([]);
|
|
|
+ const [isLoadingModels, setIsLoadingModels] = useState(false);
|
|
|
|
|
|
useEffect(() => {
|
|
|
const loadApiKeys = async () => {
|
|
@@ -89,11 +92,44 @@ export const ModelSettings = () => {
|
|
|
next.delete(provider);
|
|
|
return next;
|
|
|
});
|
|
|
+
|
|
|
+ // If OpenRouter provider was saved, fetch the models
|
|
|
+ if (provider === LLMProviderEnum.OpenRouter) {
|
|
|
+ fetchOpenRouterModels();
|
|
|
+ }
|
|
|
} catch (error) {
|
|
|
console.error('Error saving API key:', error);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
+ // Function to fetch OpenRouter models
|
|
|
+ const fetchOpenRouterModels = async () => {
|
|
|
+ if (!apiKeys[LLMProviderEnum.OpenRouter]?.apiKey) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ setIsLoadingModels(true);
|
|
|
+ try {
|
|
|
+ const models = await llmProviderStore.fetchOpenRouterModels();
|
|
|
+ setOpenRouterModels(models);
|
|
|
+
|
|
|
+ // Update the llmProviderModelNames with the fetched models
|
|
|
+ const modelIds = models.map(model => model.id);
|
|
|
+ llmProviderModelNames[LLMProviderEnum.OpenRouter] = modelIds;
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Error fetching OpenRouter models:', error);
|
|
|
+ } finally {
|
|
|
+ setIsLoadingModels(false);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // Fetch OpenRouter models when the component mounts if the API key is configured
|
|
|
+ useEffect(() => {
|
|
|
+ if (apiKeys[LLMProviderEnum.OpenRouter]?.apiKey) {
|
|
|
+ fetchOpenRouterModels();
|
|
|
+ }
|
|
|
+ }, [apiKeys[LLMProviderEnum.OpenRouter]?.apiKey]);
|
|
|
+
|
|
|
const handleDelete = async (provider: LLMProviderEnum) => {
|
|
|
try {
|
|
|
await llmProviderStore.removeProvider(provider);
|
|
@@ -131,12 +167,35 @@ export const ModelSettings = () => {
|
|
|
const models: string[] = [];
|
|
|
Object.entries(apiKeys).forEach(([provider, config]) => {
|
|
|
if (config.apiKey) {
|
|
|
- models.push(...(llmProviderModelNames[provider as LLMProviderEnum] || []));
|
|
|
+ if (provider === LLMProviderEnum.OpenRouter) {
|
|
|
+ // Use the dynamically fetched OpenRouter models
|
|
|
+ models.push(...(llmProviderModelNames[LLMProviderEnum.OpenRouter] || []));
|
|
|
+ } else {
|
|
|
+ models.push(...(llmProviderModelNames[provider as LLMProviderEnum] || []));
|
|
|
+ }
|
|
|
}
|
|
|
});
|
|
|
return models.length ? models : [''];
|
|
|
};
|
|
|
|
|
|
+ // Helper function to get model display name
|
|
|
+ const getModelDisplayName = (modelId: string) => {
|
|
|
+ if (
|
|
|
+ modelId.startsWith('openai/') ||
|
|
|
+ modelId.startsWith('anthropic/') ||
|
|
|
+ modelId.startsWith('meta-llama/') ||
|
|
|
+ modelId.startsWith('google/') ||
|
|
|
+ modelId.startsWith('mistral/')
|
|
|
+ ) {
|
|
|
+ // For OpenRouter models, find the model in the fetched list
|
|
|
+ const openRouterModel = openRouterModels.find(model => model.id === modelId);
|
|
|
+ if (openRouterModel) {
|
|
|
+ return `${openRouterModel.name} (${modelId})`;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return modelId;
|
|
|
+ };
|
|
|
+
|
|
|
const handleModelChange = async (agentName: AgentNameEnum, model: string) => {
|
|
|
setSelectedModels(prev => ({
|
|
|
...prev,
|
|
@@ -187,7 +246,7 @@ export const ModelSettings = () => {
|
|
|
model =>
|
|
|
model && (
|
|
|
<option key={model} value={model}>
|
|
|
- {model}
|
|
|
+ {getModelDisplayName(model)}
|
|
|
</option>
|
|
|
),
|
|
|
)}
|
|
@@ -305,6 +364,64 @@ export const ModelSettings = () => {
|
|
|
/>
|
|
|
</div>
|
|
|
</div>
|
|
|
+
|
|
|
+ <div className="border-t border-gray-200" />
|
|
|
+
|
|
|
+ {/* OpenRouter Section */}
|
|
|
+ <div className="space-y-4">
|
|
|
+ <div className="flex items-center justify-between">
|
|
|
+ <h3 className="text-lg font-medium text-gray-700">OpenRouter</h3>
|
|
|
+ <div className="flex items-center space-x-2">
|
|
|
+ {isLoadingModels && <span className="text-sm text-gray-500">Loading models...</span>}
|
|
|
+ <Button
|
|
|
+ {...getButtonProps(LLMProviderEnum.OpenRouter)}
|
|
|
+ size="sm"
|
|
|
+ onClick={() =>
|
|
|
+ apiKeys[LLMProviderEnum.OpenRouter]?.apiKey && !modifiedProviders.has(LLMProviderEnum.OpenRouter)
|
|
|
+ ? handleDelete(LLMProviderEnum.OpenRouter)
|
|
|
+ : handleSave(LLMProviderEnum.OpenRouter)
|
|
|
+ }
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div className="space-y-3">
|
|
|
+ <input
|
|
|
+ type="password"
|
|
|
+ placeholder="OpenRouter API key"
|
|
|
+ value={apiKeys[LLMProviderEnum.OpenRouter]?.apiKey || ''}
|
|
|
+ onChange={e => handleApiKeyChange(LLMProviderEnum.OpenRouter, e.target.value)}
|
|
|
+ className="w-full p-2 rounded-md bg-gray-50 border border-gray-200 focus:border-blue-400 focus:ring-2 focus:ring-blue-200 outline-none"
|
|
|
+ />
|
|
|
+ <input
|
|
|
+ type="text"
|
|
|
+ placeholder="Custom Base URL (Optional)"
|
|
|
+ value={apiKeys[LLMProviderEnum.OpenRouter]?.baseUrl || ''}
|
|
|
+ onChange={e =>
|
|
|
+ handleApiKeyChange(
|
|
|
+ LLMProviderEnum.OpenRouter,
|
|
|
+ apiKeys[LLMProviderEnum.OpenRouter]?.apiKey || '',
|
|
|
+ e.target.value,
|
|
|
+ )
|
|
|
+ }
|
|
|
+ className="w-full p-2 rounded-md bg-gray-50 border border-gray-200 focus:border-blue-400 focus:ring-2 focus:ring-blue-200 outline-none"
|
|
|
+ />
|
|
|
+ {openRouterModels.length > 0 && (
|
|
|
+ <div className="mt-2 p-2 bg-gray-50 rounded-md">
|
|
|
+ <p className="text-sm font-medium text-gray-700 mb-1">Available Models: {openRouterModels.length}</p>
|
|
|
+ <div className="text-xs text-gray-500 max-h-24 overflow-y-auto">
|
|
|
+ {openRouterModels.slice(0, 5).map(model => (
|
|
|
+ <div key={model.id} className="mb-1">
|
|
|
+ {model.name} ({model.id})
|
|
|
+ </div>
|
|
|
+ ))}
|
|
|
+ {openRouterModels.length > 5 && (
|
|
|
+ <div className="text-xs text-gray-500">...and {openRouterModels.length - 5} more</div>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|