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
139 changes: 54 additions & 85 deletions src/app/(dashboard)/dashboard/providers/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -412,10 +412,7 @@ interface CompatibleModelsSectionProps {
onDeleteAlias: (alias: string) => void;
connections: { id?: string; isActive?: boolean }[];
isAnthropic?: boolean;
onImportWithProgress: (
fetchModels: () => Promise<{ models: unknown[] }>,
processModel: (model: unknown) => Promise<boolean>
) => Promise<void>;
onImportWithProgress: (connectionId: string) => Promise<void>;
t: (key: string, values?: Record<string, unknown>) => string;
effectiveModelNormalize: (alias: string) => boolean;
effectiveModelPreserveDeveloper: (alias: string) => boolean;
Expand Down Expand Up @@ -1356,10 +1353,16 @@ export default function ProviderDetailPage() {
}

const syncedCount = syncData.syncedModels || 0;
const availableCount =
typeof syncData.availableModelsCount === "number"
? syncData.availableModelsCount
: Array.isArray(syncData.models)
? syncData.models.length
: syncedCount;
const syncedModelList: Array<{ id: string; name?: string }> = syncData.models || [];
const logs: string[] = [];
if (syncedModelList.length > 0) {
logs.push(`✓ ${syncedCount} models available`);
logs.push(`✓ ${availableCount} models available`);
logs.push("");
for (const m of syncedModelList) {
logs.push(` ${m.name || m.id}`);
Expand All @@ -1369,10 +1372,10 @@ export default function ProviderDetailPage() {
setImportProgress((prev) => ({
...prev,
phase: "done",
status: t("modelsImported", { count: syncedCount }),
total: syncedCount,
current: syncedCount,
importedCount: syncedCount,
status: t("modelsImported", { count: availableCount }),
total: availableCount,
current: availableCount,
importedCount: availableCount,
logs,
}));

Expand Down Expand Up @@ -2004,10 +2007,7 @@ export default function ProviderDetailPage() {
};

// Shared import handler for CompatibleModelsSection
const handleCompatibleImportWithProgress = async (
fetchModels: () => Promise<{ models: any[] }>,
processModel: (model: any) => Promise<boolean>
) => {
const handleCompatibleImportWithProgress = async (connectionId: string) => {
setShowImportModal(true);
setImportProgress({
current: 0,
Expand All @@ -2020,61 +2020,68 @@ export default function ProviderDetailPage() {
});

try {
const data = await fetchModels();
const models = data.models || [];
if (models.length === 0) {
setImportProgress((prev) => ({
...prev,
phase: "done",
status: t("noModelsFound"),
logs: [t("noModelsReturnedFromEndpoint")],
}));
return;
const response = await fetch(`/api/providers/${connectionId}/sync-models?mode=import`, {
method: "POST",
signal: AbortSignal.timeout(60_000),
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || t("failedImportModels"));
}

setImportProgress((prev) => ({
...prev,
phase: "importing",
total: models.length,
status: t("importingModelsProgress", { current: 0, total: models.length }),
logs: [t("foundModelsStartingImport", { count: models.length })],
}));

let importedCount = 0;
for (let i = 0; i < models.length; i++) {
const model = models[i];
const modelId = model.id || model.name || model.model;
if (!modelId) continue;
const importedModels = Array.isArray(data.importedModels) ? data.importedModels : [];
const importedCount =
typeof data.importedCount === "number" ? data.importedCount : importedModels.length;
const changedCount =
typeof data.importedChanges?.total === "number"
? data.importedChanges.total
: importedCount;

if (importedModels.length === 0) {
setImportProgress((prev) => ({
...prev,
current: i + 1,
status: t("importingModelsProgress", { current: i + 1, total: models.length }),
logs: [...prev.logs, t("importingModelById", { modelId })],
phase: "done",
status:
importedCount > 0
? t("importSuccessCount", { count: importedCount })
: t("noNewModelsAdded"),
logs: [
importedCount > 0
? t("importDoneCount", { count: importedCount })
: t("noNewModelsAdded"),
],
importedCount,
}));

const added = await processModel(model);
if (added) importedCount += 1;
if (changedCount > 0) {
setTimeout(() => {
window.location.reload();
}, 2000);
}
return;
}

setImportProgress((prev) => ({
...prev,
phase: "done",
current: models.length,
total: importedModels.length,
current: importedModels.length,
status:
importedCount > 0
? t("importSuccessCount", { count: importedCount })
: t("noNewModelsAdded"),
logs: [
...prev.logs,
t("foundModelsStartingImport", { count: importedModels.length }),
...importedModels.map((model: any) =>
t("importingModelById", { modelId: model.id || model.name || model.model })
),
importedCount > 0
? t("importDoneCount", { count: importedCount })
: t("noNewModelsAdded"),
],
importedCount,
}));

if (importedCount > 0) {
if (changedCount > 0) {
setTimeout(() => {
window.location.reload();
}, 2000);
Expand Down Expand Up @@ -4493,49 +4500,11 @@ function CompatibleModelsSection({
const handleImport = async () => {
if (!allowImport || importing) return;
const activeConnection = connections.find((conn) => conn.isActive !== false);
if (!activeConnection) return;
if (!activeConnection?.id) return;

setImporting(true);
try {
const workingAliases = { ...modelAliases };
await onImportWithProgress(
// fetchModels callback
async () => {
const res = await fetch(`/api/providers/${activeConnection.id}/models?refresh=true`);
const data = await res.json();
if (!res.ok) throw new Error(data.error || t("failedImportModels"));
return data;
},
// processModel callback
async (model: any) => {
const modelId = model.id || model.name || model.model;
if (!modelId) return false;
const resolvedAlias = resolveAlias(modelId, workingAliases);
if (!resolvedAlias) return false;

// Save to customModels DB FIRST - only create alias if this succeeds
const customModelRes = await fetch("/api/provider-models", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
provider: providerStorageAlias,
modelId,
modelName: model.name || modelId,
source: "imported",
}),
});

if (!customModelRes.ok) {
notify.error(t("failedSaveImportedModel"));
return false;
}

// Only create alias after customModel is saved successfully
await onSetAlias(modelId, resolvedAlias, providerStorageAlias);
workingAliases[resolvedAlias] = `${providerStorageAlias}/${modelId}`;
return true;
}
);
await onImportWithProgress(activeConnection.id);
} catch (error) {
console.error("Error importing models:", error);
notify.error(t("failedImportModelsTryAgain"));
Expand Down
16 changes: 10 additions & 6 deletions src/app/api/providers/[id]/models/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -986,9 +986,14 @@ export async function GET(
return buildApiDiscoveryResponse(models);
}

const config =
provider in PROVIDER_MODELS_CONFIG
? PROVIDER_MODELS_CONFIG[provider as keyof typeof PROVIDER_MODELS_CONFIG]
: undefined;

// Static model providers (no remote /models API)
const staticModels = getStaticModelsForProvider(provider);
if (staticModels) {
if (!config && staticModels) {
return buildResponse({
provider,
connectionId,
Expand All @@ -1011,11 +1016,10 @@ export async function GET(
});
}

const config =
provider in PROVIDER_MODELS_CONFIG
? PROVIDER_MODELS_CONFIG[provider as keyof typeof PROVIDER_MODELS_CONFIG]
: undefined;
const localCatalog = getStaticModelsForProvider(provider) || PROVIDER_MODELS[provider] || [];
const localCatalog =
(config
? PROVIDER_MODELS[provider] || staticModels
: staticModels || PROVIDER_MODELS[provider]) || [];
if (!config && localCatalog.length > 0) {
return buildResponse({
provider,
Expand Down
Loading