feat(web): 首页只回答三件事、计划页巡检置顶与分组折叠、任务列表去噪(DEAI-2)
首页:顶部一行状态(自动化开关 + 只在异常时出现的服务 / 下载盘 / 站点账号 / 下载器问题), 下面只有「在忙什么」「要我处理」「最近入库」三块;去掉运行状态、计划、缓存、来源健康、 满足率与 0 值铺陈;0 订阅时写「还没有订阅。去搜索添加」。只读对账移出首页,设置里放进 自动化 →「高级」(与存储预算、流水保留、缓存有效期一起默认收起)。 计划页:「智能体巡检」移到最上面——巡检数量少、由用户自己建、每次会花钱,是这页最需要 看的;自动任务任务多、多数不用管,放在下面。自动任务按分类分组、每组可折叠:有失败或 运行中的组默认展开,其余收起为「N 个任务 · 下次 xx」一行,折叠状态记在 localStorage; 维护类整组叫「数据清理」,未接入调度的备份不列。巡检卡只留名称、开关、间隔、上次结果与 费用,目标 / 能力 / 预算 / 模型 / 最近会话收进「详情」。去掉顶部 6 个计数卡。定时任务说明 去掉表名、字段名与实现备注。 任务页:默认隐藏系统任务(无业务对象的引擎步骤、定时任务的成功运行),可一键显示; 标题不再用 ID;耗时移入技术信息;< 1 秒写「< 1 秒」;费用两位小数,不足一分写「< $0.01」。 下载盘相关文案改用「暂停线 / 恢复线」。 Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
f0d4935fa7
commit
260f3ab27f
23 files changed
+559
-816
No files matched your search
@@ -1,12 +1,11 @@
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { SCHEDULE_CHANGED_EVENT, type ScheduledJob } from "@umt/contracts";
|
||||
import { AlertTriangle, Bot, CalendarClock, CircleCheck, Coins, LoaderCircle, Plus, XCircle } from "lucide-react";
|
||||
import { AlertTriangle, Bot, CalendarClock, Plus } from "lucide-react";
|
||||
import { type ReactNode, useEffect, useRef, useState } from "react";
|
||||
import { useLocation } from "react-router";
|
||||
import { usePiUpstreamModels, usePiUpstreams } from "~/api/pi.ts";
|
||||
import { useScheduleBoard } from "~/api/schedules.ts";
|
||||
import { EmptyState, ErrorState, LoadingState } from "~/components/console/empty-state.tsx";
|
||||
import { StatTile } from "~/components/console/stat-tile.tsx";
|
||||
import { Button } from "~/components/ui/button.tsx";
|
||||
import { qk } from "~/lib/query-keys.ts";
|
||||
import { useSse } from "~/lib/sse.ts";
|
||||
@@ -16,8 +15,9 @@ import { AutoTaskList } from "./auto-task-list.tsx";
|
||||
import { formatUsd } from "./labels.ts";
|
||||
import { RunsSheet } from "./runs-sheet.tsx";
|
||||
|
||||
// 计划页(FE-6,/console/schedules):「自动任务」= 程序按固定周期执行的定时任务(紧凑列表);
|
||||
// 「Agent 巡检」= 到点发起 Agent 会话的定时任务(卡片)。自动任务的运行记录跳任务页按该定时任务筛选。
|
||||
// 计划页(FE-6,/console/schedules):「智能体巡检」= 到点发起智能体会话的定时任务(卡片,放最上面:
|
||||
// 数量少、由用户自己建、会花钱,是这页最需要看的);「自动任务」= 程序按固定周期执行的定时任务
|
||||
// (按分类折叠的紧凑列表,维护类合成「数据清理」)。自动任务的运行记录跳任务页按该定时任务筛选。
|
||||
|
||||
/** 每秒一跳的当前时间(倒计时用) */
|
||||
export function useNow(ms = 1000) {
|
||||
@@ -69,7 +69,17 @@ export function planStats(items: ScheduledJob[]) {
|
||||
};
|
||||
}
|
||||
|
||||
function SectionHead({ id, title, hint, actions }: { id: string; title: string; hint: string; actions?: ReactNode }) {
|
||||
function SectionHead({
|
||||
id,
|
||||
title,
|
||||
hint,
|
||||
actions,
|
||||
}: {
|
||||
id: string;
|
||||
title: string;
|
||||
hint: ReactNode;
|
||||
actions?: ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<div className="mb-3 flex flex-wrap items-end gap-x-3 gap-y-2">
|
||||
<div className="min-w-0">
|
||||
@@ -107,7 +117,6 @@ export function PlanBoard({ highlightJob }: { highlightJob?: string }) {
|
||||
}
|
||||
: null;
|
||||
const s = planStats(items);
|
||||
const val = (n: number) => (board.isPending ? "…" : board.isError ? "—" : n);
|
||||
|
||||
// 首页计数链到 #auto / #checks:数据到了再滚过去
|
||||
const loaded = !!data;
|
||||
@@ -117,59 +126,8 @@ export function PlanBoard({ highlightJob }: { highlightJob?: string }) {
|
||||
if (id === "auto" || id === "checks") document.getElementById(id)?.scrollIntoView({ block: "start" });
|
||||
}, [loaded, hash, highlightJob]);
|
||||
|
||||
const jump = (id: string) => document.getElementById(id)?.scrollIntoView({ block: "start", behavior: "smooth" });
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mb-4 grid gap-3 lg:grid-cols-2">
|
||||
<div className="grid gap-1.5">
|
||||
<p className="px-1 text-meta font-medium text-muted-foreground">自动任务</p>
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
<StatTile
|
||||
label="已启用"
|
||||
icon={CircleCheck}
|
||||
value={val(s.auto.enabled)}
|
||||
hint={board.isPending ? undefined : `共 ${s.auto.total} 项`}
|
||||
onClick={() => jump("auto")}
|
||||
/>
|
||||
<StatTile
|
||||
label="上次失败"
|
||||
icon={XCircle}
|
||||
tone={s.auto.failed > 0 ? "destructive" : "neutral"}
|
||||
value={val(s.auto.failed)}
|
||||
onClick={() => jump("auto")}
|
||||
/>
|
||||
<StatTile label="运行中" icon={LoaderCircle} value={val(s.auto.running)} onClick={() => jump("auto")} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid gap-1.5">
|
||||
<p className="px-1 text-meta font-medium text-muted-foreground">智能体巡检</p>
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
<StatTile
|
||||
label="已启用"
|
||||
icon={Bot}
|
||||
value={val(s.checks.enabled)}
|
||||
hint={board.isPending ? undefined : `共 ${s.checks.total} 项`}
|
||||
onClick={() => jump("checks")}
|
||||
/>
|
||||
<StatTile
|
||||
label="上次失败"
|
||||
icon={XCircle}
|
||||
tone={s.checks.failed > 0 ? "destructive" : "neutral"}
|
||||
value={val(s.checks.failed)}
|
||||
onClick={() => jump("checks")}
|
||||
/>
|
||||
<StatTile
|
||||
label="近 7 天费用"
|
||||
icon={Coins}
|
||||
value={data ? formatUsd(s.checks.weekCost) : "…"}
|
||||
hint={data ? `${s.checks.weekRuns} 次会话` : undefined}
|
||||
onClick={() => jump("checks")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{data && !data.schedulerOnline ? (
|
||||
<p
|
||||
role="status"
|
||||
@@ -186,24 +144,21 @@ export function PlanBoard({ highlightJob }: { highlightJob?: string }) {
|
||||
<ErrorState error={board.error} onRetry={() => board.refetch()} />
|
||||
) : (
|
||||
<div className="grid gap-8">
|
||||
<section aria-labelledby="auto">
|
||||
<SectionHead
|
||||
id="auto"
|
||||
title="自动任务"
|
||||
hint="程序按固定周期执行,不调用模型。间隔可直接改,改过的可恢复默认。"
|
||||
/>
|
||||
{autoJobs.length === 0 ? (
|
||||
<EmptyState icon={CalendarClock} title="还没有自动任务" description="worker 启动后自动登记。" />
|
||||
) : (
|
||||
<AutoTaskList jobs={autoJobs} now={now} highlightJob={highlightJob} />
|
||||
)}
|
||||
</section>
|
||||
|
||||
<section aria-labelledby="checks">
|
||||
<SectionHead
|
||||
id="checks"
|
||||
title="智能体巡检"
|
||||
hint="到点按目标发起一次智能体会话,只在能力白名单和预算内执行;触发条件不满足时跳过,不调用模型。"
|
||||
hint={
|
||||
s.checks.total
|
||||
? [
|
||||
`${s.checks.enabled} 个启用`,
|
||||
s.checks.failed ? `${s.checks.failed} 个上次失败` : null,
|
||||
s.checks.weekRuns ? `近 7 天 ${s.checks.weekRuns} 次,${formatUsd(s.checks.weekCost)}` : null,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" · ")
|
||||
: "到点让智能体按目标检查并汇报。"
|
||||
}
|
||||
actions={
|
||||
<Button size="sm" onClick={() => setEditing({ job: null, key: Date.now() })}>
|
||||
<Plus />
|
||||
@@ -229,6 +184,24 @@ export function PlanBoard({ highlightJob }: { highlightJob?: string }) {
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
<section aria-labelledby="auto">
|
||||
<SectionHead
|
||||
id="auto"
|
||||
title="自动任务"
|
||||
hint={[
|
||||
`${s.auto.enabled} 个启用`,
|
||||
s.auto.running ? `${s.auto.running} 个运行中` : null,
|
||||
s.auto.failed ? `${s.auto.failed} 个上次失败` : null,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" · ")}
|
||||
/>
|
||||
{autoJobs.length === 0 ? (
|
||||
<EmptyState icon={CalendarClock} title="还没有自动任务" description="worker 启动后自动登记。" />
|
||||
) : (
|
||||
<AutoTaskList jobs={autoJobs} now={now} highlightJob={highlightJob} />
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
Reference in new issue
Block a user