mirror of
https://github.com/OpenHands/OpenHands.git
synced 2026-03-22 13:47:19 +08:00
feat(frontend): disable the build button while the agent is running (planning agent) (#12643)
This commit is contained in:
@@ -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" />,
|
||||
|
||||
@@ -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 }) => (
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user