feat(控制台): 去掉待处理中心,问题回到发生处
- 侧栏与命令面板没有「待处理」;/console/inbox?kind= 按问题类型跳到整理记录「需要处理」、下载面板、运行状态或订阅 - 整理记录加「需要处理」筛选,行内写原因与推荐动作(指定作品 / 重新整理 / 让智能体识别 / 替换·保留两者·跳过 / 忽略), 详情页同样可处理;处理按钮从原待处理页抽成 components/decisions/decision-actions.tsx 复用 - 铃铛只放等你批准的智能体操作与系统健康;首页「要我处理」与运行状态「需要关注」放汇总计数,运行状态加智能体用量 - 设置 → 智能体:预算卡改为「用量与限制」(只剩单次工具调用与时长,附花费统计),自动调查卡加「整理识别不到作品时叫智能体识别」 - 集数待确认的原因显示在作品页集数映射(可确认建议规则 / 智能体建议)与订阅详情;未识别资源在媒体库「未识别」里直接选候选或指定 - 下载、站点、任务里指向待处理的链接改到对应页面 Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
This commit is contained in:
1 parent
1c1a954703
commit
fa76d7563c
36 files changed
+1209
-921
No files matched your search
@@ -0,0 +1,87 @@
|
||||
import type { AttentionSummary, Decision } from "@umt/contracts";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { approvalHref, HEALTH_HREF } from "../console/inbox-bell.tsx";
|
||||
import { attentionRows } from "./attention-summary.tsx";
|
||||
|
||||
// 首页 / 运行状态的「需要关注」汇总计数与铃铛跳转(2026-10-06 去掉待处理中心后)。
|
||||
|
||||
const id = (n: number) => `00000000-0000-4000-8000-${String(n).padStart(12, "0")}`;
|
||||
const dec = (n: number, over: Partial<Decision>): Decision =>
|
||||
({
|
||||
id: id(n),
|
||||
kind: "other",
|
||||
status: "open",
|
||||
severity: "warning",
|
||||
title: "t",
|
||||
summary: null,
|
||||
subjectType: null,
|
||||
subjectId: null,
|
||||
dedupeKey: `k${n}`,
|
||||
evidence: {},
|
||||
options: [],
|
||||
resolution: null,
|
||||
taskId: null,
|
||||
agentRunId: null,
|
||||
attempts: 0,
|
||||
nextReviewAt: null,
|
||||
resolvedBy: null,
|
||||
resolvedAt: null,
|
||||
createdAt: "2026-10-06T00:00:00.000Z",
|
||||
updatedAt: "2026-10-06T00:00:00.000Z",
|
||||
...over,
|
||||
}) as Decision;
|
||||
|
||||
const empty: AttentionSummary = { organize: 0, approvals: { total: 0, items: [] }, health: { total: 0, items: [] } };
|
||||
|
||||
describe("需要关注汇总", () => {
|
||||
it("只列非零项", () => {
|
||||
expect(attentionRows(empty)).toEqual([]);
|
||||
expect(attentionRows(undefined)).toEqual([]);
|
||||
});
|
||||
|
||||
it("整理需要处理 / 等你批准 / 系统健康按问题归类,点进去到对应页面", () => {
|
||||
const rows = attentionRows({
|
||||
organize: 3,
|
||||
approvals: {
|
||||
total: 1,
|
||||
items: [dec(1, { kind: "approval_required", subjectType: "agent_run", subjectId: id(9) })],
|
||||
},
|
||||
health: {
|
||||
total: 3,
|
||||
items: [
|
||||
dec(2, { kind: "source_unreachable" }),
|
||||
dec(3, { kind: "downloader_offline" }),
|
||||
dec(4, { kind: "other", dedupeKey: "library_root_offline:x" }),
|
||||
],
|
||||
},
|
||||
});
|
||||
expect(rows.map((r) => [r.text, r.to])).toEqual([
|
||||
["3 条整理需要处理", "/console/organize?tab=records&attention=1"],
|
||||
["1 个操作等你批准", `/console/agent/${id(9)}`],
|
||||
["1 个站点不可达", HEALTH_HREF],
|
||||
["1 个下载器离线", HEALTH_HREF],
|
||||
["1 个媒体库目录离线", HEALTH_HREF],
|
||||
]);
|
||||
});
|
||||
|
||||
it("健康问题超过列出的条数时补一行", () => {
|
||||
const rows = attentionRows({ ...empty, health: { total: 5, items: [dec(2, { kind: "space_insufficient" })] } });
|
||||
expect(rows.map((r) => r.text)).toEqual(["1 个下载盘空间不足", "4 个系统问题"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("铃铛跳转", () => {
|
||||
it("智能体会话 / 下载计划 / 下载任务", () => {
|
||||
expect(approvalHref({ subjectType: "agent_run", subjectId: id(1), agentRunId: null })).toBe(
|
||||
`/console/agent/${id(1)}`,
|
||||
);
|
||||
expect(approvalHref({ subjectType: "acquisition_plan", subjectId: id(2), agentRunId: null })).toBe(
|
||||
`/console/downloads?plan=${id(2)}`,
|
||||
);
|
||||
expect(approvalHref({ subjectType: "download_task", subjectId: id(3), agentRunId: null })).toBe(
|
||||
`/console/downloads/${id(3)}`,
|
||||
);
|
||||
expect(approvalHref({ subjectType: null, subjectId: null, agentRunId: null })).toBe("/console/agent");
|
||||
expect(HEALTH_HREF).toBe("/console/status#attention");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,78 @@
|
||||
import type { AttentionSummary, Decision } from "@umt/contracts";
|
||||
import { ChevronRight } from "lucide-react";
|
||||
import { Link } from "react-router";
|
||||
import { approvalHref, HEALTH_HREF } from "~/components/console/inbox-bell.tsx";
|
||||
import { consolePath } from "~/components/console/nav.ts";
|
||||
import { ORGANIZE_ATTENTION_PATH } from "~/routes/console/redirects.ts";
|
||||
|
||||
// 首页与运行状态共用的「需要关注」汇总计数(2026-10-06 去掉待处理中心后):
|
||||
// 「3 条整理需要处理」「1 个站点不可达」…,点进去到对应页面;只列非零项。
|
||||
|
||||
/** 系统健康事项按问题归类成一句(量词 + 说法) */
|
||||
function healthKind(d: Pick<Decision, "kind" | "dedupeKey">): { key: string; unit: string } {
|
||||
if (d.dedupeKey.startsWith("library_root_offline:")) return { key: "library_root", unit: "个媒体库目录离线" };
|
||||
switch (d.kind) {
|
||||
case "source_unreachable":
|
||||
return { key: d.kind, unit: "个站点不可达" };
|
||||
case "downloader_offline":
|
||||
return { key: d.kind, unit: "个下载器离线" };
|
||||
case "cache_volume_unknown":
|
||||
return { key: d.kind, unit: "个下载目录读不到容量" };
|
||||
case "space_insufficient":
|
||||
return { key: d.kind, unit: "个下载盘空间不足" };
|
||||
default:
|
||||
return { key: "other", unit: "个系统问题" };
|
||||
}
|
||||
}
|
||||
|
||||
export interface AttentionRow {
|
||||
key: string;
|
||||
text: string;
|
||||
to: string;
|
||||
}
|
||||
|
||||
/** 汇总 → 一行一类(非零才列) */
|
||||
export function attentionRows(s: AttentionSummary | undefined): AttentionRow[] {
|
||||
if (!s) return [];
|
||||
const rows: AttentionRow[] = [];
|
||||
if (s.organize) rows.push({ key: "organize", text: `${s.organize} 条整理需要处理`, to: ORGANIZE_ATTENTION_PATH });
|
||||
if (s.approvals.total) {
|
||||
const first = s.approvals.items[0];
|
||||
rows.push({
|
||||
key: "approvals",
|
||||
text: `${s.approvals.total} 个操作等你批准`,
|
||||
to: s.approvals.total === 1 && first ? approvalHref(first) : consolePath("agent"),
|
||||
});
|
||||
}
|
||||
const counts = new Map<string, { unit: string; n: number }>();
|
||||
for (const d of s.health.items) {
|
||||
const k = healthKind(d);
|
||||
const cur = counts.get(k.key);
|
||||
counts.set(k.key, { unit: k.unit, n: (cur?.n ?? 0) + 1 });
|
||||
}
|
||||
const listed = [...counts.values()].reduce((a, x) => a + x.n, 0);
|
||||
if (s.health.total > listed) {
|
||||
const cur = counts.get("other");
|
||||
counts.set("other", { unit: "个系统问题", n: (cur?.n ?? 0) + s.health.total - listed });
|
||||
}
|
||||
for (const [key, v] of counts) rows.push({ key, text: `${v.n} ${v.unit}`, to: HEALTH_HREF });
|
||||
return rows;
|
||||
}
|
||||
|
||||
export function AttentionList({ rows }: { rows: AttentionRow[] }) {
|
||||
return (
|
||||
<ul className="divide-y divide-border/60">
|
||||
{rows.map((r) => (
|
||||
<li key={r.key}>
|
||||
<Link
|
||||
to={r.to}
|
||||
className="flex min-w-0 items-center justify-between gap-3 py-1.5 text-sm text-foreground hover:underline"
|
||||
>
|
||||
<span className="truncate">{r.text}</span>
|
||||
<ChevronRight className="h-3.5 w-3.5 shrink-0 text-muted-foreground" aria-hidden />
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user