Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ui/desktop/src/components/ConfigContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ export const ConfigProvider: React.FC<ConfigProviderProps> = ({ children }) => {
return providersData;
} catch (error) {
console.error('Failed to fetch providers:', error);
return [];
return providersListRef.current;
}
}
return providersListRef.current;
Expand Down
20 changes: 18 additions & 2 deletions ui/desktop/src/components/settings/models/modelInterface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export interface ProviderModelsResult {
provider: ProviderDetails;
models: string[] | null;
error: string | null;
warning: string | null;
}

export async function fetchModelsForProviders(
Expand All @@ -61,22 +62,37 @@ export async function fetchModelsForProviders(
const downloadedModels = allModels
.filter((m) => m.status.state === 'Downloaded')
.map((m) => m.id);
return { provider: p, models: downloadedModels, error: null };
return { provider: p, models: downloadedModels, error: null, warning: null };
}

const response = await getProviderModels({
path: { name: p.name },
throwOnError: true,
});
const models = response.data || [];
return { provider: p, models, error: null };
return { provider: p, models, error: null, warning: null };
} catch (e: unknown) {
// For custom providers, fall back to the configured model list
if (p.provider_type === 'Custom') {
const fallbackModels = p.metadata.known_models.map((m) => m.name);
if (fallbackModels.length > 0) {
console.warn(`Failed to fetch models for ${p.name}:`, getErrorMessage(e));
return {
provider: p,
models: fallbackModels,
error: null,
warning: `Could not fetch models from provider — showing configured models instead.`,
};
}
}

const errMsg = getErrorMessage(e);
const errorMessage = `Failed to fetch models for ${p.name}${errMsg ? `: ${errMsg}` : ''}`;
return {
provider: p,
models: null,
error: errorMessage,
warning: null,
};
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,9 @@ export const SwitchModelModal = ({
const [provider, setProvider] = useState<string | null>(
initialProvider || currentProvider || null
);
const [model, setModel] = useState<string>(currentModel || '');
const [model, setModel] = useState<string>(
initialProvider && initialProvider !== currentProvider ? '' : currentModel || ''
);
const [isCustomModel, setIsCustomModel] = useState(false);
const [validationErrors, setValidationErrors] = useState({
provider: '',
Expand All @@ -118,6 +120,7 @@ export const SwitchModelModal = ({
const [loadingModels, setLoadingModels] = useState<boolean>(false);
const [userClearedModel, setUserClearedModel] = useState(false);
const [providerErrors, setProviderErrors] = useState<Record<string, string>>({});
const [providerWarnings, setProviderWarnings] = useState<Record<string, string>>({});
const [thinkingLevel, setThinkingLevel] = useState<string>('low');
const [claudeThinkingType, setClaudeThinkingType] = useState<string>('disabled');
const [claudeThinkingEffort, setClaudeThinkingEffort] = useState<string>('high');
Expand Down Expand Up @@ -231,7 +234,11 @@ export const SwitchModelModal = ({
if (claudeThinkingType === 'adaptive') {
upsert('CLAUDE_THINKING_EFFORT', claudeThinkingEffort, false).catch(console.warn);
} else if (claudeThinkingType === 'enabled') {
upsert('CLAUDE_THINKING_BUDGET', parseInt(claudeThinkingBudget, 10) || 16000, false).catch(console.warn);
upsert(
'CLAUDE_THINKING_BUDGET',
parseInt(claudeThinkingBudget, 10) || 16000,
false
).catch(console.warn);
}
}

Expand Down Expand Up @@ -297,8 +304,12 @@ export const SwitchModelModal = ({
options: { value: string; label: string; provider: string; providerType: ProviderType }[];
}[] = [];
const errorMap: Record<string, string> = {};
const warningMap: Record<string, string> = {};

results.forEach(({ provider: p, models, error }) => {
results.forEach(({ provider: p, models, error, warning }) => {
if (warning) {
warningMap[p.name] = warning;
}
if (error) {
errorMap[p.name] = error;
return;
Expand Down Expand Up @@ -332,8 +343,9 @@ export const SwitchModelModal = ({
}
});

// Save provider errors to state
// Save provider errors and warnings to state
setProviderErrors(errorMap);
setProviderWarnings(warningMap);

setModelOptions(groupedOptions);
setOriginalModelOptions(groupedOptions);
Expand Down Expand Up @@ -680,6 +692,13 @@ export const SwitchModelModal = ({
{attemptedSubmit && validationErrors.model && (
<div className="text-red-500 text-sm mt-1">{validationErrors.model}</div>
)}
{provider && providerWarnings[provider] && (
<div className="rounded-md bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 p-3 mt-2">
<div className="text-sm text-yellow-700 dark:text-yellow-300">
{providerWarnings[provider]}
</div>
</div>
)}
</div>
) : (
<div className="flex flex-col gap-2">
Expand Down
6 changes: 4 additions & 2 deletions ui/desktop/src/components/settings/providers/ProviderGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -196,11 +196,13 @@ function ProviderCards({
const handleCreateCustomProvider = useCallback(
async (data: UpdateCustomProviderRequest) => {
const { createCustomProvider } = await import('../../../api');
await createCustomProvider({ body: data, throwOnError: true });
const result = await createCustomProvider({ body: data, throwOnError: true });
const providerId = result.data?.replace('Custom provider added - ID: ', '') || null;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Stop parsing provider ID from human-readable response text

This extracts the new provider ID by stripping a hard-coded phrase from a free-form success message. Because the API response type is just string, any wording change or backend/UI version skew will produce a bad providerId, and the follow-up model picker opens without the intended provider preselected. Returning a structured payload (or resolving the new provider from refreshed provider data) avoids this brittle coupling.

Useful? React with 👍 / 👎.

setShowCustomProviderModal(false);
if (refreshProviders) {
refreshProviders();
await refreshProviders();
}
setSwitchModelProvider(providerId);
setShowSwitchModelModal(true);
},
[refreshProviders]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,10 @@ export default function ProviderSettings({
}, []); // Intentionally not including loadProviders in deps to prevent reloading

// This function will be passed to ProviderGrid for manual refreshes after config changes
const refreshProviders = useCallback(() => {
const refreshProviders = useCallback(async () => {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the biggest issue, not blocking here.

if (initialLoadDone.current) {
getProviders(true).then((result) => {
if (result) setProviders(result);
});
const result = await getProviders(true);
if (result) setProviders(result);
}
}, [getProviders]);

Expand Down
Loading