docs(plan): v0.2 adds Pi subagents and scheduled-jobs board
Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
1 parent
b839ed4056
commit
16ab2a6c84
97 files changed
+592
-648
No files matched your search
@@ -25,7 +25,7 @@ export const TASK_KIND_LABEL: Record<TaskKind, string> = {
|
||||
refreshMetadata: "刷新元数据",
|
||||
reconcileLibrary: "资产对账",
|
||||
cleanupDownloadCache: "清理缓存",
|
||||
resolveDecision: "处理待办",
|
||||
resolveDecision: "处理待处理事项",
|
||||
agentRun: "Agent 运行",
|
||||
};
|
||||
|
||||
@@ -76,7 +76,7 @@ export function BusinessTasks() {
|
||||
body: op === "cancel" ? { reason: "控制台取消" } : {},
|
||||
}).then((d) => Task.parse(d)),
|
||||
onSuccess: (_t, v) => {
|
||||
toast.success(v.op === "cancel" ? "已请求取消(只停后续步骤)" : "已重新排队");
|
||||
toast.success(v.op === "cancel" ? "已请求取消,后续步骤不再执行" : "已重新排队");
|
||||
void qc.invalidateQueries({ queryKey: ["tasks"] });
|
||||
},
|
||||
onError: (e) => toast.error(errorMessage(e)),
|
||||
@@ -218,7 +218,7 @@ export function BusinessTasks() {
|
||||
empty={{
|
||||
icon: ListTodo,
|
||||
title: kind || status ? "没有符合筛选的任务" : "还没有业务任务",
|
||||
description: "提交下载、核验入库、缓存清理、对账等后台工作都会记在这里。",
|
||||
description: "提交下载、核验入库、清理缓存、对账等后台任务会出现在这里。",
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
|
||||
@@ -61,7 +61,7 @@ export function BusinessTimeline({ events, loading }: { events: TimelineEvent[];
|
||||
/>
|
||||
)}
|
||||
{loading && events.length ? (
|
||||
<p className="text-meta text-muted-foreground">正在补全关联的整理记录与引擎 run…</p>
|
||||
<p className="text-meta text-muted-foreground">正在加载关联的整理记录与引擎运行…</p>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -63,8 +63,8 @@ function VolumeBar({ v }: { v: Volume }) {
|
||||
<span>保留锁定 {formatBytes(v.retentionLockedBytes, 1)}</span>
|
||||
</div>
|
||||
<div className="mt-1.5 flex flex-wrap gap-1.5">
|
||||
{pct === null ? <StatusBadge tone="warning">读不到磁盘状态:已保守阻止新提交</StatusBadge> : null}
|
||||
{tripped ? <StatusBadge tone="destructive">已达硬阈值:停止新提交</StatusBadge> : null}
|
||||
{pct === null ? <StatusBadge tone="warning">读不到磁盘状态,已暂停新提交</StatusBadge> : null}
|
||||
{tripped ? <StatusBadge tone="destructive">已达硬阈值,停止新提交</StatusBadge> : null}
|
||||
{v.pausedByThreshold ? <StatusBadge tone="warning">阈值暂停 {v.pausedByThreshold} 个任务</StatusBadge> : null}
|
||||
</div>
|
||||
{v.notes.map((n) => (
|
||||
@@ -93,12 +93,12 @@ export function CacheVolumes() {
|
||||
</Button>
|
||||
}
|
||||
title="按规则清理下载缓存?"
|
||||
description="只清理已核验入库、且满足保留期与 H&R 的纳管任务;未纳管任务与未满足保留的不会动。"
|
||||
description="只清理已入库、且满足保留期与 H&R 的纳管任务,其余不动。"
|
||||
confirmLabel="清理"
|
||||
onConfirm={() =>
|
||||
cleanup.mutateAsync(undefined).then(
|
||||
(r) => {
|
||||
toast.success(`已排队清理任务 ${r.taskId.slice(0, 8)}`, { description: "后台执行,结果见任务详情" });
|
||||
toast.success(`已排队清理任务 ${r.taskId.slice(0, 8)}`, { description: "结果见任务详情" });
|
||||
navigate(`/console/engine/${r.runId}`);
|
||||
},
|
||||
(e) => toast.error(errorMessage(e)),
|
||||
@@ -111,7 +111,7 @@ export function CacheVolumes() {
|
||||
) : vols.error ? (
|
||||
<ErrorState plain error={vols.error} onRetry={() => void vols.refetch()} />
|
||||
) : !vols.data.length ? (
|
||||
<p className="text-caption text-muted-foreground">还没有下载器缓存卷的统计(未配置下载器或尚未巡检)。</p>
|
||||
<p className="text-caption text-muted-foreground">还没有缓存卷数据。配置下载器并完成巡检后显示。</p>
|
||||
) : (
|
||||
<div className="grid gap-5 md:grid-cols-2">
|
||||
{vols.data.map((v) => (
|
||||
|
||||
@@ -66,14 +66,14 @@ export function PauseResume({ t, size = "sm" }: { t: DownloadTask; size?: "sm" |
|
||||
variant="outline"
|
||||
size={size}
|
||||
disabled={act.isPending}
|
||||
title={threshold ? "阈值暂停:缓存回落到恢复水位后由系统自动恢复" : undefined}
|
||||
title={threshold ? "阈值暂停,缓存回落到恢复水位后自动恢复" : undefined}
|
||||
onKeyDown={(e) => e.stopPropagation()}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
act.mutate(
|
||||
{ id: t.id, action: canResume ? "resume" : "pause" },
|
||||
{
|
||||
onSuccess: () => toast.success(canResume ? "已恢复" : "已暂停(记为用户暂停,阈值恢复不会自动恢复它)"),
|
||||
onSuccess: () => toast.success(canResume ? "已恢复" : "已暂停。阈值回落时不会自动恢复"),
|
||||
onError: (err) => toast.error(errorMessage(err)),
|
||||
},
|
||||
);
|
||||
@@ -128,13 +128,12 @@ export function DownloadDetail({ task }: { task: DownloadTask }) {
|
||||
{task.pauseReason === "threshold" ? (
|
||||
<p className="flex items-start gap-2 rounded-lg border border-warning/40 bg-warning/10 px-3 py-2 text-caption text-warning">
|
||||
<ShieldAlert className="mt-0.5 h-3.5 w-3.5 shrink-0" />
|
||||
缓存卷达到硬阈值,系统暂停了这个纳管任务。水位回落到恢复线后自动恢复;手动恢复、优先级或 Agent
|
||||
推荐都不能绕过阈值。
|
||||
缓存卷达到硬阈值,任务已暂停,水位回落到恢复线后自动恢复。手动恢复和 Agent 都不能绕过阈值。
|
||||
</p>
|
||||
) : null}
|
||||
{task.noProgressSince ? (
|
||||
<p className="rounded-lg border border-warning/40 bg-warning/10 px-3 py-2 text-caption text-warning">
|
||||
自 {formatDateTime(task.noProgressSince)} 起无进展。系统只记录证据并汇入待处理,不会自动换源。
|
||||
自 {formatDateTime(task.noProgressSince)} 起无进展,已记入待处理,不会自动换源。
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
|
||||
@@ -44,9 +44,7 @@ function PlanPreview({ planId }: { planId: string }) {
|
||||
return (
|
||||
<p className="rounded-lg border border-border px-3 py-2 text-caption text-muted-foreground">
|
||||
计划已生成(planId {planId.slice(0, 8)}…),
|
||||
{isMissingEndpoint(art.error)
|
||||
? "计划内容不存在或已过期,请重新预览。"
|
||||
: `读取计划失败:${errorMessage(art.error)}`}
|
||||
{isMissingEndpoint(art.error) ? "计划已过期,重新预览。" : `读取计划失败:${errorMessage(art.error)}`}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
@@ -65,7 +63,7 @@ function PlanPreview({ planId }: { planId: string }) {
|
||||
<div className="space-y-2">
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
<StatusBadge>{org.operation}</StatusBadge>
|
||||
{org.crossDevice ? <StatusBadge tone="warning">跨卷:Copy → 核验 → 删源</StatusBadge> : null}
|
||||
{org.crossDevice ? <StatusBadge tone="warning">跨卷:复制 → 核验 → 删源</StatusBadge> : null}
|
||||
{org.requiresApproval ? <StatusBadge tone="warning">需要审批</StatusBadge> : null}
|
||||
{blocking.length ? <StatusBadge tone="destructive">阻塞冲突 {blocking.length}</StatusBadge> : null}
|
||||
</div>
|
||||
@@ -155,7 +153,7 @@ export function OrganizeDialog({
|
||||
const pathErr = Object.entries(e).find(([k]) => k.startsWith("sourcePaths"));
|
||||
setErrors({
|
||||
...e,
|
||||
paths: pathErr ? "每行一个以 / 开头的绝对路径" : e[""] ? "填写源路径(每行一个绝对路径)" : "",
|
||||
paths: pathErr ? "每行一个以 / 开头的绝对路径" : e[""] ? "填写源路径" : "",
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -165,8 +163,8 @@ export function OrganizeDialog({
|
||||
if (dryRun) {
|
||||
setPlanId(r.planId);
|
||||
if (r.truncated)
|
||||
toast.warning("源目录文件数超过上限,预览只含前一部分文件", {
|
||||
description: "正式执行会被拒绝,请把源路径缩小到具体的作品目录",
|
||||
toast.warning("源目录文件过多,预览只含前一部分", {
|
||||
description: "执行会被拒绝。把源路径缩小到具体的作品目录",
|
||||
});
|
||||
} else {
|
||||
toast.success("已发起整理");
|
||||
@@ -183,9 +181,7 @@ export function OrganizeDialog({
|
||||
<DialogContent className="max-h-[90vh] overflow-y-auto sm:max-w-xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>手动整理</DialogTitle>
|
||||
<DialogDescription>
|
||||
先预览计划(不动文件),确认后再执行。Move 跨卷时按 Copy → 核验 → 删源执行。
|
||||
</DialogDescription>
|
||||
<DialogDescription>先预览计划(不动文件),确认后执行。跨卷移动按复制 → 核验 → 删源执行。</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="grid gap-1">
|
||||
{downloadTaskId ? (
|
||||
@@ -266,7 +262,7 @@ export function OrganizeDialog({
|
||||
<FormField
|
||||
label="动作"
|
||||
htmlFor="org-action"
|
||||
hint={action === "move" ? "Move 需要对应授权" : "保留源文件(做种不受影响)"}
|
||||
hint={action === "move" ? "移动需要对应授权" : "保留源文件,不影响做种"}
|
||||
>
|
||||
<Select
|
||||
value={action}
|
||||
@@ -279,8 +275,8 @@ export function OrganizeDialog({
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent position="popper">
|
||||
<SelectItem value="copy">Copy</SelectItem>
|
||||
<SelectItem value="move">Move</SelectItem>
|
||||
<SelectItem value="copy">复制</SelectItem>
|
||||
<SelectItem value="move">移动</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</FormField>
|
||||
|
||||
Reference in new issue
Block a user