mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-05 06:42:12 +03:00
fix: sync managed model aliases with visibility
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -363,7 +363,7 @@ interface PassthroughModelRowProps {
|
||||
isHidden?: boolean;
|
||||
copied?: string;
|
||||
onCopy: (text: string, key: string) => void;
|
||||
onDeleteAlias: () => void;
|
||||
onDeleteAlias?: () => void;
|
||||
t: (key: string, values?: Record<string, unknown>) => string;
|
||||
showDeveloperToggle?: boolean;
|
||||
effectiveModelNormalize: (modelId: string, protocol?: string) => boolean;
|
||||
@@ -381,6 +381,7 @@ interface PassthroughModelRowProps {
|
||||
interface PassthroughModelsSectionProps {
|
||||
providerAlias: string;
|
||||
modelAliases: Record<string, string>;
|
||||
availableModels?: CompatModelRow[];
|
||||
customModels?: CompatModelRow[];
|
||||
copied?: string;
|
||||
onCopy: (text: string, key: string) => void;
|
||||
@@ -421,6 +422,7 @@ interface CompatibleModelsSectionProps {
|
||||
providerStorageAlias: string;
|
||||
providerDisplayAlias: string;
|
||||
modelAliases: Record<string, string>;
|
||||
availableModels?: CompatModelRow[];
|
||||
customModels?: CompatModelRow[];
|
||||
fallbackModels?: CompatModelRow[];
|
||||
allowImport: boolean;
|
||||
@@ -2729,8 +2731,10 @@ export default function ProviderDetailPage() {
|
||||
notify.error(detail || t("failedSaveCustomModel"));
|
||||
return;
|
||||
}
|
||||
// Optimistic update: refresh model meta
|
||||
await fetchProviderModelMeta().catch(() => {});
|
||||
await Promise.all([
|
||||
fetchProviderModelMeta().catch(() => {}),
|
||||
fetchAliases().catch(() => {}),
|
||||
]);
|
||||
} catch {
|
||||
notify.error(t("failedSaveCustomModel"));
|
||||
} finally {
|
||||
@@ -2756,7 +2760,10 @@ export default function ProviderDetailPage() {
|
||||
notify.error(detail || t("failedSaveCustomModel"));
|
||||
return;
|
||||
}
|
||||
await fetchProviderModelMeta().catch(() => {});
|
||||
await Promise.all([
|
||||
fetchProviderModelMeta().catch(() => {}),
|
||||
fetchAliases().catch(() => {}),
|
||||
]);
|
||||
} catch {
|
||||
notify.error(t("failedSaveCustomModel"));
|
||||
} finally {
|
||||
@@ -2824,6 +2831,7 @@ export default function ProviderDetailPage() {
|
||||
providerStorageAlias={providerStorageAlias}
|
||||
providerDisplayAlias={providerDisplayAlias}
|
||||
modelAliases={modelAliases}
|
||||
availableModels={syncedAvailableModels}
|
||||
customModels={modelMeta.customModels}
|
||||
fallbackModels={compatibleFallbackModels}
|
||||
description={description}
|
||||
@@ -2883,6 +2891,7 @@ export default function ProviderDetailPage() {
|
||||
<PassthroughModelsSection
|
||||
providerAlias={providerAlias}
|
||||
modelAliases={modelAliases}
|
||||
availableModels={syncedAvailableModels}
|
||||
customModels={modelMeta.customModels}
|
||||
copied={copied}
|
||||
onCopy={copy}
|
||||
@@ -4171,6 +4180,7 @@ function ModelVisibilityToolbar({
|
||||
function PassthroughModelsSection({
|
||||
providerAlias,
|
||||
modelAliases,
|
||||
availableModels = [],
|
||||
customModels = [],
|
||||
copied,
|
||||
onCopy,
|
||||
@@ -4196,24 +4206,78 @@ function PassthroughModelsSection({
|
||||
const [modelFilter, setModelFilter] = useState("");
|
||||
const customModelMap = useMemo(() => buildCompatMap(customModels), [customModels]);
|
||||
|
||||
const providerAliases = Object.entries(modelAliases).filter(([, model]: [string, any]) =>
|
||||
(model as string).startsWith(`${providerAlias}/`)
|
||||
const providerAliases = useMemo(
|
||||
() =>
|
||||
Object.entries(modelAliases).filter(([, model]: [string, any]) =>
|
||||
(model as string).startsWith(`${providerAlias}/`)
|
||||
),
|
||||
[modelAliases, providerAlias]
|
||||
);
|
||||
|
||||
const allModels = providerAliases.map(([alias, fullModel]: [string, any]) => {
|
||||
const fmStr = fullModel as string;
|
||||
const allModels = useMemo(() => {
|
||||
const prefix = `${providerAlias}/`;
|
||||
const modelId = fmStr.startsWith(prefix) ? fmStr.slice(prefix.length) : fmStr;
|
||||
const customModel = customModelMap.get(modelId);
|
||||
return {
|
||||
modelId,
|
||||
fullModel,
|
||||
alias,
|
||||
displayName: alias,
|
||||
source: customModel ? customModel.source || "custom" : "alias",
|
||||
isHidden: isModelHidden(modelId),
|
||||
const aliasByModelId = new Map<string, string>();
|
||||
const fullModelByModelId = new Map<string, string>();
|
||||
const rows: Array<{
|
||||
modelId: string;
|
||||
fullModel: string;
|
||||
alias: string | null;
|
||||
displayName: string;
|
||||
source: string;
|
||||
isHidden: boolean;
|
||||
}> = [];
|
||||
const seenModelIds = new Set<string>();
|
||||
|
||||
for (const [alias, fullModel] of providerAliases) {
|
||||
const fmStr = fullModel as string;
|
||||
const modelId = fmStr.startsWith(prefix) ? fmStr.slice(prefix.length) : fmStr;
|
||||
aliasByModelId.set(modelId, alias as string);
|
||||
fullModelByModelId.set(modelId, fmStr);
|
||||
}
|
||||
|
||||
const addModel = (model: CompatModelRow, source: string) => {
|
||||
if (!model?.id || seenModelIds.has(model.id)) return;
|
||||
const fullModel = fullModelByModelId.get(model.id) || `${providerAlias}/${model.id}`;
|
||||
rows.push({
|
||||
modelId: model.id,
|
||||
fullModel,
|
||||
alias: aliasByModelId.get(model.id) || null,
|
||||
displayName: model.name || model.id,
|
||||
source,
|
||||
isHidden: isModelHidden(model.id),
|
||||
});
|
||||
seenModelIds.add(model.id);
|
||||
};
|
||||
});
|
||||
|
||||
for (const model of availableModels) {
|
||||
addModel(model, "imported");
|
||||
}
|
||||
|
||||
for (const model of customModels) {
|
||||
addModel(
|
||||
model,
|
||||
normalizeModelCatalogSource(model.source) === "imported" ? "imported" : "custom"
|
||||
);
|
||||
}
|
||||
|
||||
for (const [alias, fullModel] of providerAliases) {
|
||||
const fmStr = fullModel as string;
|
||||
const modelId = fmStr.startsWith(prefix) ? fmStr.slice(prefix.length) : fmStr;
|
||||
if (!modelId || seenModelIds.has(modelId)) continue;
|
||||
const customModel = customModelMap.get(modelId);
|
||||
rows.push({
|
||||
modelId,
|
||||
fullModel: fmStr,
|
||||
alias: alias as string,
|
||||
displayName: alias as string,
|
||||
source: customModel ? customModel.source || "custom" : "alias",
|
||||
isHidden: isModelHidden(modelId),
|
||||
});
|
||||
seenModelIds.add(modelId);
|
||||
}
|
||||
|
||||
return rows;
|
||||
}, [availableModels, customModelMap, customModels, isModelHidden, providerAlias, providerAliases]);
|
||||
const filteredModels = allModels.filter((model) =>
|
||||
matchesModelCatalogQuery(modelFilter, {
|
||||
modelId: model.modelId,
|
||||
@@ -4312,7 +4376,7 @@ function PassthroughModelsSection({
|
||||
isHidden={isHidden}
|
||||
copied={copied}
|
||||
onCopy={onCopy}
|
||||
onDeleteAlias={() => onDeleteAlias(alias)}
|
||||
onDeleteAlias={alias ? () => onDeleteAlias(alias) : undefined}
|
||||
t={t}
|
||||
showDeveloperToggle
|
||||
effectiveModelNormalize={effectiveModelNormalize}
|
||||
@@ -4446,13 +4510,15 @@ function PassthroughModelRow({
|
||||
showDeveloperToggle={showDeveloperToggle}
|
||||
disabled={compatDisabled}
|
||||
/>
|
||||
<button
|
||||
onClick={onDeleteAlias}
|
||||
className="rounded p-1 text-red-500 hover:bg-red-50"
|
||||
title={t("removeModel")}
|
||||
>
|
||||
<span className="material-symbols-outlined text-sm">delete</span>
|
||||
</button>
|
||||
{onDeleteAlias && (
|
||||
<button
|
||||
onClick={onDeleteAlias}
|
||||
className="rounded p-1 text-red-500 hover:bg-red-50"
|
||||
title={t("removeModel")}
|
||||
>
|
||||
<span className="material-symbols-outlined text-sm">delete</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -4981,6 +5047,7 @@ function CompatibleModelsSection({
|
||||
providerStorageAlias,
|
||||
providerDisplayAlias,
|
||||
modelAliases,
|
||||
availableModels = [],
|
||||
customModels = [],
|
||||
fallbackModels = [],
|
||||
description,
|
||||
@@ -5026,35 +5093,75 @@ function CompatibleModelsSection({
|
||||
);
|
||||
|
||||
const allModels = useMemo(() => {
|
||||
const rows = providerAliases.map(([alias, fullModel]: [string, any]) => {
|
||||
const fmStr = fullModel as string;
|
||||
const prefix = `${providerStorageAlias}/`;
|
||||
const modelId = fmStr.startsWith(prefix) ? fmStr.slice(prefix.length) : fmStr;
|
||||
const customModel = customModelMap.get(modelId);
|
||||
return {
|
||||
modelId,
|
||||
alias,
|
||||
displayName: alias,
|
||||
source: customModel ? customModel.source || "custom" : "alias",
|
||||
isHidden: isModelHidden(modelId),
|
||||
};
|
||||
});
|
||||
const prefix = `${providerStorageAlias}/`;
|
||||
const aliasByModelId = new Map<string, string>();
|
||||
const rows: Array<{
|
||||
modelId: string;
|
||||
alias: string | null;
|
||||
displayName: string;
|
||||
source: string;
|
||||
isHidden: boolean;
|
||||
}> = [];
|
||||
const seenModelIds = new Set<string>();
|
||||
|
||||
const seenModelIds = new Set(rows.map((row) => row.modelId));
|
||||
for (const model of fallbackModels) {
|
||||
if (!model?.id || seenModelIds.has(model.id)) continue;
|
||||
for (const [alias, fullModel] of providerAliases) {
|
||||
const fmStr = fullModel as string;
|
||||
const modelId = fmStr.startsWith(prefix) ? fmStr.slice(prefix.length) : fmStr;
|
||||
aliasByModelId.set(modelId, alias as string);
|
||||
}
|
||||
|
||||
const addModel = (model: CompatModelRow, source: string) => {
|
||||
if (!model?.id || seenModelIds.has(model.id)) return;
|
||||
rows.push({
|
||||
modelId: model.id,
|
||||
alias: null,
|
||||
alias: aliasByModelId.get(model.id) || null,
|
||||
displayName: model.name || model.id,
|
||||
source: "fallback",
|
||||
source,
|
||||
isHidden: isModelHidden(model.id),
|
||||
});
|
||||
seenModelIds.add(model.id);
|
||||
};
|
||||
|
||||
for (const model of availableModels) {
|
||||
addModel(model, "imported");
|
||||
}
|
||||
|
||||
for (const model of customModels) {
|
||||
addModel(
|
||||
model,
|
||||
normalizeModelCatalogSource(model.source) === "imported" ? "imported" : "custom"
|
||||
);
|
||||
}
|
||||
|
||||
for (const model of fallbackModels) {
|
||||
addModel(model, "fallback");
|
||||
}
|
||||
|
||||
for (const [alias, fullModel] of providerAliases) {
|
||||
const fmStr = fullModel as string;
|
||||
const modelId = fmStr.startsWith(prefix) ? fmStr.slice(prefix.length) : fmStr;
|
||||
if (!modelId || seenModelIds.has(modelId)) continue;
|
||||
const customModel = customModelMap.get(modelId);
|
||||
rows.push({
|
||||
modelId,
|
||||
alias: alias as string,
|
||||
displayName: alias as string,
|
||||
source: customModel ? customModel.source || "custom" : "alias",
|
||||
isHidden: isModelHidden(modelId),
|
||||
});
|
||||
seenModelIds.add(modelId);
|
||||
}
|
||||
|
||||
return rows;
|
||||
}, [customModelMap, fallbackModels, isModelHidden, providerAliases, providerStorageAlias]);
|
||||
}, [
|
||||
availableModels,
|
||||
customModelMap,
|
||||
customModels,
|
||||
fallbackModels,
|
||||
isModelHidden,
|
||||
providerAliases,
|
||||
providerStorageAlias,
|
||||
]);
|
||||
const filteredModels = allModels.filter((model) =>
|
||||
matchesModelCatalogQuery(modelFilter, {
|
||||
modelId: model.modelId,
|
||||
|
||||
@@ -9,6 +9,11 @@ import {
|
||||
mergeModelCompatOverride,
|
||||
type ModelCompatPatch,
|
||||
} from "@/lib/localDb";
|
||||
import {
|
||||
deleteManagedAvailableModelAliases,
|
||||
deleteManagedAvailableModelAliasesForProvider,
|
||||
syncManagedAvailableModelAliases,
|
||||
} from "@/lib/providerModels/managedAvailableModels";
|
||||
import {
|
||||
AI_PROVIDERS,
|
||||
isOpenAICompatibleProvider,
|
||||
@@ -305,9 +310,20 @@ export async function PATCH(request) {
|
||||
}
|
||||
}
|
||||
|
||||
const aliasChanges =
|
||||
body.isHidden === true
|
||||
? { removed: await deleteManagedAvailableModelAliases(provider, modelIds), assigned: [] }
|
||||
: {
|
||||
removed: [],
|
||||
assigned: (
|
||||
await syncManagedAvailableModelAliases(provider, modelIds, { pruneMissing: false })
|
||||
).assignedAliases,
|
||||
};
|
||||
|
||||
return Response.json({
|
||||
ok: true,
|
||||
updated: modelIds.length,
|
||||
aliasChanges,
|
||||
models: await getCustomModels(provider),
|
||||
modelCompatOverrides: getModelCompatOverrides(provider),
|
||||
});
|
||||
@@ -353,7 +369,8 @@ export async function DELETE(request) {
|
||||
const all = searchParams.get("all");
|
||||
if (all === "true") {
|
||||
await replaceCustomModels(provider, [], { allowEmpty: true });
|
||||
return Response.json({ cleared: true });
|
||||
const removedAliases = await deleteManagedAvailableModelAliasesForProvider(provider);
|
||||
return Response.json({ cleared: true, aliasChanges: { removed: removedAliases } });
|
||||
}
|
||||
|
||||
if (!modelId) {
|
||||
@@ -369,7 +386,8 @@ export async function DELETE(request) {
|
||||
}
|
||||
|
||||
const removed = await removeCustomModel(provider, modelId);
|
||||
return Response.json({ removed });
|
||||
const removedAliases = await deleteManagedAvailableModelAliases(provider, [modelId]);
|
||||
return Response.json({ removed, aliasChanges: { removed: removedAliases } });
|
||||
} catch (error) {
|
||||
console.error("Error removing provider model:", error);
|
||||
return Response.json(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
deleteModelAlias,
|
||||
getModelAliases,
|
||||
getModelIsHidden,
|
||||
getProviderNodeById,
|
||||
setModelAlias,
|
||||
} from "@/lib/localDb";
|
||||
@@ -34,11 +35,71 @@ async function getProviderDisplayPrefix(providerId: string): Promise<string> {
|
||||
return typeof prefix === "string" && prefix.trim().length > 0 ? prefix.trim() : providerId;
|
||||
}
|
||||
|
||||
function normalizeModelIds(modelIds: string[]): string[] {
|
||||
return Array.from(
|
||||
new Set(
|
||||
modelIds.map((modelId) => (typeof modelId === "string" ? modelId.trim() : "")).filter(Boolean)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function getManagedFullModelSet(providerId: string, modelIds: string[]): Set<string> {
|
||||
const storagePrefix = getProviderStoragePrefix(providerId);
|
||||
return new Set(normalizeModelIds(modelIds).map((modelId) => `${storagePrefix}/${modelId}`));
|
||||
}
|
||||
|
||||
export async function deleteManagedAvailableModelAliases(
|
||||
providerId: string,
|
||||
modelIds: string[]
|
||||
): Promise<string[]> {
|
||||
if (!usesManagedAvailableModels(providerId)) return [];
|
||||
|
||||
const targetFullModels = getManagedFullModelSet(providerId, modelIds);
|
||||
if (targetFullModels.size === 0) return [];
|
||||
|
||||
const existingAliasesRaw = await getModelAliases();
|
||||
const removedAliases: string[] = [];
|
||||
|
||||
for (const [alias, value] of Object.entries(existingAliasesRaw)) {
|
||||
if (typeof value !== "string" || !targetFullModels.has(value)) continue;
|
||||
await deleteModelAlias(alias);
|
||||
removedAliases.push(alias);
|
||||
}
|
||||
|
||||
return removedAliases;
|
||||
}
|
||||
|
||||
export async function deleteManagedAvailableModelAliasesForProvider(
|
||||
providerId: string
|
||||
): Promise<string[]> {
|
||||
if (!usesManagedAvailableModels(providerId)) return [];
|
||||
|
||||
const storagePrefix = getProviderStoragePrefix(providerId);
|
||||
const existingAliasesRaw = await getModelAliases();
|
||||
const removedAliases: string[] = [];
|
||||
|
||||
for (const [alias, value] of Object.entries(existingAliasesRaw)) {
|
||||
if (typeof value !== "string" || !value.startsWith(`${storagePrefix}/`)) continue;
|
||||
await deleteModelAlias(alias);
|
||||
removedAliases.push(alias);
|
||||
}
|
||||
|
||||
return removedAliases;
|
||||
}
|
||||
|
||||
export async function syncManagedAvailableModelAliases(
|
||||
providerId: string,
|
||||
modelIds: string[],
|
||||
{ pruneMissing = true }: { pruneMissing?: boolean } = {}
|
||||
) {
|
||||
if (!usesManagedAvailableModels(providerId)) {
|
||||
return {
|
||||
assignedAliases: [],
|
||||
removedAliases: [],
|
||||
storagePrefix: getProviderStoragePrefix(providerId),
|
||||
};
|
||||
}
|
||||
|
||||
const storagePrefix = getProviderStoragePrefix(providerId);
|
||||
const displayPrefix = await getProviderDisplayPrefix(providerId);
|
||||
const existingAliasesRaw = await getModelAliases();
|
||||
@@ -49,11 +110,7 @@ export async function syncManagedAvailableModelAliases(
|
||||
})
|
||||
);
|
||||
|
||||
const targetModelIds = Array.from(
|
||||
new Set(
|
||||
modelIds.map((modelId) => (typeof modelId === "string" ? modelId.trim() : "")).filter(Boolean)
|
||||
)
|
||||
);
|
||||
const targetModelIds = normalizeModelIds(modelIds);
|
||||
const targetFullModels = new Set(targetModelIds.map((modelId) => `${storagePrefix}/${modelId}`));
|
||||
const removedAliases: string[] = [];
|
||||
|
||||
@@ -71,6 +128,17 @@ export async function syncManagedAvailableModelAliases(
|
||||
const assignedAliases: string[] = [];
|
||||
|
||||
for (const modelId of targetModelIds) {
|
||||
if (getModelIsHidden(providerId, modelId)) {
|
||||
const fullModel = `${storagePrefix}/${modelId}`;
|
||||
for (const [alias, value] of Object.entries(workingAliases)) {
|
||||
if (value !== fullModel) continue;
|
||||
await deleteModelAlias(alias);
|
||||
delete workingAliases[alias];
|
||||
removedAliases.push(alias);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const fullModel = `${storagePrefix}/${modelId}`;
|
||||
const alias = resolveManagedModelAlias({
|
||||
modelId,
|
||||
|
||||
@@ -240,9 +240,10 @@ export async function importManagedModels({
|
||||
|
||||
let syncedAliases = 0;
|
||||
if (usesManagedAvailableModels(providerId) && (mode === "merge" || discoveredModels.length > 0)) {
|
||||
const aliasModelIds = mode === "sync" ? syncedAvailableModels : discoveredModels;
|
||||
const aliasSync = await syncManagedAvailableModelAliases(
|
||||
providerId,
|
||||
discoveredModels.map((model) => model.id),
|
||||
aliasModelIds.map((model) => model.id),
|
||||
{ pruneMissing: mode === "sync" }
|
||||
);
|
||||
syncedAliases = aliasSync.assignedAliases.length;
|
||||
|
||||
Reference in New Issue
Block a user