Refactor pipeline task handling and UI flows

This commit is contained in:
2026-06-13 21:09:49 +02:00
parent 21c7e4c9a4
commit ff4c572157
24 changed files with 1346 additions and 236 deletions

View File

@@ -2,12 +2,13 @@
import { useMemo, useState } from "react";
import { useQuery } from "convex/react";
import { useMutation, useQuery } from "convex/react";
import { FunctionReturnType } from "convex/server";
import { Activity, Files, FileSearch, SquarePen } from "lucide-react";
import { Activity, Files, FileSearch, RotateCcw, SquarePen } from "lucide-react";
import Link from "next/link";
import { api } from "@/convex/_generated/api";
import type { Id } from "@/convex/_generated/dataModel";
import { Skeleton } from "@/components/ui/skeleton";
import { Badge } from "@/components/ui/badge";
import {
@@ -96,7 +97,9 @@ function AuditsBoardLoading() {
export function AuditsBoard() {
const dashboardRows = useQuery(api.audits.listDashboardRows, { limit: 100 });
const retryAuditRun = useMutation(api.audits.retryAuditRun);
const [activeFilter, setActiveFilter] = useState<AuditStatusFilter>("all");
const [retryingRunId, setRetryingRunId] = useState<string | null>(null);
const rows = useMemo(() => {
if (!dashboardRows) {
return [];
@@ -141,6 +144,14 @@ export function AuditsBoard() {
{ label: "Pipeline", value: "generation", count: statusCounts.generation },
{ label: "Fehlgeschlagen", value: "failed", count: statusCounts.failed },
];
const handleRetryAudit = async (runId: Id<"agentRuns">) => {
setRetryingRunId(runId);
try {
await retryAuditRun({ runId });
} finally {
setRetryingRunId(null);
}
};
if (dashboardRows === undefined) {
return <AuditsBoardLoading />;
@@ -260,6 +271,41 @@ export function AuditsBoard() {
{row.errorSummary}
</p>
) : null}
{row.kind === "generation" ? (
<div className="min-w-0 rounded-md border border-border/75 bg-background/60 p-3">
<div className="flex items-center justify-between gap-3 text-xs">
<span className="font-medium text-muted-foreground">
{row.progress.step}/{row.progress.total} · {row.progress.label}
</span>
<span className="tabular-nums text-muted-foreground">
{row.progress.percent}%
</span>
</div>
<div
aria-label={row.progress.label}
aria-valuemax={100}
aria-valuemin={0}
aria-valuenow={row.progress.percent}
className="mt-2 h-2 overflow-hidden rounded-full bg-muted"
role="progressbar"
>
<div
className="h-full rounded-full bg-primary transition-all"
style={{ width: `${row.progress.percent}%` }}
/>
</div>
{row.retry.isRetrying ? (
<p className="mt-2 text-xs text-muted-foreground">
Versuch {row.retry.attempt}/{row.retry.maxAttempts} läuft
</p>
) : null}
{row.retry.isRetrying && row.retry.lastRetryReason ? (
<p className="mt-1 text-xs text-muted-foreground">
{row.retry.lastRetryReason}
</p>
) : null}
</div>
) : null}
</div>
<div className="mt-auto flex justify-end">
@@ -271,6 +317,18 @@ export function AuditsBoard() {
<SquarePen className="size-4" aria-hidden="true" />
Öffnen
</Link>
) : row.canRetry ? (
<button
className="inline-flex min-h-8 items-center gap-1 rounded-md px-2 text-sm font-semibold text-primary hover:bg-muted disabled:cursor-not-allowed disabled:opacity-60"
disabled={retryingRunId === row.runId}
onClick={() => {
void handleRetryAudit(row.runId);
}}
type="button"
>
<RotateCcw className="size-4" aria-hidden="true" />
Erneut starten
</button>
) : (
<span className="inline-flex min-h-8 items-center text-sm text-muted-foreground">
Pipeline läuft