mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-24 12:44:18 +08:00
4efab15174
The primary model field in the Agents defaults panel now displays as read-only, eliminating the duplicate edit entry with the "Set as Default Model" button on provider cards. Fallback models and runtime parameters remain editable.
231 lines
7.6 KiB
TypeScript
231 lines
7.6 KiB
TypeScript
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<OpenClawAgentsDefaults | null>(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 (
|
|
<div className="px-6 pt-4 pb-8 flex items-center justify-center min-h-[200px]">
|
|
<div className="text-sm text-muted-foreground">
|
|
{t("common.loading")}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="px-6 pt-4 pb-8">
|
|
<p className="text-sm text-muted-foreground mb-6">
|
|
{t("openclaw.agents.description")}
|
|
</p>
|
|
|
|
{/* Model Configuration Card */}
|
|
<div className="rounded-xl border border-border bg-card p-5 mb-4">
|
|
<h3 className="text-sm font-medium mb-4">
|
|
{t("openclaw.agents.modelSection")}
|
|
</h3>
|
|
|
|
<div className="space-y-4">
|
|
<div>
|
|
<Label className="mb-1.5 block">
|
|
{t("openclaw.agents.primaryModel")}
|
|
</Label>
|
|
<div className="h-9 px-3 flex items-center rounded-md border border-input bg-muted/50 font-mono text-xs text-muted-foreground">
|
|
{primaryModel || t("openclaw.agents.notSet")}
|
|
</div>
|
|
<p className="text-xs text-muted-foreground mt-1">
|
|
{t("openclaw.agents.primaryModelHint")}
|
|
</p>
|
|
</div>
|
|
|
|
<div>
|
|
<Label className="mb-1.5 block">
|
|
{t("openclaw.agents.fallbackModels")}
|
|
</Label>
|
|
<Input
|
|
value={fallbacks}
|
|
onChange={(e) => setFallbacks(e.target.value)}
|
|
placeholder="provider/model-a, provider/model-b"
|
|
className="font-mono text-xs"
|
|
/>
|
|
<p className="text-xs text-muted-foreground mt-1">
|
|
{t("openclaw.agents.fallbackModelsHint")}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Runtime Parameters Card */}
|
|
<div className="rounded-xl border border-border bg-card p-5 mb-4">
|
|
<h3 className="text-sm font-medium mb-4">
|
|
{t("openclaw.agents.runtimeSection")}
|
|
</h3>
|
|
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
|
<div>
|
|
<Label className="mb-1.5 block">
|
|
{t("openclaw.agents.workspace")}
|
|
</Label>
|
|
<Input
|
|
value={workspace}
|
|
onChange={(e) => setWorkspace(e.target.value)}
|
|
placeholder="~/projects"
|
|
className="font-mono text-xs"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<Label className="mb-1.5 block">
|
|
{t("openclaw.agents.timeout")}
|
|
</Label>
|
|
<Input
|
|
type="number"
|
|
value={timeout}
|
|
onChange={(e) => setTimeout_(e.target.value)}
|
|
placeholder="300"
|
|
className="font-mono text-xs"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<Label className="mb-1.5 block">
|
|
{t("openclaw.agents.contextTokens")}
|
|
</Label>
|
|
<Input
|
|
type="number"
|
|
value={contextTokens}
|
|
onChange={(e) => setContextTokens(e.target.value)}
|
|
placeholder="200000"
|
|
className="font-mono text-xs"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<Label className="mb-1.5 block">
|
|
{t("openclaw.agents.maxConcurrent")}
|
|
</Label>
|
|
<Input
|
|
type="number"
|
|
value={maxConcurrent}
|
|
onChange={(e) => setMaxConcurrent(e.target.value)}
|
|
placeholder="4"
|
|
className="font-mono text-xs"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Save button */}
|
|
<div className="flex justify-end">
|
|
<Button
|
|
size="sm"
|
|
onClick={handleSave}
|
|
disabled={saveAgentsMutation.isPending}
|
|
>
|
|
<Save className="w-4 h-4 mr-1" />
|
|
{saveAgentsMutation.isPending ? t("common.saving") : t("common.save")}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default AgentsDefaultsPanel;
|