diff --git a/frontend/src/components/features/chat/change-agent-button.tsx b/frontend/src/components/features/chat/change-agent-button.tsx new file mode 100644 index 0000000000..eaaa02b9c7 --- /dev/null +++ b/frontend/src/components/features/chat/change-agent-button.tsx @@ -0,0 +1,92 @@ +import React, { useMemo } from "react"; +import { useTranslation } from "react-i18next"; +import { Typography } from "#/ui/typography"; +import { I18nKey } from "#/i18n/declaration"; +import CodeTagIcon from "#/icons/code-tag.svg?react"; +import ChevronDownSmallIcon from "#/icons/chevron-down-small.svg?react"; +import LessonPlanIcon from "#/icons/lesson-plan.svg?react"; +import { useConversationStore } from "#/state/conversation-store"; +import { ChangeAgentContextMenu } from "./change-agent-context-menu"; +import { cn } from "#/utils/utils"; +import { USE_PLANNING_AGENT } from "#/utils/feature-flags"; + +export function ChangeAgentButton() { + const { t } = useTranslation(); + const [contextMenuOpen, setContextMenuOpen] = React.useState(false); + + const conversationMode = useConversationStore( + (state) => state.conversationMode, + ); + + const setConversationMode = useConversationStore( + (state) => state.setConversationMode, + ); + + const shouldUsePlanningAgent = USE_PLANNING_AGENT(); + + const handleButtonClick = (event: React.MouseEvent) => { + event.preventDefault(); + event.stopPropagation(); + setContextMenuOpen(!contextMenuOpen); + }; + + const handleCodeClick = (event: React.MouseEvent) => { + event.preventDefault(); + event.stopPropagation(); + setConversationMode("code"); + }; + + const handlePlanClick = (event: React.MouseEvent) => { + event.preventDefault(); + event.stopPropagation(); + setConversationMode("plan"); + }; + + const isExecutionAgent = conversationMode === "code"; + + const buttonLabel = useMemo(() => { + if (isExecutionAgent) { + return t(I18nKey.COMMON$CODE); + } + return t(I18nKey.COMMON$PLAN); + }, [isExecutionAgent, t]); + + const buttonIcon = useMemo(() => { + if (isExecutionAgent) { + return ; + } + return ; + }, [isExecutionAgent]); + + if (!shouldUsePlanningAgent) { + return null; + } + + return ( +
+ + {contextMenuOpen && ( + setContextMenuOpen(false)} + onCodeClick={handleCodeClick} + onPlanClick={handlePlanClick} + /> + )} +
+ ); +} diff --git a/frontend/src/components/features/chat/change-agent-context-menu.tsx b/frontend/src/components/features/chat/change-agent-context-menu.tsx new file mode 100644 index 0000000000..6e88ce97d4 --- /dev/null +++ b/frontend/src/components/features/chat/change-agent-context-menu.tsx @@ -0,0 +1,81 @@ +import React from "react"; +import { useTranslation } from "react-i18next"; +import { I18nKey } from "#/i18n/declaration"; +import CodeTagIcon from "#/icons/code-tag.svg?react"; +import LessonPlanIcon from "#/icons/lesson-plan.svg?react"; +import { ContextMenu } from "#/ui/context-menu"; +import { ContextMenuListItem } from "../context-menu/context-menu-list-item"; +import { ContextMenuIconText } from "../context-menu/context-menu-icon-text"; +import { useClickOutsideElement } from "#/hooks/use-click-outside-element"; +import { cn } from "#/utils/utils"; +import { CONTEXT_MENU_ICON_TEXT_CLASSNAME } from "#/utils/constants"; + +const contextMenuListItemClassName = cn( + "cursor-pointer p-0 h-auto hover:bg-transparent", + CONTEXT_MENU_ICON_TEXT_CLASSNAME, +); + +const contextMenuIconTextClassName = + "gap-2 p-2 hover:bg-[#5C5D62] rounded h-[30px]"; + +interface ChangeAgentContextMenuProps { + onClose: () => void; + onCodeClick?: (event: React.MouseEvent) => void; + onPlanClick?: (event: React.MouseEvent) => void; +} + +export function ChangeAgentContextMenu({ + onClose, + onCodeClick, + onPlanClick, +}: ChangeAgentContextMenuProps) { + const { t } = useTranslation(); + const menuRef = useClickOutsideElement(onClose); + + const handleCodeClick = (event: React.MouseEvent) => { + event.preventDefault(); + event.stopPropagation(); + onCodeClick?.(event); + onClose(); + }; + + const handlePlanClick = (event: React.MouseEvent) => { + event.preventDefault(); + event.stopPropagation(); + onPlanClick?.(event); + onClose(); + }; + + return ( + + + + + + + + + ); +} diff --git a/frontend/src/components/features/chat/components/chat-input-actions.tsx b/frontend/src/components/features/chat/components/chat-input-actions.tsx index abe226520e..7683464499 100644 --- a/frontend/src/components/features/chat/components/chat-input-actions.tsx +++ b/frontend/src/components/features/chat/components/chat-input-actions.tsx @@ -8,6 +8,7 @@ import { generateAgentStateChangeEvent } from "#/services/agent-state-service"; import { AgentState } from "#/types/agent-state"; import { useV1PauseConversation } from "#/hooks/mutation/use-v1-pause-conversation"; import { useV1ResumeConversation } from "#/hooks/mutation/use-v1-resume-conversation"; +import { ChangeAgentButton } from "../change-agent-button"; interface ChatInputActionsProps { disabled: boolean; @@ -56,7 +57,10 @@ export function ChatInputActions({ return (
- +
+ + +
+ +