open-swe/ui/src/components/agents/WorkflowApprovalCard.tsx

139 lines
4.4 KiB
TypeScript
Raw Normal View History

import { Check, FileCode2, ShieldAlert, X } from "lucide-react"
import type { WorkflowApproval } from "@/lib/agents/api"
import { Button } from "@/components/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card"
type ApprovalStatus = "pending" | "approved" | "rejected" | string
interface WorkflowApprovalCardProps {
approval: WorkflowApproval
isOwner: boolean
isPending: boolean
onApprove: (fingerprint: string) => void
onReject: (fingerprint: string) => void
}
function statusBadgeClass(status: ApprovalStatus) {
switch (status) {
case "approved":
return "bg-green-500/10 text-green-600 dark:text-green-400"
case "rejected":
return "bg-red-500/10 text-red-600 dark:text-red-400"
default:
return "bg-amber-500/10 text-amber-600 dark:text-amber-400"
}
}
function statusLabel(status: ApprovalStatus) {
switch (status) {
case "approved":
return "Approved"
case "rejected":
return "Rejected"
default:
return "Pending approval"
}
}
export function WorkflowApprovalCard({
approval,
isOwner,
isPending,
onApprove,
onReject,
}: WorkflowApprovalCardProps) {
const actionable = isOwner && approval.status === "pending" && !isPending
const decided = approval.status !== "pending"
return (
<Card className="mx-auto mb-4 w-full max-w-3xl border-amber-500/20 bg-amber-500/5 dark:bg-amber-500/10">
<CardHeader className="pb-2">
<div className="flex items-start justify-between gap-3">
<div className="flex items-center gap-2">
<ShieldAlert className="size-4 text-amber-600 dark:text-amber-400" />
<CardTitle className="text-sm font-medium">
Workflow push approval required
</CardTitle>
</div>
<span
className={`rounded px-1.5 py-0.5 text-[10px] font-medium tracking-wide uppercase ${statusBadgeClass(
approval.status
)}`}
>
{statusLabel(approval.status)}
</span>
</div>
<CardDescription className="text-xs">
Open SWE is trying to push GitHub workflow file changes in{" "}
<strong>{approval.repo}</strong> on <strong>{approval.branch}</strong>
. Approve only if this exact workflow diff is expected.
</CardDescription>
</CardHeader>
<CardContent className="pb-3">
<div className="space-y-2">
<div className="flex items-center gap-2 text-[11px] font-medium text-[var(--ui-text-muted)]">
<FileCode2 className="size-3.5" />
Changed workflow files
</div>
<ul className="space-y-1">
{approval.files.map((file) => (
<li
key={file}
className="font-mono text-[11px] break-all text-[var(--ui-text)]"
>
{file}
</li>
))}
</ul>
<div className="pt-1 text-[11px] text-[var(--ui-text-muted)]">
Fingerprint:{" "}
<code className="rounded bg-[var(--ui-panel)] px-1 py-0.5 text-[var(--ui-text)]">
{approval.fingerprint}
</code>
</div>
</div>
</CardContent>
{actionable ? (
<CardFooter className="gap-2 border-t border-[var(--ui-border)] pt-3">
<Button
size="sm"
onClick={() => onApprove(approval.fingerprint)}
disabled={isPending}
>
<Check className="mr-1 size-3.5" />
Approve
</Button>
<Button
size="sm"
variant="destructive"
onClick={() => onReject(approval.fingerprint)}
disabled={isPending}
>
<X className="mr-1 size-3.5" />
Reject
</Button>
</CardFooter>
) : decided ? (
<CardFooter className="border-t border-[var(--ui-border)] pt-3 text-[11px] text-[var(--ui-text-muted)]">
This workflow push has been {approval.status}. If the workflow files
change, a new fingerprint will be required.
</CardFooter>
) : null}
{!isOwner && approval.status === "pending" && (
<CardFooter className="border-t border-[var(--ui-border)] pt-3 text-[11px] text-[var(--ui-text-muted)]">
Only the thread owner can approve or reject this workflow push.
</CardFooter>
)}
</Card>
)
}