feat(frontend): disable the build button while the agent is running (planning agent) (#12643)

This commit is contained in:
Hiep Le
2026-01-30 15:13:05 +07:00
committed by GitHub
parent fdb04dfe5d
commit 7836136ff8
4 changed files with 103 additions and 3 deletions

View File

@@ -111,6 +111,75 @@ describe("PlanPreview", () => {
expect(onBuildClick).toHaveBeenCalledTimes(1);
});
it("should disable Build button when isBuildDisabled is true", () => {
// Arrange
renderWithProviders(
<PlanPreview
planContent="Plan content"
onBuildClick={vi.fn()}
isBuildDisabled={true}
/>,
);
// Act
const buildButton = screen.getByTestId("plan-preview-build-button");
// Assert
expect(buildButton).toBeDisabled();
});
it("should not disable Build button when isBuildDisabled is false", () => {
// Arrange
renderWithProviders(
<PlanPreview
planContent="Plan content"
onBuildClick={vi.fn()}
isBuildDisabled={false}
/>,
);
// Act
const buildButton = screen.getByTestId("plan-preview-build-button");
// Assert
expect(buildButton).not.toBeDisabled();
});
it("should not disable Build button when isBuildDisabled is undefined", () => {
// Arrange
renderWithProviders(
<PlanPreview planContent="Plan content" onBuildClick={vi.fn()} />,
);
// Act
const buildButton = screen.getByTestId("plan-preview-build-button");
// Assert
expect(buildButton).not.toBeDisabled();
});
it("should not call onBuildClick when Build button is disabled and clicked", async () => {
// Arrange
const user = userEvent.setup();
const onBuildClick = vi.fn();
renderWithProviders(
<PlanPreview
planContent="Plan content"
onBuildClick={onBuildClick}
isBuildDisabled={true}
/>,
);
const buildButton = screen.getByTestId("plan-preview-build-button");
// Act
await user.click(buildButton);
// Assert
expect(onBuildClick).not.toHaveBeenCalled();
});
it("should render header with PLAN_MD text", () => {
const { container } = renderWithProviders(
<PlanPreview planContent="Plan content" />,

View File

@@ -19,6 +19,11 @@ vi.mock("#/hooks/query/use-config", () => ({
}),
}));
// Mock useConversationId (EventMessage -> useAgentState -> useActiveConversation -> useConversationId)
vi.mock("#/hooks/use-conversation-id", () => ({
useConversationId: () => ({ conversationId: "test-conversation-id" }),
}));
// Mock PlanPreview component to verify it's rendered
vi.mock("#/components/features/chat/plan-preview", () => ({
PlanPreview: ({ planContent }: { planContent?: string | null }) => (

View File

@@ -6,6 +6,7 @@ import { USE_PLANNING_AGENT } from "#/utils/feature-flags";
import { Typography } from "#/ui/typography";
import { I18nKey } from "#/i18n/declaration";
import { MarkdownRenderer } from "#/components/features/markdown/markdown-renderer";
import { cn } from "#/utils/utils";
import { useSelectConversationTab } from "#/hooks/use-select-conversation-tab";
import { planHeadings } from "#/components/features/markdown/plan-headings";
@@ -15,10 +16,16 @@ interface PlanPreviewProps {
/** Raw plan content from PLAN.md file */
planContent?: string | null;
onBuildClick?: () => void;
/** Whether the Build button should be disabled (e.g., while streaming) */
isBuildDisabled?: boolean;
}
/* eslint-disable i18next/no-literal-string */
export function PlanPreview({ planContent, onBuildClick }: PlanPreviewProps) {
export function PlanPreview({
planContent,
onBuildClick,
isBuildDisabled,
}: PlanPreviewProps) {
const { t } = useTranslation();
const { selectTab } = useSelectConversationTab();
@@ -90,7 +97,13 @@ export function PlanPreview({ planContent, onBuildClick }: PlanPreviewProps) {
<button
type="button"
onClick={onBuildClick}
className="bg-white flex items-center justify-center h-[26px] px-2 rounded-[4px] w-[93px] hover:opacity-90 transition-opacity cursor-pointer"
disabled={isBuildDisabled}
className={cn(
"bg-white flex items-center justify-center h-[26px] px-2 rounded-[4px] w-[93px] transition-opacity",
isBuildDisabled
? "opacity-50 cursor-not-allowed"
: "hover:opacity-90 cursor-pointer",
)}
data-testid="plan-preview-build-button"
>
<Typography.Text className="font-medium text-[14px] text-black leading-5">

View File

@@ -11,6 +11,8 @@ import {
import { MicroagentStatus } from "#/types/microagent-status";
import { useConfig } from "#/hooks/query/use-config";
import { useConversationStore } from "#/stores/conversation-store";
import { useAgentState } from "#/hooks/use-agent-state";
import { AgentState } from "#/types/agent-state";
// TODO: Implement V1 feedback functionality when API supports V1 event IDs
// import { useFeedbackExists } from "#/hooks/query/use-feedback-exists";
import {
@@ -153,6 +155,12 @@ export function EventMessage({
}: EventMessageProps) {
const { data: config } = useConfig();
const { planContent } = useConversationStore();
const { curAgentState } = useAgentState();
// Disable Build button while agent is running (streaming)
const isAgentRunning =
curAgentState === AgentState.RUNNING ||
curAgentState === AgentState.LOADING;
// V1 events use string IDs, but useFeedbackExists expects number
// For now, we'll skip feedback functionality for V1 events
@@ -214,7 +222,12 @@ export function EventMessage({
planPreviewEventIds &&
shouldShowPlanPreview(event.id, planPreviewEventIds)
) {
return <PlanPreview planContent={planContent} />;
return (
<PlanPreview
planContent={planContent}
isBuildDisabled={isAgentRunning}
/>
);
}
// Not the designated preview event for this phase - render nothing
// This prevents duplicate previews within the same phase