feat(web): W3-3 site workbench, sources/feeds/snapshots, downloads with business timeline, organization records and agent sessions
- sites + /sites/:siteId: organized view with snapshot-first release list, inventory overlay, candidate reasons, volatile-field recheck, stale marking that keeps existing data, live search, unified download entry; origin view placeholder (M2) - sources: feed CRUD/enable/fetch/reset, cursor/health/gap hints; per-site snapshot history - tasks: download tasks with pause/resume (threshold pause cannot be bypassed), plans, cache volume thresholds and cleanup, business timeline (observation → plan → download → organization → agent/engine) - organize: record list/detail with file-level results, manual organize with dryRun preview - agent: session list/page and layout drawer with context prefix; streaming events, tool cards, rejection alerts, compaction, budget meters, model text vs server report; mock switch (?mock=1) until W2-D endpoints land Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
6280d52ec3
commit
7103bc8f7c
35 files changed
+6134
-45
No files matched your search
@@ -0,0 +1,68 @@
|
||||
import { ExternalLink } from "lucide-react";
|
||||
import { Link } from "react-router";
|
||||
import type { TimelineEvent, TimelineStage } from "~/api/tasks.ts";
|
||||
import { LoadingState } from "~/components/console/empty-state.tsx";
|
||||
import { StatusBadge } from "~/components/console/status-badge.tsx";
|
||||
import { Timeline } from "~/components/console/timeline.tsx";
|
||||
|
||||
// 业务时间线(参考 MovieClaw task-activity):把观察 → 计划 → 下载 → 整理 → Agent 串成一个业务过程,
|
||||
// 阶段计数与首页/侧栏同口径;每条事件都有文字说明,不只靠颜色。
|
||||
|
||||
export const STAGE_LABEL: Record<TimelineStage, string> = {
|
||||
observation: "观察",
|
||||
plan: "计划",
|
||||
download: "下载",
|
||||
organization: "整理",
|
||||
agent: "Agent",
|
||||
engine: "引擎",
|
||||
};
|
||||
|
||||
const ORDER: TimelineStage[] = ["observation", "plan", "download", "organization", "agent", "engine"];
|
||||
|
||||
export function BusinessTimeline({ events, loading }: { events: TimelineEvent[]; loading?: boolean }) {
|
||||
const counts = new Map<TimelineStage, number>();
|
||||
for (const e of events) counts.set(e.stage, (counts.get(e.stage) ?? 0) + 1);
|
||||
const ongoing = events.filter((e) => e.ongoing);
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{ORDER.map((s) => {
|
||||
const n = counts.get(s) ?? 0;
|
||||
const bad = events.some((e) => e.stage === s && e.tone === "destructive");
|
||||
return (
|
||||
<StatusBadge key={s} tone={bad ? "destructive" : n ? "success" : "neutral"}>
|
||||
{STAGE_LABEL[s]} {n ? `· ${n}` : "· 无"}
|
||||
</StatusBadge>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{ongoing.length ? (
|
||||
<p className="text-caption text-muted-foreground">当前:{ongoing.map((e) => e.title).join(";")}</p>
|
||||
) : null}
|
||||
{loading && !events.length ? (
|
||||
<LoadingState rows={3} />
|
||||
) : (
|
||||
<Timeline
|
||||
items={events.map((e) => ({
|
||||
id: e.key,
|
||||
at: e.at,
|
||||
tone: e.tone,
|
||||
meta: STAGE_LABEL[e.stage],
|
||||
title: e.href ? (
|
||||
<Link to={e.href} className="inline-flex items-center gap-1 underline-offset-4 hover:underline">
|
||||
{e.title}
|
||||
<ExternalLink className="h-3 w-3" />
|
||||
</Link>
|
||||
) : (
|
||||
e.title
|
||||
),
|
||||
description: e.detail ?? undefined,
|
||||
}))}
|
||||
/>
|
||||
)}
|
||||
{loading && events.length ? (
|
||||
<p className="text-meta text-muted-foreground">正在补全关联的整理记录与引擎 run…</p>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user