fix(webui): reduce automation dashboard copy

This commit is contained in:
chengyongru 2026-06-14 01:03:53 +08:00
parent 17e3183598
commit 5f2f694034
12 changed files with 60 additions and 94 deletions

View File

@ -1264,18 +1264,6 @@ export function SettingsView({
} }
}; };
const refreshAutomations = async () => {
setAutomationsLoading(true);
setAutomationsError(null);
try {
setAutomations(await fetchAutomations(token));
} catch (err) {
setAutomationsError((err as Error).message);
} finally {
setAutomationsLoading(false);
}
};
const handleAutomationAction = async ( const handleAutomationAction = async (
action: AutomationAction, action: AutomationAction,
job: SessionAutomationJob, job: SessionAutomationJob,
@ -1585,7 +1573,6 @@ export function SettingsView({
error={automationsError} error={automationsError}
onQueryChange={setAutomationsQuery} onQueryChange={setAutomationsQuery}
onFilterChange={setAutomationsFilter} onFilterChange={setAutomationsFilter}
onRefresh={refreshAutomations}
onAction={handleAutomationAction} onAction={handleAutomationAction}
onRequestDelete={setAutomationPendingDelete} onRequestDelete={setAutomationPendingDelete}
/> />
@ -3350,7 +3337,6 @@ function AutomationsSettings({
error, error,
onQueryChange, onQueryChange,
onFilterChange, onFilterChange,
onRefresh,
onAction, onAction,
onRequestDelete, onRequestDelete,
}: { }: {
@ -3362,7 +3348,6 @@ function AutomationsSettings({
error: string | null; error: string | null;
onQueryChange: (value: string) => void; onQueryChange: (value: string) => void;
onFilterChange: (value: AutomationFilter) => void; onFilterChange: (value: AutomationFilter) => void;
onRefresh: () => void;
onAction: (action: AutomationAction, job: SessionAutomationJob) => void | Promise<void>; onAction: (action: AutomationAction, job: SessionAutomationJob) => void | Promise<void>;
onRequestDelete: (job: SessionAutomationJob) => void; onRequestDelete: (job: SessionAutomationJob) => void;
}) { }) {
@ -3389,38 +3374,16 @@ function AutomationsSettings({
return ( return (
<div className="space-y-5"> <div className="space-y-5">
<section className="rounded-[24px] border border-border/50 bg-card/82 px-4 py-4 shadow-[0_18px_65px_rgba(15,23,42,0.07)] backdrop-blur-xl sm:px-5"> <section className="rounded-[24px] border border-border/50 bg-card/82 px-4 py-4 shadow-[0_18px_65px_rgba(15,23,42,0.07)] backdrop-blur-xl sm:px-5">
<div className="flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between"> <div className="flex justify-end">
<p className="max-w-[38rem] text-[13px] leading-6 text-muted-foreground"> <Button asChild className="h-9 rounded-full px-3 text-[13px]">
{tx( <a href="#/new">
"settings.automations.description", <Plus className="mr-2 h-3.5 w-3.5" aria-hidden />
"Review cron reminders, recurring agent turns, one-time jobs, and protected system jobs.", {tx("settings.automations.newInChat", "New in chat")}
)} </a>
</p> </Button>
<div className="flex shrink-0 flex-wrap gap-2">
<Button
type="button"
variant="outline"
onClick={onRefresh}
disabled={loading}
className="h-9 rounded-full px-3 text-[13px]"
>
{loading ? (
<Loader2 className="mr-2 h-3.5 w-3.5 animate-spin" aria-hidden />
) : (
<RotateCcw className="mr-2 h-3.5 w-3.5" aria-hidden />
)}
{tx("settings.automations.refresh", "Refresh")}
</Button>
<Button asChild className="h-9 rounded-full px-3 text-[13px]">
<a href="#/new">
<Plus className="mr-2 h-3.5 w-3.5" aria-hidden />
{tx("settings.automations.newInChat", "New in chat")}
</a>
</Button>
</div>
</div> </div>
<div className="mt-5 grid gap-2 sm:grid-cols-4"> <div className="mt-4 grid gap-2 sm:grid-cols-4">
<AutomationStat label={tx("settings.automations.stats.active", "Active")} value={activeCount} /> <AutomationStat label={tx("settings.automations.stats.active", "Active")} value={activeCount} />
<AutomationStat label={tx("settings.automations.stats.paused", "Paused")} value={pausedCount} /> <AutomationStat label={tx("settings.automations.stats.paused", "Paused")} value={pausedCount} />
<AutomationStat label={tx("settings.automations.stats.failed", "Failed")} value={failedCount} /> <AutomationStat label={tx("settings.automations.stats.failed", "Failed")} value={failedCount} />
@ -3569,23 +3532,36 @@ function AutomationRow({
) : null} ) : null}
{history.length ? ( {history.length ? (
<div className="mt-3 flex flex-wrap gap-1.5"> <div className="mt-3">
{history.slice(-4).map((record) => ( <div className="mb-1.5 text-[11px] font-medium uppercase tracking-normal text-muted-foreground">
<span {tx("settings.automations.history.recent", "Recent runs")}
key={`${record.run_at_ms}:${record.status}`} </div>
className={cn( <div className="flex flex-wrap gap-1.5">
"rounded-full px-2 py-1 text-[11px]", {history.slice(-4).map((record) => {
record.status === "error" const statusLabel = automationRunStatusLabel(record.status, tx);
? "bg-destructive/10 text-destructive" const duration = formatAutomationRunDuration(record.duration_ms, locale, tx);
: record.status === "skipped" const visibleLabel = record.status === "ok" ? duration : `${statusLabel} · ${duration}`;
? "bg-amber-500/10 text-amber-700 dark:text-amber-300" const detail = record.error || fmtDateTime(record.run_at_ms, locale);
: "bg-emerald-500/10 text-emerald-700 dark:text-emerald-300", const accessibleLabel = `${statusLabel} · ${duration} · ${detail}`;
)} return (
title={record.error || fmtDateTime(record.run_at_ms, locale)} <span
> key={`${record.run_at_ms}:${record.status}`}
{automationRunStatusLabel(record.status, tx)} · {formatAutomationRunDuration(record.duration_ms, locale, tx)} className={cn(
</span> "rounded-full px-2 py-1 text-[11px]",
))} record.status === "error"
? "bg-destructive/10 text-destructive"
: record.status === "skipped"
? "bg-amber-500/10 text-amber-700 dark:text-amber-300"
: "bg-emerald-500/10 text-emerald-700 dark:text-emerald-300",
)}
title={accessibleLabel}
aria-label={accessibleLabel}
>
{visibleLabel}
</span>
);
})}
</div>
</div> </div>
) : null} ) : null}
</div> </div>

View File

@ -466,8 +466,6 @@
"automations": { "automations": {
"kicker": "Workspace automations", "kicker": "Workspace automations",
"title": "Automations", "title": "Automations",
"description": "Review cron reminders, recurring agent turns, one-time jobs, and protected system jobs.",
"refresh": "Refresh",
"newInChat": "New in chat", "newInChat": "New in chat",
"stats": { "stats": {
"active": "Active", "active": "Active",
@ -551,7 +549,8 @@
"history": { "history": {
"ok": "Completed", "ok": "Completed",
"error": "Error", "error": "Error",
"skipped": "Skipped" "skipped": "Skipped",
"recent": "Recent runs"
}, },
"duration": { "duration": {
"lessThanSecond": "< 1 second" "lessThanSecond": "< 1 second"

View File

@ -466,8 +466,6 @@
"automations": { "automations": {
"kicker": "Automatizaciones del espacio", "kicker": "Automatizaciones del espacio",
"title": "Automatizaciones", "title": "Automatizaciones",
"description": "Revisa recordatorios cron, turnos recurrentes del agente, tareas de una vez y tareas protegidas del sistema.",
"refresh": "Actualizar",
"newInChat": "Crear en chat", "newInChat": "Crear en chat",
"stats": { "stats": {
"active": "Activas", "active": "Activas",
@ -551,7 +549,8 @@
"history": { "history": {
"ok": "Completada", "ok": "Completada",
"error": "Error", "error": "Error",
"skipped": "Omitida" "skipped": "Omitida",
"recent": "Ejecuciones recientes"
}, },
"duration": { "duration": {
"lessThanSecond": "menos de 1 segundo" "lessThanSecond": "menos de 1 segundo"

View File

@ -466,8 +466,6 @@
"automations": { "automations": {
"kicker": "Automatisations de lespace", "kicker": "Automatisations de lespace",
"title": "Automatisations", "title": "Automatisations",
"description": "Passez en revue les rappels cron, tours récurrents de lagent, tâches ponctuelles et tâches système protégées.",
"refresh": "Actualiser",
"newInChat": "Créer dans le chat", "newInChat": "Créer dans le chat",
"stats": { "stats": {
"active": "Actives", "active": "Actives",
@ -551,7 +549,8 @@
"history": { "history": {
"ok": "Terminée", "ok": "Terminée",
"error": "Erreur", "error": "Erreur",
"skipped": "Ignorée" "skipped": "Ignorée",
"recent": "Exécutions récentes"
}, },
"duration": { "duration": {
"lessThanSecond": "moins de 1 seconde" "lessThanSecond": "moins de 1 seconde"

View File

@ -466,8 +466,6 @@
"automations": { "automations": {
"kicker": "Otomasi ruang kerja", "kicker": "Otomasi ruang kerja",
"title": "Otomasi", "title": "Otomasi",
"description": "Tinjau pengingat cron, giliran agen berulang, tugas sekali jalan, dan tugas sistem terlindungi.",
"refresh": "Segarkan",
"newInChat": "Buat di chat", "newInChat": "Buat di chat",
"stats": { "stats": {
"active": "Aktif", "active": "Aktif",
@ -551,7 +549,8 @@
"history": { "history": {
"ok": "Selesai", "ok": "Selesai",
"error": "Error", "error": "Error",
"skipped": "Dilewati" "skipped": "Dilewati",
"recent": "Eksekusi terbaru"
}, },
"duration": { "duration": {
"lessThanSecond": "kurang dari 1 detik" "lessThanSecond": "kurang dari 1 detik"

View File

@ -466,8 +466,6 @@
"automations": { "automations": {
"kicker": "ワークスペースの自動タスク", "kicker": "ワークスペースの自動タスク",
"title": "自動タスク", "title": "自動タスク",
"description": "cron リマインダー、定期的な agent ターン、一回限りのタスク、保護されたシステムタスクを確認できます。",
"refresh": "更新",
"newInChat": "チャットで作成", "newInChat": "チャットで作成",
"stats": { "stats": {
"active": "実行中", "active": "実行中",
@ -551,7 +549,8 @@
"history": { "history": {
"ok": "完了", "ok": "完了",
"error": "エラー", "error": "エラー",
"skipped": "スキップ" "skipped": "スキップ",
"recent": "最近の実行"
}, },
"duration": { "duration": {
"lessThanSecond": "1 秒未満" "lessThanSecond": "1 秒未満"

View File

@ -466,8 +466,6 @@
"automations": { "automations": {
"kicker": "작업 공간 자동화", "kicker": "작업 공간 자동화",
"title": "자동화", "title": "자동화",
"description": "cron 알림, 반복 agent 작업, 일회성 작업, 보호된 시스템 작업을 확인합니다.",
"refresh": "새로 고침",
"newInChat": "채팅에서 만들기", "newInChat": "채팅에서 만들기",
"stats": { "stats": {
"active": "활성", "active": "활성",
@ -551,7 +549,8 @@
"history": { "history": {
"ok": "완료", "ok": "완료",
"error": "오류", "error": "오류",
"skipped": "건너뜀" "skipped": "건너뜀",
"recent": "최근 실행"
}, },
"duration": { "duration": {
"lessThanSecond": "1초 미만" "lessThanSecond": "1초 미만"

View File

@ -466,8 +466,6 @@
"automations": { "automations": {
"kicker": "Tự động hóa không gian làm việc", "kicker": "Tự động hóa không gian làm việc",
"title": "Tự động hóa", "title": "Tự động hóa",
"description": "Xem nhắc nhở cron, lượt agent định kỳ, tác vụ một lần và tác vụ hệ thống được bảo vệ.",
"refresh": "Làm mới",
"newInChat": "Tạo trong chat", "newInChat": "Tạo trong chat",
"stats": { "stats": {
"active": "Đang chạy", "active": "Đang chạy",
@ -551,7 +549,8 @@
"history": { "history": {
"ok": "Hoàn tất", "ok": "Hoàn tất",
"error": "Lỗi", "error": "Lỗi",
"skipped": "Đã bỏ qua" "skipped": "Đã bỏ qua",
"recent": "Lượt chạy gần đây"
}, },
"duration": { "duration": {
"lessThanSecond": "dưới 1 giây" "lessThanSecond": "dưới 1 giây"

View File

@ -466,8 +466,6 @@
"automations": { "automations": {
"kicker": "工作区自动任务", "kicker": "工作区自动任务",
"title": "自动任务", "title": "自动任务",
"description": "统一查看 cron 提醒、周期性 agent 任务、一次性任务和系统任务。",
"refresh": "刷新",
"newInChat": "在聊天中创建", "newInChat": "在聊天中创建",
"stats": { "stats": {
"active": "运行中", "active": "运行中",
@ -551,7 +549,8 @@
"history": { "history": {
"ok": "完成", "ok": "完成",
"error": "错误", "error": "错误",
"skipped": "已跳过" "skipped": "已跳过",
"recent": "最近运行"
}, },
"duration": { "duration": {
"lessThanSecond": "不到 1 秒" "lessThanSecond": "不到 1 秒"

View File

@ -466,8 +466,6 @@
"automations": { "automations": {
"kicker": "工作區自動任務", "kicker": "工作區自動任務",
"title": "自動任務", "title": "自動任務",
"description": "集中查看 cron 提醒、週期性 agent 任務、一次性任務和系統任務。",
"refresh": "重新整理",
"newInChat": "在聊天中建立", "newInChat": "在聊天中建立",
"stats": { "stats": {
"active": "執行中", "active": "執行中",
@ -551,7 +549,8 @@
"history": { "history": {
"ok": "完成", "ok": "完成",
"error": "錯誤", "error": "錯誤",
"skipped": "已略過" "skipped": "已略過",
"recent": "最近執行"
}, },
"duration": { "duration": {
"lessThanSecond": "不到 1 秒" "lessThanSecond": "不到 1 秒"

View File

@ -506,14 +506,14 @@ describe("App layout", () => {
fireEvent.click(within(sidebar).getByRole("button", { name: "自动任务" })); fireEvent.click(within(sidebar).getByRole("button", { name: "自动任务" }));
expect(await screen.findByRole("heading", { name: "自动任务" })).toBeInTheDocument(); expect(await screen.findByRole("heading", { name: "自动任务" })).toBeInTheDocument();
expect(screen.getByText("统一查看 cron 提醒、周期性 agent 任务、一次性任务和系统任务。")).toBeInTheDocument();
expect(screen.getByRole("button", { name: "刷新" })).toBeInTheDocument();
expect(screen.getByText("任务队列")).toBeInTheDocument(); expect(screen.getByText("任务队列")).toBeInTheDocument();
expect(screen.getByText("每日检查")).toBeInTheDocument(); expect(screen.getByText("每日检查")).toBeInTheDocument();
expect(screen.getByText("检查仓库状态")).toBeInTheDocument(); expect(screen.getByText("检查仓库状态")).toBeInTheDocument();
expect(screen.getByText("每 1天")).toBeInTheDocument(); expect(screen.getByText("每 1天")).toBeInTheDocument();
expect(screen.getByText("完成 · 不到 1 秒")).toBeInTheDocument(); expect(screen.getByText("最近运行")).toBeInTheDocument();
expect(screen.getByText("不到 1 秒")).toBeInTheDocument();
expect(screen.queryByText("Workspace automations")).not.toBeInTheDocument(); expect(screen.queryByText("Workspace automations")).not.toBeInTheDocument();
expect(screen.queryByRole("button", { name: "刷新" })).not.toBeInTheDocument();
expect(document.title).toBe("自动任务 · nanobot"); expect(document.title).toBe("自动任务 · nanobot");
}); });

View File

@ -49,8 +49,6 @@ const LOCALIZED_SETTINGS_COPY_KEYS = [
"settings.nav.advanced", "settings.nav.advanced",
"sidebar.automations", "sidebar.automations",
"settings.automations.title", "settings.automations.title",
"settings.automations.description",
"settings.automations.refresh",
"settings.automations.newInChat", "settings.automations.newInChat",
"settings.automations.filters.active", "settings.automations.filters.active",
"settings.automations.queue", "settings.automations.queue",
@ -59,6 +57,7 @@ const LOCALIZED_SETTINGS_COPY_KEYS = [
"settings.automations.labels.schedule", "settings.automations.labels.schedule",
"settings.automations.status.active", "settings.automations.status.active",
"settings.automations.history.ok", "settings.automations.history.ok",
"settings.automations.history.recent",
"settings.automations.duration.lessThanSecond", "settings.automations.duration.lessThanSecond",
"settings.automations.deleteTitle", "settings.automations.deleteTitle",
"settings.sections.interface", "settings.sections.interface",