import React, { useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { Plus, Trash2, Save, TriangleAlert } from "lucide-react"; import { toast } from "sonner"; import { useOpenClawTools, useSaveOpenClawTools } 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 { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import type { OpenClawToolsConfig, OpenClawToolsProfile } from "@/types"; import { getOpenClawToolsProfileSelectValue, getOpenClawUnsupportedProfile, OPENCLAW_TOOL_PROFILES, OPENCLAW_UNSET_PROFILE, OPENCLAW_UNSUPPORTED_PROFILE, } from "./utils"; interface ListItem { id: string; value: string; } const ToolsPanel: React.FC = () => { const { t } = useTranslation(); const { data: toolsData, isLoading } = useOpenClawTools(); const saveToolsMutation = useSaveOpenClawTools(); const [config, setConfig] = useState({}); const [allowList, setAllowList] = useState([]); const [denyList, setDenyList] = useState([]); useEffect(() => { if (toolsData) { setConfig(toolsData); setAllowList( (toolsData.allow ?? []).map((value) => ({ id: crypto.randomUUID(), value, })), ); setDenyList( (toolsData.deny ?? []).map((value) => ({ id: crypto.randomUUID(), value, })), ); } }, [toolsData]); const unsupportedProfile = getOpenClawUnsupportedProfile(config.profile); const profileLabels = useMemo>( () => ({ minimal: t("openclaw.tools.profileMinimal", { defaultValue: "Minimal", }), coding: t("openclaw.tools.profileCoding", { defaultValue: "Coding", }), messaging: t("openclaw.tools.profileMessaging", { defaultValue: "Messaging", }), full: t("openclaw.tools.profileFull", { defaultValue: "Full", }), }), [t], ); const handleSave = async () => { try { const { profile, allow, deny, ...other } = config; const newConfig: OpenClawToolsConfig = { ...other, profile, allow: allowList.map((item) => item.value).filter((s) => s.trim()), deny: denyList.map((item) => item.value).filter((s) => s.trim()), }; await saveToolsMutation.mutateAsync(newConfig); toast.success(t("openclaw.tools.saveSuccess")); } catch (error) { const detail = extractErrorMessage(error); toast.error(t("openclaw.tools.saveFailed"), { description: detail || undefined, }); } }; const updateListItem = ( setList: React.Dispatch>, index: number, value: string, ) => { setList((prev) => prev.map((item, i) => (i === index ? { ...item, value } : item)), ); }; const removeListItem = ( setList: React.Dispatch>, index: number, ) => { setList((prev) => prev.filter((_, i) => i !== index)); }; if (isLoading) { return (
{t("common.loading")}
); } return (

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

{unsupportedProfile && ( {t("openclaw.tools.unsupportedProfileTitle", { defaultValue: "Unsupported tools profile", })} {t("openclaw.tools.unsupportedProfileDescription", { value: unsupportedProfile, defaultValue: "The current tools.profile value '{{value}}' is not in the supported OpenClaw list. It will be preserved until you choose a new value.", })} )}
{allowList.map((item, index) => (
updateListItem(setAllowList, index, e.target.value) } placeholder={t("openclaw.tools.patternPlaceholder")} className="font-mono text-xs" />
))}
{denyList.map((item, index) => (
updateListItem(setDenyList, index, e.target.value) } placeholder={t("openclaw.tools.patternPlaceholder")} className="font-mono text-xs" />
))}
); }; export default ToolsPanel;