feat(web): simplify subscription and manual download flows and sync UHD styles
This commit is contained in:
1 parent
27c0f27434
commit
c3c4456913
33 files changed
+1083
-633
No files matched your search
@@ -100,9 +100,10 @@ export function usePlanFromSearch() {
|
||||
api
|
||||
.post<unknown>(`/search/releases/${encodeURIComponent(siteId)}/${encodeURIComponent(siteReleaseId)}/plan`, body)
|
||||
.then((d) => parseOrThrow(ManualAcquisitionResult, d, "/search/releases/plan")),
|
||||
onSuccess: () => {
|
||||
void qc.invalidateQueries({ queryKey: ["plans"] });
|
||||
void qc.invalidateQueries({ queryKey: ["slots"] });
|
||||
onSuccess: (r) => {
|
||||
if (r.submitted) void qc.invalidateQueries({ queryKey: ["downloads"] });
|
||||
void qc.invalidateQueries({ queryKey: ["inventory"] });
|
||||
void qc.invalidateQueries({ queryKey: ["library"] });
|
||||
void qc.invalidateQueries({ queryKey: searchKeys.all });
|
||||
},
|
||||
});
|
||||
|
||||
@@ -37,6 +37,7 @@ function useInvalidate() {
|
||||
return () => {
|
||||
void qc.invalidateQueries({ queryKey: slotKeys.all });
|
||||
void qc.invalidateQueries({ queryKey: ["library"] });
|
||||
void qc.invalidateQueries({ queryKey: ["subscriptions"] });
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import type { Decision, DecisionStatus, DownloadTask } from "@umt/contracts";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { buildTimeline } from "./tasks.ts";
|
||||
|
||||
const task = {
|
||||
id: "download",
|
||||
name: "作品",
|
||||
origin: "user",
|
||||
status: "queued",
|
||||
createdAt: "2026-10-13T00:00:00Z",
|
||||
} as unknown as DownloadTask;
|
||||
const decision = (status: DecisionStatus): Decision =>
|
||||
({
|
||||
id: "decision",
|
||||
status,
|
||||
title: "等待补全资源规格",
|
||||
summary: "规格缺少编码",
|
||||
severity: "critical",
|
||||
subjectType: "download_task",
|
||||
subjectId: "download",
|
||||
agentRunId: "agent",
|
||||
createdAt: "2026-10-13T00:00:01Z",
|
||||
updatedAt: "2026-10-13T00:00:02Z",
|
||||
evidence: {},
|
||||
}) as Decision;
|
||||
|
||||
describe("下载时间线的异常状态", () => {
|
||||
it.each([
|
||||
["open", "待处理", "destructive", false],
|
||||
["investigating", "智能体调查中", "neutral", true],
|
||||
["awaiting_user", "等你决定", "destructive", false],
|
||||
["resolved", "已处理", "success", false],
|
||||
["dismissed", "已忽略", "neutral", false],
|
||||
["expired", "已过期", "neutral", false],
|
||||
] as const)("%s按实际状态显示,不凭会话ID判断仍需人工", (status, label, tone, ongoing) => {
|
||||
const events = buildTimeline({ task, decisions: [decision(status)] });
|
||||
expect(events.find((e) => e.key === "dec-decision")).toMatchObject({
|
||||
title: `${label}:等待补全资源规格`,
|
||||
tone,
|
||||
ongoing,
|
||||
href: "/console/downloads?open=download",
|
||||
});
|
||||
expect(events.find((e) => e.key === "agent-decision")).toMatchObject({
|
||||
title: "智能体调查记录",
|
||||
tone: "neutral",
|
||||
href: "/console/agent/agent",
|
||||
});
|
||||
});
|
||||
|
||||
it("交还人工时展示已有恢复建议,计划事项定位到计划", () => {
|
||||
const d = {
|
||||
...decision("awaiting_user"),
|
||||
subjectType: "acquisition_plan",
|
||||
subjectId: "plan",
|
||||
evidence: { autoRecover: { suggestion: "请确认资源详情的编码" } },
|
||||
};
|
||||
expect(buildTimeline({ task, decisions: [d] }).find((e) => e.key === "dec-decision")).toMatchObject({
|
||||
detail: "请确认资源详情的编码",
|
||||
href: "/console/downloads?plan=plan",
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import {
|
||||
type AcquisitionPlan,
|
||||
cursorPage,
|
||||
@@ -10,9 +10,12 @@ import {
|
||||
type TaskKind,
|
||||
type TaskStatus,
|
||||
} from "@umt/contracts";
|
||||
import { recordPath, taskPath } from "~/components/console/record-sheet.tsx";
|
||||
import { consolePath } from "~/components/console/nav.ts";
|
||||
import { recordPath, subjectLink, taskPath } from "~/components/console/record-sheet.tsx";
|
||||
import { DECISION_STATUS } from "~/components/library/labels.ts";
|
||||
import { organizationActionLabel, organizationLabel } from "~/lib/labels.ts";
|
||||
import { humanizeReason, reasonLine } from "~/lib/reasons.ts";
|
||||
import { useSse } from "~/lib/sse.ts";
|
||||
import { PAUSE_REASON_LABEL, PLAN_STATUS_LABEL } from "./downloads.ts";
|
||||
import { apiParsed } from "./parse.ts";
|
||||
|
||||
@@ -60,6 +63,14 @@ export function useSourceRelease(id: string | null | undefined) {
|
||||
/** 与该下载有关的待处理事项(subject = 下载任务或计划),含 Agent 调查 run */
|
||||
export function useSubjectDecisions(subjectIds: string[]) {
|
||||
const ids = subjectIds.filter(Boolean);
|
||||
const qc = useQueryClient();
|
||||
useSse(
|
||||
["decision.created", "decision.updated"],
|
||||
() => {
|
||||
void qc.invalidateQueries({ queryKey: ["decisions", "subjects", ids] });
|
||||
},
|
||||
ids.length > 0,
|
||||
);
|
||||
return useQuery({
|
||||
queryKey: ["decisions", "subjects", ids],
|
||||
enabled: ids.length > 0,
|
||||
@@ -256,23 +267,29 @@ export function buildTimeline(input: {
|
||||
});
|
||||
}
|
||||
for (const d of decisions) {
|
||||
const state = DECISION_STATUS[d.status];
|
||||
const needsUser = d.status === "open" || d.status === "awaiting_user";
|
||||
const recovery = d.evidence.autoRecover as { suggestion?: unknown } | undefined;
|
||||
const suggestion =
|
||||
d.status === "awaiting_user" && typeof recovery?.suggestion === "string" ? recovery.suggestion : null;
|
||||
ev.push({
|
||||
key: `dec-${d.id}`,
|
||||
stage: "agent",
|
||||
at: d.createdAt,
|
||||
title: `需要处理:${d.title}`,
|
||||
detail: d.summary,
|
||||
tone: d.severity === "critical" ? "destructive" : "warning",
|
||||
href: "/console/inbox",
|
||||
title: `${state.label}:${d.title}`,
|
||||
detail: suggestion ?? d.summary,
|
||||
tone: needsUser && d.severity === "critical" ? "destructive" : state.tone,
|
||||
href: subjectLink(d) ?? (d.agentRunId ? consolePath("agent", d.agentRunId) : consolePath("agent")),
|
||||
ongoing: d.status === "investigating",
|
||||
});
|
||||
if (d.agentRunId)
|
||||
ev.push({
|
||||
key: `agent-${d.id}`,
|
||||
stage: "agent",
|
||||
at: d.updatedAt,
|
||||
title: "智能体已介入调查",
|
||||
title: "智能体调查记录",
|
||||
tone: "neutral",
|
||||
href: `/console/agent/${d.agentRunId}`,
|
||||
href: consolePath("agent", d.agentRunId),
|
||||
});
|
||||
}
|
||||
for (const t of tasks) {
|
||||
|
||||
Reference in new issue
Block a user