feat(frontend): add handler for 'create a plan' button click (#11806)

This commit is contained in:
Hiep Le 2025-12-01 11:08:00 -05:00 committed by GitHub
parent 6c821ab73e
commit 6c2862ae08
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 81 additions and 51 deletions

View File

@ -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 ||

View 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 };
};

View File

@ -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)}