mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-09-30 12:43:16 +00:00
feat: clickable shared PR header in git panel and reviews (#1620)
* feat: clickable shared PR header in git panel and reviews Replace the standalone "View PR" button in the agent git panel with a clickable PR title, matching the reviews view. Extract a shared PrHeader component reused by both the git panel and the review main body. Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> * refactor: drop PrHeader wrapper, use shared component directly The review-side PrHeader was just a thin adapter mapping detail -> the shared component's props. Inline it at the call site and use the shared PrHeader directly so there's a single component. Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> --------- Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
This commit is contained in:
parent
83cb40a01c
commit
85c0f63e29
3 changed files with 113 additions and 86 deletions
|
|
@ -10,11 +10,9 @@ import {
|
|||
useFileTreeSelection,
|
||||
} from "@pierre/trees/react"
|
||||
import {
|
||||
ArrowSquareOutIcon,
|
||||
ArrowsInIcon,
|
||||
ArrowsOutIcon,
|
||||
CaretDownIcon,
|
||||
GitPullRequestIcon,
|
||||
SidebarSimpleIcon,
|
||||
} from "@phosphor-icons/react"
|
||||
import type { FileContents } from "@pierre/diffs/react"
|
||||
|
|
@ -26,6 +24,7 @@ import type { ChangedFileSummaryItem } from "@/components/agents/messages"
|
|||
import { agentsApi } from "@/lib/agents/api"
|
||||
import { useAgentThreadPrDiff } from "@/lib/agents/queries"
|
||||
import { ReviewTab } from "@/components/agents/ReviewTab"
|
||||
import { PrHeader } from "@/components/agents/PrHeader"
|
||||
import { buttonVariants } from "@/components/ui/button"
|
||||
import {
|
||||
DIFF_VIRTUALIZER_CONFIG,
|
||||
|
|
@ -518,38 +517,16 @@ export function AgentGitPanel({
|
|||
) : (
|
||||
<>
|
||||
{pr && (
|
||||
<div className="border-b border-[var(--ui-border)] px-4 py-3">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="min-w-0">
|
||||
<div className="truncate text-sm font-medium text-[var(--ui-text)]">
|
||||
{pr.title} #{pr.number}
|
||||
</div>
|
||||
<div className="mt-1 flex items-center gap-2 text-[11px] text-[var(--ui-text-dim)]">
|
||||
<span className="inline-flex items-center gap-1 rounded border border-[var(--ui-border)] px-1.5 py-0.5 capitalize">
|
||||
<GitPullRequestIcon className="size-3" />
|
||||
{pr.state}
|
||||
</span>
|
||||
<span>
|
||||
{pr.headRef} → {pr.baseRef}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{pr.url && (
|
||||
<a
|
||||
href={pr.url}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className={buttonVariants({
|
||||
variant: "outline",
|
||||
size: "sm",
|
||||
})}
|
||||
>
|
||||
<ArrowSquareOutIcon className="size-3" />
|
||||
View PR
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<PrHeader
|
||||
className="border-b border-[var(--ui-border)] px-4 py-3"
|
||||
url={pr.url}
|
||||
title={pr.title}
|
||||
number={pr.number}
|
||||
state={pr.state}
|
||||
headRef={pr.headRef}
|
||||
baseRef={pr.baseRef}
|
||||
titleClassName="truncate text-sm"
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="flex items-center gap-1 border-b border-[var(--ui-border)] px-3 py-2">
|
||||
|
|
|
|||
88
ui/src/components/agents/PrHeader.tsx
Normal file
88
ui/src/components/agents/PrHeader.tsx
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
import { GitPullRequestIcon } from "@phosphor-icons/react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const STATE_STYLES: Record<string, string> = {
|
||||
open: "border-emerald-600/40 text-emerald-500",
|
||||
draft: "border-border text-muted-foreground",
|
||||
merged: "border-purple-600/40 text-purple-500",
|
||||
closed: "border-red-600/40 text-red-500",
|
||||
}
|
||||
|
||||
export interface PrHeaderProps {
|
||||
url: string
|
||||
title: string
|
||||
state: string
|
||||
headRef: string
|
||||
baseRef: string
|
||||
number?: number | null
|
||||
author?: string | null
|
||||
stats?: {
|
||||
changedFiles: number
|
||||
additions: number
|
||||
deletions: number
|
||||
} | null
|
||||
className?: string
|
||||
titleClassName?: string
|
||||
}
|
||||
|
||||
export function PrHeader({
|
||||
url,
|
||||
title,
|
||||
state,
|
||||
headRef,
|
||||
baseRef,
|
||||
number,
|
||||
author,
|
||||
stats,
|
||||
className,
|
||||
titleClassName,
|
||||
}: PrHeaderProps) {
|
||||
return (
|
||||
<div className={className}>
|
||||
<span
|
||||
className={cn(
|
||||
"inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-[11px] capitalize",
|
||||
STATE_STYLES[state] ?? STATE_STYLES.open
|
||||
)}
|
||||
>
|
||||
<GitPullRequestIcon className="size-3" />
|
||||
{state}
|
||||
</span>
|
||||
<h1 className={cn("mt-2 text-base font-medium", titleClassName)}>
|
||||
<a
|
||||
href={url}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="hover:underline"
|
||||
>
|
||||
{title}
|
||||
{number != null && (
|
||||
<span className="text-muted-foreground"> #{number}</span>
|
||||
)}
|
||||
</a>
|
||||
</h1>
|
||||
<div className="mt-2 flex flex-wrap items-center gap-2 text-xs text-muted-foreground">
|
||||
{author && (
|
||||
<span className="font-medium text-foreground">{author}</span>
|
||||
)}
|
||||
<span className="rounded border border-border px-1.5 py-0.5 font-mono text-[11px]">
|
||||
{baseRef}
|
||||
</span>
|
||||
<span>←</span>
|
||||
<span className="rounded border border-border px-1.5 py-0.5 font-mono text-[11px]">
|
||||
{headRef}
|
||||
</span>
|
||||
{stats && (
|
||||
<>
|
||||
<span>
|
||||
{stats.changedFiles} file{stats.changedFiles === 1 ? "" : "s"}
|
||||
</span>
|
||||
<span className="text-emerald-500">+{stats.additions}</span>
|
||||
<span className="text-red-500">-{stats.deletions}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -22,7 +22,6 @@ import {
|
|||
CodeIcon,
|
||||
CopyIcon,
|
||||
FlagIcon,
|
||||
GitPullRequestIcon,
|
||||
InfoIcon,
|
||||
LinkIcon,
|
||||
ListBulletsIcon,
|
||||
|
|
@ -68,6 +67,7 @@ import type {
|
|||
import type { ChatAttachment } from "@/components/agents/ReviewChat"
|
||||
import type { DiffStyle } from "@/components/agents/utils/diffUtils"
|
||||
import { Markdown } from "@/components/agents/ported"
|
||||
import { PrHeader } from "@/components/agents/PrHeader"
|
||||
import {
|
||||
ReviewChat,
|
||||
ReviewChatComposerProvider,
|
||||
|
|
@ -1120,7 +1120,19 @@ function ReviewBodyInner({
|
|||
config={DIFF_VIRTUALIZER_CONFIG}
|
||||
>
|
||||
<div ref={scrollerProbe} aria-hidden className="hidden" />
|
||||
<PrHeader detail={detail} />
|
||||
<PrHeader
|
||||
url={detail.url}
|
||||
title={detail.pr.title}
|
||||
state={detail.pr.state}
|
||||
headRef={detail.pr.head_ref}
|
||||
baseRef={detail.pr.base_ref}
|
||||
author={detail.pr.author?.login}
|
||||
stats={{
|
||||
changedFiles: detail.pr.changed_files,
|
||||
additions: detail.pr.additions,
|
||||
deletions: detail.pr.deletions,
|
||||
}}
|
||||
/>
|
||||
<div className="mt-4 rounded-lg border border-border bg-card p-4">
|
||||
{detail.pr.body ? (
|
||||
<Markdown
|
||||
|
|
@ -1256,56 +1268,6 @@ function DiffStyleButton({
|
|||
)
|
||||
}
|
||||
|
||||
function PrHeader({ detail }: { detail: ReviewDetail }) {
|
||||
const { pr } = detail
|
||||
const stateStyles: Record<string, string> = {
|
||||
open: "border-emerald-600/40 text-emerald-500",
|
||||
draft: "border-border text-muted-foreground",
|
||||
merged: "border-purple-600/40 text-purple-500",
|
||||
closed: "border-red-600/40 text-red-500",
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<span
|
||||
className={cn(
|
||||
"inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-[11px] capitalize",
|
||||
stateStyles[pr.state] ?? stateStyles.open
|
||||
)}
|
||||
>
|
||||
<GitPullRequestIcon className="size-3" />
|
||||
{pr.state}
|
||||
</span>
|
||||
<h1 className="mt-2 text-base font-medium">
|
||||
<a
|
||||
href={detail.url}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="hover:underline"
|
||||
>
|
||||
{pr.title}
|
||||
</a>
|
||||
</h1>
|
||||
<div className="mt-2 flex flex-wrap items-center gap-2 text-xs text-muted-foreground">
|
||||
{pr.author && (
|
||||
<span className="font-medium text-foreground">{pr.author.login}</span>
|
||||
)}
|
||||
<span className="rounded border border-border px-1.5 py-0.5 font-mono text-[11px]">
|
||||
{pr.base_ref}
|
||||
</span>
|
||||
<span>←</span>
|
||||
<span className="rounded border border-border px-1.5 py-0.5 font-mono text-[11px]">
|
||||
{pr.head_ref}
|
||||
</span>
|
||||
<span>
|
||||
{pr.changed_files} file{pr.changed_files === 1 ? "" : "s"}
|
||||
</span>
|
||||
<span className="text-emerald-500">+{pr.additions}</span>
|
||||
<span className="text-red-500">-{pr.deletions}</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function GroupHeader({ group }: { group: ResolvedGroup }) {
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue