feat(web): settings with 9 sidebar sections and Agent upstream management
- /console/settings#<section>[/<anchor>]; legacy ?tab= redirected in clientLoader - SETTINGS_SECTIONS in contracts with field-ownership test - Agent section: upstream list, create wizard (provider -> credentials/model -> login/test/default), edit drawer with OAuth browser/device login, test, set default, delete - sites section with site registration, snapshot/rate-limit drawer and account credentials - path mappings moved into downloader / media-server drawers; metadata and account sections Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
459991421e
commit
c67f6a9682
25 files changed
+3842
-942
No files matched your search
@@ -0,0 +1,199 @@
|
||||
import { PiUpstreamsBlock } from "./pi-upstreams.tsx";
|
||||
import type { FieldValue } from "./resource-section.tsx";
|
||||
import { num, str } from "./resource-section.tsx";
|
||||
import { type CardSpec, n, SettingsCard, SettingsGate } from "./settings-card.tsx";
|
||||
|
||||
// Agent 分区:上游(pi_upstream)+ 上下文与子代理(pi)+ 预算(budget.agent* 与单次上限)+ 自动调查 + 高级
|
||||
|
||||
/** 可清空的数字:空串 → null */
|
||||
const nullNum = (v: FieldValue | undefined) => (str(v) === "" ? null : Number(str(v)));
|
||||
|
||||
const RUNTIME_CARD: CardSpec = {
|
||||
anchor: "runtime",
|
||||
title: "上下文与子代理",
|
||||
fields: [
|
||||
{
|
||||
name: "pi.contextBudget",
|
||||
label: "上下文预算(token)",
|
||||
kind: "number",
|
||||
hint: "4096–2000000,超过模型窗口时按窗口算",
|
||||
},
|
||||
{ name: "pi.subagentsEnabled", label: "允许子代理", kind: "switch" },
|
||||
{ name: "pi.compaction.enabled", label: "上下文压缩", kind: "switch" },
|
||||
{
|
||||
name: "pi.compaction.thresholdRatio",
|
||||
label: "压缩触发比例",
|
||||
kind: "number",
|
||||
hint: "0.3–0.95,用到预算的这个比例时压缩",
|
||||
visible: (v) => v["pi.compaction.enabled"] === true,
|
||||
},
|
||||
],
|
||||
fromSettings: (s) => ({
|
||||
"pi.contextBudget": n(s.pi.contextBudget),
|
||||
"pi.subagentsEnabled": s.pi.subagentsEnabled,
|
||||
"pi.compaction.enabled": s.pi.compaction.enabled,
|
||||
"pi.compaction.thresholdRatio": n(s.pi.compaction.thresholdRatio),
|
||||
}),
|
||||
toPatch: (v) => ({
|
||||
pi: {
|
||||
contextBudget: num(v["pi.contextBudget"]),
|
||||
subagentsEnabled: v["pi.subagentsEnabled"] === true,
|
||||
compaction: {
|
||||
enabled: v["pi.compaction.enabled"] === true,
|
||||
thresholdRatio: num(v["pi.compaction.thresholdRatio"]),
|
||||
},
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
const BUDGET_CARD: CardSpec = {
|
||||
anchor: "budget",
|
||||
title: "预算",
|
||||
description: "超出每日预算的运行停在「超预算」,等你放行。",
|
||||
fields: [
|
||||
{ name: "budget.agentMaxRunsPerDay", label: "每日运行次数", kind: "number" },
|
||||
{ name: "budget.agentMaxCostUsdPerDay", label: "每日费用(USD)", kind: "number" },
|
||||
{ name: "agent.maxToolCalls", label: "单次工具调用上限", kind: "number", hint: "1–500" },
|
||||
{ name: "agent.maxDurationSec", label: "单次时长上限(秒)", kind: "number" },
|
||||
{ name: "agent.maxTokensPerRun", label: "单次 token 上限", kind: "number", hint: "留空不限" },
|
||||
{ name: "agent.maxCostUsdPerRun", label: "单次费用上限(USD)", kind: "number", hint: "留空不限" },
|
||||
],
|
||||
fromSettings: (s) => ({
|
||||
"budget.agentMaxRunsPerDay": n(s.budget.agentMaxRunsPerDay),
|
||||
"budget.agentMaxCostUsdPerDay": n(s.budget.agentMaxCostUsdPerDay),
|
||||
"agent.maxToolCalls": n(s.agent.maxToolCalls),
|
||||
"agent.maxDurationSec": n(s.agent.maxDurationSec),
|
||||
"agent.maxTokensPerRun": n(s.agent.maxTokensPerRun),
|
||||
"agent.maxCostUsdPerRun": n(s.agent.maxCostUsdPerRun),
|
||||
}),
|
||||
toPatch: (v) => ({
|
||||
budget: {
|
||||
agentMaxRunsPerDay: num(v["budget.agentMaxRunsPerDay"]),
|
||||
agentMaxCostUsdPerDay: num(v["budget.agentMaxCostUsdPerDay"]),
|
||||
},
|
||||
agent: {
|
||||
maxToolCalls: num(v["agent.maxToolCalls"]),
|
||||
maxDurationSec: num(v["agent.maxDurationSec"]),
|
||||
maxTokensPerRun: nullNum(v["agent.maxTokensPerRun"]),
|
||||
maxCostUsdPerRun: nullNum(v["agent.maxCostUsdPerRun"]),
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
const INVESTIGATE_CARD: CardSpec = {
|
||||
anchor: "investigate",
|
||||
title: "自动调查",
|
||||
description: "待处理事项(识别有歧义、下载失败等)创建时,自动发起一次有界调查。",
|
||||
fields: [
|
||||
{ name: "agent.autoInvestigate", label: "自动调查", kind: "switch", wide: true },
|
||||
{
|
||||
name: "agent.autoInvestigateMode",
|
||||
label: "模式",
|
||||
kind: "select",
|
||||
options: [
|
||||
{ value: "investigate", label: "只读调查,结论交还给你" },
|
||||
{ value: "recover", label: "授权内恢复(重试任务、重新校验、确认识别)" },
|
||||
],
|
||||
hint: "恢复只在已发布策略或持续授权覆盖时执行,换源、删除、清理始终排除;没有授权时按只读调查",
|
||||
wide: true,
|
||||
visible: (v) => v["agent.autoInvestigate"] === true,
|
||||
},
|
||||
{
|
||||
name: "agent.investigateMaxToolCalls",
|
||||
label: "工具调用上限",
|
||||
kind: "number",
|
||||
hint: "1–100",
|
||||
visible: (v) => v["agent.autoInvestigate"] === true,
|
||||
},
|
||||
{
|
||||
name: "agent.investigateMaxAttempts",
|
||||
label: "同一根因最多恢复次数",
|
||||
kind: "number",
|
||||
hint: "1–10,用完后只交还给你",
|
||||
visible: (v) => v["agent.autoInvestigate"] === true && v["agent.autoInvestigateMode"] === "recover",
|
||||
},
|
||||
{
|
||||
name: "agent.recoverVerifySec",
|
||||
label: "恢复后核实时长(秒)",
|
||||
kind: "number",
|
||||
hint: "等下载或任务状态变化的最长时间",
|
||||
visible: (v) => v["agent.autoInvestigate"] === true && v["agent.autoInvestigateMode"] === "recover",
|
||||
},
|
||||
],
|
||||
fromSettings: (s) => ({
|
||||
"agent.autoInvestigate": s.agent.autoInvestigate,
|
||||
"agent.autoInvestigateMode": s.agent.autoInvestigateMode,
|
||||
"agent.investigateMaxToolCalls": n(s.agent.investigateMaxToolCalls),
|
||||
"agent.investigateMaxAttempts": n(s.agent.investigateMaxAttempts),
|
||||
"agent.recoverVerifySec": n(s.agent.recoverVerifySec),
|
||||
}),
|
||||
toPatch: (v) => ({
|
||||
agent: {
|
||||
autoInvestigate: v["agent.autoInvestigate"] === true,
|
||||
autoInvestigateMode: v["agent.autoInvestigateMode"] === "recover" ? "recover" : "investigate",
|
||||
investigateMaxToolCalls: num(v["agent.investigateMaxToolCalls"]),
|
||||
investigateMaxAttempts: num(v["agent.investigateMaxAttempts"]),
|
||||
recoverVerifySec: num(v["agent.recoverVerifySec"]),
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
const ADVANCED_CARD: CardSpec = {
|
||||
anchor: "advanced",
|
||||
title: "高级",
|
||||
fields: [
|
||||
{ name: "agent.goalPollSec", label: "目标轮询间隔(秒)", kind: "number", hint: "等下载、入库结束时的检查间隔" },
|
||||
{
|
||||
name: "agent.goalWaitHours",
|
||||
label: "目标最长等待(小时)",
|
||||
kind: "number",
|
||||
hint: "超时后按「仍在等待」汇报结束",
|
||||
},
|
||||
{
|
||||
name: "agent.sessionDir",
|
||||
label: "会话目录",
|
||||
kind: "text",
|
||||
wide: true,
|
||||
placeholder: "留空用 DATA_DIR/agent-sessions",
|
||||
hint: "绝对路径,api 与 worker 都要可读写;有运行中的会话时不要改",
|
||||
},
|
||||
{
|
||||
name: "agent.fakeScript",
|
||||
label: "fake 脚本(JSON)",
|
||||
kind: "textarea",
|
||||
hint: "只对 fake 上游生效,留空用内置演示脚本;格式见 docs/runbook/agent.md",
|
||||
},
|
||||
],
|
||||
fromSettings: (s) => ({
|
||||
"agent.goalPollSec": n(s.agent.goalPollSec),
|
||||
"agent.goalWaitHours": n(s.agent.goalWaitHours),
|
||||
"agent.sessionDir": s.agent.sessionDir ?? "",
|
||||
"agent.fakeScript": s.agent.fakeScript ?? "",
|
||||
}),
|
||||
toPatch: (v) => ({
|
||||
agent: {
|
||||
goalPollSec: num(v["agent.goalPollSec"]),
|
||||
goalWaitHours: num(v["agent.goalWaitHours"]),
|
||||
sessionDir: str(v["agent.sessionDir"]) || null,
|
||||
fakeScript: str(v["agent.fakeScript"]) || null,
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
export function AgentSection() {
|
||||
return (
|
||||
<div className="grid gap-8">
|
||||
<PiUpstreamsBlock />
|
||||
<SettingsGate>
|
||||
{(s) => (
|
||||
<>
|
||||
<SettingsCard section="agent" spec={RUNTIME_CARD} settings={s} />
|
||||
<SettingsCard section="agent" spec={BUDGET_CARD} settings={s} />
|
||||
<SettingsCard section="agent" spec={INVESTIGATE_CARD} settings={s} />
|
||||
<SettingsCard section="agent" spec={ADVANCED_CARD} settings={s} />
|
||||
</>
|
||||
)}
|
||||
</SettingsGate>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user