mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-09-30 06:53:14 +00:00
feat: relocate review chunks and embed review in git panel (#1590)
* feat: relocate review chunks and embed review in git panel Move the AI-sorted review chunk navigator out of the global left sidebar and into the review page's own main panel (sidebar keeps the thread list), and surface the review main body inside the agent thread's Git > Review sub-tab with an expand link to the full review page. The review main body + side panel + helpers are extracted into a shared ReviewMainBody component with "full" and "embedded" variants. Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com> * chore: move ReviewTab into its own file 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
3992d3ef5d
commit
63e4c2baac
6 changed files with 2087 additions and 2008 deletions
|
|
@ -24,6 +24,7 @@ import type { AgentThread, Message } from "@/lib/agents/types"
|
|||
import type { ThreadPrDiffFile } from "@/lib/agents/api"
|
||||
import type { ChangedFileSummaryItem } from "@/components/agents/messages"
|
||||
import { useAgentThreadPrDiff } from "@/lib/agents/queries"
|
||||
import { ReviewTab } from "@/components/agents/ReviewTab"
|
||||
import { buttonVariants } from "@/components/ui/button"
|
||||
import {
|
||||
DIFF_VIRTUALIZER_CONFIG,
|
||||
|
|
@ -554,7 +555,9 @@ export function AgentGitPanel({ thread, messages }: AgentGitPanelProps) {
|
|||
</div>
|
||||
|
||||
<div className="flex min-h-0 flex-1">
|
||||
{tab === "diff" && files.length > 0 ? (
|
||||
{tab === "review" ? (
|
||||
<ReviewTab thread={thread} />
|
||||
) : tab === "diff" && files.length > 0 ? (
|
||||
<WorkerPoolContextProvider
|
||||
poolOptions={DIFF_WORKER_POOL_OPTIONS}
|
||||
highlighterOptions={DIFF_WORKER_HIGHLIGHTER_OPTIONS}
|
||||
|
|
@ -585,15 +588,18 @@ export function AgentGitPanel({ thread, messages }: AgentGitPanelProps) {
|
|||
</div>
|
||||
)}
|
||||
|
||||
{fullScreen && !isMobile && files.length > 0 && (
|
||||
<div className="w-72 shrink-0 border-l border-[var(--ui-border)] bg-[var(--ui-surface)]">
|
||||
<FileTreeExplorer
|
||||
files={files}
|
||||
selectedTreePath={selectedTreePath}
|
||||
onSelect={setSelectedTreePath}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{tab === "diff" &&
|
||||
fullScreen &&
|
||||
!isMobile &&
|
||||
files.length > 0 && (
|
||||
<div className="w-72 shrink-0 border-l border-[var(--ui-border)] bg-[var(--ui-surface)]">
|
||||
<FileTreeExplorer
|
||||
files={files}
|
||||
selectedTreePath={selectedTreePath}
|
||||
onSelect={setSelectedTreePath}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -27,11 +27,6 @@ import type { SessionUser } from "@/lib/api"
|
|||
import type { AgentSource, AgentThread } from "@/lib/agents/types"
|
||||
import type { SidebarLayout } from "@/components/sidebar-layout"
|
||||
import { SidebarUserMenu } from "@/components/SidebarUserMenu"
|
||||
import {
|
||||
ReviewSidebarPanel,
|
||||
ReviewSidebarProvider,
|
||||
useReviewSidebarData,
|
||||
} from "@/components/agents/ReviewSidebar"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import {
|
||||
SidebarCollapseButton,
|
||||
|
|
@ -114,7 +109,6 @@ export function AgentsSidebar({
|
|||
useSeedAgentThreadDetails(visibleThreads, activeThreadId)
|
||||
useRunCompletionNotifier(visibleThreads, activeThreadId)
|
||||
const groups = groupThreads(activeThreads)
|
||||
const reviewSidebar = useReviewSidebarData()
|
||||
|
||||
return (
|
||||
<SidebarFrame
|
||||
|
|
@ -164,44 +158,40 @@ export function AgentsSidebar({
|
|||
})}
|
||||
</nav>
|
||||
|
||||
{reviewSidebar ? (
|
||||
<ReviewSidebarPanel data={reviewSidebar} />
|
||||
) : (
|
||||
<div className="min-h-0 flex-1 overflow-y-auto px-2 pb-2">
|
||||
<ThreadGroup
|
||||
label="Today"
|
||||
threads={groups.today}
|
||||
activeThreadId={activeThreadId}
|
||||
onNavigate={layout.closeOnMobile}
|
||||
/>
|
||||
<ThreadGroup
|
||||
label="Last 7 days"
|
||||
threads={groups.last7}
|
||||
activeThreadId={activeThreadId}
|
||||
onNavigate={layout.closeOnMobile}
|
||||
defaultCollapsed
|
||||
/>
|
||||
<ThreadGroup
|
||||
label="Last 30 days"
|
||||
threads={groups.last30}
|
||||
activeThreadId={activeThreadId}
|
||||
onNavigate={layout.closeOnMobile}
|
||||
defaultCollapsed
|
||||
/>
|
||||
<ThreadGroup
|
||||
label="Older"
|
||||
threads={groups.older}
|
||||
activeThreadId={activeThreadId}
|
||||
onNavigate={layout.closeOnMobile}
|
||||
/>
|
||||
<ResolvedThreadGroup
|
||||
threads={resolvedThreads}
|
||||
hasMore={resolvedHasMore}
|
||||
activeThreadId={activeThreadId}
|
||||
onNavigate={layout.closeOnMobile}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="min-h-0 flex-1 overflow-y-auto px-2 pb-2">
|
||||
<ThreadGroup
|
||||
label="Today"
|
||||
threads={groups.today}
|
||||
activeThreadId={activeThreadId}
|
||||
onNavigate={layout.closeOnMobile}
|
||||
/>
|
||||
<ThreadGroup
|
||||
label="Last 7 days"
|
||||
threads={groups.last7}
|
||||
activeThreadId={activeThreadId}
|
||||
onNavigate={layout.closeOnMobile}
|
||||
defaultCollapsed
|
||||
/>
|
||||
<ThreadGroup
|
||||
label="Last 30 days"
|
||||
threads={groups.last30}
|
||||
activeThreadId={activeThreadId}
|
||||
onNavigate={layout.closeOnMobile}
|
||||
defaultCollapsed
|
||||
/>
|
||||
<ThreadGroup
|
||||
label="Older"
|
||||
threads={groups.older}
|
||||
activeThreadId={activeThreadId}
|
||||
onNavigate={layout.closeOnMobile}
|
||||
/>
|
||||
<ResolvedThreadGroup
|
||||
threads={resolvedThreads}
|
||||
hasMore={resolvedHasMore}
|
||||
activeThreadId={activeThreadId}
|
||||
onNavigate={layout.closeOnMobile}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="p-2">
|
||||
<SidebarUserMenu user={user} showSettingsLink />
|
||||
|
|
@ -546,17 +536,15 @@ export function AgentsShell({
|
|||
}) {
|
||||
const layout = useSidebarLayout()
|
||||
return (
|
||||
<ReviewSidebarProvider>
|
||||
<SidebarLayoutProvider value={layout}>
|
||||
<div className="agents-ui flex h-svh overflow-hidden bg-[var(--ui-bg)]">
|
||||
<AgentsSidebar
|
||||
user={user}
|
||||
activeThreadId={activeThreadId}
|
||||
layout={layout}
|
||||
/>
|
||||
<div className="flex min-w-0 flex-1">{children}</div>
|
||||
</div>
|
||||
</SidebarLayoutProvider>
|
||||
</ReviewSidebarProvider>
|
||||
<SidebarLayoutProvider value={layout}>
|
||||
<div className="agents-ui flex h-svh overflow-hidden bg-[var(--ui-bg)]">
|
||||
<AgentsSidebar
|
||||
user={user}
|
||||
activeThreadId={activeThreadId}
|
||||
layout={layout}
|
||||
/>
|
||||
<div className="flex min-w-0 flex-1">{children}</div>
|
||||
</div>
|
||||
</SidebarLayoutProvider>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
1944
ui/src/components/agents/ReviewMainBody.tsx
Normal file
1944
ui/src/components/agents/ReviewMainBody.tsx
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,12 +1,4 @@
|
|||
import {
|
||||
createContext,
|
||||
memo,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from "react"
|
||||
import { memo, useCallback, useEffect, useMemo, useState } from "react"
|
||||
import {
|
||||
FileTree,
|
||||
useFileTree,
|
||||
|
|
@ -27,7 +19,10 @@ import type {
|
|||
import type { ReviewDiffFile } from "@/lib/api"
|
||||
import { Markdown } from "@/components/agents/ported"
|
||||
import { Skeleton } from "@/components/ui/skeleton"
|
||||
import { TREE_UNSAFE_CSS, treeThemeStyle } from "@/components/agents/AgentGitPanel"
|
||||
import {
|
||||
TREE_UNSAFE_CSS,
|
||||
treeThemeStyle,
|
||||
} from "@/components/agents/AgentGitPanel"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function reviewFileGitStatus(status: ReviewDiffFile["status"]): GitStatus {
|
||||
|
|
@ -61,38 +56,6 @@ export interface ReviewSidebarData {
|
|||
onSelectGroup: (index: number) => void
|
||||
}
|
||||
|
||||
const ReviewSidebarContext = createContext<{
|
||||
data: ReviewSidebarData | null
|
||||
setData: (data: ReviewSidebarData | null) => void
|
||||
} | null>(null)
|
||||
|
||||
export function ReviewSidebarProvider({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
const [data, setData] = useState<ReviewSidebarData | null>(null)
|
||||
const value = useMemo(() => ({ data, setData }), [data])
|
||||
return (
|
||||
<ReviewSidebarContext.Provider value={value}>
|
||||
{children}
|
||||
</ReviewSidebarContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export function useReviewSidebarData(): ReviewSidebarData | null {
|
||||
return useContext(ReviewSidebarContext)?.data ?? null
|
||||
}
|
||||
|
||||
export function useRegisterReviewSidebar(data: ReviewSidebarData) {
|
||||
const setData = useContext(ReviewSidebarContext)?.setData
|
||||
useEffect(() => {
|
||||
if (!setData) return
|
||||
setData(data)
|
||||
return () => setData(null)
|
||||
}, [setData, data])
|
||||
}
|
||||
|
||||
export function ReviewSidebarPanel({ data }: { data: ReviewSidebarData }) {
|
||||
const hasGroups = data.groups !== null && data.groups.length > 0
|
||||
const showAi = data.view === "ai" && hasGroups
|
||||
|
|
|
|||
72
ui/src/components/agents/ReviewTab.tsx
Normal file
72
ui/src/components/agents/ReviewTab.tsx
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
import { useNavigate } from "@tanstack/react-router"
|
||||
import { useQuery } from "@tanstack/react-query"
|
||||
|
||||
import type { AgentThread } from "@/lib/agents/types"
|
||||
import { ReviewMainBody } from "@/components/agents/ReviewMainBody"
|
||||
import { api } from "@/lib/api"
|
||||
|
||||
// The git panel's "Review" sub-tab: the PR's Open SWE review rendered inline (no
|
||||
// side panel / chat), with an expand affordance that opens the full review page.
|
||||
export function ReviewTab({ thread }: { thread: AgentThread }) {
|
||||
const navigate = useNavigate()
|
||||
const pr = thread.pr
|
||||
const [owner, repo] = thread.repoFullName.split("/")
|
||||
const number = pr?.number ?? null
|
||||
const enabled = Boolean(owner && repo && number !== null)
|
||||
|
||||
const detail = useQuery({
|
||||
queryKey: ["review", owner, repo, number],
|
||||
queryFn: () =>
|
||||
api.getReview(owner as string, repo as string, number as number),
|
||||
enabled,
|
||||
refetchInterval: (query) =>
|
||||
query.state.data?.status === "running" ? 5000 : false,
|
||||
})
|
||||
const diff = useQuery({
|
||||
queryKey: ["reviewDiff", owner, repo, number],
|
||||
queryFn: () =>
|
||||
api.getReviewDiff(owner as string, repo as string, number as number),
|
||||
enabled,
|
||||
})
|
||||
|
||||
if (!enabled) {
|
||||
return (
|
||||
<div className="min-h-0 flex-1 overflow-y-auto p-6 text-center text-xs text-[var(--ui-text-dim)]">
|
||||
Open a pull request to see its review here.
|
||||
</div>
|
||||
)
|
||||
}
|
||||
if (detail.isLoading) {
|
||||
return (
|
||||
<div className="min-h-0 flex-1 overflow-y-auto p-6 text-center text-xs text-[var(--ui-text-dim)]">
|
||||
Loading review…
|
||||
</div>
|
||||
)
|
||||
}
|
||||
if (detail.error || !detail.data) {
|
||||
return (
|
||||
<div className="min-h-0 flex-1 overflow-y-auto p-6 text-center text-xs text-[var(--ui-text-dim)]">
|
||||
No review for this pull request yet.
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<ReviewMainBody
|
||||
key={detail.data.head_sha}
|
||||
detail={detail.data}
|
||||
diffFiles={diff.data?.files ?? null}
|
||||
variant="embedded"
|
||||
onExpand={() =>
|
||||
navigate({
|
||||
to: "/agents/reviews/$owner/$repo/$number",
|
||||
params: {
|
||||
owner: owner as string,
|
||||
repo: repo as string,
|
||||
number: String(number),
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue