fix(ui): remove automated document spam and clean up Runs page
Remove "Create status report on schedule" default automation rule that was creating empty draft documents every 60s. Simplify Runs page by removing 3 summary cards and replacing 10-card metadata grid with compact row layout. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
b6d92ac634
commit
144f35463b
3 files changed
+48
-87
No files matched your search
@@ -87,14 +87,6 @@ export const DEFAULT_AUTOMATION_RULES: ReadonlyArray<{
|
||||
actionConfigJson: JSON.stringify({ meetingType: "retrospective", title: "Sprint Retrospective", durationMinutes: 45 }),
|
||||
priority: 55,
|
||||
},
|
||||
{
|
||||
name: "Create status report on schedule",
|
||||
description: "Periodically create a status report document for the company.",
|
||||
trigger: "schedule",
|
||||
action: "create_document",
|
||||
actionConfigJson: JSON.stringify({ documentType: "status_report", title: "Automated Status Report" }),
|
||||
priority: 30,
|
||||
},
|
||||
{
|
||||
name: "Auto-assign work to idle agents",
|
||||
description: "When an agent has been idle for 10+ minutes, automatically assign available unassigned tasks from the backlog.",
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { ListChecks, PlayCircle, WarningCircle } from "@phosphor-icons/react";
|
||||
import type { AgentRecord, RunRecord, TaskRecord, WorkspaceRecord } from "@shared/types";
|
||||
import { EmptyState } from "../ui";
|
||||
import { useT } from "../../i18n";
|
||||
import { LOG_CHUNK_BYTES, initialLogState, isActiveStatus, isAttentionStatus, RunSummaryCard } from "./run-helpers";
|
||||
import { LOG_CHUNK_BYTES, initialLogState, isActiveStatus } from "./run-helpers";
|
||||
import type { RunLogState } from "./run-helpers";
|
||||
import { RunQueue } from "./RunQueue";
|
||||
import { RunDetail } from "./RunDetail";
|
||||
@@ -70,16 +69,6 @@ export function RunCenter({
|
||||
});
|
||||
}, [agentMap, runs, search, statusFilter, taskMap, workspaceMap]);
|
||||
|
||||
const groupedCounts = useMemo(() => {
|
||||
let active = 0, attention = 0, completed = 0;
|
||||
for (const run of runs) {
|
||||
if (isActiveStatus(run.status)) active += 1;
|
||||
else if (isAttentionStatus(run.status)) attention += 1;
|
||||
else completed += 1;
|
||||
}
|
||||
return { active, attention, completed };
|
||||
}, [runs]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedRunId && !runs.some((run) => run.id === selectedRunId)) {
|
||||
const fallbackRun = runs[0];
|
||||
@@ -229,60 +218,36 @@ export function RunCenter({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-10">
|
||||
<div className="grid gap-3 md:grid-cols-3">
|
||||
<RunSummaryCard
|
||||
label={t("runs.activeLabel")}
|
||||
value={groupedCounts.active}
|
||||
detail={t("runs.queuedOrRunning")}
|
||||
icon={PlayCircle}
|
||||
/>
|
||||
<RunSummaryCard
|
||||
label={t("runs.needsAttention")}
|
||||
value={groupedCounts.attention}
|
||||
detail={t("runs.failedTimedInterrupted")}
|
||||
icon={WarningCircle}
|
||||
tone="danger"
|
||||
/>
|
||||
<RunSummaryCard
|
||||
label={t("runs.completedLabel")}
|
||||
value={groupedCounts.completed}
|
||||
detail={t("runs.finishedWithout")}
|
||||
icon={ListChecks}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-8 xl:grid-cols-[minmax(0,420px)_minmax(0,1fr)]">
|
||||
<RunQueue
|
||||
filteredRuns={filteredRuns}
|
||||
selectedRun={selectedRun}
|
||||
taskMap={taskMap}
|
||||
agentMap={agentMap}
|
||||
workspaceMap={workspaceMap}
|
||||
statusFilter={statusFilter}
|
||||
setStatusFilter={setStatusFilter}
|
||||
search={search}
|
||||
setSearch={setSearch}
|
||||
onSelectRun={onSelectRun}
|
||||
t={t}
|
||||
/>
|
||||
|
||||
<div className="grid gap-8 xl:grid-cols-[minmax(0,420px)_minmax(0,1fr)]">
|
||||
<RunQueue
|
||||
filteredRuns={filteredRuns}
|
||||
{selectedRun ? (
|
||||
<RunDetail
|
||||
selectedRun={selectedRun}
|
||||
taskMap={taskMap}
|
||||
agentMap={agentMap}
|
||||
workspaceMap={workspaceMap}
|
||||
statusFilter={statusFilter}
|
||||
setStatusFilter={setStatusFilter}
|
||||
search={search}
|
||||
setSearch={setSearch}
|
||||
onSelectRun={onSelectRun}
|
||||
logState={logState}
|
||||
onOpenConsole={onOpenConsole}
|
||||
onCancelRun={onCancelRun}
|
||||
onOpenLogPath={onOpenLogPath}
|
||||
onReloadLog={() => setLogReloadKey((current) => current + 1)}
|
||||
onLoadOlderLogs={() => void loadOlderLogs()}
|
||||
t={t}
|
||||
/>
|
||||
|
||||
{selectedRun ? (
|
||||
<RunDetail
|
||||
selectedRun={selectedRun}
|
||||
taskMap={taskMap}
|
||||
agentMap={agentMap}
|
||||
workspaceMap={workspaceMap}
|
||||
logState={logState}
|
||||
onOpenConsole={onOpenConsole}
|
||||
onCancelRun={onCancelRun}
|
||||
onOpenLogPath={onOpenLogPath}
|
||||
onReloadLog={() => setLogReloadKey((current) => current + 1)}
|
||||
onLoadOlderLogs={() => void loadOlderLogs()}
|
||||
t={t}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import type { AgentRecord, RunRecord, TaskRecord, WorkspaceRecord } from "@share
|
||||
import { formatMoney, formatTime } from "../../lib/formatters";
|
||||
import { ActionButton, Panel, StatusPill } from "../ui";
|
||||
import type { TranslationFn } from "../../i18n";
|
||||
import { RunMeta, isActiveStatus } from "./run-helpers";
|
||||
import { isActiveStatus } from "./run-helpers";
|
||||
import type { RunLogState } from "./run-helpers";
|
||||
|
||||
interface RunDetailProps {
|
||||
@@ -26,11 +26,19 @@ interface RunDetailProps {
|
||||
t: TranslationFn;
|
||||
}
|
||||
|
||||
function DetailRow({ label, children }: { label: string; children: React.ReactNode }) {
|
||||
return (
|
||||
<div className="flex items-center justify-between py-2">
|
||||
<span className="text-[12px] text-[color:var(--muted)]">{label}</span>
|
||||
<span className="text-[13px] text-[color:var(--text)]">{children}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function RunDetail({
|
||||
selectedRun,
|
||||
taskMap,
|
||||
agentMap,
|
||||
workspaceMap,
|
||||
logState,
|
||||
onOpenConsole,
|
||||
onCancelRun,
|
||||
@@ -39,10 +47,13 @@ export function RunDetail({
|
||||
onLoadOlderLogs,
|
||||
t,
|
||||
}: RunDetailProps) {
|
||||
const task = taskMap.get(selectedRun.taskId);
|
||||
const agent = agentMap.get(selectedRun.agentId);
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<Panel
|
||||
title={t("runs.runDetail")}
|
||||
title={task?.title ?? selectedRun.summary ?? t("runs.runDetail")}
|
||||
action={(
|
||||
<div className="flex items-center gap-2">
|
||||
<ActionButton
|
||||
@@ -61,25 +72,22 @@ export function RunDetail({
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
<RunMeta label="Status" value={<StatusPill status={selectedRun.status} />} />
|
||||
<RunMeta label="Connector" value={selectedRun.connectorId} />
|
||||
<RunMeta label="Task" value={taskMap.get(selectedRun.taskId)?.title ?? selectedRun.taskId} />
|
||||
<RunMeta label="Agent" value={agentMap.get(selectedRun.agentId)?.name ?? selectedRun.agentId} />
|
||||
<RunMeta label="Workspace" value={selectedRun.workspaceId ? (workspaceMap.get(selectedRun.workspaceId)?.name ?? selectedRun.workspaceId) : "No exclusive workspace lock"} />
|
||||
<RunMeta label="Model" value={selectedRun.model ?? "Connector default"} />
|
||||
<RunMeta label="Started" value={formatTime(selectedRun.startedAt)} />
|
||||
<RunMeta label="Finished" value={formatTime(selectedRun.finishedAt)} />
|
||||
<RunMeta label="Cost" value={formatMoney(selectedRun.costUsd)} />
|
||||
<RunMeta label="Log bytes" value={logState.totalBytes.toLocaleString()} />
|
||||
<div className="divide-y divide-[color:var(--line)]">
|
||||
<DetailRow label="Status"><StatusPill status={selectedRun.status} /></DetailRow>
|
||||
<DetailRow label="Agent">{agent?.name ?? selectedRun.agentId}</DetailRow>
|
||||
<DetailRow label="Connector">{selectedRun.connectorId}</DetailRow>
|
||||
{selectedRun.model ? <DetailRow label="Model">{selectedRun.model}</DetailRow> : null}
|
||||
<DetailRow label="Started">{formatTime(selectedRun.startedAt)}</DetailRow>
|
||||
{selectedRun.finishedAt ? <DetailRow label="Finished">{formatTime(selectedRun.finishedAt)}</DetailRow> : null}
|
||||
<DetailRow label="Cost">{formatMoney(selectedRun.costUsd)}</DetailRow>
|
||||
</div>
|
||||
{selectedRun.errorMessage ? (
|
||||
<div className="mt-5 rounded-[10px] border border-[color:var(--danger)] bg-[color:var(--danger-soft)] px-4 py-3 text-[13px] text-[color:var(--danger)]">
|
||||
<div className="mt-4 rounded-[10px] border border-[color:var(--danger)] bg-[color:var(--danger-soft)] px-4 py-3 text-[13px] text-[color:var(--danger)]">
|
||||
{selectedRun.errorMessage}
|
||||
</div>
|
||||
) : null}
|
||||
{selectedRun.logPath ? (
|
||||
<div className="mt-5">
|
||||
<div className="mt-4">
|
||||
<ActionButton
|
||||
label={t("runs.openLogFile")}
|
||||
icon={FolderOpen}
|
||||
@@ -91,7 +99,6 @@ export function RunDetail({
|
||||
|
||||
<Panel
|
||||
title={t("runs.logTail")}
|
||||
eyebrow={t("runs.incrementalReader")}
|
||||
action={(
|
||||
<div className="flex items-center gap-2">
|
||||
<ActionButton
|
||||
@@ -116,9 +123,6 @@ export function RunDetail({
|
||||
</div>
|
||||
) : null}
|
||||
<div className="mb-3 flex items-center justify-between text-[11px] text-[color:var(--muted)]">
|
||||
<span>
|
||||
Showing bytes {logState.offset.toLocaleString()} to {logState.nextOffset.toLocaleString()} of {logState.totalBytes.toLocaleString()}
|
||||
</span>
|
||||
<span>{logState.loading ? "Refreshing\u2026" : logState.eof ? "At latest tail" : "Tail window loaded"}</span>
|
||||
</div>
|
||||
<div className="max-h-[420px] overflow-auto rounded-[10px] border border-[color:var(--line)] bg-[color:var(--panel)] px-4 py-3">
|
||||
|
||||
Reference in new issue
Block a user