feat(web): W4-A wire console to real endpoints
- Agent page uses contracts (AgentRun/AgentRunDetail/AgentGoalReport) instead of a local mirror; ?mock=1 stays as an explicit demo switch; run snapshot refreshes on agent.run_status; stream auto-follow only while the user stays near the bottom (abort stays reachable) - settings: Pi provider select incl. fake, new Agent runtime card (budgets, fakeScript) - download trace uses /tasks?subjectId= and /organization-records?downloadTaskId= instead of time-window engine run matching and path matching; new business-task tab with cancel/retry - workbench download dialog posts /acquisition-plans and shows per-slot rejection reasons - preference tag revision history; subscription search by title/kind; identity/alias/subscription lists show entity titles; slot cards show current asset summary; policy preview sample titles - DecisionSummary / IdentityRevisionList / ReleaseIdentityResult / TaskAccepted / PolicyPreviewSummary / OrganizePlanArtifactContent taken from contracts - docs: SSE for console (cookie) vs GET /events?after=&limit= JSON replay for API keys Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
34e8751b45
commit
1023642ef9
30 files changed
+703
-271
No files matched your search
@@ -1,9 +1,8 @@
|
||||
import type { AcquisitionSlot, WorkbenchRelease } from "@umt/contracts";
|
||||
import type { AcquisitionSlot, ManualAcquisitionResult, WorkbenchRelease } from "@umt/contracts";
|
||||
import { Bot, Download } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { Link } from "react-router";
|
||||
import { toast } from "sonner";
|
||||
import { isMissingEndpoint } from "~/api/parse.ts";
|
||||
import { useSubmitRelease } from "~/api/releases.ts";
|
||||
import { openAgentDrawer } from "~/components/agent/agent-store.ts";
|
||||
import { FilterSelect } from "~/components/console/filter-bar.tsx";
|
||||
@@ -42,21 +41,28 @@ export function DownloadDialog({
|
||||
}) {
|
||||
const submit = useSubmitRelease();
|
||||
const [slotId, setSlotId] = useState("");
|
||||
const [missing, setMissing] = useState(false);
|
||||
const [rejected, setRejected] = useState<ManualAcquisitionResult["rejected"] | null>(null);
|
||||
const [done, setDone] = useState<string | null>(null);
|
||||
const slotName = (id: string | null) => slots.find((s) => s.id === id)?.name ?? null;
|
||||
|
||||
const go = () =>
|
||||
submit.mutate(
|
||||
{ sourceReleaseId: release.id, ...(slotId ? { slotId } : {}) },
|
||||
{
|
||||
onSuccess: (plan) => {
|
||||
setDone(plan.id);
|
||||
toast.success(`已生成下载计划(${plan.status}),进度见“下载与任务”`);
|
||||
},
|
||||
onError: (e) => {
|
||||
if (isMissingEndpoint(e)) setMissing(true);
|
||||
else toast.error(errorMessage(e));
|
||||
sourceReleaseId: release.id,
|
||||
...(slotId ? { slotId } : release.entity ? { entityId: release.entity.id } : {}),
|
||||
},
|
||||
{
|
||||
onSuccess: (r) => {
|
||||
if (r.submitted && r.plan) {
|
||||
setRejected(null);
|
||||
setDone(r.plan.id);
|
||||
toast.success(`已提交(计划${slotName(r.slotId) ? ` · ${slotName(r.slotId)}` : ""}),进度见“下载与任务”`);
|
||||
} else {
|
||||
setRejected(r.rejected);
|
||||
toast.warning("没有生成下载计划,原因见对话框");
|
||||
}
|
||||
},
|
||||
onError: (e) => toast.error(errorMessage(e)),
|
||||
},
|
||||
);
|
||||
|
||||
@@ -65,7 +71,7 @@ export function DownloadDialog({
|
||||
open={open}
|
||||
onOpenChange={(o) => {
|
||||
if (!o) {
|
||||
setMissing(false);
|
||||
setRejected(null);
|
||||
setDone(null);
|
||||
}
|
||||
onOpenChange(o);
|
||||
@@ -97,14 +103,21 @@ export function DownloadDialog({
|
||||
) : (
|
||||
<p className="text-meta text-muted-foreground">
|
||||
{release.entity
|
||||
? "该作品还没有版本目标,提交时按默认规则建临时目标。"
|
||||
: "资源尚未识别到本地作品,提交后先进入识别。"}
|
||||
? "该作品还没有版本目标(Slot),需要先在订阅里建目标才能提交。"
|
||||
: "资源尚未识别到本地作品,请先在“身份与元数据”指定作品。"}
|
||||
</p>
|
||||
)}
|
||||
{missing ? (
|
||||
{rejected ? (
|
||||
<div className="rounded-xl border border-warning/40 bg-warning/10 px-3 py-2.5 text-caption text-warning">
|
||||
后端还没有开放手动提交端点(POST /acquisition-plans)。可以委托 Agent 用 download.plan / download.submit
|
||||
能力走同一流程提交。
|
||||
<p className="mb-1">没有生成下载计划:</p>
|
||||
<ul className="list-disc space-y-0.5 pl-5">
|
||||
{rejected.map((r, i) => (
|
||||
<li key={`${r.slotId}-${r.code}-${i}`} className="break-words">
|
||||
{slotName(r.slotId) ? `${slotName(r.slotId)}:` : ""}
|
||||
{r.message}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
) : null}
|
||||
{done ? (
|
||||
@@ -132,7 +145,7 @@ export function DownloadDialog({
|
||||
<Bot />
|
||||
委托 Agent
|
||||
</Button>
|
||||
<Button onClick={go} disabled={submit.isPending || !!done}>
|
||||
<Button onClick={go} disabled={submit.isPending || !!done || !release.entity}>
|
||||
<Download />
|
||||
{submit.isPending ? "提交中…" : "提交下载"}
|
||||
</Button>
|
||||
|
||||
Reference in new issue
Block a user