mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-10-02 13:13: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,
|
useFileTreeSelection,
|
||||||
} from "@pierre/trees/react"
|
} from "@pierre/trees/react"
|
||||||
import {
|
import {
|
||||||
ArrowSquareOutIcon,
|
|
||||||
ArrowsInIcon,
|
ArrowsInIcon,
|
||||||
ArrowsOutIcon,
|
ArrowsOutIcon,
|
||||||
CaretDownIcon,
|
CaretDownIcon,
|
||||||
GitPullRequestIcon,
|
|
||||||
SidebarSimpleIcon,
|
SidebarSimpleIcon,
|
||||||
} from "@phosphor-icons/react"
|
} from "@phosphor-icons/react"
|
||||||
import type { FileContents } from "@pierre/diffs/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 { agentsApi } from "@/lib/agents/api"
|
||||||
import { useAgentThreadPrDiff } from "@/lib/agents/queries"
|
import { useAgentThreadPrDiff } from "@/lib/agents/queries"
|
||||||
import { ReviewTab } from "@/components/agents/ReviewTab"
|
import { ReviewTab } from "@/components/agents/ReviewTab"
|
||||||
|
import { PrHeader } from "@/components/agents/PrHeader"
|
||||||
import { buttonVariants } from "@/components/ui/button"
|
import { buttonVariants } from "@/components/ui/button"
|
||||||
import {
|
import {
|
||||||
DIFF_VIRTUALIZER_CONFIG,
|
DIFF_VIRTUALIZER_CONFIG,
|
||||||
|
|
@ -518,38 +517,16 @@ export function AgentGitPanel({
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{pr && (
|
{pr && (
|
||||||
<div className="border-b border-[var(--ui-border)] px-4 py-3">
|
<PrHeader
|
||||||
<div className="flex items-start justify-between gap-3">
|
className="border-b border-[var(--ui-border)] px-4 py-3"
|
||||||
<div className="min-w-0">
|
url={pr.url}
|
||||||
<div className="truncate text-sm font-medium text-[var(--ui-text)]">
|
title={pr.title}
|
||||||
{pr.title} #{pr.number}
|
number={pr.number}
|
||||||
</div>
|
state={pr.state}
|
||||||
<div className="mt-1 flex items-center gap-2 text-[11px] text-[var(--ui-text-dim)]">
|
headRef={pr.headRef}
|
||||||
<span className="inline-flex items-center gap-1 rounded border border-[var(--ui-border)] px-1.5 py-0.5 capitalize">
|
baseRef={pr.baseRef}
|
||||||
<GitPullRequestIcon className="size-3" />
|
titleClassName="truncate text-sm"
|
||||||
{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>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex items-center gap-1 border-b border-[var(--ui-border)] px-3 py-2">
|
<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,
|
CodeIcon,
|
||||||
CopyIcon,
|
CopyIcon,
|
||||||
FlagIcon,
|
FlagIcon,
|
||||||
GitPullRequestIcon,
|
|
||||||
InfoIcon,
|
InfoIcon,
|
||||||
LinkIcon,
|
LinkIcon,
|
||||||
ListBulletsIcon,
|
ListBulletsIcon,
|
||||||
|
|
@ -68,6 +67,7 @@ import type {
|
||||||
import type { ChatAttachment } from "@/components/agents/ReviewChat"
|
import type { ChatAttachment } from "@/components/agents/ReviewChat"
|
||||||
import type { DiffStyle } from "@/components/agents/utils/diffUtils"
|
import type { DiffStyle } from "@/components/agents/utils/diffUtils"
|
||||||
import { Markdown } from "@/components/agents/ported"
|
import { Markdown } from "@/components/agents/ported"
|
||||||
|
import { PrHeader } from "@/components/agents/PrHeader"
|
||||||
import {
|
import {
|
||||||
ReviewChat,
|
ReviewChat,
|
||||||
ReviewChatComposerProvider,
|
ReviewChatComposerProvider,
|
||||||
|
|
@ -1120,7 +1120,19 @@ function ReviewBodyInner({
|
||||||
config={DIFF_VIRTUALIZER_CONFIG}
|
config={DIFF_VIRTUALIZER_CONFIG}
|
||||||
>
|
>
|
||||||
<div ref={scrollerProbe} aria-hidden className="hidden" />
|
<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">
|
<div className="mt-4 rounded-lg border border-border bg-card p-4">
|
||||||
{detail.pr.body ? (
|
{detail.pr.body ? (
|
||||||
<Markdown
|
<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 }) {
|
function GroupHeader({ group }: { group: ResolvedGroup }) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue