"use client"; import { useCallback, useEffect, useMemo, useState } from "react"; import { Eye, EyeOff } from "lucide-react"; import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { getImageGeneration, newImageModelDraft, putImageGeneration, type ImageGenerationSettings, type ImageModel, type ImageModelStatus, type PutImageGenerationRequest, } from "@/lib/imageGeneration"; const inputClass = "w-full rounded-md border border-border bg-background px-2 py-1 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-60"; const SIZE_OPTIONS = ["1024x1024", "1536x1024", "1024x1536"]; function Field({ label, hint, children, }: { label: string; hint?: string; children: React.ReactNode; }) { return ( ); } /** Editable model state: the masked GET shape plus the raw params JSON text. */ type ModelDraft = ImageModelStatus & { paramsText: string }; interface DraftState { default_model: string; timeout_seconds: number; models: ModelDraft[]; } function toModelDraft(model: ImageModelStatus): ModelDraft { return { ...model, paramsText: model.params && Object.keys(model.params).length > 0 ? JSON.stringify(model.params, null, 2) : "", }; } function toDraftState(settings: ImageGenerationSettings): DraftState { return { default_model: settings.default_model, timeout_seconds: settings.timeout_seconds, models: settings.models.map(toModelDraft), }; } type ParsedParams = | { ok: true; value: Record } | { ok: false; message: string }; function parseParams(text: string): ParsedParams { const trimmed = text.trim(); if (!trimmed) return { ok: true, value: {} }; try { const parsed: unknown = JSON.parse(trimmed); if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) { return { ok: false, message: "params must be a JSON object" }; } return { ok: true, value: parsed as Record }; } catch { return { ok: false, message: "invalid JSON" }; } } /** Build the PUT payload; returns null when any params text is invalid. */ function buildPayload(draft: DraftState): PutImageGenerationRequest | null { const models: ImageModel[] = []; for (const model of draft.models) { const params = parseParams(model.paramsText); if (!params.ok) return null; models.push({ id: model.id, name: model.name, provider: model.provider, api_key: model.api_key, base_url: model.base_url, supports_generation: model.supports_generation, supports_edit: model.supports_edit, default_size: model.default_size, default_quality: model.default_quality, params: params.value, }); } return { default_model: draft.default_model, timeout_seconds: draft.timeout_seconds, models, }; } function ImageModelCard({ model, onChange, onRemove, }: { model: ModelDraft; onChange: (next: ModelDraft) => void; onRemove: () => void; }) { const [showKey, setShowKey] = useState(false); const paramsResult = parseParams(model.paramsText); const paramsError = paramsResult.ok ? null : paramsResult.message; return (
{model.id || "(new model)"} {model.provider}
onChange({ ...model, id: event.target.value })} /> onChange({ ...model, name: event.target.value }) } />
onChange({ ...model, base_url: event.target.value }) } />
onChange({ ...model, default_quality: event.target.value }) } />
onChange({ ...model, api_key: event.target.value }) } />