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
17 changes: 10 additions & 7 deletions src/app/(dashboard)/dashboard/providers/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -460,7 +460,7 @@ interface CooldownTimerProps {

function getModelSourceBadgeClass(source?: string): string {
switch (normalizeModelCatalogSource(source)) {
case "api-sync":
case "imported":
return "border-sky-500/30 bg-sky-500/10 text-sky-300";
case "custom":
return "border-emerald-500/30 bg-emerald-500/10 text-emerald-300";
Expand Down Expand Up @@ -1041,12 +1041,12 @@ export default function ProviderDetailPage() {
const isOAuth = providerSupportsOAuth && !providerSupportsPat;
const registryModels = getModelsByProviderId(providerId);
// Prefer synced API-discovered models when available, then merge built-ins
// and user-managed custom/imported models without duplicating IDs.
// and user-managed custom models without duplicating IDs.
const models = useMemo(() => {
if (providerId === "gemini") {
return syncedAvailableModels.map((model: any) => ({
...model,
source: model?.source === "api-sync" ? "api-sync" : "api-sync",
source: "imported",
}));
}

Expand All @@ -1061,15 +1061,15 @@ export default function ProviderDetailPage() {
.map((model: any) => ({
id: model.id,
name: model.name || model.id,
source: "api-sync",
source: "imported",
}));
const knownIds = new Set([...registryIds, ...syncedExtras.map((model: any) => model.id)]);
const customExtras = modelMeta.customModels
.filter((cm: any) => cm.id && !knownIds.has(cm.id))
.map((cm: any) => ({
id: cm.id,
name: cm.name || cm.id,
source: cm.source === "api-sync" ? "api-sync" : "custom",
source: normalizeModelCatalogSource(cm.source) === "imported" ? "imported" : "custom",
}));
return [...builtInModels, ...syncedExtras, ...customExtras];
}, [providerId, registryModels, syncedAvailableModels, modelMeta.customModels]);
Expand Down Expand Up @@ -2097,6 +2097,9 @@ export default function ProviderDetailPage() {
typeof data.importedChanges?.total === "number"
? data.importedChanges.total
: importedCount;
const totalChangedCount =
changedCount +
(typeof data.customModelChanges?.total === "number" ? data.customModelChanges.total : 0);

if (importedModels.length === 0) {
setImportProgress((prev) => ({
Expand All @@ -2113,7 +2116,7 @@ export default function ProviderDetailPage() {
],
importedCount,
}));
if (changedCount > 0) {
if (totalChangedCount > 0) {
setTimeout(() => {
window.location.reload();
}, 2000);
Expand Down Expand Up @@ -2142,7 +2145,7 @@ export default function ProviderDetailPage() {
importedCount,
}));

if (changedCount > 0) {
if (totalChangedCount > 0) {
setTimeout(() => {
window.location.reload();
}, 2000);
Expand Down
81 changes: 49 additions & 32 deletions src/app/api/pricing/models/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { NextResponse } from "next/server";
import { REGISTRY } from "@omniroute/open-sse/config/providerRegistry.ts";
import { getAllCustomModels, getPricing } from "@/lib/localDb";
import { getAllCustomModels, getAllSyncedAvailableModels, getPricing } from "@/lib/localDb";

function asRecord(value: unknown): Record<string, unknown> {
return value && typeof value === "object" && !Array.isArray(value)
Expand All @@ -20,8 +20,9 @@ function asModelArray(value: unknown): Array<{ id?: string; name?: string }> {
* GET /api/pricing/models
* Returns the full model catalog merged from three sources:
* 1. providerRegistry (hardcoded)
* 2. customModels (DB — user-added or imported via /models)
* 3. pricing data (DB — models with pricing configured but not in sources 1/2)
* 2. syncedAvailableModels (DB — discovered/imported from provider /models)
* 3. customModels (DB — manually added models)
* 4. pricing data (DB — models with pricing configured but not in sources 1/2/3)
*/
export async function GET() {
try {
Expand All @@ -46,25 +47,14 @@ export async function GET() {
};
}

// ── 2. Custom models (DB) ───────────────────────────────────────
let customModelsMap: Record<string, unknown> = {};
try {
customModelsMap = asRecord(await getAllCustomModels());
} catch {
/* DB may not be ready */
}

for (const [providerId, rawModels] of Object.entries(customModelsMap)) {
const models = asModelArray(rawModels);
// Resolve alias — check if a registry entry maps this providerId
let alias = providerId;
const resolveAlias = (providerId: string) => {
for (const entry of Object.values(REGISTRY)) {
if (entry.id === providerId) {
alias = entry.alias || entry.id;
break;
}
if (entry.id === providerId) return entry.alias || entry.id;
}
return providerId;
};

const ensureCatalogProvider = (providerId: string, alias: string) => {
if (!catalog[alias]) {
catalog[alias] = {
id: providerId,
Expand All @@ -75,25 +65,52 @@ export async function GET() {
models: [],
};
}
return catalog[alias];
};

const appendDbModels = (providerId: string, rawModels: unknown) => {
const models = asModelArray(rawModels);
const alias = resolveAlias(providerId);
const providerCatalog = ensureCatalogProvider(providerId, alias);
const existingIds = new Set(providerCatalog.models.map((m) => m.id));

const existingIds = new Set(catalog[alias].models.map((m) => m.id));
for (const model of models) {
const modelId = typeof model.id === "string" ? model.id : null;
if (!modelId || existingIds.has(modelId)) {
continue;
}
if (!existingIds.has(modelId)) {
catalog[alias].models.push({
id: modelId,
name: typeof model.name === "string" && model.name.trim() ? model.name : modelId,
custom: true,
});
existingIds.add(modelId);
}
if (!modelId || existingIds.has(modelId)) continue;
providerCatalog.models.push({
id: modelId,
name: typeof model.name === "string" && model.name.trim() ? model.name : modelId,
custom: true,
});
existingIds.add(modelId);
}
};

// ── 2. Synced available models (DB) ─────────────────────────────
let syncedModelsMap: Record<string, unknown> = {};
try {
syncedModelsMap = asRecord(await getAllSyncedAvailableModels());
} catch {
/* DB may not be ready */
}

for (const [providerId, rawModels] of Object.entries(syncedModelsMap)) {
appendDbModels(providerId, rawModels);
}

// ── 3. Custom models (DB) ───────────────────────────────────────
let customModelsMap: Record<string, unknown> = {};
try {
customModelsMap = asRecord(await getAllCustomModels());
} catch {
/* DB may not be ready */
}

for (const [providerId, rawModels] of Object.entries(customModelsMap)) {
appendDbModels(providerId, rawModels);
}

// ── 3. Pricing-only models (DB) ─────────────────────────────────
// ── 4. Pricing-only models (DB) ─────────────────────────────────
let pricingData: Record<string, any> = {};
try {
pricingData = await getPricing();
Expand Down
Loading
Loading