From ee84ae8bf207432e1538ca6db9ecea6d8066c797 Mon Sep 17 00:00:00 2001 From: m4 Date: Wed, 12 Aug 2026 21:02:20 +0800 Subject: [PATCH] feat(components): wire config export/import into registry and image editors --- src/app/components/ImageModelsEditor.tsx | 95 ++++++++++++++++++------ src/app/components/RegistryEditor.tsx | 53 +++++++++++++ 2 files changed, 126 insertions(+), 22 deletions(-) diff --git a/src/app/components/ImageModelsEditor.tsx b/src/app/components/ImageModelsEditor.tsx index 4c48f9e..37c7535 100644 --- a/src/app/components/ImageModelsEditor.tsx +++ b/src/app/components/ImageModelsEditor.tsx @@ -5,6 +5,8 @@ import { useTranslations } from "next-intl"; import { Eye, EyeOff } from "lucide-react"; import { toast } from "sonner"; import { errorToast } from "@/lib/errorReporter"; +import { ConfigImportDialog } from "@/app/components/ConfigImportDialog"; +import { downloadTextFile, exportFilename } from "@/lib/configTransfer"; import { Button } from "@/components/ui/button"; import { getImageGeneration, @@ -372,6 +374,28 @@ export function ImageModelsEditor() { const [forbidden, setForbidden] = useState(false); const [saving, setSaving] = useState(false); const t = useTranslations("imageModels"); + const tc = useTranslations("configTransfer"); + const [importOpen, setImportOpen] = useState(false); + const [exporting, setExporting] = useState(false); + + const runExport = useCallback(async () => { + setExporting(true); + try { + const response = await fetch("/api/image-generation/export", { + cache: "no-store", + }); + if (!response.ok) { + errorToast( + "models.image-export", + tc("exportFailed", { status: String(response.status) }) + ); + return; + } + downloadTextFile(exportFilename("image-generation"), await response.text()); + } finally { + setExporting(false); + } + }, [tc]); const load = useCallback(async () => { setLoadError(null); @@ -497,28 +521,49 @@ export function ImageModelsEditor() {

{t("modelsHeading")}

- +
+ + + +
{draft.models.length === 0 && (

{t("empty")}

@@ -570,6 +615,12 @@ export function ImageModelsEditor() {

{t("apiKeyWriteOnlyNote")}

+
); } diff --git a/src/app/components/RegistryEditor.tsx b/src/app/components/RegistryEditor.tsx index 0198e51..df84724 100644 --- a/src/app/components/RegistryEditor.tsx +++ b/src/app/components/RegistryEditor.tsx @@ -5,6 +5,8 @@ import { useTranslations } from "next-intl"; import { ChevronDown, ChevronRight, Eye, EyeOff } from "lucide-react"; import { toast } from "sonner"; import { errorToast } from "@/lib/errorReporter"; +import { ConfigImportDialog } from "@/app/components/ConfigImportDialog"; +import { downloadTextFile, exportFilename } from "@/lib/configTransfer"; import { Button } from "@/components/ui/button"; import { invalidateAvailableModels } from "@/app/hooks/useAvailableModels"; import type { @@ -934,6 +936,9 @@ function ProviderEditor({ * controls from `adapter_specs` and surfaces 422 detail paths verbatim. */ export function RegistryEditor() { const t = useTranslations("modelConfig"); + const tc = useTranslations("configTransfer"); + const [importOpen, setImportOpen] = useState(false); + const [exporting, setExporting] = useState(false); const [data, setData] = useState(null); const [draft, setDraft] = useState(null); const [credentialWrites, setCredentialWrites] = useState>( @@ -983,6 +988,25 @@ export function RegistryEditor() { } }, [t]); + const runExport = useCallback(async () => { + setExporting(true); + try { + const response = await fetch("/api/model-registry/export", { + cache: "no-store", + }); + if (!response.ok) { + errorToast( + "models.registry-export", + tc("exportFailed", { status: String(response.status) }) + ); + return; + } + downloadTextFile(exportFilename("model-registry"), await response.text()); + } finally { + setExporting(false); + } + }, [tc]); + useEffect(() => { void load(); }, [load]); @@ -1282,6 +1306,29 @@ export function RegistryEditor() { {t("registry.unsavedChanges")} )} + + + +

+ {tc("exportSecretsWarning")} +

@@ -1536,6 +1583,12 @@ export function RegistryEditor() { mono: (chunks) => {chunks}, })}

+
); }