feat(gemini): progress dialog on key save, remove hardcoded registry, categorize by endpoint

- Show import progress dialog when adding Gemini API key with model
  list, completion status, and Close button
- Remove hardcoded Gemini model registry — models come exclusively
  from Google API sync per API key
- Hide "Import from /models" button for Gemini (sync handles it)
- Remove server-side fire-and-forget auto-sync (client handles it)
- Categorize synced Gemini models by endpoint type in catalog:
  embedding, image, and audio (transcription + speech)
- Add modelsImported and close i18n keys to all 33 languages
This commit is contained in:
Chris Staley
2026-04-02 14:38:28 -06:00
parent 7f785b8fa5
commit 2341bba973
37 changed files with 203 additions and 67 deletions

View File

@@ -192,22 +192,9 @@ export const REGISTRY: Record<string, RegistryEntry> = {
clientSecretEnv: "GEMINI_OAUTH_CLIENT_SECRET",
clientSecretDefault: "",
},
models: [
{ id: "gemini-3.1-pro-high", name: "Gemini 3.1 Pro High" },
{ id: "gemini-3.1-pro-low", name: "Gemini 3.1 Pro Low" },
{ id: "gemini-3.1-pro", name: "Gemini 3.1 Pro" },
{ id: "gemini-3-1-pro", name: "Gemini 3.1 Pro (Alt ID)" },
{ id: "gemini-3.1-pro-preview", name: "Gemini 3.1 Pro Preview" },
{ id: "gemini-3.1-flash-lite-preview", name: "Gemini 3.1 Flash Lite Preview" },
{ id: "gemini-3-flash-preview", name: "Gemini 3 Flash Preview" },
{ id: "gemini-2.5-pro", name: "Gemini 2.5 Pro" },
{ id: "gemini-2.5-flash", name: "Gemini 2.5 Flash" },
{ id: "gemini-2.5-flash-lite", name: "Gemini 2.5 Flash Lite" },
{ id: "gemini-2.0-flash", name: "Gemini 2.0 Flash" },
{ id: "gemini-2.0-flash-exp", name: "Gemini 2.0 Flash Exp" },
{ id: "gemini-1.5-pro", name: "Gemini 1.5 Pro" },
{ id: "gemini-1.5-flash", name: "Gemini 1.5 Flash" },
],
models: [],
// Models are populated from Google's API via sync-models (per API key).
// No hardcoded fallback — show nothing until a key is added.
},
"gemini-cli": {

View File

@@ -1093,11 +1093,70 @@ export default function ProviderDetailPage() {
body: JSON.stringify({ provider: providerId, ...formData }),
});
if (res.ok) {
const connectionData = await res.json();
const newConnection = connectionData?.connection;
await fetchConnections();
setShowAddApiKeyModal(false);
// For Gemini: auto-sync is fire-and-forget, poll for model updates
if (providerId === "gemini") {
setTimeout(() => fetchProviderModelMeta(), 3000);
// For Gemini: show progress dialog and sync models from endpoint
if (providerId === "gemini" && newConnection?.id) {
setShowImportModal(true);
setImportProgress({
current: 0,
total: 0,
phase: "fetching",
status: t("fetchingModels"),
logs: [],
error: "",
importedCount: 0,
});
try {
const syncRes = await fetch(`/api/providers/${newConnection.id}/sync-models`, {
method: "POST",
});
const syncData = await syncRes.json();
if (!syncRes.ok || syncData.error) {
setImportProgress((prev) => ({
...prev,
phase: "error",
status: t("failedFetchModels"),
error: syncData.error?.message || syncData.error || t("failedImportModels"),
}));
return null;
}
const syncedCount = syncData.syncedModels || 0;
const syncedModelList: Array<{ id: string; name?: string }> = syncData.models || [];
const logs: string[] = [];
if (syncedModelList.length > 0) {
logs.push(`${syncedCount} models available`);
logs.push("");
for (const m of syncedModelList) {
logs.push(` ${m.name || m.id}`);
}
}
setImportProgress((prev) => ({
...prev,
phase: "done",
status: t("modelsImported", { count: syncedCount }),
total: syncedCount,
current: syncedCount,
importedCount: syncedCount,
logs,
}));
await fetchProviderModelMeta();
} catch (syncError) {
setImportProgress((prev) => ({
...prev,
phase: "error",
status: t("failedFetchModels"),
error: String(syncError),
}));
}
}
return null;
}
@@ -1968,7 +2027,7 @@ export default function ProviderDetailPage() {
);
}
const importButton = (
const importButton = providerId === "gemini" ? null : (
<div className="flex items-center gap-2 mb-4">
<Button
size="sm"
@@ -2748,11 +2807,16 @@ export default function ProviderDetailPage() {
</div>
)}
{/* Auto-reload notice */}
{importProgress.phase === "done" && importProgress.importedCount > 0 && (
<p className="text-xs text-text-muted text-center animate-pulse">
{t("pageAutoRefresh")}
</p>
{/* Close button */}
{importProgress.phase === "done" && (
<div className="flex justify-center">
<button
onClick={() => setShowImportModal(false)}
className="px-4 py-2 text-sm font-medium rounded-lg bg-primary text-white hover:opacity-90 transition-opacity"
>
{t("close") || "Close"}
</button>
</div>
)}
</div>
</Modal>

View File

@@ -15,7 +15,6 @@ import { getConsistentMachineId } from "@/shared/utils/machineId";
import { syncToCloud } from "@/lib/cloudSync";
import { createProviderSchema } from "@/shared/validation/schemas";
import { isValidationFailure, validateBody } from "@/shared/validation/helpers";
import { buildModelSyncInternalHeaders } from "@/shared/services/modelSyncScheduler";
// GET /api/providers - List all connections
export async function GET() {
@@ -140,22 +139,7 @@ export async function POST(request: Request) {
testStatus: testStatus || "unknown",
});
// Auto-trigger model sync for Gemini when API key is saved
if (provider === "gemini" && newConnection?.id) {
try {
const origin = new URL(request.url).origin;
fetch(`${origin}/api/providers/${newConnection.id}/sync-models`, {
method: "POST",
headers: {
...buildModelSyncInternalHeaders(),
cookie: request.headers.get("cookie") || "",
},
}).catch((e) => console.error("Auto-sync failed for gemini:", e));
} catch (e) {
// Non-blocking — don't fail the connection save
console.error("Failed to trigger auto-sync for gemini:", e);
}
}
// Note: Gemini model sync is now triggered client-side with progress dialog
// Hide sensitive fields
const result: Record<string, any> = { ...newConnection };

View File

@@ -180,7 +180,7 @@ export async function getUnifiedModelsResponse(
connections = connections.filter((c) => c.isActive !== false);
} catch (e) {
// If database not available, show no provider models (safe default)
console.log("Could not fetch providers, showing only combos/custom models");
console.log("[catalog] Could not fetch providers:", e);
}
// Get provider nodes (for compatible providers with custom prefixes)
@@ -255,30 +255,6 @@ export async function getUnifiedModelsResponse(
const registryEntry = REGISTRY[alias] || REGISTRY[canonicalProviderId];
const defaultContextLength = registryEntry?.defaultContextLength;
// For Gemini: use synced API models exclusively (no hardcoded fallback)
if (alias === "gemini") {
try {
const syncedModels = await getSyncedAvailableModels("gemini");
for (const sm of syncedModels) {
const aliasId = `gemini/${sm.id}`;
if (getModelIsHidden("gemini", sm.id)) continue;
models.push({
id: aliasId,
object: "model",
created: timestamp,
owned_by: "gemini",
permission: [],
root: sm.id,
parent: null,
...(sm.inputTokenLimit ? { context_length: sm.inputTokenLimit } : {}),
});
}
} catch {
// No synced models — show nothing for Gemini
}
continue; // Always skip hardcoded models for Gemini
}
for (const model of providerModels) {
const aliasId = `${alias}/${model.id}`;
if (getModelIsHidden(canonicalProviderId, model.id)) continue;
@@ -321,6 +297,63 @@ export async function getUnifiedModelsResponse(
}
}
// Gemini: synced API models exclusively (outside PROVIDER_MODELS loop since registry is empty)
if (activeAliases.has("gemini") && !blockedProviders.has("gemini")) {
try {
const syncedModels = await getSyncedAvailableModels("gemini");
for (const sm of syncedModels) {
const aliasId = `gemini/${sm.id}`;
if (getModelIsHidden("gemini", sm.id)) continue;
// Convert supportedEndpoints to type/subtype for endpoint categorization
const endpoints = Array.isArray(sm.supportedEndpoints)
? sm.supportedEndpoints
: ["chat"];
let modelType: string | undefined;
if (endpoints.includes("embeddings")) modelType = "embedding";
else if (endpoints.includes("images")) modelType = "image";
else if (endpoints.includes("audio")) modelType = "audio";
models.push({
id: aliasId,
object: "model",
created: timestamp,
owned_by: "gemini",
permission: [],
root: sm.id,
parent: null,
...(modelType ? { type: modelType } : {}),
...(modelType === "audio" ? { subtype: "transcription" } : {}),
...(sm.inputTokenLimit ? { context_length: sm.inputTokenLimit } : {}),
...(endpoints.length > 1 || !endpoints.includes("chat")
? { supported_endpoints: endpoints }
: {}),
});
// For audio models, also add a speech variant so they appear in both sections
if (modelType === "audio") {
models.push({
id: aliasId,
object: "model",
created: timestamp,
owned_by: "gemini",
permission: [],
root: sm.id,
parent: null,
type: "audio",
subtype: "speech",
...(sm.inputTokenLimit ? { context_length: sm.inputTokenLimit } : {}),
...(endpoints.length > 1 || !endpoints.includes("chat")
? { supported_endpoints: endpoints }
: {}),
});
}
}
} catch {
// No synced models — show nothing for Gemini
}
}
// Helper: check if a provider is active (by provider id or alias)
const isProviderActive = (provider: string) => {
if (activeAliases.size === 0) return false; // No active connections = show nothing
@@ -419,6 +452,8 @@ export async function getUnifiedModelsResponse(
try {
const customModelsMap = (await getAllCustomModels()) as Record<string, unknown>;
for (const [providerId, rawProviderCustomModels] of Object.entries(customModelsMap)) {
// Skip Gemini — handled by syncedAvailableModels above
if (providerId === "gemini") continue;
const providerCustomModels = Array.isArray(rawProviderCustomModels)
? rawProviderCustomModels.filter(
(model): model is Record<string, unknown> =>

View File

@@ -1491,6 +1491,7 @@
"importFailed": "فشل الاستيراد",
"noNewModelsAdded": "لم تتم إضافة نماذج جديدة.",
"adding": "جارٍ الإضافة...",
"close": "Close",
"importingModelsTitle": "استيراد النماذج",
"copyModel": "نموذج النسخ",
"removeModel": "إزالة النموذج",
@@ -1651,6 +1652,7 @@
"autoSyncEnabled": "تم تمكين المزامنة التلقائية — سيتم تحديث النماذج بشكل دوري",
"autoSyncDisabled": "تم تعطيل المزامنة التلقائية",
"autoSyncToggleFailed": "فشل في تبديل المزامنة التلقائية",
"modelsImported": "{count} models imported",
"allModelsAlreadyImported": "جميع النماذج مستوردة بالفعل",
"noNewModelsToImport": "لا توجد نماذج جديدة للاستيراد — جميع النماذج موجودة بالفعل في السجل أو قائمة النماذج المخصصة",
"skippingExistingModels": "تخطي {count} نماذج موجودة",

View File

@@ -1491,6 +1491,7 @@
"importFailed": "Неуспешно импортиране",
"noNewModelsAdded": "Няма добавени нови модели.",
"adding": "Добавяне...",
"close": "Close",
"importingModelsTitle": "Импортиране на модели",
"copyModel": "Копиране на модел",
"removeModel": "Премахване на модела",
@@ -1651,6 +1652,7 @@
"autoSyncEnabled": "Автоматичното синхронизиране е активирано — моделите ще се опресняват периодично",
"autoSyncDisabled": "Автоматичното синхронизиране е деактивирано",
"autoSyncToggleFailed": "Неуспешно превключване на автоматичното синхронизиране",
"modelsImported": "{count} models imported",
"allModelsAlreadyImported": "Всички модели вече са импортирани",
"noNewModelsToImport": "Няма нови модели за импортиране — всички модели вече са в регистъра или списъка с персонализирани модели",
"skippingExistingModels": "Пропускане на {count} съществуващи модела",

View File

@@ -1555,6 +1555,7 @@
"importFailed": "Import selhal",
"noNewModelsAdded": "Nebyly přidány žádné nové modely.",
"adding": "Přidávám...",
"close": "Close",
"importingModelsTitle": "Import modelů",
"copyModel": "Kopírovat model",
"removeModel": "Odebrat model",
@@ -1714,6 +1715,7 @@
"autoSyncEnabled": "Automatická synchronizace povolena modely se budou pravidelně obnovovat",
"autoSyncDisabled": "Automatická synchronizace zakázána",
"autoSyncToggleFailed": "Nepodařilo se přepnout automatickou synchronizaci",
"modelsImported": "{count} models imported",
"allModelsAlreadyImported": "Všechny modely jsou již importovány",
"noNewModelsToImport": "Žádné nové modely k importu — všechny modely jsou již v registru nebo v seznamu vlastních modelů",
"skippingExistingModels": "Přeskakování {count} existujících modelů",

View File

@@ -1491,6 +1491,7 @@
"importFailed": "Import mislykkedes",
"noNewModelsAdded": "Der blev ikke tilføjet nye modeller.",
"adding": "Tilføjer...",
"close": "Close",
"importingModelsTitle": "Import af modeller",
"copyModel": "Kopi model",
"removeModel": "Fjern modellen",
@@ -1651,6 +1652,7 @@
"autoSyncEnabled": "Automatisk synkronisering aktiveret - modellerne opdateres med jævne mellemrum",
"autoSyncDisabled": "Automatisk synkronisering deaktiveret",
"autoSyncToggleFailed": "Automatisk synkronisering kunne ikke slås til eller fra",
"modelsImported": "{count} models imported",
"allModelsAlreadyImported": "Alle modeller er allerede importeret",
"noNewModelsToImport": "Ingen nye modeller at importere — alle modeller findes allerede i registreret eller brugerdefineret liste",
"skippingExistingModels": "Springer {count} eksisterende modeller over",

View File

@@ -1491,6 +1491,7 @@
"importFailed": "Der Import ist fehlgeschlagen",
"noNewModelsAdded": "Es wurden keine neuen Modelle hinzugefügt.",
"adding": "Hinzufügen...",
"close": "Close",
"importingModelsTitle": "Modelle importieren",
"copyModel": "Modell kopieren",
"removeModel": "Modell entfernen",
@@ -1651,6 +1652,7 @@
"autoSyncEnabled": "Auto-Sync aktiviert — Modelle werden regelmäßig aktualisiert",
"autoSyncDisabled": "Auto-Sync deaktiviert",
"autoSyncToggleFailed": "Auto-Sync umschalten fehlgeschlagen",
"modelsImported": "{count} models imported",
"allModelsAlreadyImported": "Alle Modelle sind bereits importiert",
"noNewModelsToImport": "Keine neuen Modelle zum Importieren — alle Modelle sind bereits in der Registry oder der Liste benutzerdefinierter Modelle",
"skippingExistingModels": "Überspringe {count} vorhandene Modelle",

View File

@@ -1542,6 +1542,7 @@
"chatCompletions": "Chat Completions",
"importingModels": "Importing...",
"importFromModels": "Import from /models",
"modelsImported": "{count} models imported",
"allModelsAlreadyImported": "All models already imported",
"noNewModelsToImport": "No new models to import — all models are already in the registry or custom models list",
"skippingExistingModels": "Skipping {count} existing models",
@@ -1563,6 +1564,7 @@
"importFailed": "Import failed",
"noNewModelsAdded": "No new models were added.",
"adding": "Adding...",
"close": "Close",
"importingModelsTitle": "Importing Models",
"copyModel": "Copy model",
"removeModel": "Remove model",

View File

@@ -1491,6 +1491,7 @@
"importFailed": "Importación fallida",
"noNewModelsAdded": "No se agregaron nuevos modelos.",
"adding": "Añadiendo...",
"close": "Close",
"importingModelsTitle": "Importar modelos",
"copyModel": "Copiar modelo",
"removeModel": "Quitar modelo",
@@ -1651,6 +1652,7 @@
"autoSyncEnabled": "Sincronización automática activada — los modelos se actualizarán periódicamente",
"autoSyncDisabled": "Sincronización automática desactivada",
"autoSyncToggleFailed": "Error al alternar sincronización automática",
"modelsImported": "{count} models imported",
"allModelsAlreadyImported": "Todos los modelos ya están importados",
"noNewModelsToImport": "No hay modelos nuevos para importar — todos los modelos ya están en el registro o en la lista de modelos personalizados",
"skippingExistingModels": "Omitiendo {count} modelos existentes",

View File

@@ -1491,6 +1491,7 @@
"importFailed": "Tuonti epäonnistui",
"noNewModelsAdded": "Uusia malleja ei lisätty.",
"adding": "Lisätään...",
"close": "Close",
"importingModelsTitle": "Mallien tuonti",
"copyModel": "Kopioi malli",
"removeModel": "Poista malli",
@@ -1651,6 +1652,7 @@
"autoSyncEnabled": "Automaattinen synkronointi käytössä mallit päivittyvät säännöllisesti",
"autoSyncDisabled": "Automaattinen synkronointi poistettu käytöstä",
"autoSyncToggleFailed": "Automaattisen synkronoinnin vaihtaminen epäonnistui",
"modelsImported": "{count} models imported",
"allModelsAlreadyImported": "Kaikki mallit on jo tuotu",
"noNewModelsToImport": "Ei uusia malleja tuotavaksi — kaikki mallit ovat jo rekisterissä tai mukautetulla mallilistalla",
"skippingExistingModels": "Ohitetaan {count} olemassa olevaa mallia",

View File

@@ -1491,6 +1491,7 @@
"importFailed": "Échec de l'importation",
"noNewModelsAdded": "Aucun nouveau modèle n'a été ajouté.",
"adding": "Ajout...",
"close": "Close",
"importingModelsTitle": "Importation de modèles",
"copyModel": "Copier le modèle",
"removeModel": "Supprimer le modèle",
@@ -1651,6 +1652,7 @@
"autoSyncEnabled": "Synchronisation automatique activée — les modèles seront actualisés périodiquement",
"autoSyncDisabled": "Synchronisation automatique désactivée",
"autoSyncToggleFailed": "Échec de l'activation de la synchronisation automatique",
"modelsImported": "{count} models imported",
"allModelsAlreadyImported": "Tous les modèles sont déjà importés",
"noNewModelsToImport": "Aucun nouveau modèle à importer — tous les modèles sont déjà dans le registre ou la liste de modèles personnalisés",
"skippingExistingModels": "Ignorance de {count} modèles existants",

View File

@@ -1491,6 +1491,7 @@
"importFailed": "הייבוא נכשל",
"noNewModelsAdded": "לא נוספו דגמים חדשים.",
"adding": "מוסיף...",
"close": "Close",
"importingModelsTitle": "ייבוא דגמים",
"copyModel": "העתק דגם",
"removeModel": "הסר את הדגם",
@@ -1651,6 +1652,7 @@
"autoSyncEnabled": "סנכרון אוטומטי מופעל - הדגמים יתרעננו מעת לעת",
"autoSyncDisabled": "הסנכרון האוטומטי מושבת",
"autoSyncToggleFailed": "החלפת הסנכרון האוטומטי נכשלה",
"modelsImported": "{count} models imported",
"allModelsAlreadyImported": "כל הדגמים כבר מיובאים",
"noNewModelsToImport": "אין דגמים חדשים לייבוא — כל הדגמים כבר קיימים ברישום או ברשימת הדגמים המותאמים",
"skippingExistingModels": "מדלג על {count} דגמים קיימים",

View File

@@ -1511,6 +1511,7 @@
"importFailed": "आयात विफल",
"noNewModelsAdded": "कोई नया मॉडल नहीं जोड़ा गया.",
"adding": "जोड़ा जा रहा है...",
"close": "Close",
"importingModelsTitle": "मॉडल आयात करना",
"copyModel": "मॉडल कॉपी करें",
"removeModel": "मॉडल हटाएँ",
@@ -1666,6 +1667,7 @@
"compatUpstreamHeadersHint": "High-privilege setting — same trust level as editing provider API credentials; only trusted admins should use it. Merged after OmniRoute adds auth from the provider API key. If a custom header uses the same name as an existing one (e.g. Authorization), your value fully replaces the auto-generated header (including the Bearer token) — the upstream only sees what you typed, not the key from settings. Misconfiguration can cause 401 or broken upstream auth. One row per header (e.g. extra Authentication for some gateways). Hover or focus the value to preview. Saves on blur, outside click, or closing this panel.",
"compatUpstreamHeadersLabel": "Extra upstream headers",
"compatUpstreamRemoveRow": "Remove row",
"modelsImported": "{count} models imported",
"allModelsAlreadyImported": "सभी मॉडल पहले से ही आयातित हैं",
"noNewModelsToImport": "आयात करने के लिए कोई नए मॉडल नहीं — सभी मॉडल पहले से ही रजिस्ट्री या कस्टम मॉडल सूची में हैं",
"skippingExistingModels": "{count} मौजूदा मॉडल छोड़े जा रहे हैं",

View File

@@ -1491,6 +1491,7 @@
"importFailed": "Az importálás sikertelen",
"noNewModelsAdded": "Nem adtak hozzá új modelleket.",
"adding": "Hozzáadás...",
"close": "Close",
"importingModelsTitle": "Modellek importálása",
"copyModel": "Modell másolása",
"removeModel": "Modell eltávolítása",
@@ -1651,6 +1652,7 @@
"autoSyncEnabled": "Automatikus szinkronizálás engedélyezve a modellek rendszeresen frissülnek",
"autoSyncDisabled": "Az automatikus szinkronizálás letiltva",
"autoSyncToggleFailed": "Failed to toggle auto-sync",
"modelsImported": "{count} models imported",
"allModelsAlreadyImported": "Minden modell már importálva van",
"noNewModelsToImport": "Nincs új modell az importáláshoz — minden modell már a nyilvántartásban vagy az egyéni modellek listájában van",
"skippingExistingModels": "{count} meglévő modell kihagyása",

View File

@@ -1491,6 +1491,7 @@
"importFailed": "Impor gagal",
"noNewModelsAdded": "Tidak ada model baru yang ditambahkan.",
"adding": "Menambahkan...",
"close": "Close",
"importingModelsTitle": "Mengimpor Model",
"copyModel": "Salin modelnya",
"removeModel": "Hapus modelnya",
@@ -1651,6 +1652,7 @@
"autoSyncEnabled": "Sinkronisasi otomatis diaktifkan — model akan disegarkan secara berkala",
"autoSyncDisabled": "Sinkronisasi otomatis dinonaktifkan",
"autoSyncToggleFailed": "Gagal mengaktifkan sinkronisasi otomatis",
"modelsImported": "{count} models imported",
"allModelsAlreadyImported": "Semua model sudah diimpor",
"noNewModelsToImport": "Tidak ada model baru untuk diimpor — semua model sudah ada di registri atau daftar model kustom",
"skippingExistingModels": "Melewatkan {count} model yang sudah ada",

View File

@@ -1560,6 +1560,7 @@
"importFailed": "आयात विफल",
"noNewModelsAdded": "कोई नया मॉडल नहीं जोड़ा गया.",
"adding": "जोड़ा जा रहा है...",
"close": "Close",
"importingModelsTitle": "मॉडल आयात करना",
"copyModel": "मॉडल कॉपी करें",
"removeModel": "मॉडल हटाएँ",
@@ -1714,6 +1715,7 @@
"modelsPathLabel": "मॉडल समापन बिंदु पथ",
"modelsPathPlaceholder": "/मॉडल",
"modelsPathHint": "सत्यापन के लिए कस्टम मॉडल पथ (जैसे /v4/मॉडल)",
"modelsImported": "{count} models imported",
"allModelsAlreadyImported": "Semua model sudah diimpor",
"noNewModelsToImport": "Tidak ada model baru untuk diimpor — semua model sudah ada di registri atau daftar model kustom",
"skippingExistingModels": "Melewatkan {count} model yang sudah ada",

View File

@@ -1491,6 +1491,7 @@
"importFailed": "Importazione non riuscita",
"noNewModelsAdded": "Non sono stati aggiunti nuovi modelli.",
"adding": "Aggiunta...",
"close": "Close",
"importingModelsTitle": "Importazione di modelli",
"copyModel": "Copia modello",
"removeModel": "Rimuovi il modello",
@@ -1651,6 +1652,7 @@
"autoSyncEnabled": "Sincronizzazione automatica abilitata — i modelli verranno aggiornati periodicamente",
"autoSyncDisabled": "Sincronizzazione automatica disabilitata",
"autoSyncToggleFailed": "Impossibile attivare la sincronizzazione automatica",
"modelsImported": "{count} models imported",
"allModelsAlreadyImported": "Tutti i modelli sono già importati",
"noNewModelsToImport": "Nessun nuovo modello da importare — tutti i modelli sono già nel registro o nell'elenco dei modelli personalizzati",
"skippingExistingModels": "Salto {count} modelli esistenti",

View File

@@ -1491,6 +1491,7 @@
"importFailed": "インポートに失敗しました",
"noNewModelsAdded": "新しいモデルは追加されませんでした。",
"adding": "追加中...",
"close": "Close",
"importingModelsTitle": "モデルのインポート",
"copyModel": "モデルをコピーする",
"removeModel": "モデルの削除",
@@ -1651,6 +1652,7 @@
"autoSyncEnabled": "自動同期有効 — モデルは定期的に更新されます",
"autoSyncDisabled": "自動同期無効",
"autoSyncToggleFailed": "自動同期の切り替えに失敗",
"modelsImported": "{count} models imported",
"allModelsAlreadyImported": "すべてのモデルは既にインポート済みです",
"noNewModelsToImport": "インポートする新しいモデルはありません — すべてのモデルは既にレジストリまたはカスタムモデルリストにあります",
"skippingExistingModels": "{count}件の既存モデルをスキップ",

View File

@@ -1491,6 +1491,7 @@
"importFailed": "가져오기 실패",
"noNewModelsAdded": "새로운 모델이 추가되지 않았습니다.",
"adding": "추가 중...",
"close": "Close",
"importingModelsTitle": "모델 가져오기",
"copyModel": "모델 복사",
"removeModel": "모델 삭제",
@@ -1651,6 +1652,7 @@
"autoSyncEnabled": "자동 동기화 활성화 — 모델이 주기적으로 업데이트됩니다",
"autoSyncDisabled": "자동 동기화 비활성화",
"autoSyncToggleFailed": "자동 동기화 전환 실패",
"modelsImported": "{count} models imported",
"allModelsAlreadyImported": "모든 모델이 이미 가져왔습니다",
"noNewModelsToImport": "가져올 새 모델 없음 — 모든 모델이 이미 레지스트리 또는 사용자 정의 모델 목록에 있습니다",
"skippingExistingModels": "{count}개의 기존 모델 건너뛰기",

View File

@@ -1491,6 +1491,7 @@
"importFailed": "Import gagal",
"noNewModelsAdded": "Tiada model baharu ditambahkan.",
"adding": "Menambah...",
"close": "Close",
"importingModelsTitle": "Mengimport Model",
"copyModel": "Salin model",
"removeModel": "Alih keluar model",
@@ -1651,6 +1652,7 @@
"autoSyncEnabled": "Autosegerak didayakan — model akan dimuat semula secara berkala",
"autoSyncDisabled": "Autosegerak dilumpuhkan",
"autoSyncToggleFailed": "Gagal untuk menogol autosegerak",
"modelsImported": "{count} models imported",
"allModelsAlreadyImported": "Semua model sudah diimport",
"noNewModelsToImport": "Tiada model baru untuk diimport — semua model sudah ada dalam registri atau senarai model tersuai",
"skippingExistingModels": "Melangkau {count} model sedia ada",

View File

@@ -1491,6 +1491,7 @@
"importFailed": "Importeren is mislukt",
"noNewModelsAdded": "Er zijn geen nieuwe modellen toegevoegd.",
"adding": "Toevoegen...",
"close": "Close",
"importingModelsTitle": "Modellen importeren",
"copyModel": "Kopieermodel",
"removeModel": "Model verwijderen",
@@ -1651,6 +1652,7 @@
"autoSyncEnabled": "Automatische synchronisatie ingeschakeld: modellen worden periodiek vernieuwd",
"autoSyncDisabled": "Automatische synchronisatie uitgeschakeld",
"autoSyncToggleFailed": "Kan automatische synchronisatie niet in- of uitschakelen",
"modelsImported": "{count} models imported",
"allModelsAlreadyImported": "Alle modellen zijn al geïmporteerd",
"noNewModelsToImport": "Geen nieuwe modellen om te importeren — alle modellen staan al in het register of de lijst met aangepaste modellen",
"skippingExistingModels": "{count} bestaande modellen overgeslagen",

View File

@@ -1491,6 +1491,7 @@
"importFailed": "Import mislyktes",
"noNewModelsAdded": "Ingen nye modeller ble lagt til.",
"adding": "Legger til...",
"close": "Close",
"importingModelsTitle": "Importere modeller",
"copyModel": "Kopier modell",
"removeModel": "Fjern modellen",
@@ -1651,6 +1652,7 @@
"autoSyncEnabled": "Automatisk synkronisering aktivert modellene oppdateres med jevne mellomrom",
"autoSyncDisabled": "Automatisk synkronisering er deaktivert",
"autoSyncToggleFailed": "Kunne ikke slå på automatisk synkronisering",
"modelsImported": "{count} models imported",
"allModelsAlreadyImported": "Alle modeller er allerede importert",
"noNewModelsToImport": "Ingen nye modeller å importere — alle modeller finnes allerede i registeret eller listen over egendefinerte modeller",
"skippingExistingModels": "Hopper over {count} eksisterende modeller",

View File

@@ -1491,6 +1491,7 @@
"importFailed": "Nabigo ang pag-import",
"noNewModelsAdded": "Walang naidagdag na mga bagong modelo.",
"adding": "Idinaragdag...",
"close": "Close",
"importingModelsTitle": "Pag-import ng mga Modelo",
"copyModel": "Kopyahin ang modelo",
"removeModel": "Alisin ang modelo",
@@ -1651,6 +1652,7 @@
"autoSyncEnabled": "Pinagana ang auto-sync — pana-panahong magre-refresh ang mga modelo",
"autoSyncDisabled": "Na-disable ang auto-sync",
"autoSyncToggleFailed": "Nabigong i-toggle ang auto-sync",
"modelsImported": "{count} models imported",
"allModelsAlreadyImported": "Lahat ng mga modelo ay nai-import na",
"noNewModelsToImport": "Walang bagong modelo na i-import — lahat ng mga modelo ay nasa registry o custom na listahan na",
"skippingExistingModels": "Pinapalampas ang {count} na umiiral na mga modelo",

View File

@@ -1491,6 +1491,7 @@
"importFailed": "Import nie powiódł się",
"noNewModelsAdded": "Nie dodano żadnych nowych modeli.",
"adding": "Dodawanie...",
"close": "Close",
"importingModelsTitle": "Importowanie modeli",
"copyModel": "Skopiuj model",
"removeModel": "Usuń model",
@@ -1651,6 +1652,7 @@
"autoSyncEnabled": "Automatyczna synchronizacja włączona — modele będą okresowo odświeżane",
"autoSyncDisabled": "Automatyczna synchronizacja wyłączona",
"autoSyncToggleFailed": "Nie udało się przełączyć automatycznej synchronizacji",
"modelsImported": "{count} models imported",
"allModelsAlreadyImported": "Wszystkie modele są już zaimportowane",
"noNewModelsToImport": "Brak nowych modeli do zaimportowania — wszystkie modele są już w rejestrze lub na liście modeli niestandardowych",
"skippingExistingModels": "Pomijanie {count} istniejących modeli",

View File

@@ -1491,6 +1491,7 @@
"importFailed": "Falha na importação",
"noNewModelsAdded": "Nenhum novo modelo foi adicionado.",
"adding": "Adicionando...",
"close": "Close",
"importingModelsTitle": "Importando Modelos",
"copyModel": "Copiar modelo",
"removeModel": "Remover modelo",
@@ -1657,6 +1658,7 @@
"autoSyncEnabled": "Sincronização automática habilitada os modelos serão atualizados periodicamente",
"autoSyncDisabled": "Sincronização automática desativada",
"autoSyncToggleFailed": "Falha ao alternar a sincronização automática",
"modelsImported": "{count} models imported",
"allModelsAlreadyImported": "Todos os modelos já foram importados",
"noNewModelsToImport": "Nenhum modelo novo para importar — todos os modelos já estão no registro ou na lista de modelos personalizados",
"skippingExistingModels": "Ignorando {count} modelos existentes"

View File

@@ -1503,6 +1503,7 @@
"importFailed": "Falha na importação",
"noNewModelsAdded": "Nenhum novo modelo foi adicionado.",
"adding": "Adicionando...",
"close": "Close",
"importingModelsTitle": "Importando Modelos",
"copyModel": "Copiar modelo",
"removeModel": "Remover modelo",
@@ -1663,6 +1664,7 @@
"autoSyncEnabled": "Sincronização automática ativada — modelos serão atualizados periodicamente",
"autoSyncDisabled": "Sincronização automática desativada",
"autoSyncToggleFailed": "Falha ao alternar sincronização automática",
"modelsImported": "{count} models imported",
"allModelsAlreadyImported": "Todos os modelos já foram importados",
"noNewModelsToImport": "Nenhum modelo novo para importar — todos os modelos já estão no registo ou na lista de modelos personalizados",
"skippingExistingModels": "A ignorar {count} modelos existentes",

View File

@@ -1491,6 +1491,7 @@
"importFailed": "Importul nu a reușit",
"noNewModelsAdded": "Nu au fost adăugate modele noi.",
"adding": "Se adaugă...",
"close": "Close",
"importingModelsTitle": "Importul modelelor",
"copyModel": "Copiați modelul",
"removeModel": "Eliminați modelul",
@@ -1651,6 +1652,7 @@
"autoSyncEnabled": "Sincronizare automată activată — modelele se vor reîmprospăta periodic",
"autoSyncDisabled": "Sincronizarea automată a fost dezactivată",
"autoSyncToggleFailed": "Nu s-a putut comuta sincronizarea automată",
"modelsImported": "{count} models imported",
"allModelsAlreadyImported": "Toate modelele sunt deja importate",
"noNewModelsToImport": "Niciun model nou de importat — toate modelele sunt deja în registru sau în lista de modele personalizate",
"skippingExistingModels": "Se omit {count} modele existente",

View File

@@ -1491,6 +1491,7 @@
"importFailed": "Импорт не удался",
"noNewModelsAdded": "Новых моделей не добавлялось.",
"adding": "Добавление...",
"close": "Close",
"importingModelsTitle": "Импорт моделей",
"copyModel": "Копировать модель",
"removeModel": "Удалить модель",
@@ -1651,6 +1652,7 @@
"autoSyncEnabled": "Автосинхронизация включена — модели будут периодически обновляться",
"autoSyncDisabled": "Автосинхронизация отключена",
"autoSyncToggleFailed": "Не удалось переключить автосинхронизацию",
"modelsImported": "{count} models imported",
"allModelsAlreadyImported": "Все модели уже импортированы",
"noNewModelsToImport": "Нет новых моделей для импорта — все модели уже есть в реестре или списке пользовательских моделей",
"skippingExistingModels": "Пропуск {count} существующих моделей",

View File

@@ -1491,6 +1491,7 @@
"importFailed": "Import zlyhal",
"noNewModelsAdded": "Neboli pridané žiadne nové modely.",
"adding": "Pridáva sa...",
"close": "Close",
"importingModelsTitle": "Importovanie modelov",
"copyModel": "Kopírovať model",
"removeModel": "Odstráňte model",
@@ -1651,6 +1652,7 @@
"autoSyncEnabled": "Automatická synchronizácia povolená modely sa budú pravidelne obnovovať",
"autoSyncDisabled": "Automatická synchronizácia je zakázaná",
"autoSyncToggleFailed": "Nepodarilo sa prepnúť automatickú synchronizáciu",
"modelsImported": "{count} models imported",
"allModelsAlreadyImported": "Všetky modely sú už importované",
"noNewModelsToImport": "Žiadne nové modely na import — všetky modely sú už v registri alebo v zozname vlastných modelov",
"skippingExistingModels": "Preskakujem {count} existujúcich modelov",

View File

@@ -1491,6 +1491,7 @@
"importFailed": "Importen misslyckades",
"noNewModelsAdded": "Inga nya modeller tillkom.",
"adding": "Lägger till...",
"close": "Close",
"importingModelsTitle": "Importera modeller",
"copyModel": "Kopiera modell",
"removeModel": "Ta bort modellen",
@@ -1651,6 +1652,7 @@
"autoSyncEnabled": "Automatisk synkronisering aktiverad — modeller uppdateras regelbundet",
"autoSyncDisabled": "Automatisk synkronisering inaktiverad",
"autoSyncToggleFailed": "Det gick inte att växla automatisk synkronisering",
"modelsImported": "{count} models imported",
"allModelsAlreadyImported": "Alla modeller är redan importerade",
"noNewModelsToImport": "Inga nya modeller att importera — alla modeller finns redan i registret eller listan över anpassade modeller",
"skippingExistingModels": "Hoppar över {count} befintliga modeller",

View File

@@ -1491,6 +1491,7 @@
"importFailed": "การนำเข้าล้มเหลว",
"noNewModelsAdded": "ไม่มีการเพิ่มโมเดลใหม่",
"adding": "กำลังเพิ่ม...",
"close": "Close",
"importingModelsTitle": "การนำเข้าโมเดล",
"copyModel": "คัดลอกโมเดล",
"removeModel": "ลบโมเดล",
@@ -1651,6 +1652,7 @@
"autoSyncEnabled": "เปิดใช้งานการซิงค์อัตโนมัติ — โมเดลจะรีเฟรชเป็นระยะ",
"autoSyncDisabled": "ปิดใช้งานการซิงค์อัตโนมัติแล้ว",
"autoSyncToggleFailed": "ไม่สามารถสลับการซิงค์อัตโนมัติ",
"modelsImported": "{count} models imported",
"allModelsAlreadyImported": "นำเข้าโมเดลทั้งหมดแล้ว",
"noNewModelsToImport": "ไม่มีโมเดลใหม่ที่จะนำเข้า — โมเดลทั้งหมดมีอยู่แล้วในรีจิสทรีหรือรายการโมเดลที่กำหนดเอง",
"skippingExistingModels": "ข้าม {count} โมเดลที่มีอยู่",

View File

@@ -1560,6 +1560,7 @@
"importFailed": "İçe aktarma başarısız oldu",
"noNewModelsAdded": "Yeni model eklenmedi.",
"adding": "Ekleniyor...",
"close": "Close",
"importingModelsTitle": "Modelleri İçe Aktarma",
"copyModel": "Modeli kopyala",
"removeModel": "Modeli kaldır",
@@ -1714,6 +1715,7 @@
"modelsPathLabel": "Model Uç Noktası Yolu",
"modelsPathPlaceholder": "/models",
"modelsPathHint": "Doğrulama için özel model yolu (ör. /v4/models)",
"modelsImported": "{count} models imported",
"allModelsAlreadyImported": "Tüm modeller zaten içe aktarıldı",
"noNewModelsToImport": "İçe aktarılacak yeni model yok — tüm modeller zaten kayıt defterinde veya özel modeller listesinde",
"skippingExistingModels": "{count} mevcut model atlanıyor",

View File

@@ -1491,6 +1491,7 @@
"importFailed": "Помилка імпорту",
"noNewModelsAdded": "Нові моделі не додано.",
"adding": "Додавання...",
"close": "Close",
"importingModelsTitle": "Імпорт моделей",
"copyModel": "Копія моделі",
"removeModel": "Зняти модель",
@@ -1651,6 +1652,7 @@
"autoSyncEnabled": "Автоматична синхронізація ввімкнена — моделі періодично оновлюватимуться",
"autoSyncDisabled": "Автоматична синхронізація вимкнена",
"autoSyncToggleFailed": "Не вдалося вимкнути автоматичну синхронізацію",
"modelsImported": "{count} models imported",
"allModelsAlreadyImported": "Усі моделі вже імпортовано",
"noNewModelsToImport": "Немає нових моделей для імпорту — усі моделі вже є в реєстрі або списку користувацьких моделей",
"skippingExistingModels": "Пропуск {count} наявних моделей",

View File

@@ -1491,6 +1491,7 @@
"importFailed": "Nhập không thành công",
"noNewModelsAdded": "Không có mô hình mới được thêm vào.",
"adding": "Đang thêm...",
"close": "Close",
"importingModelsTitle": "Nhập mô hình",
"copyModel": "Sao chép mô hình",
"removeModel": "Xóa mô hình",
@@ -1651,6 +1652,7 @@
"autoSyncEnabled": "Đã bật tự động đồng bộ hóa - các mô hình sẽ làm mới định kỳ",
"autoSyncDisabled": "Tự động đồng bộ hóa đã bị tắt",
"autoSyncToggleFailed": "Không chuyển đổi được tính năng tự động đồng bộ hóa",
"modelsImported": "{count} models imported",
"allModelsAlreadyImported": "Tất cả mô hình đã được nhập",
"noNewModelsToImport": "Không có mô hình mới để nhập — tất cả mô hình đã có trong danh mục hoặc danh sách mô hình tùy chỉnh",
"skippingExistingModels": "Bỏ qua {count} mô hình hiện có",

View File

@@ -1560,6 +1560,7 @@
"importFailed": "导入失败",
"noNewModelsAdded": "没有添加新模型。",
"adding": "添加...",
"close": "Close",
"importingModelsTitle": "导入模型",
"copyModel": "复制模型",
"removeModel": "删除模型",
@@ -1714,6 +1715,7 @@
"modelsPathLabel": "模型端点路径",
"modelsPathPlaceholder": "/models",
"modelsPathHint": "为验证流程自定义模型路径(例如:/v4/models",
"modelsImported": "{count} models imported",
"allModelsAlreadyImported": "所有模型已导入",
"noNewModelsToImport": "没有新模型可导入 — 所有模型已在注册表或自定义模型列表中",
"skippingExistingModels": "跳过 {count} 个已有模型",