Merge PR #2261: fix: align managed model cleanup for imported models (thanks @InkshadeWoods)

Adds deleteSyncedAvailableModelsForProvider() for full provider-level
cleanup, fixes delete-alias button visibility (source=alias only),
compatible models section gets proper 3-way delete logic.
This commit is contained in:
diegosouzapw
2026-05-14 15:47:56 -03:00
5 changed files with 59 additions and 11 deletions

View File

@@ -2735,10 +2735,7 @@ export default function ProviderDetailPage() {
notify.error(detail || t("failedSaveCustomModel"));
return;
}
await Promise.all([
fetchProviderModelMeta().catch(() => {}),
fetchAliases().catch(() => {}),
]);
await Promise.all([fetchProviderModelMeta().catch(() => {}), fetchAliases().catch(() => {})]);
} catch {
notify.error(t("failedSaveCustomModel"));
} finally {
@@ -2764,10 +2761,7 @@ export default function ProviderDetailPage() {
notify.error(detail || t("failedSaveCustomModel"));
return;
}
await Promise.all([
fetchProviderModelMeta().catch(() => {}),
fetchAliases().catch(() => {}),
]);
await Promise.all([fetchProviderModelMeta().catch(() => {}), fetchAliases().catch(() => {})]);
} catch {
notify.error(t("failedSaveCustomModel"));
} finally {
@@ -4281,7 +4275,14 @@ function PassthroughModelsSection({
}
return rows;
}, [availableModels, customModelMap, customModels, isModelHidden, providerAlias, providerAliases]);
}, [
availableModels,
customModelMap,
customModels,
isModelHidden,
providerAlias,
providerAliases,
]);
const filteredModels = allModels.filter((model) =>
matchesModelCatalogQuery(modelFilter, {
modelId: model.modelId,
@@ -4380,7 +4381,7 @@ function PassthroughModelsSection({
isHidden={isHidden}
copied={copied}
onCopy={onCopy}
onDeleteAlias={alias ? () => onDeleteAlias(alias) : undefined}
onDeleteAlias={source === "alias" && alias ? () => onDeleteAlias(alias) : undefined}
t={t}
showDeveloperToggle
effectiveModelNormalize={effectiveModelNormalize}
@@ -5350,7 +5351,13 @@ function CompatibleModelsSection({
isHidden={isHidden}
copied={copied}
onCopy={onCopy}
onDeleteAlias={() => handleDeleteModel(modelId, alias)}
onDeleteAlias={
source === "custom" || source === "manual"
? () => handleDeleteModel(modelId, alias)
: source === "alias" && alias
? () => onDeleteAlias(alias)
: undefined
}
t={t}
showDeveloperToggle={!isAnthropic}
effectiveModelNormalize={effectiveModelNormalize}

View File

@@ -4,6 +4,7 @@ import {
addCustomModel,
removeCustomModel,
replaceCustomModels,
deleteSyncedAvailableModelsForProvider,
updateCustomModel,
getModelCompatOverrides,
mergeModelCompatOverride,
@@ -369,9 +370,12 @@ export async function DELETE(request) {
const all = searchParams.get("all");
if (all === "true") {
await replaceCustomModels(provider, [], { allowEmpty: true });
const syncedAvailableModelListsRemoved =
await deleteSyncedAvailableModelsForProvider(provider);
const removedAliases = await deleteManagedAvailableModelAliasesForProvider(provider);
return Response.json({
cleared: true,
syncedAvailableModelListsRemoved,
aliasChanges: { removed: removedAliases, assigned: [] },
});
}

View File

@@ -707,6 +707,22 @@ export async function deleteSyncedAvailableModelsForConnection(
return getSyncedAvailableModels(providerId);
}
/**
* Delete all synced models for every connection belonging to a provider.
* Returns the number of connection-scoped synced model lists removed.
*/
export async function deleteSyncedAvailableModelsForProvider(providerId: string): Promise<number> {
const db = getDbInstance();
const keyPrefix = `${providerId}:`;
const result = db
.prepare(
"DELETE FROM key_value WHERE namespace = 'syncedAvailableModels' AND substr(key, 1, ?) = ?"
)
.run(keyPrefix.length, keyPrefix);
backupDbFile("pre-write");
return Number(result.changes || 0);
}
export async function updateCustomModel(
providerId: string,
modelId: string,

View File

@@ -65,6 +65,7 @@ export {
getAllSyncedAvailableModels,
replaceSyncedAvailableModelsForConnection,
deleteSyncedAvailableModelsForConnection,
deleteSyncedAvailableModelsForProvider,
} from "./db/models";
export type { ModelCompatPerProtocol, ModelCompatPatch, SyncedAvailableModel } from "./db/models";

View File

@@ -64,3 +64,23 @@ test("merge mode builds aliases from discovered models without pruning missing p
assert.equal(aliases["model-a"], undefined);
assert.equal(aliases["model-b"], "openrouter/shared/model-b");
});
test("provider-level synced model deletion removes only that provider", async () => {
await modelsDb.replaceSyncedAvailableModelsForConnection("openrouter", "conn-a", [
{ id: "shared/model-a", name: "Model A", source: "imported" },
]);
await modelsDb.replaceSyncedAvailableModelsForConnection("openrouter", "conn-b", [
{ id: "shared/model-b", name: "Model B", source: "imported" },
]);
await modelsDb.replaceSyncedAvailableModelsForConnection("openai", "conn-a", [
{ id: "shared/model-c", name: "Model C", source: "imported" },
]);
const removed = await modelsDb.deleteSyncedAvailableModelsForProvider("openrouter");
assert.equal(removed, 2);
assert.deepEqual(await modelsDb.getSyncedAvailableModels("openrouter"), []);
assert.deepEqual(await modelsDb.getSyncedAvailableModels("openai"), [
{ id: "shared/model-c", name: "Model C", source: "imported" },
]);
});