mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-29 18:05:37 +08:00
feat: overhaul OpenClaw config panels with JSON5 round-trip write engine
- Add json-five crate for JSON5 serialization preserving comments and formatting - Rewrite openclaw_config.rs with comment-preserving JSON5 read/write engine - Add Tauri commands: get_openclaw_live_provider, write_openclaw_config_section - Redesign EnvPanel as full JSON editor with structured error handling - Add tools.profile selection (minimal/coding/messaging/full) to ToolsPanel - Add legacy timeout migration support to AgentsDefaultsPanel - Add OpenClawHealthBanner component for config validation warnings - Add supporting hooks, mutations, utility functions, and unit tests
This commit is contained in:
@@ -1,12 +1,13 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Plus, Trash2, Save } from "lucide-react";
|
||||
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,
|
||||
@@ -14,15 +15,20 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import type { OpenClawToolsConfig } from "@/types";
|
||||
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 PROFILE_OPTIONS = ["default", "strict", "permissive", "custom"];
|
||||
|
||||
const ToolsPanel: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const { data: toolsData, isLoading } = useOpenClawTools();
|
||||
@@ -35,31 +41,59 @@ const ToolsPanel: React.FC = () => {
|
||||
if (toolsData) {
|
||||
setConfig(toolsData);
|
||||
setAllowList(
|
||||
(toolsData.allow ?? []).map((v) => ({
|
||||
(toolsData.allow ?? []).map((value) => ({
|
||||
id: crypto.randomUUID(),
|
||||
value: v,
|
||||
value,
|
||||
})),
|
||||
);
|
||||
setDenyList(
|
||||
(toolsData.deny ?? []).map((v) => ({
|
||||
(toolsData.deny ?? []).map((value) => ({
|
||||
id: crypto.randomUUID(),
|
||||
value: v,
|
||||
value,
|
||||
})),
|
||||
);
|
||||
}
|
||||
}, [toolsData]);
|
||||
|
||||
const unsupportedProfile = getOpenClawUnsupportedProfile(config.profile);
|
||||
|
||||
const profileLabels = useMemo<Record<OpenClawToolsProfile, string>>(
|
||||
() => ({
|
||||
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 { allow, deny, ...other } = config;
|
||||
const newConfig: OpenClawToolsConfig = {
|
||||
...other,
|
||||
profile: config.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"));
|
||||
|
||||
const outcome = await saveToolsMutation.mutateAsync(newConfig);
|
||||
toast.success(t("openclaw.tools.saveSuccess"), {
|
||||
description: outcome.backupPath
|
||||
? t("openclaw.backupCreated", {
|
||||
path: outcome.backupPath,
|
||||
defaultValue: "Backup created: {{path}}",
|
||||
})
|
||||
: undefined,
|
||||
});
|
||||
} catch (error) {
|
||||
const detail = extractErrorMessage(error);
|
||||
toast.error(t("openclaw.tools.saveFailed"), {
|
||||
@@ -101,29 +135,63 @@ const ToolsPanel: React.FC = () => {
|
||||
{t("openclaw.tools.description")}
|
||||
</p>
|
||||
|
||||
{/* Profile selector */}
|
||||
{unsupportedProfile && (
|
||||
<Alert className="mb-6 border-amber-500/30 bg-amber-500/5">
|
||||
<TriangleAlert className="h-4 w-4" />
|
||||
<AlertTitle>
|
||||
{t("openclaw.tools.unsupportedProfileTitle", {
|
||||
defaultValue: "Unsupported tools profile",
|
||||
})}
|
||||
</AlertTitle>
|
||||
<AlertDescription>
|
||||
{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.",
|
||||
})}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<div className="mb-6">
|
||||
<Label className="mb-2 block">{t("openclaw.tools.profile")}</Label>
|
||||
<Select
|
||||
value={config.profile ?? "default"}
|
||||
onValueChange={(val) =>
|
||||
setConfig((prev) => ({ ...prev, profile: val }))
|
||||
}
|
||||
value={getOpenClawToolsProfileSelectValue(config.profile)}
|
||||
onValueChange={(value) => {
|
||||
if (value === OPENCLAW_UNSUPPORTED_PROFILE) return;
|
||||
if (value === OPENCLAW_UNSET_PROFILE) {
|
||||
setConfig((prev) => ({ ...prev, profile: undefined }));
|
||||
return;
|
||||
}
|
||||
setConfig((prev) => ({ ...prev, profile: value }));
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="w-[200px]">
|
||||
<SelectTrigger className="w-[220px]">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{PROFILE_OPTIONS.map((opt) => (
|
||||
<SelectItem key={opt} value={opt}>
|
||||
{t(`openclaw.tools.profiles.${opt}`)}
|
||||
<SelectItem value={OPENCLAW_UNSET_PROFILE}>
|
||||
{t("openclaw.tools.profileUnset", {
|
||||
defaultValue: "Not set",
|
||||
})}
|
||||
</SelectItem>
|
||||
{unsupportedProfile && (
|
||||
<SelectItem
|
||||
value={OPENCLAW_UNSUPPORTED_PROFILE}
|
||||
disabled={true}
|
||||
>{`${unsupportedProfile} (${t("openclaw.tools.unsupportedProfileLabel", {
|
||||
defaultValue: "unsupported",
|
||||
})})`}</SelectItem>
|
||||
)}
|
||||
{OPENCLAW_TOOL_PROFILES.map((profile) => (
|
||||
<SelectItem key={profile} value={profile}>
|
||||
{profileLabels[profile]}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* Allow list */}
|
||||
<div className="mb-6">
|
||||
<Label className="mb-2 block">{t("openclaw.tools.allowList")}</Label>
|
||||
<div className="space-y-2">
|
||||
@@ -163,7 +231,6 @@ const ToolsPanel: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Deny list */}
|
||||
<div className="mb-6">
|
||||
<Label className="mb-2 block">{t("openclaw.tools.denyList")}</Label>
|
||||
<div className="space-y-2">
|
||||
@@ -203,7 +270,6 @@ const ToolsPanel: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Save button */}
|
||||
<div className="flex justify-end">
|
||||
<Button
|
||||
size="sm"
|
||||
|
||||
Reference in New Issue
Block a user