import React, { useState, useEffect } from "react"; import { useTranslation } from "react-i18next"; import { Plus, Trash2, Save, Eye, EyeOff } from "lucide-react"; import { toast } from "sonner"; import { useOpenClawEnv, useSaveOpenClawEnv } from "@/hooks/useOpenClaw"; import { extractErrorMessage } from "@/utils/errorUtils"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import type { OpenClawEnvConfig } from "@/types"; interface EnvEntry { id: string; key: string; value: string; isNew?: boolean; } const EnvPanel: React.FC = () => { const { t } = useTranslation(); const { data: envData, isLoading } = useOpenClawEnv(); const saveEnvMutation = useSaveOpenClawEnv(); const [entries, setEntries] = useState([]); const [visibleKeys, setVisibleKeys] = useState>(new Set()); useEffect(() => { if (envData) { const items: EnvEntry[] = Object.entries(envData).map(([key, value]) => ({ id: crypto.randomUUID(), key, value: String(value ?? ""), })); setEntries(items.length > 0 ? items : []); } }, [envData]); const handleSave = async () => { try { const env: OpenClawEnvConfig = {}; const seen = new Set(); for (const entry of entries) { const trimmedKey = entry.key.trim(); if (trimmedKey) { if (seen.has(trimmedKey)) { toast.error(t("openclaw.env.duplicateKey", { key: trimmedKey })); return; } seen.add(trimmedKey); env[trimmedKey] = entry.value; } } await saveEnvMutation.mutateAsync(env); toast.success(t("openclaw.env.saveSuccess")); } catch (error) { const detail = extractErrorMessage(error); toast.error(t("openclaw.env.saveFailed"), { description: detail || undefined, }); } }; const addEntry = () => { setEntries((prev) => [ ...prev, { id: crypto.randomUUID(), key: "", value: "", isNew: true }, ]); }; const removeEntry = (index: number) => { setEntries((prev) => prev.filter((_, i) => i !== index)); }; const updateEntry = (index: number, field: "key" | "value", val: string) => { setEntries((prev) => prev.map((entry, i) => i === index ? { ...entry, [field]: val } : entry, ), ); }; const toggleVisibility = (key: string) => { setVisibleKeys((prev) => { const next = new Set(prev); if (next.has(key)) { next.delete(key); } else { next.add(key); } return next; }); }; const isApiKey = (key: string) => /key|token|secret|password/i.test(key); if (isLoading) { return (
{t("common.loading")}
); } return (

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

{entries.map((entry, index) => { const sensitive = isApiKey(entry.key); const visibilityId = entry.key || `__new_${index}`; const visible = visibleKeys.has(visibilityId); return (
updateEntry(index, "key", e.target.value)} placeholder={t("openclaw.env.keyPlaceholder")} className="font-mono text-xs" autoFocus={entry.isNew} />
updateEntry(index, "value", e.target.value)} placeholder={t("openclaw.env.valuePlaceholder")} className="font-mono text-xs" /> {sensitive && ( )}
); })}
); }; export default EnvPanel;