copy: second-round copy review across console, settings, agent and server messages
- Trim descriptions, empty states, confirm buttons and toasts to facts - Unify terms per glossary: 待处理/待识别/整理记录/运行记录/凭证/子会话/Agent 上游 - Subscription-login termsNote states facts only (borrowed client identity, ToS/ban risk) - Shorter, concrete goal templates for built-in Agent checks - Human-readable nouns instead of table names in instance CRUD errors - Audit labels for pi_upstream / schedule / automation_grant actions - Glossary: add 上游、订阅登录、凭证、定时任务、巡检、运行记录、子会话、停止、持续授权 Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
c67f6a9682
commit
31a54186c7
58 files changed
+200
-188
No files matched your search
@@ -209,8 +209,8 @@ export function useAbortAgentRun() {
|
||||
return useMutation({
|
||||
mutationFn: async (id: string) => {
|
||||
if (agentMockEnabled()) return mockDetail(id, { abort: true }).run;
|
||||
return apiMutate(`${AGENT_BASE}/:id/abort`, { pathParams: { id }, body: { reason: "用户在控制台中止" } }).then(
|
||||
(d) => AgentRun.parse(d),
|
||||
return apiMutate(`${AGENT_BASE}/:id/abort`, { pathParams: { id }, body: { reason: "在控制台停止" } }).then((d) =>
|
||||
AgentRun.parse(d),
|
||||
);
|
||||
},
|
||||
onSuccess: () => qc.invalidateQueries({ queryKey: agentKeys.all }),
|
||||
@@ -236,7 +236,7 @@ export const RUN_STATUS_LABEL: Record<AgentRunStatus, string> = {
|
||||
waiting: "等待中",
|
||||
succeeded: "已完成",
|
||||
failed: "失败",
|
||||
cancelled: "已中止",
|
||||
cancelled: "已停止",
|
||||
budget_exceeded: "超出预算",
|
||||
};
|
||||
|
||||
|
||||
@@ -174,10 +174,10 @@ export function ToolCallCard({
|
||||
reason={authReason ?? call.reason ?? "未授权"}
|
||||
explanation={
|
||||
authReason
|
||||
? '有副作用的能力必须命中有效的持续授权(automation_grant)或单次审批(approval),由服务端在执行前核对;模型声称"用户已同意"不会被采信。需要放行时到「设置 → 持续授权」发放,或对计划申请单次审批。'
|
||||
? '有副作用的能力需要有效的持续授权或单次审批,由服务端在执行前核对,模型声称"用户已同意"无效。发布收录策略会生成持续授权,也可以为计划申请单次审批。'
|
||||
: mode === "plan_only" && writeKind
|
||||
? "本次是「只要方案」:白名单只放行只读、预览、提案类能力,执行类调用一律拒绝。要真正执行,请按方案以执行模式重新发起。"
|
||||
: "这个能力不在本次运行的能力白名单内(或已超出调用预算),调用在到达处理器前就被拦下。Agent 无法绕过。"
|
||||
? "本次是「只要方案」,只放行只读、预览、提案类能力。要执行,点「按方案执行」。"
|
||||
: "这个能力不在本次运行的白名单内,或已超出调用预算,调用没有执行。"
|
||||
}
|
||||
parameters={[
|
||||
{ id: "call", label: "调用 ID", value: call.toolCallId },
|
||||
@@ -219,9 +219,7 @@ export function ToolCallCard({
|
||||
<dt className="text-muted-foreground">幂等键</dt>
|
||||
<dd className="min-w-0 font-mono break-all text-foreground/80">
|
||||
{audit?.idempotencyKey ??
|
||||
(meta && meta.kind !== "read"
|
||||
? "由服务端生成(能力:版本:运行:输入哈希),审计未取到"
|
||||
: "只读能力不记操作账本")}
|
||||
(meta && meta.kind !== "read" ? "服务端已生成,审计里没有取到" : "只读能力不记操作账本")}
|
||||
</dd>
|
||||
{audit?.grantId || audit?.approvalId ? (
|
||||
<>
|
||||
@@ -298,13 +296,13 @@ function EndNotice({ item }: { item: Extract<StreamItem, { kind: "end" }> }) {
|
||||
{budget
|
||||
? `超出预算${item.budget ? `(${BUDGET_LABEL[item.budget] ?? item.budget})` : ""},已停止`
|
||||
: aborted
|
||||
? "已中止"
|
||||
? "已停止"
|
||||
: "运行失败"}
|
||||
</p>
|
||||
{item.message ? <p className="mt-0.5 text-caption break-words">{item.message}</p> : null}
|
||||
{budget ? (
|
||||
<p className="mt-1 text-meta text-muted-foreground">
|
||||
已完成的外部动作不会撤销;可调大预算后追加消息续跑。
|
||||
已完成的外部动作不会撤销。调大预算后追加消息可继续运行。
|
||||
</p>
|
||||
) : null}
|
||||
</MessageBubbleContent>
|
||||
@@ -636,7 +634,7 @@ export function ApprovalItem({ approval: a }: { approval: Approval }) {
|
||||
<ApprovalCard
|
||||
status={status}
|
||||
title={`批准执行这份${PLAN_LABEL[a.planType] ?? a.planType}?`}
|
||||
description={a.reason ?? "审批绑定计划 ID、payload hash 与版本号;计划变化后旧审批自动失效。"}
|
||||
description={a.reason ?? "计划变化后,这份审批自动失效。"}
|
||||
badges={
|
||||
<>
|
||||
{risk ? <RiskBadge level={risk} /> : null}
|
||||
@@ -645,7 +643,7 @@ export function ApprovalItem({ approval: a }: { approval: Approval }) {
|
||||
}
|
||||
onApprove={() => act("approved")}
|
||||
onReject={() => act("rejected")}
|
||||
disabledReason={admin ? undefined : "只有控制台管理员会话能批准或拒绝"}
|
||||
disabledReason={admin ? undefined : "只有管理员能批准或拒绝"}
|
||||
result={
|
||||
a.decidedAt
|
||||
? `${a.status === "approved" ? "批准" : a.status === "rejected" ? "拒绝" : "处理"}于 ${formatDateTime(a.decidedAt)}`
|
||||
@@ -736,7 +734,7 @@ export function FinalReport({
|
||||
<StreamingResponse status="complete" copyText={said || undefined} contentClassName="max-h-60 overflow-auto">
|
||||
{said || "(没有文字汇报)"}
|
||||
</StreamingResponse>
|
||||
<p className="mt-2 text-meta text-muted-foreground">模型的自述,仅供参考,不作为完成依据。</p>
|
||||
<p className="mt-2 text-meta text-muted-foreground">仅供参考,不作为完成依据。</p>
|
||||
</div>
|
||||
<div className="min-w-0 rounded-xl border border-border/70 bg-foreground/[0.02] p-3">
|
||||
<p className="mb-1.5 flex items-center gap-1.5 text-label font-semibold tracking-[0.18em] text-muted-foreground uppercase">
|
||||
@@ -798,12 +796,12 @@ export function FinalReport({
|
||||
/>
|
||||
) : null}
|
||||
<p className="mt-2 text-meta text-muted-foreground">
|
||||
汇总自下载计划、下载任务与整理记录(生成于 {formatDateTime(report.generatedAt)}
|
||||
);"已提交"只代表进入下载器,整理成功才算入库。
|
||||
取自下载计划、下载任务和整理记录(生成于 {formatDateTime(report.generatedAt)}
|
||||
)。"已提交"指已进入下载器,整理成功才算入库。
|
||||
</p>
|
||||
</>
|
||||
) : (
|
||||
<p className="mt-2 text-meta text-muted-foreground">服务端汇报尚未生成;以任务页、整理记录的实际状态为准。</p>
|
||||
<p className="mt-2 text-meta text-muted-foreground">服务端汇报还没有生成,以任务和整理记录的实际状态为准。</p>
|
||||
)}
|
||||
{run.error ? (
|
||||
<p className="mt-2 text-caption break-words text-destructive">
|
||||
@@ -833,7 +831,7 @@ export function RunAnnouncer({ status, finished }: { status: AgentRunStatus; fin
|
||||
prev.current = status;
|
||||
}, [status]);
|
||||
useEffect(() => {
|
||||
if (finished) setMsg("回复完成,最终汇报已生成");
|
||||
if (finished) setMsg("最终汇报已生成");
|
||||
}, [finished]);
|
||||
return (
|
||||
<span className="sr-only" role="status" aria-live="polite">
|
||||
|
||||
@@ -93,7 +93,7 @@ export function AgentDrawer() {
|
||||
Agent
|
||||
</SheetTitle>
|
||||
<SheetDescription className="break-words">
|
||||
{context?.label ? `当前对象:${context.label}` : "交代目标,Agent 在授权范围内边执行边汇报。"}
|
||||
{context?.label ? `当前对象:${context.label}` : "交代目标,Agent 在授权范围内执行并汇报。"}
|
||||
<span className="ml-1 hidden text-muted-foreground/75 sm:inline">
|
||||
({agentShortcutLabel()} 开关,Esc 关闭)
|
||||
</span>
|
||||
|
||||
@@ -37,16 +37,16 @@ import {
|
||||
import { SubagentTree } from "./agent-subagent-tree.tsx";
|
||||
import { useAgentStream } from "./use-agent-stream.ts";
|
||||
|
||||
const TRIGGER_LABEL = { user: "用户发起", event: "事件触发", schedule: "定时", subagent: "子代理" } as const;
|
||||
const TRIGGER_LABEL = { user: "用户发起", event: "事件触发", schedule: "定时", subagent: "子会话" } as const;
|
||||
|
||||
export const MODE_HELP: Record<AgentMode, { label: string; help: string }> = {
|
||||
execute: {
|
||||
label: "授权内执行",
|
||||
help: "在已授权范围内执行;未授权的动作会被服务端拒绝并在对话里标出。",
|
||||
help: "未授权的动作会被服务端拒绝并在对话里标出。",
|
||||
},
|
||||
plan_only: {
|
||||
label: "只要方案",
|
||||
help: "只调查、出方案,不执行任何写操作;满意后可一键按方案执行。",
|
||||
help: "只调查、出方案,不执行写操作。之后可按方案执行。",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -134,7 +134,7 @@ export function GoalComposer({
|
||||
<div className="pt-2">
|
||||
<p className="mb-1.5 flex items-center gap-1.5 px-1 text-meta text-muted-foreground">
|
||||
<Sparkles aria-hidden className="size-3.5" />
|
||||
试试这些
|
||||
常用目标
|
||||
</p>
|
||||
<ul aria-label="快捷目标" className="flex flex-col gap-1.5">
|
||||
{suggestions.map((s) => (
|
||||
@@ -267,9 +267,7 @@ export function AgentRunPanel({
|
||||
className="flex items-center gap-2 rounded-xl border border-warning/40 bg-warning/10 px-3 py-2 text-caption text-warning"
|
||||
>
|
||||
<WifiOff aria-hidden className="size-4 shrink-0" />
|
||||
<span className="min-w-0 flex-1">
|
||||
实时连接中断,正在重连;恢复后按 after= 补读漏掉的事件,期间每 2.5 秒轮询兜底。
|
||||
</span>
|
||||
<span className="min-w-0 flex-1">实时连接中断,正在重连。期间每 2.5 秒轮询一次。</span>
|
||||
</div>
|
||||
) : null}
|
||||
{live.error ? (
|
||||
@@ -320,7 +318,7 @@ export function AgentRunPanel({
|
||||
maxRows={6}
|
||||
maxLength={8000}
|
||||
aria-label="追加消息"
|
||||
placeholder={streaming ? "补充或纠正(运行中也可追加)" : "追加消息,继续这次会话"}
|
||||
placeholder={streaming ? "补充或纠正(运行中也可追加)" : "追加消息"}
|
||||
toolbar={
|
||||
mode === "plan_only" && finished ? (
|
||||
<Button type="button" size="sm" variant="outline" onClick={executePlan} disabled={create.isPending}>
|
||||
@@ -334,7 +332,7 @@ export function AgentRunPanel({
|
||||
/>
|
||||
<p className="px-1 text-meta text-muted-foreground">
|
||||
{mode === "plan_only" && finished
|
||||
? "「按方案执行」会以授权内执行模式新开一次运行,仍受服务端授权校验。"
|
||||
? "「按方案执行」会新开一次授权内执行的运行,仍受服务端授权校验。"
|
||||
: MODE_HELP[mode].help}{" "}
|
||||
Enter 发送,Shift+Enter 换行{streaming ? ",停止 = 中止本次运行" : ""}。
|
||||
</p>
|
||||
@@ -344,12 +342,12 @@ export function AgentRunPanel({
|
||||
open={confirmStop}
|
||||
onOpenChange={setConfirmStop}
|
||||
title="停止这次运行?"
|
||||
description="停止后续步骤;已经完成的外部动作(如已提交的下载)不会被撤销。"
|
||||
confirmLabel="停止"
|
||||
description="后续步骤不再执行,已完成的外部动作(如已提交的下载)不会撤销。"
|
||||
confirmLabel="停止运行"
|
||||
destructive
|
||||
onConfirm={() =>
|
||||
abort.mutateAsync(r.id).then(
|
||||
() => toast.success("已请求中止"),
|
||||
() => toast.success("已请求停止"),
|
||||
(e) => {
|
||||
toast.error(errorMessage(e));
|
||||
throw e;
|
||||
|
||||
@@ -75,7 +75,7 @@ export function AgentSessionList({
|
||||
plain
|
||||
icon={Bot}
|
||||
title={status ? "没有这个状态的会话" : "还没有会话"}
|
||||
description={status ? "换个筛选看看。" : "交代一个目标开始;也可以在任意页面右上角打开 Agent 侧栏。"}
|
||||
description={status ? undefined : "交代一个目标开始。任意页面都能从右下角打开 Agent。"}
|
||||
/>
|
||||
) : (
|
||||
<ul className="space-y-1">
|
||||
|
||||
@@ -26,7 +26,7 @@ function budgetText(b: Record<string, number | undefined>) {
|
||||
if (b.maxTokens !== undefined) parts.push(`${tokens(b.maxTokens)} token`);
|
||||
if (b.maxDurationSec !== undefined) parts.push(`${b.maxDurationSec}s`);
|
||||
if (b.maxCostUsd !== undefined) parts.push(`$${b.maxCostUsd}`);
|
||||
return parts.length ? parts.join(" · ") : "按设置缺省";
|
||||
return parts.length ? parts.join(" · ") : "沿用设置";
|
||||
}
|
||||
|
||||
function Node({ node, byParent, depth }: { node: AgentRunNode; byParent: Map<string, AgentRunNode[]>; depth: number }) {
|
||||
|
||||
@@ -42,7 +42,7 @@ const RULES: Rule[] = [
|
||||
},
|
||||
{
|
||||
match: /^\/console\/identity/,
|
||||
goals: ["帮我识别待识别池里的文件", "找出外部身份映射可能错误的作品", "补全缺少元数据的作品"],
|
||||
goals: ["识别待识别的文件", "找出外部身份可能错误的作品", "补全缺少元数据的作品"],
|
||||
},
|
||||
{
|
||||
match: /^\/console\/subscriptions/,
|
||||
@@ -50,7 +50,7 @@ const RULES: Rule[] = [
|
||||
},
|
||||
{
|
||||
match: /^\/console\/sources/,
|
||||
goals: ["哪些来源不新鲜了,原因是什么", "检查所有 RSS 的抓取状态", "最近一天各来源抓到了什么"],
|
||||
goals: ["哪些来源不新鲜了,原因是什么", "检查所有 Feed 的抓取状态", "最近一天各来源抓到了什么"],
|
||||
},
|
||||
{
|
||||
match: /^\/console\/engine/,
|
||||
|
||||
@@ -104,7 +104,7 @@ export function StreamingResponse({
|
||||
{actions ? (
|
||||
<div className="mt-1 flex items-center gap-0.5 animate-in fade-in-0 duration-base motion-reduce:animate-none">
|
||||
{copyText ? (
|
||||
<ActionButton label={copied ? "已复制" : "复制这段回复"} onClick={copy}>
|
||||
<ActionButton label={copied ? "已复制" : "复制回复"} onClick={copy}>
|
||||
{copied ? <Check className="size-3.5" /> : <Copy className="size-3.5" />}
|
||||
</ActionButton>
|
||||
) : null}
|
||||
|
||||
@@ -154,7 +154,7 @@ export function AgentLauncher() {
|
||||
</kbd>
|
||||
</span>
|
||||
<span className="max-w-56 truncate text-meta leading-4 text-muted-foreground">
|
||||
{open ? "Esc 也可关闭" : (summary ?? "交代目标,边做边汇报")}
|
||||
{open ? "Esc 关闭" : (summary ?? "交代目标,边做边汇报")}
|
||||
</span>
|
||||
</span>
|
||||
</button>,
|
||||
@@ -207,7 +207,7 @@ export function AgentNavEntry({ collapsed = false, onNavigate }: { collapsed?: b
|
||||
{count ? <PulseDot /> : null}
|
||||
</span>
|
||||
<span className="truncate text-meta text-muted-foreground">
|
||||
{count ? `${count} 个会话进行中` : "交代目标,随时唤出"}
|
||||
{count ? `${count} 个会话进行中` : "交代目标,边做边汇报"}
|
||||
</span>
|
||||
</span>
|
||||
{count ? (
|
||||
@@ -225,8 +225,8 @@ export function AgentNavEntry({ collapsed = false, onNavigate }: { collapsed?: b
|
||||
export function AgentShortcutsHelp() {
|
||||
const shortcut = agentShortcutLabel();
|
||||
const rows: [string, string][] = [
|
||||
[shortcut, "任意页面打开 / 收起 Agent 抽屉(带当前页面上下文)"],
|
||||
["Esc", "关闭 Agent 抽屉"],
|
||||
[shortcut, "打开 / 收起 Agent,附带当前页面"],
|
||||
["Esc", "关闭 Agent"],
|
||||
["Enter", "发送目标;Shift+Enter 换行"],
|
||||
];
|
||||
return (
|
||||
|
||||
@@ -174,7 +174,7 @@ export const NAV_GROUPS: NavGroup[] = [
|
||||
to: "/console/schedules",
|
||||
label: "定时任务",
|
||||
icon: Timer,
|
||||
description: "周期任务与 Agent 巡检的开关、间隔、立即执行与历史",
|
||||
description: "周期任务与 Agent 巡检的开关、间隔和运行记录",
|
||||
},
|
||||
{
|
||||
to: "/console/engine",
|
||||
|
||||
@@ -142,7 +142,7 @@ export function AgentCheckDialog({
|
||||
<DialogContent className="max-h-[90vh] overflow-y-auto sm:max-w-xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{job ? "编辑巡检" : "新建巡检"}</DialogTitle>
|
||||
<DialogDescription>到点按目标入队一次 Agent 会话,只在白名单与预算内行动。</DialogDescription>
|
||||
<DialogDescription>到点按目标发起一次 Agent 会话,只在白名单和预算内执行。</DialogDescription>
|
||||
</DialogHeader>
|
||||
<form
|
||||
className="grid gap-1"
|
||||
@@ -171,7 +171,7 @@ export function AgentCheckDialog({
|
||||
onChange={(e) => set("goal", e.target.value)}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField label="能力白名单" hint="不选即只读类全部">
|
||||
<FormField label="能力白名单" hint="不选则放行全部只读能力">
|
||||
<div className="grid max-h-48 gap-1 overflow-y-auto rounded-tight border border-border/70 px-2.5 py-2 sm:grid-cols-2">
|
||||
{READONLY_CAPS.map((c) => {
|
||||
const on = d.caps.includes(c.id);
|
||||
@@ -235,7 +235,7 @@ export function AgentCheckDialog({
|
||||
label="间隔"
|
||||
htmlFor="ac-every"
|
||||
error={errors.every}
|
||||
hint={cronJob ? "cron 调度请在卡片上修改" : undefined}
|
||||
hint={cronJob ? "cron 表达式在卡片上修改" : undefined}
|
||||
>
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
|
||||
@@ -33,7 +33,7 @@ export const TRIGGER_LABEL: Record<ScheduleTrigger, string> = {
|
||||
};
|
||||
|
||||
export const CHECK_TRIGGER_LABEL: Record<AgentCheckTrigger, string> = {
|
||||
always: "每次都跑",
|
||||
always: "每次运行",
|
||||
open_decisions: "有待处理时",
|
||||
stalled_downloads: "有下载无进展时",
|
||||
subscriptions_active: "有追踪订阅时",
|
||||
|
||||
@@ -17,7 +17,7 @@ export function RunsSheet({ job, onClose }: { job: ScheduledJob | null; onClose:
|
||||
<DetailSheet
|
||||
open={!!job}
|
||||
onOpenChange={(o) => !o && onClose()}
|
||||
title={job ? `${job.name} · 历史` : "历史"}
|
||||
title={job ? `${job.name} · 运行记录` : "运行记录"}
|
||||
description={
|
||||
job ? `${describeSchedule(job.schedule)} · 累计 ${job.runCount} 次,失败 ${job.failCount} 次` : undefined
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ function AgentCheckSummary({ config }: { config: Partial<AgentCheckConfig> }) {
|
||||
预算:{budget.maxToolCalls ? `${budget.maxToolCalls} 次调用` : "默认"}
|
||||
{budget.maxDurationSec ? ` · ${budget.maxDurationSec} 秒` : ""}
|
||||
</span>
|
||||
<span>能力:{allow.length ? `${allow.length} 项` : "只读全部"}</span>
|
||||
<span>能力:{allow.length ? `${allow.length} 项` : "全部只读能力"}</span>
|
||||
{config.allowRecover ? <span>允许授权内恢复</span> : null}
|
||||
{config.reportAsDecision ? <span>写入待处理</span> : null}
|
||||
</div>
|
||||
@@ -156,7 +156,7 @@ export function ScheduleCard({
|
||||
</Button>
|
||||
<Button size="sm" variant="ghost" onClick={() => onHistory(job)}>
|
||||
<History />
|
||||
查看历史
|
||||
运行记录
|
||||
</Button>
|
||||
<span className="ml-auto flex items-center gap-1">
|
||||
{job.customized ? (
|
||||
@@ -184,8 +184,8 @@ export function ScheduleCard({
|
||||
</Button>
|
||||
}
|
||||
title={`删除「${job.name}」?`}
|
||||
description="运行历史一并删除,不影响已产生的 Agent 会话。"
|
||||
confirmLabel="删除"
|
||||
description="运行记录一并删除,已产生的 Agent 会话保留。"
|
||||
confirmLabel="删除巡检"
|
||||
destructive
|
||||
onConfirm={() =>
|
||||
del.mutateAsync({ id: job.id }).then(
|
||||
|
||||
@@ -10,7 +10,7 @@ const nullNum = (v: FieldValue | undefined) => (str(v) === "" ? null : Number(st
|
||||
|
||||
const RUNTIME_CARD: CardSpec = {
|
||||
anchor: "runtime",
|
||||
title: "上下文与子代理",
|
||||
title: "上下文与子会话",
|
||||
fields: [
|
||||
{
|
||||
name: "pi.contextBudget",
|
||||
@@ -18,7 +18,7 @@ const RUNTIME_CARD: CardSpec = {
|
||||
kind: "number",
|
||||
hint: "4096–2000000,超过模型窗口时按窗口算",
|
||||
},
|
||||
{ name: "pi.subagentsEnabled", label: "允许子代理", kind: "switch" },
|
||||
{ name: "pi.subagentsEnabled", label: "允许子会话", kind: "switch" },
|
||||
{ name: "pi.compaction.enabled", label: "上下文压缩", kind: "switch" },
|
||||
{
|
||||
name: "pi.compaction.thresholdRatio",
|
||||
@@ -49,7 +49,7 @@ const RUNTIME_CARD: CardSpec = {
|
||||
const BUDGET_CARD: CardSpec = {
|
||||
anchor: "budget",
|
||||
title: "预算",
|
||||
description: "超出每日预算的运行停在「超预算」,等你放行。",
|
||||
description: "超出每日预算的运行停在「超出预算」,等你放行。",
|
||||
fields: [
|
||||
{ name: "budget.agentMaxRunsPerDay", label: "每日运行次数", kind: "number" },
|
||||
{ name: "budget.agentMaxCostUsdPerDay", label: "每日费用(USD)", kind: "number" },
|
||||
|
||||
@@ -105,7 +105,7 @@ export function ApiKeysSection() {
|
||||
section="api-keys"
|
||||
anchor="keys"
|
||||
title="API Key"
|
||||
description="签发给外部程序(媒体服务器插件、脚本)的 Key,按权限范围授权。控制台走登录会话,不用 Key;浏览器本机 Token 在「账户与安全」。"
|
||||
description="签发给外部程序(媒体服务器插件、脚本)的 Key,按权限范围授权。本机 Token 在「账户与安全」。"
|
||||
plain
|
||||
actions={
|
||||
<Button size="sm" onClick={() => setOpen(true)}>
|
||||
@@ -227,7 +227,7 @@ export function ApiKeysSection() {
|
||||
onOpenChange={(o) => !o && setRevoking(null)}
|
||||
title={`撤销「${revoking?.name ?? ""}」?`}
|
||||
description="使用这个 Key 的程序立即失去访问权限,无法恢复。"
|
||||
confirmLabel="撤销"
|
||||
confirmLabel="撤销 Key"
|
||||
destructive
|
||||
onConfirm={async () => {
|
||||
if (!revoking) return;
|
||||
|
||||
@@ -162,7 +162,7 @@ function Switches() {
|
||||
description="从备份恢复后先开启:只核对库存与路径,不做自动写入。核对完再关闭。"
|
||||
checked={s.automation.readOnlyReconcile}
|
||||
disabled={update.isPending}
|
||||
onChange={(v) => set({ readOnlyReconcile: v }, v ? "已进入只读对账" : "已退出只读对账")}
|
||||
onChange={(v) => set({ readOnlyReconcile: v }, v ? "已开启只读对账" : "已关闭只读对账")}
|
||||
/>
|
||||
</div>
|
||||
</SettingsBlock>
|
||||
|
||||
@@ -87,7 +87,7 @@ function DownloadersTable() {
|
||||
config={{
|
||||
noun: "下载器实例",
|
||||
description:
|
||||
"只接管带管理标签的任务。多个实例共用一块缓存盘时填相同的缓存卷标识,水位合并计算。点行编辑实例和它的路径映射。",
|
||||
"只接管带管理标签的任务。多个实例共用一块缓存盘时填相同的缓存卷标识,水位合并计算。点行编辑实例和路径映射。",
|
||||
emptyHint: "添加 qBittorrent 后才能提交下载。",
|
||||
drawerExtra: (r) => <PathMappingsPanel viewKind="downloader" instanceId={r.id} />,
|
||||
columns: [
|
||||
@@ -181,7 +181,7 @@ function MediaServersTable() {
|
||||
hooks={{ list, create, update, remove } as never}
|
||||
config={{
|
||||
noun: "媒体服务器",
|
||||
description: "只用来查询作品有无、季集和版本;查不到不等于缺失。点行编辑实例和它的路径映射。",
|
||||
description: "只用来查询作品有无、季集和版本;查不到不等于缺失。点行编辑实例和路径映射。",
|
||||
drawerExtra: (r) => <PathMappingsPanel viewKind="media_server" instanceId={r.id} />,
|
||||
columns: [
|
||||
{
|
||||
|
||||
@@ -11,7 +11,7 @@ import { type CardSpec, SettingsCard, SettingsGate } from "./settings-card.tsx";
|
||||
const TMDB_CARD: CardSpec = {
|
||||
anchor: "tmdb",
|
||||
title: "TMDB",
|
||||
description: "这里没填时使用环境变量 TMDB_API_KEY / TMDB_ACCESS_TOKEN / TMDB_BASE_URL。",
|
||||
description: "留空时用环境变量 TMDB_API_KEY / TMDB_ACCESS_TOKEN / TMDB_BASE_URL。",
|
||||
fields: [
|
||||
{
|
||||
name: "metadata.tmdbApiKey",
|
||||
|
||||
@@ -211,7 +211,7 @@ export function PathMappingsPanel({
|
||||
onOpenChange={(o) => !o && setDeleting(null)}
|
||||
title="删除路径映射?"
|
||||
description="依赖这条映射的路径换算会失效,磁盘文件不动。"
|
||||
confirmLabel="删除"
|
||||
confirmLabel="删除映射"
|
||||
destructive
|
||||
onConfirm={async () => {
|
||||
if (!deleting) return;
|
||||
|
||||
@@ -102,7 +102,7 @@ export function PiOAuthLogin({
|
||||
abortRef.current = ac;
|
||||
while (!ac.signal.aborted) {
|
||||
if (Date.now() > new Date(s.expiresAt).getTime()) {
|
||||
finish("expired", "超过有效期仍未完成授权。");
|
||||
finish("expired", "授权已超时。");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@@ -256,7 +256,7 @@ export function PiOAuthLogin({
|
||||
) : (
|
||||
<form onSubmit={submitPasted} noValidate className="grid gap-2 rounded-lg border border-border/60 p-3">
|
||||
<p className="text-caption text-muted-foreground">
|
||||
在自己的浏览器打开授权链接。授权后会跳到 localhost 地址,页面打不开是正常的,把地址栏里的完整 URL 粘到下面。
|
||||
在浏览器打开授权链接。授权后会跳到 localhost 地址,页面打不开属正常,把地址栏里的完整 URL 粘贴到下面。
|
||||
</p>
|
||||
{session.authorizeUrl ? (
|
||||
<div className="flex min-w-0 items-center gap-2">
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Input } from "~/components/ui/input.tsx";
|
||||
export const GROUP_LABEL: Record<PiProviderGroup, string> = {
|
||||
api_key: "API Key",
|
||||
subscription: "订阅登录",
|
||||
cloud: "云凭据",
|
||||
cloud: "云凭证",
|
||||
custom: "兼容端点",
|
||||
test: "测试",
|
||||
};
|
||||
@@ -16,7 +16,7 @@ export const GROUP_ORDER: PiProviderGroup[] = ["api_key", "subscription", "cloud
|
||||
export const AUTH_LABEL: Record<PiAuthKind, string> = {
|
||||
api_key: "API Key",
|
||||
oauth: "订阅登录",
|
||||
none: "不需要凭据",
|
||||
none: "不需要凭证",
|
||||
};
|
||||
|
||||
export const THINKING_LABEL: Record<PiThinkingLevel, string> = {
|
||||
|
||||
@@ -10,7 +10,7 @@ import { errorMessage } from "~/lib/api.ts";
|
||||
// 上游测试:默认只验凭据与模型(不产生费用);"发一次最小补全"真实调用一次模型
|
||||
|
||||
const ERROR_KIND: Record<string, string> = {
|
||||
auth: "凭据被拒",
|
||||
auth: "凭证被拒",
|
||||
oauth: "登录失效",
|
||||
model: "模型不可用",
|
||||
network: "网络错误",
|
||||
@@ -28,7 +28,7 @@ export function PiTestPanel({ upstream, model }: { upstream: PiUpstream; model?:
|
||||
try {
|
||||
const r = await test.mutateAsync({ id: upstream.id, body: { mode, ...(model ? { model } : {}) } });
|
||||
setResult(r);
|
||||
if (r.ok) toast.success(mode === "auth" ? "凭据可用" : "补全成功");
|
||||
if (r.ok) toast.success(mode === "auth" ? "凭证可用" : "补全成功");
|
||||
else toast.error(r.message ?? "测试失败");
|
||||
} catch (err) {
|
||||
setResult(null);
|
||||
@@ -51,7 +51,7 @@ export function PiTestPanel({ upstream, model }: { upstream: PiUpstream; model?:
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-meta text-muted-foreground">
|
||||
「测试」只检查凭据和模型,不产生费用;最小补全会真实调用一次模型(最多 16 个 token)。
|
||||
「测试」只检查凭证和模型,不产生费用。最小补全会调用一次模型(最多 16 个 token)。
|
||||
</p>
|
||||
{result ? (
|
||||
<div className="flex flex-wrap items-center gap-2 text-caption">
|
||||
|
||||
@@ -51,7 +51,7 @@ export function PiUpstreamsBlock() {
|
||||
section="agent"
|
||||
anchor="upstream"
|
||||
title="上游"
|
||||
description="Agent 调用的模型上游。没指定上游的运行用默认上游;密钥和登录凭据只写入,不回显。"
|
||||
description="没指定上游的运行用默认上游。密钥和登录凭证保存后不回显。"
|
||||
plain
|
||||
actions={
|
||||
<Button size="sm" onClick={() => setWizard(true)}>
|
||||
@@ -291,7 +291,7 @@ function UpstreamDrawer({
|
||||
disabled={pending}
|
||||
/>
|
||||
<FieldControl
|
||||
spec={{ name: "enabled", label: "启用", kind: "switch", hint: "停用后用它的运行直接失败" }}
|
||||
spec={{ name: "enabled", label: "启用", kind: "switch", hint: "停用后,使用它的运行会失败" }}
|
||||
value={v.enabled}
|
||||
onChange={(x) => setV((s) => ({ ...s, enabled: x === true }))}
|
||||
disabled={pending}
|
||||
@@ -377,7 +377,7 @@ function UpstreamDrawer({
|
||||
onOpenChange={setDeleting}
|
||||
title={`删除上游「${upstream.name}」?`}
|
||||
description={
|
||||
upstream.isDefault ? "它是默认上游,删除后没有默认上游;凭据一并删除,无法恢复。" : "凭据一并删除,无法恢复。"
|
||||
upstream.isDefault ? "它是默认上游,删除后没有默认上游;凭证一并删除,无法恢复。" : "凭证一并删除,无法恢复。"
|
||||
}
|
||||
confirmLabel="删除上游"
|
||||
destructive
|
||||
|
||||
@@ -22,7 +22,7 @@ import { API_OPTIONS, AUTH_LABEL, GROUP_LABEL, GROUP_ORDER, ModelInput, THINKING
|
||||
import { PiTestPanel } from "./pi-test.tsx";
|
||||
import { FieldControl } from "./resource-section.tsx";
|
||||
|
||||
// 新建 Agent 上游:① 选 provider → ② 填凭据与模型并创建 → ③ 登录 / 测试,可设为默认
|
||||
// 新建 Agent 上游:① 选 provider → ② 填凭证与模型并创建 → ③ 登录 / 测试,可设为默认
|
||||
|
||||
type Step = "provider" | "config" | "verify";
|
||||
|
||||
@@ -51,7 +51,7 @@ export function PiUpstreamWizard({ open, onClose }: { open: boolean; onClose: ()
|
||||
step === "provider"
|
||||
? "第 1 步,共 3 步:选 provider"
|
||||
: step === "config"
|
||||
? "第 2 步,共 3 步:凭据与模型"
|
||||
? "第 2 步,共 3 步:凭证与模型"
|
||||
: created?.authKind === "oauth"
|
||||
? "第 3 步,共 3 步:登录并测试。上游已创建。"
|
||||
: "第 3 步,共 3 步:测试。上游已创建。";
|
||||
@@ -246,7 +246,7 @@ function ConfigStep({
|
||||
) : null}
|
||||
{authKind === "none" && provider.group === "cloud" ? (
|
||||
<p className="mb-3 text-caption text-muted-foreground sm:col-span-2">
|
||||
不存密钥,运行时读服务端进程的环境凭据(AWS 凭据链 / Google ADC)。
|
||||
不存密钥,运行时读服务端进程的环境凭证(AWS 凭证链 / Google ADC)。
|
||||
</p>
|
||||
) : null}
|
||||
{showBaseUrl ? (
|
||||
|
||||
@@ -145,7 +145,7 @@ export function ResourceSection<Row extends { id: string }>({
|
||||
onOpenChange={(o) => !o && setDeleting(null)}
|
||||
title={`删除${config.noun}?`}
|
||||
description={config.deleteHint ?? "删除后无法恢复。"}
|
||||
confirmLabel="删除"
|
||||
confirmLabel={`删除${config.noun}`}
|
||||
destructive
|
||||
onConfirm={async () => {
|
||||
if (!deleting) return;
|
||||
|
||||
@@ -598,7 +598,7 @@ function AccountsBlock() {
|
||||
onOpenChange={(o) => !o && setDeleting(null)}
|
||||
title={`删除账号「${deleting?.label ?? ""}」?`}
|
||||
description="账号和凭证一并删除,无法恢复。"
|
||||
confirmLabel="删除"
|
||||
confirmLabel="删除账号"
|
||||
destructive
|
||||
onConfirm={async () => {
|
||||
if (!deleting) return;
|
||||
|
||||
@@ -72,13 +72,13 @@ const ORG_ACTION: Record<string, string> = {
|
||||
export const organizationActionLabel = (v: string) => ORG_ACTION[v] ?? v;
|
||||
|
||||
const AGENT_RUN: Record<string, Labeled> = {
|
||||
queued: { label: "排队", tone: "neutral" },
|
||||
running: { label: "执行中", tone: "neutral" },
|
||||
waiting: { label: "等待", tone: "warning" },
|
||||
succeeded: { label: "完成", tone: "success" },
|
||||
queued: { label: "排队中", tone: "neutral" },
|
||||
running: { label: "运行中", tone: "neutral" },
|
||||
waiting: { label: "等待中", tone: "warning" },
|
||||
succeeded: { label: "已完成", tone: "success" },
|
||||
failed: { label: "失败", tone: "destructive" },
|
||||
cancelled: { label: "已取消", tone: "neutral" },
|
||||
budget_exceeded: { label: "超预算", tone: "warning" },
|
||||
cancelled: { label: "已停止", tone: "neutral" },
|
||||
budget_exceeded: { label: "超出预算", tone: "warning" },
|
||||
};
|
||||
export const agentRunLabel = (v: string | null | undefined) => pick(AGENT_RUN, v);
|
||||
|
||||
|
||||
@@ -18,14 +18,11 @@ export default function AgentPage() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHeader
|
||||
title="Agent 会话"
|
||||
description="交代目标,Agent 在授权范围内边执行边汇报;只要方案时,它只调查不执行。"
|
||||
/>
|
||||
<PageHeader title="Agent 会话" description="交代目标,Agent 在授权范围内执行并汇报。只要方案时只调查,不执行。" />
|
||||
{mock ? (
|
||||
<div className="mb-4 flex flex-wrap items-center gap-3 rounded-xl border border-warning/40 bg-warning/10 px-4 py-3 text-caption text-warning">
|
||||
<FlaskConical className="h-4 w-4 shrink-0" />
|
||||
<span className="min-w-0 flex-1">演示模式:会话是本地假数据,不连后端,不会产生任何动作。</span>
|
||||
<span className="min-w-0 flex-1">演示模式:会话是本地假数据,不连接后端,不执行任何操作。</span>
|
||||
<Button variant="outline" size="sm" onClick={() => window.location.assign("/console/agent?mock=0")}>
|
||||
退出演示
|
||||
</Button>
|
||||
@@ -54,8 +51,8 @@ export default function AgentPage() {
|
||||
<div className="flex gap-3 rounded-xl border border-border/70 p-4 text-caption text-muted-foreground">
|
||||
<ShieldCheck aria-hidden className="mt-0.5 size-4 shrink-0" />
|
||||
<div className="space-y-1">
|
||||
<p>每一次工具调用都经服务端授权校验:不在白名单或没有授权覆盖的调用会被拒绝,并在对话里醒目标出。</p>
|
||||
<p>运行结束后,汇报分两栏:模型的自述只作参考,"系统核实的结果"取自下载计划、下载任务与整理记录。</p>
|
||||
<p>每次工具调用都经服务端授权校验,不在白名单或没有授权的调用会被拒绝并在对话里标出。</p>
|
||||
<p>汇报分两栏:模型自述仅供参考,"系统核实的结果"取自下载计划、下载任务和整理记录。</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -37,6 +37,12 @@ const ACTION_LABEL: Record<string, string> = {
|
||||
"secret.delete": "删除凭证",
|
||||
"site_account.credentials_set": "录入站点凭证",
|
||||
"engine.run_cancel": "取消引擎运行",
|
||||
"pi_upstream.oauth_start": "开始订阅登录",
|
||||
"pi_upstream.oauth_login": "完成订阅登录",
|
||||
"pi_upstream.oauth_logout": "退出订阅登录",
|
||||
"schedule.run_now": "立即执行定时任务",
|
||||
"schedule.skip": "跳过定时任务",
|
||||
"acquisition_plan.manual_submit": "手动提交下载计划",
|
||||
};
|
||||
const VERB: Record<string, string> = { create: "新建", update: "修改", delete: "删除", test: "测试", verify: "校验" };
|
||||
const NOUN: Record<string, string> = {
|
||||
@@ -44,6 +50,9 @@ const NOUN: Record<string, string> = {
|
||||
downloader_instance: "下载器",
|
||||
media_server_instance: "媒体服务器",
|
||||
path_mapping: "路径映射",
|
||||
pi_upstream: "Agent 上游",
|
||||
schedule: "定时任务",
|
||||
automation_grant: "持续授权",
|
||||
};
|
||||
function actionLabel(a: string): string {
|
||||
if (ACTION_LABEL[a]) return ACTION_LABEL[a];
|
||||
|
||||
@@ -143,7 +143,7 @@ export default function DownloadsPage() {
|
||||
footer={
|
||||
open ? (
|
||||
<Button asChild variant="outline" size="sm">
|
||||
<Link to={consolePath("downloads", open.id)}>在独立页面打开</Link>
|
||||
<Link to={consolePath("downloads", open.id)}>打开详情页</Link>
|
||||
</Button>
|
||||
) : null
|
||||
}
|
||||
|
||||
@@ -155,10 +155,10 @@ function AutomationBanner({ a }: { a: Dashboard["automation"] }) {
|
||||
</div>
|
||||
<p className="mt-0.5 text-caption text-muted-foreground">
|
||||
{paused
|
||||
? "自动收录、下载、整理都已停下;查询与手动操作仍可用。"
|
||||
? "自动收录、下载、整理已停止,手动操作仍可用。"
|
||||
: readOnly
|
||||
? "恢复后的只读对账:只核对不写入,确认无误后再关掉。"
|
||||
: `${a.activeSubscriptions} 个订阅在追踪,按规则自动收录、下载与入库。`}
|
||||
? "只核对,不写入。确认无误后关闭。"
|
||||
: `${a.activeSubscriptions} 个订阅在追踪,自动收录、下载和整理。`}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -170,7 +170,7 @@ function AutomationBanner({ a }: { a: Dashboard["automation"] }) {
|
||||
checked={readOnly}
|
||||
disabled={update.isPending}
|
||||
onCheckedChange={(v) =>
|
||||
setAutomation({ readOnlyReconcile: v }, v ? "已进入只读对账模式" : "已退出只读对账模式").catch(() => {})
|
||||
setAutomation({ readOnlyReconcile: v }, v ? "已开启只读对账" : "已关闭只读对账").catch(() => {})
|
||||
}
|
||||
aria-label="只读对账模式"
|
||||
/>
|
||||
@@ -193,8 +193,8 @@ function AutomationBanner({ a }: { a: Dashboard["automation"] }) {
|
||||
</Button>
|
||||
}
|
||||
title="暂停全部自动化?"
|
||||
description="自动收录、下载、整理会停下,已提交的下载任务不受影响。随时可以恢复。"
|
||||
confirmLabel="暂停"
|
||||
description="自动收录、下载、整理会停止,已提交的下载任务不受影响。可随时恢复。"
|
||||
confirmLabel="暂停自动化"
|
||||
onConfirm={() => setAutomation({ enabled: false }, "自动化已暂停")}
|
||||
/>
|
||||
)}
|
||||
@@ -278,9 +278,9 @@ function CurrentWork({ w }: { w: Dashboard["currentWork"] }) {
|
||||
|
||||
function NeedsAttention({ n }: { n: Dashboard["needsAttention"] }) {
|
||||
return (
|
||||
<Section title="需要我处理" icon={AlertTriangle} to={consolePath("inbox")}>
|
||||
<Section title="待处理" icon={AlertTriangle} to={consolePath("inbox")}>
|
||||
{n.total === 0 ? (
|
||||
<EmptyState plain icon={CircleCheck} title="没有待你决定的事" className="py-6" />
|
||||
<EmptyState plain icon={CircleCheck} title="没有待处理事项" className="py-6" />
|
||||
) : (
|
||||
<>
|
||||
<p className="mb-2 text-title-sm font-semibold text-warning tabular-nums">
|
||||
@@ -308,8 +308,8 @@ function CacheSection({ c }: { c: Dashboard["cache"] }) {
|
||||
<EmptyState
|
||||
plain
|
||||
icon={HardDrive}
|
||||
title="尚未登记下载器"
|
||||
description="添加下载器实例后显示缓存卷水位。"
|
||||
title="还没有下载器"
|
||||
description="添加下载器后显示缓存水位。"
|
||||
className="py-6"
|
||||
/>
|
||||
) : (
|
||||
@@ -401,13 +401,7 @@ function SourcesSection({ s }: { s: Dashboard["sources"] }) {
|
||||
return (
|
||||
<Section title="来源健康" icon={Radio} to={consolePath("sources")}>
|
||||
{rows.length === 0 ? (
|
||||
<EmptyState
|
||||
plain
|
||||
icon={Radio}
|
||||
title="还没有来源"
|
||||
description="在站点工作台登记站点,在来源健康新建 Feed。"
|
||||
className="py-6"
|
||||
/>
|
||||
<EmptyState plain icon={Radio} title="还没有来源" description="登记站点或新建 Feed 后显示。" className="py-6" />
|
||||
) : (
|
||||
<ul className="divide-y divide-border/60">
|
||||
{rows.slice(0, 8).map((r) => {
|
||||
@@ -438,7 +432,7 @@ function RecentOutput({ items }: { items: Dashboard["recentOutput"] }) {
|
||||
return (
|
||||
<Section title="最近产出" icon={ListTodo} to={consolePath("organize")}>
|
||||
{items.length === 0 ? (
|
||||
<EmptyState plain icon={ListTodo} title="还没有入库记录" className="py-6" />
|
||||
<EmptyState plain icon={ListTodo} title="还没有整理记录" className="py-6" />
|
||||
) : (
|
||||
<ul className="divide-y divide-border/60">
|
||||
{items.slice(0, 6).map((r) => {
|
||||
@@ -478,7 +472,7 @@ export default function Home() {
|
||||
<>
|
||||
<PageHeader
|
||||
title="首页"
|
||||
description="系统是否在推进目标、完成了什么、还有什么缺口、是否需要你。"
|
||||
description="目标进度、今日产出、缺口和待处理事项。"
|
||||
actions={
|
||||
<div className="flex items-center gap-2">
|
||||
<LiveIndicator />
|
||||
@@ -499,7 +493,7 @@ export default function Home() {
|
||||
))}
|
||||
</div>
|
||||
) : q.isError ? (
|
||||
<ErrorState title="首页数据加载失败" error={q.error} onRetry={() => q.refetch()} />
|
||||
<ErrorState title="首页加载失败" error={q.error} onRetry={() => q.refetch()} />
|
||||
) : (
|
||||
<>
|
||||
<AutomationBanner a={q.data.automation} />
|
||||
|
||||
@@ -280,7 +280,7 @@ export default function IdentityPage() {
|
||||
<Button asChild variant="outline" size="sm">
|
||||
<Link to={INBOX_IDENTITY}>
|
||||
<Inbox />
|
||||
待确认身份
|
||||
确认身份
|
||||
</Link>
|
||||
</Button>
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ export default function SchedulesPage() {
|
||||
) : board.isError ? (
|
||||
<ErrorState error={board.error} onRetry={() => board.refetch()} />
|
||||
) : items.length === 0 ? (
|
||||
<EmptyState icon={CalendarClock} title="还没有登记的定时任务" description="worker 启动后会自动登记。" />
|
||||
<EmptyState icon={CalendarClock} title="还没有定时任务" description="worker 启动后自动登记。" />
|
||||
) : (
|
||||
<div className="grid items-start gap-4 md:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-5">
|
||||
{GROUP_ORDER.map((g) => {
|
||||
@@ -123,7 +123,7 @@ export default function SchedulesPage() {
|
||||
</h2>
|
||||
{jobs.length === 0 ? (
|
||||
<p className="rounded-xl border border-dashed border-border/70 px-4 py-6 text-center text-meta text-muted-foreground">
|
||||
{g === "agent" ? "暂无巡检,点「新建巡检」添加。" : "暂无任务。"}
|
||||
{g === "agent" ? "还没有巡检。" : "还没有定时任务。"}
|
||||
</p>
|
||||
) : (
|
||||
jobs.map((j) => (
|
||||
|
||||
@@ -43,7 +43,7 @@ export default function SettingsPage() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHeader title="设置" description="全局参数、接入的实例与凭证。凭证只写入,保存后不回显。" />
|
||||
<PageHeader title="设置" description="全局参数、实例与凭证。凭证保存后不回显。" />
|
||||
<div className="grid gap-6 lg:grid-cols-[13.5rem_minmax(0,1fr)] lg:gap-8">
|
||||
{/* 窄屏:顶部下拉 */}
|
||||
<div className="lg:hidden">
|
||||
|
||||
@@ -40,7 +40,7 @@ const PROMOS = [
|
||||
];
|
||||
const RESOLUTION: { value: ResolutionStatus; label: string }[] = [
|
||||
{ value: "resolved", label: "已识别" },
|
||||
{ value: "unresolved", label: "未识别" },
|
||||
{ value: "unresolved", label: "待识别" },
|
||||
{ value: "ambiguous", label: "有歧义" },
|
||||
{ value: "unidentifiable", label: "无法识别" },
|
||||
];
|
||||
@@ -358,11 +358,7 @@ export default function SiteWorkbenchPage() {
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="origin">
|
||||
<EmptyState
|
||||
icon={Globe}
|
||||
title="原站视图尚未提供"
|
||||
description={<>计划在 M2 提供。资源详情里有站点 ID 和来源时间,可据此到原站查看。</>}
|
||||
/>
|
||||
<EmptyState icon={Globe} title="原站视图暂未提供" description={<>资源详情里有站点 ID,可据此到原站查看。</>} />
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="snapshots">
|
||||
|
||||
@@ -146,7 +146,7 @@ export default function SitesPage() {
|
||||
<EmptyState
|
||||
icon={Globe}
|
||||
title="还没有登记站点"
|
||||
description="登记站点并填写账号凭证后,可在这里浏览和搜索资源。"
|
||||
description="登记站点并填写账号后,在这里浏览和搜索资源。"
|
||||
action={
|
||||
<Button size="sm" onClick={() => setRegistering(true)}>
|
||||
<Plus />
|
||||
|
||||
@@ -84,7 +84,7 @@ export default function SourcesPage() {
|
||||
<>
|
||||
<PageHeader
|
||||
title="来源健康"
|
||||
description="单个站点的快照、Feed 与账号在站点工作台。"
|
||||
description="单个站点的快照、Feed 和账号在站点工作台管理。"
|
||||
actions={
|
||||
<Button size="sm" onClick={() => setCreating(true)}>
|
||||
<Plus />
|
||||
|
||||
@@ -7,7 +7,7 @@ export default function NotFound() {
|
||||
<div className="grid min-h-screen place-items-center bg-background px-4">
|
||||
<EmptyState
|
||||
title="页面不存在"
|
||||
description="检查地址是否正确,或回到首页。"
|
||||
description="检查地址,或回到首页。"
|
||||
action={
|
||||
<Link to="/console" className={buttonVariants({ variant: "outline" })}>
|
||||
回到首页
|
||||
|
||||
Reference in new issue
Block a user