import React from "react"; import { ExternalLink, GitBranch, GitCommitHorizontal } from "lucide-react"; import { FaCodeBranch } from "react-icons/fa"; import { useTranslation } from "react-i18next"; import PrIcon from "#/icons/u-pr.svg?react"; import { GitProviderIcon } from "#/components/shared/git-provider-icon"; import { useRepositoryPullRequests } from "#/hooks/query/use-repository-git-items"; import { useUnifiedGitCommits } from "#/hooks/query/use-unified-git-commits"; import { useConversationPrimaryRepository } from "#/hooks/use-conversation-primary-repository"; import { useConversationId } from "#/hooks/use-conversation-id"; import { useSelectConversationTab } from "#/hooks/use-select-conversation-tab"; import { useSettings } from "#/hooks/query/use-settings"; import { useConversationLocalStorageState } from "#/utils/conversation-local-storage"; import { I18nKey } from "#/i18n/declaration"; import { cn, constructBranchUrl, constructRepositoryUrl } from "#/utils/utils"; import { useConversationOverviewDrawerOptional } from "./conversation-overview-drawer-context"; import { CONVERSATION_OVERVIEW_DRAWER_SECTION } from "./conversation-overview-drawer.types"; import { CONVERSATION_OVERVIEW_GIT_PART, isOverviewGitPartPinned, } from "./conversation-overview-sections"; const ROW_ICON_CLASSNAME = "size-4 shrink-0 text-[var(--oh-muted)]"; const EXTERNAL_LINK_ICON_CLASSNAME = cn( "size-3.5 shrink-0 text-[var(--oh-muted)]", "opacity-0 transition-opacity group-hover:opacity-100 group-focus-visible:opacity-100", ); // Non-localizable empty-count glyph (zero PRs). const ZERO_COUNT_LABEL = "-"; /** * Repo / branch / commits / PR rows for the overview git block. * Changes is rendered by the parent panel ahead of this section. */ export function ConversationOverviewGitSection() { const { t } = useTranslation("openhands"); const { conversationId } = useConversationId(); const { state } = useConversationLocalStorageState(conversationId); const { navigateToCommits } = useSelectConversationTab(); const overviewDrawer = useConversationOverviewDrawerOptional(); const { data: settings } = useSettings(); const { repository, provider, branch, isConnected } = useConversationPrimaryRepository(); const showRepository = isOverviewGitPartPinned( CONVERSATION_OVERVIEW_GIT_PART.repository, state.unpinnedOverviewGitParts ?? [], ); const showBranch = isOverviewGitPartPinned( CONVERSATION_OVERVIEW_GIT_PART.branch, state.unpinnedOverviewGitParts ?? [], ); const showCommits = isOverviewGitPartPinned( CONVERSATION_OVERVIEW_GIT_PART.commits, state.unpinnedOverviewGitParts ?? [], ); const showPullRequests = isOverviewGitPartPinned( CONVERSATION_OVERVIEW_GIT_PART.pull_requests, state.unpinnedOverviewGitParts ?? [], ); const pullRequestsQuery = useRepositoryPullRequests( showPullRequests ? repository : null, showPullRequests ? provider : null, ); // Same page the Files → Commits drawer uses; no separate total from the // API, so we surface the fetched count (and a trailing + when capped). const commitsQuery = useUnifiedGitCommits(); if (!isConnected || !repository || !provider) { return null; } if (!showRepository && !showBranch && !showCommits && !showPullRequests) { return null; } const providerHost = settings?.provider_tokens_set?.[provider] ?? null; const repoUrl = constructRepositoryUrl(provider, repository, providerHost); const branchUrl = branch ? constructBranchUrl(provider, repository, branch, providerHost) : null; const pullRequestsCount = pullRequestsQuery.data?.length ?? null; const commitsCount = commitsQuery.isUnsupported ? null : commitsQuery.commits.length; const commitsCountLabel = commitsCount !== null && commitsCount > 0 ? commitsQuery.hasMore ? `${commitsCount}+` : String(commitsCount) : ZERO_COUNT_LABEL; const openPullRequests = () => { overviewDrawer?.openSection( CONVERSATION_OVERVIEW_DRAWER_SECTION.pull_requests, ); }; const openCommits = () => { overviewDrawer?.closeDrawer(); navigateToCommits(); }; const showHeader = showRepository || showBranch; const showList = showCommits || showPullRequests; return (