open-swe/ui/src/routes/agents/reviews/index.tsx
Johannes du Plessis 876836bdfe
Normalize frontend typography + enable font smoothing (#1546)
Lighter, more consistent type across the dashboard and agent UI:
unify title sizes, drop semibold/bold in chrome to medium, harmonize
stray text-sm to text-xs, normalize chat markdown headings, and enable
antialiased font smoothing (fixes heavy text on dark backgrounds).

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-06-16 14:56:11 -07:00

206 lines
7.2 KiB
TypeScript

import { Link, createFileRoute } from "@tanstack/react-router"
import {
keepPreviousData,
useQuery,
useQueryClient,
} from "@tanstack/react-query"
import { useState } from "react"
import {
BugBeetleIcon,
FlagIcon,
GitPullRequestIcon,
} from "@phosphor-icons/react"
import type { ReviewSummary } from "@/lib/api"
import { Button } from "@/components/ui/button"
import { Skeleton } from "@/components/ui/skeleton"
import { api } from "@/lib/api"
import { useSession } from "@/lib/session"
import { cn } from "@/lib/utils"
export const Route = createFileRoute("/agents/reviews/")({
component: ReviewsPage,
})
function statusBadge(review: ReviewSummary) {
if (review.status === "running") {
return (
<span className="inline-flex items-center gap-1.5 text-xs text-[var(--ui-text-muted)]">
<span className="size-1.5 animate-pulse rounded-full bg-amber-500" />
Reviewing
</span>
)
}
if (review.status === "error") {
return <span className="text-xs text-[var(--ui-danger)]">Failed</span>
}
return null
}
function ReviewsPage() {
const session = useSession()
const queryClient = useQueryClient()
const [mine, setMine] = useState(true)
const [page, setPage] = useState(0)
const reviews = useQuery({
queryKey: ["reviews", mine, page],
queryFn: () => api.listReviews(page, mine),
enabled: !!session.data,
placeholderData: keepPreviousData,
refetchInterval: (query) =>
query.state.data?.reviews.some((r) => r.status === "running")
? 5000
: false,
})
const prefetch = (nextMine: boolean, nextPage: number) => {
if (nextPage < 0) return
void queryClient.prefetchQuery({
queryKey: ["reviews", nextMine, nextPage],
queryFn: () => api.listReviews(nextPage, nextMine),
})
}
const items = reviews.data?.reviews ?? []
return (
<main className="min-w-0 flex-1 overflow-y-auto">
<div className="mx-auto max-w-3xl px-6 py-8">
<h1 className="font-heading text-base font-medium text-[var(--ui-text)]">
PR Reviews
</h1>
<p className="mt-1 text-xs text-[var(--ui-text-muted)]">
Pull requests reviewed by Open SWE Review. Click into one for the full
analysis.
</p>
<div className="mt-6 flex items-center gap-1">
{(
[
[true, "My PRs"],
[false, "All"],
] as const
).map(([value, label]) => (
<button
key={label}
type="button"
onClick={() => {
setMine(value)
setPage(0)
}}
onPointerEnter={() => prefetch(value, 0)}
onFocus={() => prefetch(value, 0)}
className={cn(
"rounded-md px-2.5 py-1 text-xs transition-colors",
mine === value
? "bg-[var(--ui-sidebar-hover)] font-medium text-[var(--ui-text)]"
: "text-[var(--ui-text-muted)] hover:bg-[var(--ui-sidebar-hover)]"
)}
>
{label}
</button>
))}
</div>
<div className="mt-3 overflow-hidden rounded-lg border border-[var(--ui-border)] bg-[var(--ui-panel)]">
{reviews.isLoading && (
<div className="p-4">
<Skeleton className="h-24 w-full" />
</div>
)}
{reviews.error && (
<p className="px-4 py-3 text-xs text-[var(--ui-danger)]">
{reviews.error.message}
</p>
)}
{reviews.data && items.length === 0 && (
<p className="px-4 py-3 text-xs text-[var(--ui-text-muted)]">
{mine
? "No reviews on your PRs yet. Switch to All to see every review you have access to."
: "No reviews yet. Enable repositories under Open SWE Review settings and open a PR."}
</p>
)}
<div className="divide-y divide-[var(--ui-border)]">
{items.map((review) => (
<Link
key={review.thread_id}
to="/agents/reviews/$owner/$repo/$number"
params={{
owner: review.owner,
repo: review.repo,
number: String(review.number),
}}
className="flex items-center justify-between gap-4 px-4 py-3 transition-colors hover:bg-[var(--ui-sidebar-hover)]"
>
<div className="flex min-w-0 items-center gap-3">
<GitPullRequestIcon className="size-4 shrink-0 text-[var(--ui-text-muted)]" />
<div className="min-w-0">
<div className="truncate text-xs font-medium text-[var(--ui-text)]">
{review.title}
</div>
<div className="mt-0.5 text-xs text-[var(--ui-text-muted)]">
{review.owner}/{review.repo}#{review.number}
{review.author && !mine && (
<span className="ml-2">by {review.author}</span>
)}
{review.head_ref && (
<span className="ml-2 font-mono text-[11px]">
{review.head_ref}
</span>
)}
</div>
</div>
</div>
<div className="flex shrink-0 items-center gap-3 text-xs">
{statusBadge(review)}
<span
className={cn(
"inline-flex items-center gap-1",
review.counts.bugs > 0
? "text-[var(--ui-danger)]"
: "text-[var(--ui-text-muted)]"
)}
>
<BugBeetleIcon className="size-3.5" />
{review.counts.bugs}
</span>
<span className="inline-flex items-center gap-1 text-[var(--ui-text-muted)]">
<FlagIcon className="size-3.5" />
{review.counts.flags}
</span>
</div>
</Link>
))}
</div>
{(page > 0 || reviews.data?.has_more) && (
<div className="flex items-center justify-between gap-4 border-t border-[var(--ui-border)] px-4 py-2 text-xs">
<span className="text-[var(--ui-text-muted)]">
Page {page + 1}
</span>
<div className="flex items-center gap-2">
<Button
size="sm"
variant="outline"
disabled={page === 0}
onPointerEnter={() => prefetch(mine, page - 1)}
onClick={() => setPage((p) => Math.max(0, p - 1))}
>
Prev
</Button>
<Button
size="sm"
variant="outline"
disabled={!reviews.data?.has_more}
onPointerEnter={() => prefetch(mine, page + 1)}
onClick={() => setPage((p) => p + 1)}
>
Next
</Button>
</div>
</div>
)}
</div>
</div>
</main>
)
}