mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
feat(frontend): add handler for 'create a plan' button click (#11806)
This commit is contained in:
parent
6c821ab73e
commit
6c2862ae08
@ -12,20 +12,15 @@ import { USE_PLANNING_AGENT } from "#/utils/feature-flags";
|
||||
import { useAgentState } from "#/hooks/use-agent-state";
|
||||
import { AgentState } from "#/types/agent-state";
|
||||
import { useActiveConversation } from "#/hooks/query/use-active-conversation";
|
||||
import { useCreateConversation } from "#/hooks/mutation/use-create-conversation";
|
||||
import { displaySuccessToast } from "#/utils/custom-toast-handlers";
|
||||
import { useUnifiedWebSocketStatus } from "#/hooks/use-unified-websocket-status";
|
||||
import { useSubConversationTaskPolling } from "#/hooks/query/use-sub-conversation-task-polling";
|
||||
import { useHandlePlanClick } from "#/hooks/use-handle-plan-click";
|
||||
|
||||
export function ChangeAgentButton() {
|
||||
const [contextMenuOpen, setContextMenuOpen] = useState<boolean>(false);
|
||||
|
||||
const {
|
||||
conversationMode,
|
||||
setConversationMode,
|
||||
setSubConversationTaskId,
|
||||
subConversationTaskId,
|
||||
} = useConversationStore();
|
||||
const { conversationMode, setConversationMode, subConversationTaskId } =
|
||||
useConversationStore();
|
||||
|
||||
const webSocketStatus = useUnifiedWebSocketStatus();
|
||||
|
||||
@ -40,8 +35,6 @@ export function ChangeAgentButton() {
|
||||
const isAgentRunning = curAgentState === AgentState.RUNNING;
|
||||
|
||||
const { data: conversation } = useActiveConversation();
|
||||
const { mutate: createConversation, isPending: isCreatingConversation } =
|
||||
useCreateConversation();
|
||||
|
||||
// Poll sub-conversation task and invalidate parent conversation when ready
|
||||
useSubConversationTaskPolling(
|
||||
@ -49,6 +42,9 @@ export function ChangeAgentButton() {
|
||||
conversation?.conversation_id || null,
|
||||
);
|
||||
|
||||
// Get handlePlanClick and isCreatingConversation from custom hook
|
||||
const { handlePlanClick, isCreatingConversation } = useHandlePlanClick();
|
||||
|
||||
// Close context menu when agent starts running
|
||||
useEffect(() => {
|
||||
if ((isAgentRunning || !isWebSocketConnected) && contextMenuOpen) {
|
||||
@ -56,45 +52,6 @@ export function ChangeAgentButton() {
|
||||
}
|
||||
}, [isAgentRunning, contextMenuOpen, isWebSocketConnected]);
|
||||
|
||||
const handlePlanClick = (
|
||||
event: React.MouseEvent<HTMLButtonElement> | KeyboardEvent,
|
||||
) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
// Set conversation mode to "plan" immediately
|
||||
setConversationMode("plan");
|
||||
|
||||
// Check if sub_conversation_ids is not empty
|
||||
if (
|
||||
(conversation?.sub_conversation_ids &&
|
||||
conversation.sub_conversation_ids.length > 0) ||
|
||||
!conversation?.conversation_id
|
||||
) {
|
||||
// Do nothing if both conditions are true
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a new sub-conversation if we have a current conversation ID
|
||||
createConversation(
|
||||
{
|
||||
parentConversationId: conversation.conversation_id,
|
||||
agentType: "plan",
|
||||
},
|
||||
{
|
||||
onSuccess: (data) => {
|
||||
displaySuccessToast(
|
||||
t(I18nKey.PLANNING_AGENTT$PLANNING_AGENT_INITIALIZED),
|
||||
);
|
||||
// Track the task ID to poll for sub-conversation creation
|
||||
if (data.v1_task_id) {
|
||||
setSubConversationTaskId(data.v1_task_id);
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const isButtonDisabled =
|
||||
isAgentRunning ||
|
||||
isCreatingConversation ||
|
||||
|
||||
71
frontend/src/hooks/use-handle-plan-click.ts
Normal file
71
frontend/src/hooks/use-handle-plan-click.ts
Normal file
@ -0,0 +1,71 @@
|
||||
import { useCallback } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { useConversationStore } from "#/state/conversation-store";
|
||||
import { useActiveConversation } from "#/hooks/query/use-active-conversation";
|
||||
import { useCreateConversation } from "#/hooks/mutation/use-create-conversation";
|
||||
import { displaySuccessToast } from "#/utils/custom-toast-handlers";
|
||||
|
||||
/**
|
||||
* Custom hook that encapsulates the logic for handling plan creation.
|
||||
* Returns a function that can be called to create a plan conversation and
|
||||
* the pending state of the conversation creation.
|
||||
*
|
||||
* @returns An object containing handlePlanClick function and isCreatingConversation boolean
|
||||
*/
|
||||
export const useHandlePlanClick = () => {
|
||||
const { t } = useTranslation();
|
||||
const { setConversationMode, setSubConversationTaskId } =
|
||||
useConversationStore();
|
||||
const { data: conversation } = useActiveConversation();
|
||||
const { mutate: createConversation, isPending: isCreatingConversation } =
|
||||
useCreateConversation();
|
||||
|
||||
const handlePlanClick = useCallback(
|
||||
(event?: React.MouseEvent<HTMLButtonElement> | KeyboardEvent) => {
|
||||
event?.preventDefault();
|
||||
event?.stopPropagation();
|
||||
|
||||
// Set conversation mode to "plan" immediately
|
||||
setConversationMode("plan");
|
||||
|
||||
// Check if sub_conversation_ids is not empty
|
||||
if (
|
||||
(conversation?.sub_conversation_ids &&
|
||||
conversation.sub_conversation_ids.length > 0) ||
|
||||
!conversation?.conversation_id
|
||||
) {
|
||||
// Do nothing if both conditions are true
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a new sub-conversation if we have a current conversation ID
|
||||
createConversation(
|
||||
{
|
||||
parentConversationId: conversation.conversation_id,
|
||||
agentType: "plan",
|
||||
},
|
||||
{
|
||||
onSuccess: (data) => {
|
||||
displaySuccessToast(
|
||||
t(I18nKey.PLANNING_AGENTT$PLANNING_AGENT_INITIALIZED),
|
||||
);
|
||||
// Track the task ID to poll for sub-conversation creation
|
||||
if (data.v1_task_id) {
|
||||
setSubConversationTaskId(data.v1_task_id);
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
},
|
||||
[
|
||||
conversation,
|
||||
createConversation,
|
||||
setConversationMode,
|
||||
setSubConversationTaskId,
|
||||
t,
|
||||
],
|
||||
);
|
||||
|
||||
return { handlePlanClick, isCreatingConversation };
|
||||
};
|
||||
@ -3,11 +3,13 @@ import { I18nKey } from "#/i18n/declaration";
|
||||
import LessonPlanIcon from "#/icons/lesson-plan.svg?react";
|
||||
import { useConversationStore } from "#/state/conversation-store";
|
||||
import { MarkdownRenderer } from "#/components/features/markdown/markdown-renderer";
|
||||
import { useHandlePlanClick } from "#/hooks/use-handle-plan-click";
|
||||
|
||||
function PlannerTab() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { planContent, setConversationMode } = useConversationStore();
|
||||
const { planContent } = useConversationStore();
|
||||
const { handlePlanClick } = useHandlePlanClick();
|
||||
|
||||
if (planContent !== null && planContent !== undefined) {
|
||||
return (
|
||||
@ -27,7 +29,7 @@ function PlannerTab() {
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setConversationMode("plan")}
|
||||
onClick={handlePlanClick}
|
||||
className="flex w-[164px] h-[40px] p-2 justify-center items-center shrink-0 rounded-lg bg-white overflow-hidden text-black text-ellipsis font-sans text-[16px] not-italic font-normal leading-[20px] hover:cursor-pointer hover:opacity-80"
|
||||
>
|
||||
{t(I18nKey.COMMON$CREATE_A_PLAN)}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user