feat: dynamically load ollama models (#5309)
This commit is contained in:
@@ -285,6 +285,42 @@ impl Provider for OllamaProvider {
|
|||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn fetch_supported_models(&self) -> Result<Option<Vec<String>>, ProviderError> {
|
||||||
|
let response = self
|
||||||
|
.api_client
|
||||||
|
.response_get("api/tags")
|
||||||
|
.await
|
||||||
|
.map_err(|e| ProviderError::RequestFailed(format!("Failed to fetch models: {}", e)))?;
|
||||||
|
|
||||||
|
if !response.status().is_success() {
|
||||||
|
return Err(ProviderError::RequestFailed(format!(
|
||||||
|
"Failed to fetch models: HTTP {}",
|
||||||
|
response.status()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let json_response = response.json::<Value>().await.map_err(|e| {
|
||||||
|
ProviderError::RequestFailed(format!("Failed to parse response: {}", e))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let models = json_response
|
||||||
|
.get("models")
|
||||||
|
.and_then(|m| m.as_array())
|
||||||
|
.ok_or_else(|| {
|
||||||
|
ProviderError::RequestFailed("No models array in response".to_string())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let mut model_names: Vec<String> = models
|
||||||
|
.iter()
|
||||||
|
.filter_map(|model| model.get("name").and_then(|n| n.as_str()).map(String::from))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Sort alphabetically
|
||||||
|
model_names.sort();
|
||||||
|
|
||||||
|
Ok(Some(model_names))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OllamaProvider {
|
impl OllamaProvider {
|
||||||
|
|||||||
@@ -39,3 +39,38 @@ export async function getProviderMetadata(
|
|||||||
}
|
}
|
||||||
return matches.metadata;
|
return matches.metadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ProviderModelsResult {
|
||||||
|
provider: ProviderDetails;
|
||||||
|
models: string[] | null;
|
||||||
|
error: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches models for all active providers in parallel.
|
||||||
|
* Falls back to known_models if fetching fails or returns no models.
|
||||||
|
*/
|
||||||
|
export async function fetchModelsForProviders(
|
||||||
|
activeProviders: ProviderDetails[],
|
||||||
|
getProviderModelsFunc: (providerName: string) => Promise<string[]>
|
||||||
|
): Promise<ProviderModelsResult[]> {
|
||||||
|
const modelPromises = activeProviders.map(async (p) => {
|
||||||
|
const providerName = p.name;
|
||||||
|
try {
|
||||||
|
let models = await getProviderModelsFunc(providerName);
|
||||||
|
if ((!models || models.length === 0) && p.metadata.known_models?.length) {
|
||||||
|
models = p.metadata.known_models.map((m) => m.name);
|
||||||
|
}
|
||||||
|
return { provider: p, models, error: null };
|
||||||
|
} catch (e: unknown) {
|
||||||
|
const errorMessage = `Failed to fetch models for ${providerName}${e instanceof Error ? `: ${e.message}` : ''}`;
|
||||||
|
return {
|
||||||
|
provider: p,
|
||||||
|
models: null,
|
||||||
|
error: errorMessage,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return await Promise.all(modelPromises);
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { Button } from '../../../ui/button';
|
|||||||
import { Select } from '../../../ui/Select';
|
import { Select } from '../../../ui/Select';
|
||||||
import { Input } from '../../../ui/input';
|
import { Input } from '../../../ui/input';
|
||||||
import { getPredefinedModelsFromEnv, shouldShowPredefinedModels } from '../predefinedModelsUtils';
|
import { getPredefinedModelsFromEnv, shouldShowPredefinedModels } from '../predefinedModelsUtils';
|
||||||
|
import { fetchModelsForProviders } from '../modelInterface';
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '../../../ui/dialog';
|
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '../../../ui/dialog';
|
||||||
|
|
||||||
interface LeadWorkerSettingsProps {
|
interface LeadWorkerSettingsProps {
|
||||||
@@ -13,7 +14,7 @@ interface LeadWorkerSettingsProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function LeadWorkerSettings({ isOpen, onClose }: LeadWorkerSettingsProps) {
|
export function LeadWorkerSettings({ isOpen, onClose }: LeadWorkerSettingsProps) {
|
||||||
const { read, upsert, getProviders, remove } = useConfig();
|
const { read, upsert, getProviders, getProviderModels, remove } = useConfig();
|
||||||
const { currentModel } = useModelAndProvider();
|
const { currentModel } = useModelAndProvider();
|
||||||
const [leadModel, setLeadModel] = useState<string>('');
|
const [leadModel, setLeadModel] = useState<string>('');
|
||||||
const [workerModel, setWorkerModel] = useState<string>('');
|
const [workerModel, setWorkerModel] = useState<string>('');
|
||||||
@@ -103,13 +104,18 @@ export function LeadWorkerSettings({ isOpen, onClose }: LeadWorkerSettingsProps)
|
|||||||
const providers = await getProviders(false);
|
const providers = await getProviders(false);
|
||||||
const activeProviders = providers.filter((p) => p.is_configured);
|
const activeProviders = providers.filter((p) => p.is_configured);
|
||||||
|
|
||||||
activeProviders.forEach(({ metadata, name }) => {
|
const results = await fetchModelsForProviders(activeProviders, getProviderModels);
|
||||||
if (metadata.known_models) {
|
results.forEach(({ provider: p, models, error }) => {
|
||||||
metadata.known_models.forEach((model) => {
|
if (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (models && models.length > 0) {
|
||||||
|
models.forEach((modelName) => {
|
||||||
options.push({
|
options.push({
|
||||||
value: model.name,
|
value: modelName,
|
||||||
label: `${model.name} (${metadata.display_name})`,
|
label: `${modelName} (${p.metadata.display_name})`,
|
||||||
provider: name,
|
provider: p.name,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -128,7 +134,7 @@ export function LeadWorkerSettings({ isOpen, onClose }: LeadWorkerSettingsProps)
|
|||||||
};
|
};
|
||||||
|
|
||||||
loadConfig();
|
loadConfig();
|
||||||
}, [read, getProviders, currentModel, isOpen]);
|
}, [read, getProviders, getProviderModels, currentModel, isOpen]);
|
||||||
|
|
||||||
// If current models are not in the list (e.g., previously set to custom), switch to custom mode
|
// If current models are not in the list (e.g., previously set to custom), switch to custom mode
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import { Select } from '../../../ui/Select';
|
|||||||
import { useConfig } from '../../../ConfigContext';
|
import { useConfig } from '../../../ConfigContext';
|
||||||
import { useModelAndProvider } from '../../../ModelAndProviderContext';
|
import { useModelAndProvider } from '../../../ModelAndProviderContext';
|
||||||
import type { View } from '../../../../utils/navigationUtils';
|
import type { View } from '../../../../utils/navigationUtils';
|
||||||
import Model, { getProviderMetadata } from '../modelInterface';
|
import Model, { getProviderMetadata, fetchModelsForProviders } from '../modelInterface';
|
||||||
import { getPredefinedModelsFromEnv, shouldShowPredefinedModels } from '../predefinedModelsUtils';
|
import { getPredefinedModelsFromEnv, shouldShowPredefinedModels } from '../predefinedModelsUtils';
|
||||||
import { ProviderType } from '../../../../api';
|
import { ProviderType } from '../../../../api';
|
||||||
|
|
||||||
@@ -146,24 +146,7 @@ export const SwitchModelModal = ({ sessionId, onClose, setView }: SwitchModelMod
|
|||||||
setLoadingModels(true);
|
setLoadingModels(true);
|
||||||
|
|
||||||
// Fetching models for all providers
|
// Fetching models for all providers
|
||||||
const modelPromises = activeProviders.map(async (p) => {
|
const results = await fetchModelsForProviders(activeProviders, getProviderModels);
|
||||||
const providerName = p.name;
|
|
||||||
try {
|
|
||||||
let models = await getProviderModels(providerName);
|
|
||||||
// Fallback to known_models if server returned none
|
|
||||||
if ((!models || models.length === 0) && p.metadata.known_models?.length) {
|
|
||||||
models = p.metadata.known_models.map((m) => m.name);
|
|
||||||
}
|
|
||||||
return { provider: p, models, error: null };
|
|
||||||
} catch (e: unknown) {
|
|
||||||
return {
|
|
||||||
provider: p,
|
|
||||||
models: null,
|
|
||||||
error: `Failed to fetch models for ${providerName}${e instanceof Error ? `: ${e.message}` : ''}`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const results = await Promise.all(modelPromises);
|
|
||||||
|
|
||||||
// Process results and build grouped options
|
// Process results and build grouped options
|
||||||
const groupedOptions: {
|
const groupedOptions: {
|
||||||
|
|||||||
Reference in New Issue
Block a user