remove provider

This commit is contained in:
Arjun 2026-02-28 00:09:47 +05:30
parent cd2f25f928
commit e6cdbb393e
2 changed files with 87 additions and 21 deletions

View file

@ -175,10 +175,21 @@ function ChatInputInner({
loadModelConfig() loadModelConfig()
}, [isActive, loadModelConfig]) }, [isActive, loadModelConfig])
// Reload when model config changes (e.g. from settings dialog)
useEffect(() => {
const handler = () => { loadModelConfig() }
window.addEventListener('models-config-changed', handler)
return () => window.removeEventListener('models-config-changed', handler)
}, [loadModelConfig])
const handleModelChange = useCallback(async (key: string) => { const handleModelChange = useCallback(async (key: string) => {
const entry = configuredModels.find((m) => `${m.flavor}/${m.model}` === key) const entry = configuredModels.find((m) => `${m.flavor}/${m.model}` === key)
if (!entry) return if (!entry) return
setActiveModelKey(key) setActiveModelKey(key)
// Collect all models for this provider so the full list is preserved
const providerModels = configuredModels
.filter((m) => m.flavor === entry.flavor)
.map((m) => m.model)
try { try {
await window.ipc.invoke('models:saveConfig', { await window.ipc.invoke('models:saveConfig', {
provider: { provider: {
@ -188,6 +199,7 @@ function ChatInputInner({
headers: entry.headers, headers: entry.headers,
}, },
model: entry.model, model: entry.model,
models: providerModels,
knowledgeGraphModel: entry.knowledgeGraphModel, knowledgeGraphModel: entry.knowledgeGraphModel,
}) })
} catch { } catch {

View file

@ -278,17 +278,20 @@ function ModelSettings({ dialogOpen }: { dialogOpen: boolean }) {
} }
} }
} }
// Active provider takes precedence from top-level config // Active provider takes precedence from top-level config,
const existingModels = next[flavor].models; // but only if it exists in the providers map (wasn't deleted)
const activeModels = existingModels[0] === parsed.model if (parsed.providers?.[flavor]) {
? existingModels const existingModels = next[flavor].models;
: [parsed.model, ...existingModels.filter((m: string) => m && m !== parsed.model)]; const activeModels = existingModels[0] === parsed.model
next[flavor] = { ? existingModels
apiKey: parsed.provider.apiKey || "", : [parsed.model, ...existingModels.filter((m: string) => m && m !== parsed.model)];
baseURL: parsed.provider.baseURL || (defaultBaseURLs[flavor] || ""), next[flavor] = {
models: activeModels.length > 0 ? activeModels : [""], apiKey: parsed.provider.apiKey || "",
knowledgeGraphModel: parsed.knowledgeGraphModel || "", baseURL: parsed.provider.baseURL || (defaultBaseURLs[flavor] || ""),
}; models: activeModels.length > 0 ? activeModels : [""],
knowledgeGraphModel: parsed.knowledgeGraphModel || "",
};
}
return next; return next;
}) })
} }
@ -366,6 +369,7 @@ function ModelSettings({ dialogOpen }: { dialogOpen: boolean }) {
await window.ipc.invoke("models:saveConfig", providerConfig) await window.ipc.invoke("models:saveConfig", providerConfig)
setDefaultProvider(provider) setDefaultProvider(provider)
setTestState({ status: "success" }) setTestState({ status: "success" })
window.dispatchEvent(new Event('models-config-changed'))
toast.success("Model configuration saved") toast.success("Model configuration saved")
} else { } else {
setTestState({ status: "error", error: result.error }) setTestState({ status: "error", error: result.error })
@ -393,12 +397,50 @@ function ModelSettings({ dialogOpen }: { dialogOpen: boolean }) {
knowledgeGraphModel: config.knowledgeGraphModel.trim() || undefined, knowledgeGraphModel: config.knowledgeGraphModel.trim() || undefined,
}) })
setDefaultProvider(prov) setDefaultProvider(prov)
window.dispatchEvent(new Event('models-config-changed'))
toast.success("Default provider updated") toast.success("Default provider updated")
} catch { } catch {
toast.error("Failed to set default provider") toast.error("Failed to set default provider")
} }
}, [providerConfigs]) }, [providerConfigs])
const handleDeleteProvider = useCallback(async (prov: LlmProviderFlavor) => {
try {
const result = await window.ipc.invoke("workspace:readFile", { path: "config/models.json" })
const parsed = JSON.parse(result.data)
if (parsed?.providers?.[prov]) {
delete parsed.providers[prov]
}
// If the deleted provider is the current top-level active one,
// switch top-level config to the current default provider
if (parsed?.provider?.flavor === prov && defaultProvider && defaultProvider !== prov) {
const defConfig = providerConfigs[defaultProvider]
const defModels = defConfig.models.map(m => m.trim()).filter(Boolean)
parsed.provider = {
flavor: defaultProvider,
apiKey: defConfig.apiKey.trim() || undefined,
baseURL: defConfig.baseURL.trim() || undefined,
}
parsed.model = defModels[0] || ""
parsed.models = defModels
parsed.knowledgeGraphModel = defConfig.knowledgeGraphModel.trim() || undefined
}
await window.ipc.invoke("workspace:writeFile", {
path: "config/models.json",
data: JSON.stringify(parsed, null, 2),
})
setProviderConfigs(prev => ({
...prev,
[prov]: { apiKey: "", baseURL: defaultBaseURLs[prov] || "", models: [""], knowledgeGraphModel: "" },
}))
setTestState({ status: "idle" })
window.dispatchEvent(new Event('models-config-changed'))
toast.success("Provider configuration removed")
} catch {
toast.error("Failed to remove provider")
}
}, [defaultProvider, providerConfigs])
const renderProviderCard = (p: { id: LlmProviderFlavor; name: string; description: string }) => { const renderProviderCard = (p: { id: LlmProviderFlavor; name: string; description: string }) => {
const isDefault = defaultProvider === p.id const isDefault = defaultProvider === p.id
const isSelected = provider === p.id const isSelected = provider === p.id
@ -427,16 +469,28 @@ function ModelSettings({ dialogOpen }: { dialogOpen: boolean }) {
</div> </div>
<div className="text-xs text-muted-foreground mt-0.5">{p.description}</div> <div className="text-xs text-muted-foreground mt-0.5">{p.description}</div>
{!isDefault && hasModel && isSelected && ( {!isDefault && hasModel && isSelected && (
<span <div className="mt-1.5 flex items-center gap-3">
role="button" <span
onClick={(e) => { role="button"
e.stopPropagation() onClick={(e) => {
handleSetDefault(p.id) e.stopPropagation()
}} handleSetDefault(p.id)
className="mt-1.5 inline-flex text-[11px] text-muted-foreground hover:text-primary transition-colors cursor-pointer" }}
> className="inline-flex text-[11px] text-muted-foreground hover:text-primary transition-colors cursor-pointer"
Set as default >
</span> Set as default
</span>
<span
role="button"
onClick={(e) => {
e.stopPropagation()
handleDeleteProvider(p.id)
}}
className="inline-flex text-[11px] text-muted-foreground hover:text-destructive transition-colors cursor-pointer"
>
Remove
</span>
</div>
)} )}
</button> </button>
) )