import React, { useState, useEffect } from "react"; import { useTranslation } from "react-i18next"; import { Save } from "lucide-react"; import { toast } from "sonner"; import { useOpenClawAgentsDefaults, useSaveOpenClawAgentsDefaults, } from "@/hooks/useOpenClaw"; import { extractErrorMessage } from "@/utils/errorUtils"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import type { OpenClawAgentsDefaults } from "@/types"; const AgentsDefaultsPanel: React.FC = () => { const { t } = useTranslation(); const { data: agentsData, isLoading } = useOpenClawAgentsDefaults(); const saveAgentsMutation = useSaveOpenClawAgentsDefaults(); const [defaults, setDefaults] = useState(null); const [fallbacks, setFallbacks] = useState(""); // Extra known fields from agents.defaults const [workspace, setWorkspace] = useState(""); const [timeout, setTimeout_] = useState(""); const [contextTokens, setContextTokens] = useState(""); const [maxConcurrent, setMaxConcurrent] = useState(""); // Primary model is read-only — set via the "Set as default model" button on provider cards const primaryModel = agentsData?.model?.primary ?? ""; useEffect(() => { // agentsData is undefined while loading, null when config section is absent if (agentsData === undefined) return; setDefaults(agentsData); if (agentsData) { setFallbacks((agentsData.model?.fallbacks ?? []).join(", ")); // Extract known extra fields setWorkspace(String(agentsData.workspace ?? "")); setTimeout_(String(agentsData.timeout ?? "")); setContextTokens(String(agentsData.contextTokens ?? "")); setMaxConcurrent(String(agentsData.maxConcurrent ?? "")); } }, [agentsData]); const handleSave = async () => { try { // Preserve all unknown fields from original data const updated: OpenClawAgentsDefaults = { ...defaults }; // Model configuration — primary is read-only, preserve original value const fallbackList = fallbacks .split(",") .map((s) => s.trim()) .filter(Boolean); const origPrimary = defaults?.model?.primary; if (origPrimary) { updated.model = { primary: origPrimary, ...(fallbackList.length > 0 ? { fallbacks: fallbackList } : {}), }; } else if (fallbackList.length > 0) { // No primary set but user provided fallbacks — keep fallbacks only updated.model = { primary: "", fallbacks: fallbackList }; } // Optional fields if (workspace.trim()) updated.workspace = workspace.trim(); else delete updated.workspace; // Numeric fields: validate before saving to avoid NaN const parseNum = (v: string) => { const n = Number(v); return !isNaN(n) && isFinite(n) ? n : undefined; }; const timeoutNum = timeout.trim() ? parseNum(timeout) : undefined; if (timeoutNum !== undefined) updated.timeout = timeoutNum; else delete updated.timeout; const ctxNum = contextTokens.trim() ? parseNum(contextTokens) : undefined; if (ctxNum !== undefined) updated.contextTokens = ctxNum; else delete updated.contextTokens; const concNum = maxConcurrent.trim() ? parseNum(maxConcurrent) : undefined; if (concNum !== undefined) updated.maxConcurrent = concNum; else delete updated.maxConcurrent; await saveAgentsMutation.mutateAsync(updated); toast.success(t("openclaw.agents.saveSuccess")); } catch (error) { const detail = extractErrorMessage(error); toast.error(t("openclaw.agents.saveFailed"), { description: detail || undefined, }); } }; if (isLoading) { return (
{t("common.loading")}
); } return (

{t("openclaw.agents.description")}

{/* Model Configuration Card */}

{t("openclaw.agents.modelSection")}

{primaryModel || t("openclaw.agents.notSet")}

{t("openclaw.agents.primaryModelHint")}

setFallbacks(e.target.value)} placeholder="provider/model-a, provider/model-b" className="font-mono text-xs" />

{t("openclaw.agents.fallbackModelsHint")}

{/* Runtime Parameters Card */}

{t("openclaw.agents.runtimeSection")}

setWorkspace(e.target.value)} placeholder="~/projects" className="font-mono text-xs" />
setTimeout_(e.target.value)} placeholder="300" className="font-mono text-xs" />
setContextTokens(e.target.value)} placeholder="200000" className="font-mono text-xs" />
setMaxConcurrent(e.target.value)} placeholder="4" className="font-mono text-xs" />
{/* Save button */}
); }; export default AgentsDefaultsPanel;