mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-19 00:15:05 +00:00
refactor(ui): remove web_search config section
Remove ToolsWebSection from config page and clean up i18n keys for web search provider configuration (duckduckgo, exa, tavily, brave). Web search now configured entirely via builtin_tool_tenant_configs REST API. Delete tools-web-section.tsx component and update locale catalogs (en/vi/zh).
This commit is contained in:
1 parent
ff1ab33fbc
commit
96b75f0413
5 files changed
-276
No files matched your search
@@ -208,21 +208,6 @@
|
||||
"tools.execAskMode": "Ask Mode",
|
||||
"tools.execAskModeTip": "When to prompt the user for approval before executing a command.",
|
||||
"tools.execAllowlistLabel": "Allowed Commands (one pattern per line)",
|
||||
"tools.webSearch": "Web Search",
|
||||
"tools.webFetch": "Web fetch and browser automation",
|
||||
"tools.maxResults": "Max Results",
|
||||
"tools.braveApiKey": "API key",
|
||||
"tools.braveApiKeyTip": "Required to use Brave Search. Create a key at https://brave.com/search/api/",
|
||||
"tools.braveApiKeyPlaceholder": "BSA…",
|
||||
"tools.braveApiKeyManaged": "A key is already stored on the server and cannot be shown here.",
|
||||
"tools.webFetchPolicy": "Web Fetch Policy",
|
||||
"tools.webFetchPolicyTip": "Controls which URLs the agent can fetch. 'Allowlist' restricts to approved domains only.",
|
||||
"tools.allowedDomains": "Allowed Domains",
|
||||
"tools.blockedDomains": "Blocked Domains",
|
||||
"tools.blockedDomainsTip": "Domains always blocked regardless of policy. One entry per line, supports wildcards (*.example.com).",
|
||||
"tools.browser": "Browser Automation",
|
||||
"tools.browserEnabled": "Enabled",
|
||||
"tools.browserHeadless": "Headless",
|
||||
"tools.scrubCredentials": "Scrub Credentials",
|
||||
|
||||
"tts.title": "Text-to-Speech",
|
||||
|
||||
@@ -208,21 +208,6 @@
|
||||
"tools.execAskMode": "Chế độ hỏi",
|
||||
"tools.execAskModeTip": "Khi nào nhắc người dùng phê duyệt trước khi thực thi lệnh.",
|
||||
"tools.execAllowlistLabel": "Lệnh được phép (mỗi dòng một mẫu)",
|
||||
"tools.webSearch": "Tìm kiếm web",
|
||||
"tools.webFetch": "Tải web và tự động hóa trình duyệt",
|
||||
"tools.maxResults": "Kết quả tối đa",
|
||||
"tools.braveApiKey": "API Key",
|
||||
"tools.braveApiKeyTip": "Cần thiết để dùng Brave Search. Tạo khóa tại https://brave.com/search/api/",
|
||||
"tools.braveApiKeyPlaceholder": "BSA…",
|
||||
"tools.braveApiKeyManaged": "Khóa đã được lưu trên máy chủ và không thể hiển thị tại đây.",
|
||||
"tools.webFetchPolicy": "Chính sách tải web",
|
||||
"tools.webFetchPolicyTip": "Kiểm soát URL nào agent có thể tải. 'Danh sách cho phép' giới hạn chỉ các domain được duyệt.",
|
||||
"tools.allowedDomains": "Domain được phép",
|
||||
"tools.blockedDomains": "Domain bị chặn",
|
||||
"tools.blockedDomainsTip": "Các domain luôn bị chặn bất kể chính sách. Mỗi dòng một mục, hỗ trợ ký tự đại diện (*.example.com).",
|
||||
"tools.browser": "Tự động hóa trình duyệt",
|
||||
"tools.browserEnabled": "Đã bật",
|
||||
"tools.browserHeadless": "Không giao diện",
|
||||
"tools.scrubCredentials": "Ẩn thông tin xác thực",
|
||||
|
||||
"tts.title": "Chuyển văn bản thành giọng nói",
|
||||
|
||||
@@ -208,21 +208,6 @@
|
||||
"tools.execAskMode": "询问模式",
|
||||
"tools.execAskModeTip": "在执行命令之前何时提示用户审批。",
|
||||
"tools.execAllowlistLabel": "允许的命令(每行一个模式)",
|
||||
"tools.webSearch": "网页搜索",
|
||||
"tools.webFetch": "网页抓取和浏览器自动化",
|
||||
"tools.maxResults": "最大结果数",
|
||||
"tools.braveApiKey": "API 密钥",
|
||||
"tools.braveApiKeyTip": "使用 Brave 搜索时需要。可在 https://brave.com/search/api/ 创建密钥",
|
||||
"tools.braveApiKeyPlaceholder": "BSA…",
|
||||
"tools.braveApiKeyManaged": "密钥已保存在服务器上,无法在此显示。",
|
||||
"tools.webFetchPolicy": "网页抓取策略",
|
||||
"tools.webFetchPolicyTip": "控制 Agent 可以抓取哪些 URL。\"允许列表\"仅限已批准的域名。",
|
||||
"tools.allowedDomains": "允许的域名",
|
||||
"tools.blockedDomains": "屏蔽的域名",
|
||||
"tools.blockedDomainsTip": "无论策略如何都始终屏蔽的域名,每行一条,支持通配符(*.example.com)。",
|
||||
"tools.browser": "浏览器自动化",
|
||||
"tools.browserEnabled": "已启用",
|
||||
"tools.browserHeadless": "无界面模式",
|
||||
"tools.scrubCredentials": "擦除凭证",
|
||||
|
||||
"tts.title": "文字转语音",
|
||||
|
||||
@@ -16,7 +16,6 @@ import { AiDefaultsSection } from "./sections/ai-defaults-section";
|
||||
import { QuotaSection } from "./sections/quota-section";
|
||||
import { ToolsProfileSection } from "./sections/tools-profile-section";
|
||||
import { ToolsExecSection } from "./sections/tools-exec-section";
|
||||
import { ToolsWebSection } from "./sections/tools-web-section";
|
||||
import { ShellSecuritySection } from "./sections/shell-security-section";
|
||||
import { TtsSection } from "./sections/tts-section";
|
||||
import { CronSection } from "./sections/cron-section";
|
||||
@@ -144,11 +143,6 @@ export function ConfigPage() {
|
||||
onSave={(v) => patch({ tools: v })}
|
||||
saving={saving}
|
||||
/>
|
||||
<ToolsWebSection
|
||||
data={config.tools as any}
|
||||
onSave={(v) => patch({ tools: v })}
|
||||
saving={saving}
|
||||
/>
|
||||
<ShellSecuritySection
|
||||
data={config.tools as any}
|
||||
onSave={(v) => patch({ tools: v })}
|
||||
|
||||
@@ -1,225 +0,0 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { Save } from "lucide-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { InfoLabel } from "@/components/shared/info-label";
|
||||
import { isSecret } from "@/lib/secret";
|
||||
|
||||
|
||||
type ToolsData = Record<string, any>;
|
||||
|
||||
interface Props {
|
||||
data: ToolsData | undefined;
|
||||
onSave: (value: ToolsData) => Promise<void>;
|
||||
saving: boolean;
|
||||
}
|
||||
|
||||
export function ToolsWebSection({ data, onSave, saving }: Props) {
|
||||
const { t } = useTranslation("config");
|
||||
const [draft, setDraft] = useState<ToolsData>(data ?? {});
|
||||
const [dirty, setDirty] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setDraft(data ?? {});
|
||||
setDirty(false);
|
||||
}, [data]);
|
||||
|
||||
const updateNested = (section: string, patch: Record<string, any>) => {
|
||||
setDraft((prev) => ({
|
||||
...prev,
|
||||
[section]: { ...(prev[section] ?? {}), ...patch },
|
||||
}));
|
||||
setDirty(true);
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
const toSave: ToolsData = { ...draft };
|
||||
const web = { ...(toSave.web ?? {}) };
|
||||
const brave = { ...(web.brave ?? {}) };
|
||||
if (isSecret(brave.api_key)) {
|
||||
delete brave.api_key;
|
||||
}
|
||||
web.brave = brave;
|
||||
toSave.web = web;
|
||||
onSave(toSave);
|
||||
};
|
||||
|
||||
if (!data) return null;
|
||||
|
||||
const web = draft.web ?? {};
|
||||
const ddg = web.duckduckgo ?? {};
|
||||
const brave = web.brave ?? {};
|
||||
const webFetch = draft.web_fetch ?? {};
|
||||
const browser = draft.browser ?? {};
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-base">{t("tools.webSearch")}</CardTitle>
|
||||
<CardDescription>{t("tools.webFetch")}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{/* Web Search */}
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label>DuckDuckGo</Label>
|
||||
<Switch
|
||||
checked={ddg.enabled !== false}
|
||||
onCheckedChange={(v) => updateNested("web", { duckduckgo: { ...ddg, enabled: v } })}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-1.5">
|
||||
<Label className="text-xs text-muted-foreground">{t("tools.maxResults")}</Label>
|
||||
<Input
|
||||
type="number"
|
||||
className="text-base md:text-sm"
|
||||
value={ddg.max_results ?? ""}
|
||||
onChange={(e) => updateNested("web", { duckduckgo: { ...ddg, max_results: Number(e.target.value) } })}
|
||||
placeholder="5"
|
||||
min={1}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label>Brave Search</Label>
|
||||
<Switch
|
||||
checked={brave.enabled ?? false}
|
||||
onCheckedChange={(v) => updateNested("web", { brave: { ...brave, enabled: v } })}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-1.5">
|
||||
<Label className="text-xs text-muted-foreground">{t("tools.maxResults")}</Label>
|
||||
<Input
|
||||
type="number"
|
||||
className="text-base md:text-sm"
|
||||
value={brave.max_results ?? ""}
|
||||
onChange={(e) => updateNested("web", { brave: { ...brave, max_results: Number(e.target.value) } })}
|
||||
placeholder="5"
|
||||
min={1}
|
||||
/>
|
||||
</div>
|
||||
{brave.enabled && (
|
||||
<div className="grid gap-1.5">
|
||||
<InfoLabel tip={t("tools.braveApiKeyTip")}>{t("tools.braveApiKey")}</InfoLabel>
|
||||
<Input
|
||||
type="password"
|
||||
className="text-base md:text-sm"
|
||||
value={isSecret(brave.api_key) ? "" : (brave.api_key ?? "")}
|
||||
onChange={(e) =>
|
||||
updateNested("web", { brave: { ...brave, api_key: e.target.value } })
|
||||
}
|
||||
placeholder={t("tools.braveApiKeyPlaceholder")}
|
||||
autoComplete="off"
|
||||
/>
|
||||
{isSecret(brave.api_key) && (
|
||||
<p className="text-xs text-muted-foreground">{t("tools.braveApiKeyManaged")}</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
{/* Web Fetch */}
|
||||
<div className="grid gap-3">
|
||||
<div className="grid gap-1.5 max-w-xs">
|
||||
<InfoLabel tip={t("tools.webFetchPolicyTip")}>{t("tools.webFetchPolicy")}</InfoLabel>
|
||||
<Select
|
||||
value={webFetch.policy ?? "allow_all"}
|
||||
onValueChange={(v) => updateNested("web_fetch", { ...webFetch, policy: v })}
|
||||
>
|
||||
<SelectTrigger><SelectValue /></SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="allow_all">Allow All</SelectItem>
|
||||
<SelectItem value="allowlist">Allowlist</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
{webFetch.policy === "allowlist" && (
|
||||
<div className="grid gap-1.5">
|
||||
<Label>{t("tools.allowedDomains")}</Label>
|
||||
<Textarea
|
||||
value={(webFetch.allowed_domains ?? []).join("\n")}
|
||||
onChange={(e) =>
|
||||
updateNested("web_fetch", {
|
||||
...webFetch,
|
||||
allowed_domains: e.target.value.split("\n").filter(Boolean),
|
||||
})
|
||||
}
|
||||
className="min-h-[80px] font-mono text-xs"
|
||||
placeholder={"github.com\n*.wikipedia.org\ndocs.google.com"}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="grid gap-1.5">
|
||||
<InfoLabel tip={t("tools.blockedDomainsTip")}>{t("tools.blockedDomains")}</InfoLabel>
|
||||
<Textarea
|
||||
value={(webFetch.blocked_domains ?? []).join("\n")}
|
||||
onChange={(e) =>
|
||||
updateNested("web_fetch", {
|
||||
...webFetch,
|
||||
blocked_domains: e.target.value.split("\n").filter(Boolean),
|
||||
})
|
||||
}
|
||||
className="min-h-[80px] font-mono text-xs"
|
||||
placeholder={"ifconfig.co\nipinfo.io\n*.whatismyip.com"}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
{/* Browser */}
|
||||
<div>
|
||||
<h4 className="mb-3 text-sm font-medium">{t("tools.browser")}</h4>
|
||||
<div className="flex gap-6">
|
||||
<div className="flex items-center gap-2">
|
||||
<Label>{t("tools.browserEnabled")}</Label>
|
||||
<Switch
|
||||
checked={browser.enabled !== false}
|
||||
onCheckedChange={(v) => updateNested("browser", { enabled: v })}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Label>{t("tools.browserHeadless")}</Label>
|
||||
<Switch
|
||||
checked={browser.headless !== false}
|
||||
onCheckedChange={(v) => updateNested("browser", { headless: v })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{dirty && (
|
||||
<div className="flex justify-end pt-2">
|
||||
<Button size="sm" onClick={handleSave} disabled={saving} className="gap-1.5">
|
||||
<Save className="h-3.5 w-3.5" /> {saving ? t("saving") : t("save")}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user