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:
rootandClaude Opus 5.5 committed 2026-10-06 16:47:25 +08:00
1 parent 1c1a954703
commit fa76d7563c
36 files changed
+1209 -921

No files matched your search

@@ -25,7 +25,7 @@ describe("下载面板展示换算(DL-2)", () => {
expect(formatEta(3 * 86400 + 4 * 3600)).toBe("3 天 4 小时");
});
it("最近一次整理结果:已入库 / 整理中 / 待整理 / 入库失败 / 部分入库;有待处理事项时链到待处理", () => {
it("最近一次整理结果:已入库 / 整理中 / 待整理 / 入库失败 / 部分入库;有问题的链到整理记录就地处理", () => {
expect(organizeState({ status: "completed", organize: null })).toMatchObject({ label: "待整理", to: null });
expect(organizeState({ status: "importing", organize: null })?.label).toBe("整理中");
expect(organizeState({ status: "imported", organize: null })?.label).toBe("已入库");
@@ -48,7 +48,7 @@ describe("下载面板展示换算(DL-2)", () => {
tone: "destructive",
hint: "没识别出作品。指定作品后重新整理",
});
expect(failed?.to).toBe("/console/inbox?kind=identity_unresolved");
expect(failed?.to).toBe(`/console/organize/${RID}`);
expect(organizeState({ status: "completed", organize: rec("failed", { reason: "磁盘已满" }) })?.hint).toBe(
"磁盘已满",
);
@@ -58,7 +58,7 @@ describe("下载面板展示换算(DL-2)", () => {
);
expect(
organizeState({ status: "completed", organize: rec("partial", { reason: "blocked:target_exists" }) })?.hint,
).toBe("媒体库里已有同名文件。在待处理里选择替换或跳过");
).toBe("媒体库里已有同名文件。在整理记录里选择替换、保留两者或跳过");
// 不认识的码不显示
expect(
organizeState({ status: "completed", organize: rec("failed", { reason: "some_new_code" }) })?.hint,
@@ -28,7 +28,7 @@ export function formatEta(sec: number | null | undefined): string {
export type OrganizeState = {
label: string;
tone: "success" | "warning" | "destructive" | "neutral";
/** 点状态去哪:有未关闭的待处理事项去待处理,否则去整理记录;都没有为 null */
/** 点状态去哪:整理记录详情(有问题的在那里就地处理);没整理过为 null */
to: string | null;
/** 失败原因(人话);原因是内部码时不直接给人看 */
hint: string | null;
@@ -48,11 +48,8 @@ export function organizeState(
): OrganizeState | null {
const rec = t.organize;
if (t.managed === false && !rec && t.status !== "importing" && t.status !== "imported") return null;
const to = rec?.decision
? `${consolePath("inbox")}?kind=${rec.decision.kind}`
: rec
? consolePath("organize", rec.recordId)
: null;
// 有问题的整理在整理记录详情里就地处理(待处理中心已取消)
const to = rec ? consolePath("organize", rec.recordId) : null;
if (rec?.status === "succeeded" || (t.status === "imported" && rec?.status !== "failed"))
return { label: "已入库", tone: "success", to, hint: null };
if (rec?.status === "failed") return { label: "入库失败", tone: "destructive", to, hint: reasonHint(rec.reason) };
@@ -252,7 +252,7 @@ function OrganizeBadge({ t }: { t: DownloadBoardItem }) {
<Link
to={s.to}
onClick={(e) => e.stopPropagation()}
title={t.organize?.decision ? "去待处理" : "查看整理记录"}
title={t.organize?.decision ? "去整理记录处理" : "查看整理记录"}
className="rounded-full outline-none focus-visible:ring-2 focus-visible:ring-ring"
>
{badge}
@@ -144,14 +144,14 @@ export function DownloadDetail({ task, live }: { task: DownloadTask; live?: Down
{importFailed ? (
<p className="rounded-lg border border-destructive/40 bg-destructive/10 px-3 py-2 text-caption text-destructive">
入库失败:{reasonLine(importFailed.reason)}。
<Link to="/console/inbox" className="ml-1 underline underline-offset-4">
去待处理
<Link to={`/console/organize/${importFailed.id}`} className="ml-1 underline underline-offset-4">
去整理记录处理
</Link>
</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}
+2 -13
View File
@@ -1,5 +1,5 @@
import type { DownloadBoardPlanItem } from "@umt/contracts";
import { Check, Inbox, X } from "lucide-react";
import { Check, X } from "lucide-react";
import { Link } from "react-router";
import { toast } from "sonner";
import { useDecideApproval, useIsAdminSession } from "~/api/agent.ts";
@@ -147,18 +147,7 @@ export function PlanDetail({ p }: { p: DownloadBoardPlanItem }) {
</div>
</div>
{p.wait === "approval" ? (
<p className="flex flex-wrap items-center gap-1 text-caption text-muted-foreground">
这次下载要你批准后才提交,同一件事也在待处理里。
{p.approval?.decisionId ? (
<Link
to={`${consolePath("inbox")}?kind=approval_required`}
className="inline-flex items-center gap-0.5 underline underline-offset-4"
>
<Inbox className="size-3.5" />
去待处理
</Link>
) : null}
</p>
<p className="text-caption text-muted-foreground">这次下载要你批准后才提交,铃铛里也有提醒。</p>
) : hint ? (
<p className="text-caption text-muted-foreground">{hint}。</p>
) : null}