import React, { useLayoutEffect, useState } from "react"; import ReactDOM from "react-dom"; import { GitCommitHorizontal } from "lucide-react"; import { useTranslation } from "react-i18next"; import ArrowDownIcon from "#/icons/u-arrow-down.svg?react"; import ArrowUpIcon from "#/icons/u-arrow-up.svg?react"; import CodeBranchIcon from "#/icons/u-code-branch.svg?react"; import PrIcon from "#/icons/u-pr.svg?react"; import { useClickOutsideElement } from "#/hooks/use-click-outside-element"; import { useConversationStore } from "#/stores/conversation-store"; import { ContextMenu } from "#/ui/context-menu"; import { ContextMenuListItem } from "#/components/features/context-menu/context-menu-list-item"; import { ToolsContextMenuIconText } from "#/components/features/controls/tools-context-menu-icon-text"; import { I18nKey } from "#/i18n/declaration"; import { getCreateNewBranchPrompt, getCreatePRPrompt, getGitCommitPrompt, getGitPullPrompt, getGitPushPrompt, } from "#/utils/utils"; import { Provider } from "#/types/settings"; const GIT_MENU_ITEM_CLASSNAME = "!w-auto whitespace-nowrap"; interface ConversationGitActionsMenuProps { anchorRef: React.RefObject; onClose: () => void; gitProvider: Provider; /** Prefix for menu/item `data-testid` values (suffixes: -menu, -commit, …). */ testIdPrefix?: string; } /** * Portaled dropdown of agent-prompt git actions (commit, pull, push, PR, branch). * Anchored under a trigger; shared by the overview diffs row and header toggle. */ export function ConversationGitActionsMenu({ anchorRef, onClose, gitProvider, testIdPrefix = "conversation-git-actions", }: ConversationGitActionsMenuProps) { const { t } = useTranslation("openhands"); const menuRef = useClickOutsideElement(onClose); const setMessageToSend = useConversationStore( (state) => state.setMessageToSend, ); const [portalStyle, setPortalStyle] = useState(); useLayoutEffect(() => { const anchor = anchorRef.current; if (!anchor) { return undefined; } const updatePosition = () => { const rect = anchor.getBoundingClientRect(); setPortalStyle({ position: "fixed", top: rect.bottom + 4, right: window.innerWidth - rect.right, zIndex: 50, }); }; updatePosition(); window.addEventListener("resize", updatePosition); window.addEventListener("scroll", updatePosition, true); return () => { window.removeEventListener("resize", updatePosition); window.removeEventListener("scroll", updatePosition, true); }; }, [anchorRef]); const handleCommit = () => { setMessageToSend(getGitCommitPrompt()); onClose(); }; const handlePull = () => { setMessageToSend(getGitPullPrompt()); onClose(); }; const handlePush = () => { setMessageToSend(getGitPushPrompt(gitProvider)); onClose(); }; const handleCreatePr = () => { setMessageToSend(getCreatePRPrompt(gitProvider)); onClose(); }; const handleCreateNewBranch = () => { setMessageToSend(getCreateNewBranchPrompt()); onClose(); }; if (!portalStyle) { return null; } return ReactDOM.createPortal( } text={t(I18nKey.DIFF_VIEWER$COMMITS)} /> } text={t(I18nKey.COMMON$PULL)} /> } text={t(I18nKey.COMMON$PUSH)} /> } text={t(I18nKey.COMMON$CREATE_PR)} /> } text={t(I18nKey.COMMON$CREATE_NEW_BRANCH)} /> , document.body, ); }