feat(ui): 系统设置接入会话发送键位与 Web 搜索代理
- 会话发送键位改用 chat-send-mode 统一配置(CHAT_SEND_MODE_OPTIONS/ useChatSendMode),替换页面内自维护的模式常量。 - Web 搜索区新增代理输入框(http/socks5,留空为直连)。 Claude-Session: https://claude.ai/code/session_01RtPQYJ8W1mcaace3sHWGDm
This commit is contained in:
1 parent
be90d520a3
commit
a3fe9b767b
1 file changed
+273
-261
@@ -12,12 +12,7 @@ import { Label } from "@/components/ui/label";
|
|||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { api } from "@/lib/api";
|
import { api } from "@/lib/api";
|
||||||
import {
|
import { CHAT_SEND_MODE_OPTIONS, type ChatSendMode, setChatSendMode, useChatSendMode } from "@/lib/chat-send-mode";
|
||||||
CHAT_SEND_MODE_OPTIONS,
|
|
||||||
type ChatSendMode,
|
|
||||||
setChatSendMode,
|
|
||||||
useChatSendMode,
|
|
||||||
} from "@/lib/chat-send-mode";
|
|
||||||
import type { Settings } from "@/lib/types";
|
import type { Settings } from "@/lib/types";
|
||||||
|
|
||||||
export default function SystemSettingsPage() {
|
export default function SystemSettingsPage() {
|
||||||
@@ -82,7 +77,11 @@ export default function SystemSettingsPage() {
|
|||||||
};
|
};
|
||||||
const detectPython = () => {
|
const detectPython = () => {
|
||||||
setSaving(true);
|
setSaving(true);
|
||||||
api.detectPython().then((r) => setPyInterp(r.python_interpreter)).catch(() => undefined).finally(() => setSaving(false));
|
api
|
||||||
|
.detectPython()
|
||||||
|
.then((r) => setPyInterp(r.python_interpreter))
|
||||||
|
.catch(() => undefined)
|
||||||
|
.finally(() => setSaving(false));
|
||||||
};
|
};
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
@@ -189,280 +188,293 @@ export default function SystemSettingsPage() {
|
|||||||
<p className="text-muted-foreground text-sm">全局运行时开关</p>
|
<p className="text-muted-foreground text-sm">全局运行时开关</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Card className="max-w-2xl">
|
{/* 多列而非 grid:网络搜索卡片比其余高数倍,且高度随所选后端变化(brave/tavily
|
||||||
<CardHeader>
|
的 key 输入是条件渲染)。grid 会按最高的一张撑满整行、在旁边留下大片空白,
|
||||||
<CardTitle className="flex items-center gap-2 text-base">
|
多列则自动按内容高度平衡填充。卡片间距靠 mb 而非 gap——多列布局下
|
||||||
<RadioTowerIcon className="size-4" />
|
column-gap 只管列间距,行间距要由子元素自己给。 */}
|
||||||
流量捕获
|
<div className="columns-1 gap-4 md:gap-6 lg:columns-2">
|
||||||
</CardTitle>
|
<Card className="mb-4 break-inside-avoid md:mb-6">
|
||||||
<CardDescription>
|
<CardHeader>
|
||||||
开启后,所有 Agent 的 HTTP 流量经记录代理全量落库,并向 Agent 注入 traffic_search / traffic_get
|
<CardTitle className="flex items-center gap-2 text-base">
|
||||||
工具与代理配置(提示词含代理说明)。
|
<RadioTowerIcon className="size-4" />
|
||||||
<br />
|
流量捕获
|
||||||
关闭(默认)时不记录任何流量:Agent
|
</CardTitle>
|
||||||
<b>不会</b>拿到代理配置与流量工具,提示词也<b>不含</b>代理相关内容。切换后会即时重建 Agent 生效。
|
<CardDescription>
|
||||||
</CardDescription>
|
开启后,所有 Agent 的 HTTP 流量经记录代理全量落库,并向 Agent 注入 traffic_search / traffic_get
|
||||||
</CardHeader>
|
工具与代理配置(提示词含代理说明)。
|
||||||
<CardContent className="flex items-center justify-between gap-4">
|
<br />
|
||||||
<Label htmlFor="traffic-capture" className="text-sm font-normal text-muted-foreground">
|
关闭(默认)时不记录任何流量:Agent
|
||||||
{trafficCapture ? "已开启 · 正在记录流量并注入代理" : "已关闭 · 不记录、不注入代理"}
|
<b>不会</b>拿到代理配置与流量工具,提示词也<b>不含</b>代理相关内容。切换后会即时重建 Agent 生效。
|
||||||
</Label>
|
</CardDescription>
|
||||||
<Switch
|
</CardHeader>
|
||||||
id="traffic-capture"
|
<CardContent className="flex items-center justify-between gap-4">
|
||||||
checked={trafficCapture}
|
<Label htmlFor="traffic-capture" className="text-sm font-normal text-muted-foreground">
|
||||||
disabled={!loaded || saving}
|
{trafficCapture ? "已开启 · 正在记录流量并注入代理" : "已关闭 · 不记录、不注入代理"}
|
||||||
onCheckedChange={toggleTraffic}
|
|
||||||
/>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card className="max-w-2xl">
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className="flex items-center gap-2 text-base">
|
|
||||||
<SearchIcon className="size-4" />
|
|
||||||
网络搜索
|
|
||||||
</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
这是网络搜索的<b>总开关 + 来源配置</b>。开启后,才能在<b>每个 Agent 的配置</b>里单独选择是否启用
|
|
||||||
<b>web_search</b>(仅返回标题/链接/摘要,不抓取正文;抓取由 WebFetch 负责)。网络搜索<b>不走</b>记录代理,独立于流量捕获。
|
|
||||||
<br />
|
|
||||||
来源可选 <b>DuckDuckGo(ddgs)</b>(无需 Key)、<b>Brave(免费版)</b>(需填写 Brave API Key)或 <b>Tavily</b>(需填写 Tavily API Key)。总开关关闭时,各 Agent 的网络搜索开关不可用。
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="flex flex-col gap-4">
|
|
||||||
<div className="flex items-center justify-between gap-4">
|
|
||||||
<Label htmlFor="web-search" className="text-sm font-normal text-muted-foreground">
|
|
||||||
{webSearch ? "总开关已开启 · 可在各 Agent 配置里单独启用" : "已关闭 · 各 Agent 无法启用网络搜索"}
|
|
||||||
</Label>
|
</Label>
|
||||||
<Switch
|
<Switch
|
||||||
id="web-search"
|
id="traffic-capture"
|
||||||
checked={webSearch}
|
checked={trafficCapture}
|
||||||
disabled={!loaded || saving}
|
disabled={!loaded || saving}
|
||||||
onCheckedChange={(v) => {
|
onCheckedChange={toggleTraffic}
|
||||||
setWebSearch(v); // optimistic
|
|
||||||
saveWebSearch({ web_search_enabled: v });
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
{webSearch && (
|
<Card className="mb-4 break-inside-avoid md:mb-6">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="flex items-center gap-2 text-base">
|
||||||
|
<SearchIcon className="size-4" />
|
||||||
|
网络搜索
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
这是网络搜索的<b>总开关 + 来源配置</b>。开启后,才能在<b>每个 Agent 的配置</b>里单独选择是否启用
|
||||||
|
<b>web_search</b>(仅返回标题/链接/摘要,不抓取正文;抓取由 WebFetch 负责)。网络搜索<b>不走</b>
|
||||||
|
记录代理,独立于流量捕获。
|
||||||
|
<br />
|
||||||
|
来源可选 <b>DuckDuckGo(ddgs)</b>(无需 Key)、<b>Brave(免费版)</b>(需填写 Brave API Key)或{" "}
|
||||||
|
<b>Tavily</b>(需填写 Tavily API Key)。总开关关闭时,各 Agent 的网络搜索开关不可用。
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="flex flex-col gap-4">
|
||||||
<div className="flex items-center justify-between gap-4">
|
<div className="flex items-center justify-between gap-4">
|
||||||
<Label className="text-sm font-normal text-muted-foreground">搜索来源</Label>
|
<Label htmlFor="web-search" className="text-sm font-normal text-muted-foreground">
|
||||||
<Select
|
{webSearch ? "总开关已开启 · 可在各 Agent 配置里单独启用" : "已关闭 · 各 Agent 无法启用网络搜索"}
|
||||||
value={backend}
|
</Label>
|
||||||
|
<Switch
|
||||||
|
id="web-search"
|
||||||
|
checked={webSearch}
|
||||||
disabled={!loaded || saving}
|
disabled={!loaded || saving}
|
||||||
onValueChange={(v) => {
|
onCheckedChange={(v) => {
|
||||||
setBackend(v); // optimistic
|
setWebSearch(v); // optimistic
|
||||||
saveWebSearch({ web_search_backend: v });
|
saveWebSearch({ web_search_enabled: v });
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
<SelectTrigger className="w-48 shrink-0">
|
|
||||||
<SelectValue placeholder="选择来源" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="ddgs">DuckDuckGo(ddgs · 免费无 Key)</SelectItem>
|
|
||||||
<SelectItem value="brave-free">Brave(免费版 · 需 Key)</SelectItem>
|
|
||||||
<SelectItem value="tavily">Tavily(需 Key)</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
|
|
||||||
{webSearch && backend === "brave-free" && (
|
{webSearch && (
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex items-center justify-between gap-4">
|
||||||
<Label htmlFor="brave-key" className="text-sm font-normal text-muted-foreground">
|
<Label className="text-sm font-normal text-muted-foreground">搜索来源</Label>
|
||||||
Brave Search API Key
|
<Select
|
||||||
{braveKeySet && <span className="ml-2 text-xs text-emerald-500">已配置</span>}
|
value={backend}
|
||||||
</Label>
|
disabled={!loaded || saving}
|
||||||
<div className="flex items-center gap-2">
|
onValueChange={(v) => {
|
||||||
<Input
|
setBackend(v); // optimistic
|
||||||
id="brave-key"
|
saveWebSearch({ web_search_backend: v });
|
||||||
type="password"
|
}}
|
||||||
autoComplete="off"
|
>
|
||||||
placeholder={braveKeySet ? "已配置(留空则不变)" : "输入 Brave API Key"}
|
<SelectTrigger className="w-48 shrink-0">
|
||||||
value={braveKeyInput}
|
<SelectValue placeholder="选择来源" />
|
||||||
disabled={!loaded || savingKey}
|
</SelectTrigger>
|
||||||
onChange={(e) => setBraveKeyInput(e.target.value)}
|
<SelectContent>
|
||||||
/>
|
<SelectItem value="ddgs">DuckDuckGo(ddgs · 免费无 Key)</SelectItem>
|
||||||
|
<SelectItem value="brave-free">Brave(免费版 · 需 Key)</SelectItem>
|
||||||
|
<SelectItem value="tavily">Tavily(需 Key)</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{webSearch && backend === "brave-free" && (
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<Label htmlFor="brave-key" className="text-sm font-normal text-muted-foreground">
|
||||||
|
Brave Search API Key
|
||||||
|
{braveKeySet && <span className="ml-2 text-xs text-emerald-500">已配置</span>}
|
||||||
|
</Label>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Input
|
||||||
|
id="brave-key"
|
||||||
|
type="password"
|
||||||
|
autoComplete="off"
|
||||||
|
placeholder={braveKeySet ? "已配置(留空则不变)" : "输入 Brave API Key"}
|
||||||
|
value={braveKeyInput}
|
||||||
|
disabled={!loaded || savingKey}
|
||||||
|
onChange={(e) => setBraveKeyInput(e.target.value)}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
onClick={saveBraveKey}
|
||||||
|
disabled={!loaded || savingKey || braveKeyInput.trim() === ""}
|
||||||
|
>
|
||||||
|
保存
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{braveNeedsKey && (
|
||||||
|
<p className="text-xs text-amber-500">
|
||||||
|
已选择 Brave 但尚未配置 Key —— 在保存 Key 之前,搜索工具不会启用。
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
<p className="text-muted-foreground text-xs">
|
||||||
|
免费版额度约 2,000 次/月。前往 https://brave.com/search/api/ 获取 Key。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{webSearch && backend === "tavily" && (
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<Label htmlFor="tavily-key" className="text-sm font-normal text-muted-foreground">
|
||||||
|
Tavily Search API Key
|
||||||
|
{tavilyKeySet && <span className="ml-2 text-xs text-emerald-500">已配置</span>}
|
||||||
|
</Label>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Input
|
||||||
|
id="tavily-key"
|
||||||
|
type="password"
|
||||||
|
autoComplete="off"
|
||||||
|
placeholder={tavilyKeySet ? "已配置(留空则不变)" : "输入 Tavily API Key(tvly-…)"}
|
||||||
|
value={tavilyKeyInput}
|
||||||
|
disabled={!loaded || savingTavilyKey}
|
||||||
|
onChange={(e) => setTavilyKeyInput(e.target.value)}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
onClick={saveTavilyKey}
|
||||||
|
disabled={!loaded || savingTavilyKey || tavilyKeyInput.trim() === ""}
|
||||||
|
>
|
||||||
|
保存
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{webSearch && backend === "tavily" && !tavilyKeySet && (
|
||||||
|
<p className="text-xs text-amber-500">
|
||||||
|
已选择 Tavily 但尚未配置 Key —— 在保存 Key 之前,搜索工具不会启用。
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
<p className="text-muted-foreground text-xs">前往 https://tavily.com 注册并获取 API Key。</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{webSearch && (
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<Label htmlFor="ws-proxy" className="text-sm font-normal text-muted-foreground">
|
||||||
|
出口代理(可选)
|
||||||
|
</Label>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Input
|
||||||
|
id="ws-proxy"
|
||||||
|
autoComplete="off"
|
||||||
|
placeholder="http://host:port 或 socks5://host:port(留空=直连)"
|
||||||
|
value={proxyInput}
|
||||||
|
disabled={!loaded || savingProxy}
|
||||||
|
onChange={(e) => setProxyInput(e.target.value)}
|
||||||
|
/>
|
||||||
|
<Button type="button" onClick={saveProxy} disabled={!loaded || savingProxy}>
|
||||||
|
保存
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<p className="text-muted-foreground text-xs">
|
||||||
|
独立出口代理,仅用于访问搜索端点(VPN/SOCKS 等)。与记录流量的 MITM 代理无关;网络不通时经此代理访问。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{webSearch && (
|
||||||
|
<div className="flex items-center justify-between gap-4 border-t pt-4">
|
||||||
|
<p className="text-muted-foreground text-xs">
|
||||||
|
用当前配置(来源 + 代理 + Key)实际搜索一次「test」,验证是否可用。
|
||||||
|
</p>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={saveBraveKey}
|
variant="outline"
|
||||||
disabled={!loaded || savingKey || braveKeyInput.trim() === ""}
|
onClick={runTest}
|
||||||
|
disabled={!loaded || testing}
|
||||||
|
className="shrink-0"
|
||||||
>
|
>
|
||||||
保存
|
{testing ? "测试中…" : "测试搜索"}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
{braveNeedsKey && (
|
)}
|
||||||
<p className="text-xs text-amber-500">
|
</CardContent>
|
||||||
已选择 Brave 但尚未配置 Key —— 在保存 Key 之前,搜索工具不会启用。
|
</Card>
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
<p className="text-muted-foreground text-xs">
|
|
||||||
免费版额度约 2,000 次/月。前往 https://brave.com/search/api/ 获取 Key。
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{webSearch && backend === "tavily" && (
|
<Card className="mb-4 break-inside-avoid md:mb-6">
|
||||||
<div className="flex flex-col gap-2">
|
<CardHeader>
|
||||||
<Label htmlFor="tavily-key" className="text-sm font-normal text-muted-foreground">
|
<CardTitle className="flex items-center gap-2 text-base">
|
||||||
Tavily Search API Key
|
<RadioTowerIcon className="size-4" />
|
||||||
{tavilyKeySet && <span className="ml-2 text-xs text-emerald-500">已配置</span>}
|
自定义脚本 · Python 解释器
|
||||||
</Label>
|
</CardTitle>
|
||||||
<div className="flex items-center gap-2">
|
<CardDescription>
|
||||||
<Input
|
自定义 <b>script</b> 类型工具用它跑 Python。开机会自动检测(python3 优先);此处可手填 venv /
|
||||||
id="tavily-key"
|
特定版本的绝对路径,留空则运行时自动检测。
|
||||||
type="password"
|
</CardDescription>
|
||||||
autoComplete="off"
|
</CardHeader>
|
||||||
placeholder={tavilyKeySet ? "已配置(留空则不变)" : "输入 Tavily API Key(tvly-…)"}
|
<CardContent className="flex flex-col gap-3">
|
||||||
value={tavilyKeyInput}
|
<div className="flex items-center gap-2">
|
||||||
disabled={!loaded || savingTavilyKey}
|
<Input
|
||||||
onChange={(e) => setTavilyKeyInput(e.target.value)}
|
className="font-mono text-sm"
|
||||||
/>
|
placeholder="/usr/bin/python3(留空=自动检测)"
|
||||||
<Button
|
value={pyInterp}
|
||||||
type="button"
|
disabled={!loaded || saving}
|
||||||
onClick={saveTavilyKey}
|
onChange={(e) => setPyInterp(e.target.value)}
|
||||||
disabled={!loaded || savingTavilyKey || tavilyKeyInput.trim() === ""}
|
/>
|
||||||
>
|
<Button variant="outline" onClick={detectPython} disabled={!loaded || saving}>
|
||||||
保存
|
重新检测
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
<Button onClick={savePython} disabled={!loaded || saving}>
|
||||||
{webSearch && backend === "tavily" && !tavilyKeySet && (
|
保存
|
||||||
<p className="text-xs text-amber-500">
|
|
||||||
已选择 Tavily 但尚未配置 Key —— 在保存 Key 之前,搜索工具不会启用。
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
<p className="text-muted-foreground text-xs">
|
|
||||||
前往 https://tavily.com 注册并获取 API Key。
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{webSearch && (
|
|
||||||
<div className="flex flex-col gap-2">
|
|
||||||
<Label htmlFor="ws-proxy" className="text-sm font-normal text-muted-foreground">
|
|
||||||
出口代理(可选)
|
|
||||||
</Label>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Input
|
|
||||||
id="ws-proxy"
|
|
||||||
autoComplete="off"
|
|
||||||
placeholder="http://host:port 或 socks5://host:port(留空=直连)"
|
|
||||||
value={proxyInput}
|
|
||||||
disabled={!loaded || savingProxy}
|
|
||||||
onChange={(e) => setProxyInput(e.target.value)}
|
|
||||||
/>
|
|
||||||
<Button type="button" onClick={saveProxy} disabled={!loaded || savingProxy}>
|
|
||||||
保存
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<p className="text-muted-foreground text-xs">
|
|
||||||
独立出口代理,仅用于访问搜索端点(VPN/SOCKS 等)。与记录流量的 MITM 代理无关;网络不通时经此代理访问。
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{webSearch && (
|
|
||||||
<div className="flex items-center justify-between gap-4 border-t pt-4">
|
|
||||||
<p className="text-muted-foreground text-xs">
|
|
||||||
用当前配置(来源 + 代理 + Key)实际搜索一次「test」,验证是否可用。
|
|
||||||
</p>
|
|
||||||
<Button type="button" variant="outline" onClick={runTest} disabled={!loaded || testing} className="shrink-0">
|
|
||||||
{testing ? "测试中…" : "测试搜索"}
|
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
</CardContent>
|
||||||
</CardContent>
|
</Card>
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card className="max-w-2xl">
|
<Card className="mb-4 break-inside-avoid md:mb-6">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="flex items-center gap-2 text-base">
|
<CardTitle className="flex items-center gap-2 text-base">
|
||||||
<RadioTowerIcon className="size-4" />
|
<CpuIcon className="size-4" />
|
||||||
自定义脚本 · Python 解释器
|
工作并发 · Work Agent 数
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
自定义 <b>script</b> 类型工具用它跑 Python。开机会自动检测(python3 优先);此处可手填 venv /
|
每个任务并发运行的工作 agent 数量(默认 3)。数值越大并发探测越多、消耗也越高。修改后
|
||||||
特定版本的绝对路径,留空则运行时自动检测。
|
<b>对之后启动的任务生效</b>,正在运行的任务不受影响。
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="flex flex-col gap-3">
|
<CardContent className="flex flex-col gap-3">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Input
|
<Input
|
||||||
className="font-mono text-sm"
|
type="number"
|
||||||
placeholder="/usr/bin/python3(留空=自动检测)"
|
min={1}
|
||||||
value={pyInterp}
|
className="w-32 font-mono text-sm"
|
||||||
disabled={!loaded || saving}
|
placeholder="3"
|
||||||
onChange={(e) => setPyInterp(e.target.value)}
|
value={workers}
|
||||||
/>
|
disabled={!loaded || savingWorkers}
|
||||||
<Button variant="outline" onClick={detectPython} disabled={!loaded || saving}>
|
onChange={(e) => setWorkers(e.target.value)}
|
||||||
重新检测
|
/>
|
||||||
</Button>
|
<Button onClick={saveWorkers} disabled={!loaded || savingWorkers}>
|
||||||
<Button onClick={savePython} disabled={!loaded || saving}>
|
保存
|
||||||
保存
|
</Button>
|
||||||
</Button>
|
</div>
|
||||||
</div>
|
</CardContent>
|
||||||
</CardContent>
|
</Card>
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card className="max-w-2xl">
|
<Card className="mb-4 break-inside-avoid md:mb-6">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="flex items-center gap-2 text-base">
|
<CardTitle className="flex items-center gap-2 text-base">
|
||||||
<CpuIcon className="size-4" />
|
<KeyboardIcon className="size-4" />
|
||||||
工作并发 · Work Agent 数
|
会话输入框发送键位
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
每个任务并发运行的工作 agent 数量(默认 3)。数值越大并发探测越多、消耗也越高。修改后<b>对之后启动的任务生效</b>,正在运行的任务不受影响。
|
对话页与任务详情的主 Agent 会话输入框共用此设置,选择后立即生效、无需保存。
|
||||||
</CardDescription>
|
<br />
|
||||||
</CardHeader>
|
该偏好<b>只存在本浏览器</b>,不随账号同步,换浏览器或清理站点数据后需重新设置。
|
||||||
<CardContent className="flex flex-col gap-3">
|
</CardDescription>
|
||||||
<div className="flex items-center gap-2">
|
</CardHeader>
|
||||||
<Input
|
<CardContent className="flex items-center justify-between gap-4">
|
||||||
type="number"
|
<Label htmlFor="chat-send-mode" className="text-sm font-normal text-muted-foreground">
|
||||||
min={1}
|
发送方式
|
||||||
className="w-32 font-mono text-sm"
|
</Label>
|
||||||
placeholder="3"
|
<Select value={sendMode} onValueChange={(v) => setChatSendMode(v as ChatSendMode)}>
|
||||||
value={workers}
|
<SelectTrigger id="chat-send-mode" className="w-72">
|
||||||
disabled={!loaded || savingWorkers}
|
<SelectValue />
|
||||||
onChange={(e) => setWorkers(e.target.value)}
|
</SelectTrigger>
|
||||||
/>
|
<SelectContent>
|
||||||
<Button onClick={saveWorkers} disabled={!loaded || savingWorkers}>
|
{CHAT_SEND_MODE_OPTIONS.map((option) => (
|
||||||
保存
|
<SelectItem key={option.value} value={option.value}>
|
||||||
</Button>
|
{option.label}
|
||||||
</div>
|
</SelectItem>
|
||||||
</CardContent>
|
))}
|
||||||
</Card>
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
<Card className="max-w-2xl">
|
</CardContent>
|
||||||
<CardHeader>
|
</Card>
|
||||||
<CardTitle className="flex items-center gap-2 text-base">
|
</div>
|
||||||
<KeyboardIcon className="size-4" />
|
|
||||||
会话输入框发送键位
|
|
||||||
</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
对话页与任务详情的主 Agent 会话输入框共用此设置,选择后立即生效、无需保存。
|
|
||||||
<br />
|
|
||||||
该偏好<b>只存在本浏览器</b>,不随账号同步,换浏览器或清理站点数据后需重新设置。
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="flex items-center justify-between gap-4">
|
|
||||||
<Label htmlFor="chat-send-mode" className="text-sm font-normal text-muted-foreground">
|
|
||||||
发送方式
|
|
||||||
</Label>
|
|
||||||
<Select value={sendMode} onValueChange={(v) => setChatSendMode(v as ChatSendMode)}>
|
|
||||||
<SelectTrigger id="chat-send-mode" className="w-72">
|
|
||||||
<SelectValue />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{CHAT_SEND_MODE_OPTIONS.map((option) => (
|
|
||||||
<SelectItem key={option.value} value={option.value}>
|
|
||||||
{option.label}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user