mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
refactor(frontend): migration of agent-slice.ts to zustand (#11102)
This commit is contained in:
parent
f8f74858da
commit
e376c2bfd1
@ -1,36 +1,16 @@
|
||||
import { screen } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { renderWithProviders } from "test-utils";
|
||||
import { renderWithQueryAndI18n } from "test-utils";
|
||||
import { ServerStatus } from "#/components/features/controls/server-status";
|
||||
import { ServerStatusContextMenu } from "#/components/features/controls/server-status-context-menu";
|
||||
import { ConversationStatus } from "#/types/conversation-status";
|
||||
import { AgentState } from "#/types/agent-state";
|
||||
import { useAgentStore } from "#/stores/agent-store";
|
||||
|
||||
// Mock the conversation slice actions
|
||||
vi.mock("#/state/conversation-slice", () => ({
|
||||
setShouldStopConversation: vi.fn(),
|
||||
setShouldStartConversation: vi.fn(),
|
||||
default: {
|
||||
name: "conversation",
|
||||
initialState: {
|
||||
isRightPanelShown: true,
|
||||
shouldStopConversation: false,
|
||||
shouldStartConversation: false,
|
||||
},
|
||||
reducers: {},
|
||||
},
|
||||
}));
|
||||
|
||||
// Mock react-redux
|
||||
vi.mock("react-redux", () => ({
|
||||
useSelector: vi.fn((selector) => {
|
||||
// Mock the selector to return different agent states based on test needs
|
||||
return {
|
||||
curAgentState: AgentState.RUNNING,
|
||||
};
|
||||
}),
|
||||
Provider: ({ children }: { children: React.ReactNode }) => children,
|
||||
// Mock the agent store
|
||||
vi.mock("#/stores/agent-store", () => ({
|
||||
useAgentStore: vi.fn(),
|
||||
}));
|
||||
|
||||
// Mock the custom hooks
|
||||
@ -86,13 +66,25 @@ vi.mock("react-i18next", async () => {
|
||||
});
|
||||
|
||||
describe("ServerStatus", () => {
|
||||
// Helper function to mock agent store with specific state
|
||||
const mockAgentStore = (agentState: AgentState) => {
|
||||
vi.mocked(useAgentStore).mockReturnValue({
|
||||
curAgentState: agentState,
|
||||
setCurrentAgentState: vi.fn(),
|
||||
reset: vi.fn(),
|
||||
});
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("should render server status with different conversation statuses", () => {
|
||||
// Mock agent store to return RUNNING state
|
||||
mockAgentStore(AgentState.RUNNING);
|
||||
|
||||
// Test RUNNING status
|
||||
const { rerender } = renderWithProviders(
|
||||
const { rerender } = renderWithQueryAndI18n(
|
||||
<ServerStatus conversationStatus="RUNNING" />,
|
||||
);
|
||||
expect(screen.getByText("Running")).toBeInTheDocument();
|
||||
@ -112,7 +104,11 @@ describe("ServerStatus", () => {
|
||||
|
||||
it("should show context menu when clicked with RUNNING status", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderWithProviders(<ServerStatus conversationStatus="RUNNING" />);
|
||||
|
||||
// Mock agent store to return RUNNING state
|
||||
mockAgentStore(AgentState.RUNNING);
|
||||
|
||||
renderWithQueryAndI18n(<ServerStatus conversationStatus="RUNNING" />);
|
||||
|
||||
const statusContainer = screen.getByText("Running").closest("div");
|
||||
expect(statusContainer).toBeInTheDocument();
|
||||
@ -128,7 +124,11 @@ describe("ServerStatus", () => {
|
||||
|
||||
it("should show context menu when clicked with STOPPED status", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderWithProviders(<ServerStatus conversationStatus="STOPPED" />);
|
||||
|
||||
// Mock agent store to return STOPPED state
|
||||
mockAgentStore(AgentState.STOPPED);
|
||||
|
||||
renderWithQueryAndI18n(<ServerStatus conversationStatus="STOPPED" />);
|
||||
|
||||
const statusContainer = screen.getByText("Server Stopped").closest("div");
|
||||
expect(statusContainer).toBeInTheDocument();
|
||||
@ -144,7 +144,11 @@ describe("ServerStatus", () => {
|
||||
|
||||
it("should not show context menu when clicked with other statuses", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderWithProviders(<ServerStatus conversationStatus="STARTING" />);
|
||||
|
||||
// Mock agent store to return RUNNING state
|
||||
mockAgentStore(AgentState.RUNNING);
|
||||
|
||||
renderWithQueryAndI18n(<ServerStatus conversationStatus="STARTING" />);
|
||||
|
||||
const statusContainer = screen.getByText("Running").closest("div");
|
||||
expect(statusContainer).toBeInTheDocument();
|
||||
@ -163,7 +167,10 @@ describe("ServerStatus", () => {
|
||||
// Clear previous calls
|
||||
mockStopConversationMutate.mockClear();
|
||||
|
||||
renderWithProviders(<ServerStatus conversationStatus="RUNNING" />);
|
||||
// Mock agent store to return RUNNING state
|
||||
mockAgentStore(AgentState.RUNNING);
|
||||
|
||||
renderWithQueryAndI18n(<ServerStatus conversationStatus="RUNNING" />);
|
||||
|
||||
const statusContainer = screen.getByText("Running").closest("div");
|
||||
await user.click(statusContainer!);
|
||||
@ -182,7 +189,10 @@ describe("ServerStatus", () => {
|
||||
// Clear previous calls
|
||||
mockStartConversationMutate.mockClear();
|
||||
|
||||
renderWithProviders(<ServerStatus conversationStatus="STOPPED" />);
|
||||
// Mock agent store to return STOPPED state
|
||||
mockAgentStore(AgentState.STOPPED);
|
||||
|
||||
renderWithQueryAndI18n(<ServerStatus conversationStatus="STOPPED" />);
|
||||
|
||||
const statusContainer = screen.getByText("Server Stopped").closest("div");
|
||||
await user.click(statusContainer!);
|
||||
@ -198,7 +208,11 @@ describe("ServerStatus", () => {
|
||||
|
||||
it("should close context menu after stop server action", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderWithProviders(<ServerStatus conversationStatus="RUNNING" />);
|
||||
|
||||
// Mock agent store to return RUNNING state
|
||||
mockAgentStore(AgentState.RUNNING);
|
||||
|
||||
renderWithQueryAndI18n(<ServerStatus conversationStatus="RUNNING" />);
|
||||
|
||||
const statusContainer = screen.getByText("Running").closest("div");
|
||||
await user.click(statusContainer!);
|
||||
@ -214,7 +228,11 @@ describe("ServerStatus", () => {
|
||||
|
||||
it("should close context menu after start server action", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderWithProviders(<ServerStatus conversationStatus="STOPPED" />);
|
||||
|
||||
// Mock agent store to return STOPPED state
|
||||
mockAgentStore(AgentState.STOPPED);
|
||||
|
||||
renderWithQueryAndI18n(<ServerStatus conversationStatus="STOPPED" />);
|
||||
|
||||
const statusContainer = screen.getByText("Server Stopped").closest("div");
|
||||
await user.click(statusContainer!);
|
||||
@ -229,7 +247,10 @@ describe("ServerStatus", () => {
|
||||
});
|
||||
|
||||
it("should handle null conversation status", () => {
|
||||
renderWithProviders(<ServerStatus conversationStatus={null} />);
|
||||
// Mock agent store to return RUNNING state
|
||||
mockAgentStore(AgentState.RUNNING);
|
||||
|
||||
renderWithQueryAndI18n(<ServerStatus conversationStatus={null} />);
|
||||
|
||||
const statusText = screen.getByText("Running");
|
||||
expect(statusText).toBeInTheDocument();
|
||||
@ -247,7 +268,7 @@ describe("ServerStatusContextMenu", () => {
|
||||
});
|
||||
|
||||
it("should render stop server button when status is RUNNING", () => {
|
||||
renderWithProviders(
|
||||
renderWithQueryAndI18n(
|
||||
<ServerStatusContextMenu
|
||||
{...defaultProps}
|
||||
conversationStatus="RUNNING"
|
||||
@ -260,7 +281,7 @@ describe("ServerStatusContextMenu", () => {
|
||||
});
|
||||
|
||||
it("should render start server button when status is STOPPED", () => {
|
||||
renderWithProviders(
|
||||
renderWithQueryAndI18n(
|
||||
<ServerStatusContextMenu
|
||||
{...defaultProps}
|
||||
conversationStatus="STOPPED"
|
||||
@ -273,7 +294,7 @@ describe("ServerStatusContextMenu", () => {
|
||||
});
|
||||
|
||||
it("should not render stop server button when onStopServer is not provided", () => {
|
||||
renderWithProviders(
|
||||
renderWithQueryAndI18n(
|
||||
<ServerStatusContextMenu
|
||||
{...defaultProps}
|
||||
conversationStatus="RUNNING"
|
||||
@ -284,7 +305,7 @@ describe("ServerStatusContextMenu", () => {
|
||||
});
|
||||
|
||||
it("should not render start server button when onStartServer is not provided", () => {
|
||||
renderWithProviders(
|
||||
renderWithQueryAndI18n(
|
||||
<ServerStatusContextMenu
|
||||
{...defaultProps}
|
||||
conversationStatus="STOPPED"
|
||||
@ -298,7 +319,7 @@ describe("ServerStatusContextMenu", () => {
|
||||
const user = userEvent.setup();
|
||||
const onStopServer = vi.fn();
|
||||
|
||||
renderWithProviders(
|
||||
renderWithQueryAndI18n(
|
||||
<ServerStatusContextMenu
|
||||
{...defaultProps}
|
||||
conversationStatus="RUNNING"
|
||||
@ -316,7 +337,7 @@ describe("ServerStatusContextMenu", () => {
|
||||
const user = userEvent.setup();
|
||||
const onStartServer = vi.fn();
|
||||
|
||||
renderWithProviders(
|
||||
renderWithQueryAndI18n(
|
||||
<ServerStatusContextMenu
|
||||
{...defaultProps}
|
||||
conversationStatus="STOPPED"
|
||||
@ -331,7 +352,7 @@ describe("ServerStatusContextMenu", () => {
|
||||
});
|
||||
|
||||
it("should render correct text content for stop server button", () => {
|
||||
renderWithProviders(
|
||||
renderWithQueryAndI18n(
|
||||
<ServerStatusContextMenu
|
||||
{...defaultProps}
|
||||
conversationStatus="RUNNING"
|
||||
@ -345,7 +366,7 @@ describe("ServerStatusContextMenu", () => {
|
||||
});
|
||||
|
||||
it("should render correct text content for start server button", () => {
|
||||
renderWithProviders(
|
||||
renderWithQueryAndI18n(
|
||||
<ServerStatusContextMenu
|
||||
{...defaultProps}
|
||||
conversationStatus="STOPPED"
|
||||
@ -361,7 +382,7 @@ describe("ServerStatusContextMenu", () => {
|
||||
it("should call onClose when context menu is closed", () => {
|
||||
const onClose = vi.fn();
|
||||
|
||||
renderWithProviders(
|
||||
renderWithQueryAndI18n(
|
||||
<ServerStatusContextMenu
|
||||
{...defaultProps}
|
||||
onClose={onClose}
|
||||
@ -376,7 +397,7 @@ describe("ServerStatusContextMenu", () => {
|
||||
});
|
||||
|
||||
it("should not render any buttons for other conversation statuses", () => {
|
||||
renderWithProviders(
|
||||
renderWithQueryAndI18n(
|
||||
<ServerStatusContextMenu
|
||||
{...defaultProps}
|
||||
conversationStatus="STARTING"
|
||||
|
||||
@ -5,6 +5,18 @@ import { MemoryRouter } from "react-router";
|
||||
import { InteractiveChatBox } from "#/components/features/chat/interactive-chat-box";
|
||||
import { renderWithProviders } from "../../test-utils";
|
||||
import { AgentState } from "#/types/agent-state";
|
||||
import { useAgentStore } from "#/stores/agent-store";
|
||||
import { useConversationStore } from "#/state/conversation-store";
|
||||
|
||||
// Mock the agent store
|
||||
vi.mock("#/stores/agent-store", () => ({
|
||||
useAgentStore: vi.fn(),
|
||||
}));
|
||||
|
||||
// Mock the conversation store
|
||||
vi.mock("#/state/conversation-store", () => ({
|
||||
useConversationStore: vi.fn(),
|
||||
}));
|
||||
|
||||
// Mock React Router hooks
|
||||
vi.mock("react-router", async () => {
|
||||
@ -47,6 +59,49 @@ describe("InteractiveChatBox", () => {
|
||||
const onSubmitMock = vi.fn();
|
||||
const onStopMock = vi.fn();
|
||||
|
||||
// Helper function to mock stores
|
||||
const mockStores = (agentState: AgentState = AgentState.INIT) => {
|
||||
vi.mocked(useAgentStore).mockReturnValue({
|
||||
curAgentState: agentState,
|
||||
setCurrentAgentState: vi.fn(),
|
||||
reset: vi.fn(),
|
||||
});
|
||||
|
||||
vi.mocked(useConversationStore).mockReturnValue({
|
||||
images: [],
|
||||
files: [],
|
||||
addImages: vi.fn(),
|
||||
addFiles: vi.fn(),
|
||||
clearAllFiles: vi.fn(),
|
||||
addFileLoading: vi.fn(),
|
||||
removeFileLoading: vi.fn(),
|
||||
addImageLoading: vi.fn(),
|
||||
removeImageLoading: vi.fn(),
|
||||
submittedMessage: null,
|
||||
setShouldHideSuggestions: vi.fn(),
|
||||
setSubmittedMessage: vi.fn(),
|
||||
isRightPanelShown: true,
|
||||
selectedTab: "editor" as const,
|
||||
loadingFiles: [],
|
||||
loadingImages: [],
|
||||
messageToSend: null,
|
||||
shouldShownAgentLoading: false,
|
||||
shouldHideSuggestions: false,
|
||||
hasRightPanelToggled: true,
|
||||
setIsRightPanelShown: vi.fn(),
|
||||
setSelectedTab: vi.fn(),
|
||||
setShouldShownAgentLoading: vi.fn(),
|
||||
removeImage: vi.fn(),
|
||||
removeFile: vi.fn(),
|
||||
clearImages: vi.fn(),
|
||||
clearFiles: vi.fn(),
|
||||
clearAllLoading: vi.fn(),
|
||||
setMessageToSend: vi.fn(),
|
||||
resetConversationState: vi.fn(),
|
||||
setHasRightPanelToggled: vi.fn(),
|
||||
});
|
||||
};
|
||||
|
||||
// Helper function to render with Router context
|
||||
const renderInteractiveChatBox = (props: any, options: any = {}) => {
|
||||
return renderWithProviders(
|
||||
@ -68,22 +123,12 @@ describe("InteractiveChatBox", () => {
|
||||
});
|
||||
|
||||
it("should render", () => {
|
||||
renderInteractiveChatBox(
|
||||
{
|
||||
onSubmit: onSubmitMock,
|
||||
onStop: onStopMock,
|
||||
isWaitingForUserInput: false,
|
||||
hasSubstantiveAgentActions: false,
|
||||
optimisticUserMessage: false,
|
||||
},
|
||||
{
|
||||
preloadedState: {
|
||||
agent: {
|
||||
curAgentState: AgentState.INIT,
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
mockStores(AgentState.INIT);
|
||||
|
||||
renderInteractiveChatBox({
|
||||
onSubmit: onSubmitMock,
|
||||
onStop: onStopMock,
|
||||
});
|
||||
|
||||
const chatBox = screen.getByTestId("interactive-chat-box");
|
||||
expect(chatBox).toBeInTheDocument();
|
||||
@ -91,33 +136,12 @@ describe("InteractiveChatBox", () => {
|
||||
|
||||
it("should set custom values", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderInteractiveChatBox(
|
||||
{
|
||||
onSubmit: onSubmitMock,
|
||||
onStop: onStopMock,
|
||||
isWaitingForUserInput: true,
|
||||
hasSubstantiveAgentActions: true,
|
||||
optimisticUserMessage: false,
|
||||
},
|
||||
{
|
||||
preloadedState: {
|
||||
agent: {
|
||||
curAgentState: AgentState.AWAITING_USER_INPUT,
|
||||
},
|
||||
conversation: {
|
||||
isRightPanelShown: true,
|
||||
shouldStopConversation: false,
|
||||
shouldStartConversation: false,
|
||||
images: [],
|
||||
files: [],
|
||||
loadingFiles: [],
|
||||
loadingImages: [],
|
||||
messageToSend: null,
|
||||
shouldShownAgentLoading: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
mockStores(AgentState.AWAITING_USER_INPUT);
|
||||
|
||||
renderInteractiveChatBox({
|
||||
onSubmit: onSubmitMock,
|
||||
onStop: onStopMock,
|
||||
});
|
||||
|
||||
const textbox = screen.getByTestId("chat-input");
|
||||
|
||||
@ -129,22 +153,12 @@ describe("InteractiveChatBox", () => {
|
||||
|
||||
it("should display the image previews when images are uploaded", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderInteractiveChatBox(
|
||||
{
|
||||
onSubmit: onSubmitMock,
|
||||
onStop: onStopMock,
|
||||
isWaitingForUserInput: false,
|
||||
hasSubstantiveAgentActions: false,
|
||||
optimisticUserMessage: false,
|
||||
},
|
||||
{
|
||||
preloadedState: {
|
||||
agent: {
|
||||
curAgentState: AgentState.INIT,
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
mockStores(AgentState.INIT);
|
||||
|
||||
renderInteractiveChatBox({
|
||||
onSubmit: onSubmitMock,
|
||||
onStop: onStopMock,
|
||||
});
|
||||
|
||||
// Create a larger file to ensure it passes validation
|
||||
const fileContent = new Array(1024).fill("a").join(""); // 1KB file
|
||||
@ -166,22 +180,12 @@ describe("InteractiveChatBox", () => {
|
||||
|
||||
it("should remove the image preview when the close button is clicked", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderInteractiveChatBox(
|
||||
{
|
||||
onSubmit: onSubmitMock,
|
||||
onStop: onStopMock,
|
||||
isWaitingForUserInput: false,
|
||||
hasSubstantiveAgentActions: false,
|
||||
optimisticUserMessage: false,
|
||||
},
|
||||
{
|
||||
preloadedState: {
|
||||
agent: {
|
||||
curAgentState: AgentState.INIT,
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
mockStores(AgentState.INIT);
|
||||
|
||||
renderInteractiveChatBox({
|
||||
onSubmit: onSubmitMock,
|
||||
onStop: onStopMock,
|
||||
});
|
||||
|
||||
const fileContent = new Array(1024).fill("a").join(""); // 1KB file
|
||||
const file = new File([fileContent], "chucknorris.png", {
|
||||
@ -201,22 +205,12 @@ describe("InteractiveChatBox", () => {
|
||||
|
||||
it("should call onSubmit with the message and images", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderInteractiveChatBox(
|
||||
{
|
||||
onSubmit: onSubmitMock,
|
||||
onStop: onStopMock,
|
||||
isWaitingForUserInput: false,
|
||||
hasSubstantiveAgentActions: false,
|
||||
optimisticUserMessage: false,
|
||||
},
|
||||
{
|
||||
preloadedState: {
|
||||
agent: {
|
||||
curAgentState: AgentState.INIT,
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
mockStores(AgentState.INIT);
|
||||
|
||||
renderInteractiveChatBox({
|
||||
onSubmit: onSubmitMock,
|
||||
onStop: onStopMock,
|
||||
});
|
||||
|
||||
const textarea = screen.getByTestId("chat-input");
|
||||
|
||||
@ -242,22 +236,12 @@ describe("InteractiveChatBox", () => {
|
||||
|
||||
it("should disable the submit button when agent is loading", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderInteractiveChatBox(
|
||||
{
|
||||
onSubmit: onSubmitMock,
|
||||
onStop: onStopMock,
|
||||
isWaitingForUserInput: false,
|
||||
hasSubstantiveAgentActions: false,
|
||||
optimisticUserMessage: false,
|
||||
},
|
||||
{
|
||||
preloadedState: {
|
||||
agent: {
|
||||
curAgentState: AgentState.LOADING,
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
mockStores(AgentState.LOADING);
|
||||
|
||||
renderInteractiveChatBox({
|
||||
onSubmit: onSubmitMock,
|
||||
onStop: onStopMock,
|
||||
});
|
||||
|
||||
const button = screen.getByTestId("submit-button");
|
||||
expect(button).toBeDisabled();
|
||||
@ -268,23 +252,14 @@ describe("InteractiveChatBox", () => {
|
||||
|
||||
it("should display the stop button when agent is running and call onStop when clicked", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderInteractiveChatBox(
|
||||
{
|
||||
onSubmit: onSubmitMock,
|
||||
onStop: onStopMock,
|
||||
isWaitingForUserInput: false,
|
||||
hasSubstantiveAgentActions: true,
|
||||
optimisticUserMessage: false,
|
||||
},
|
||||
{
|
||||
preloadedState: {
|
||||
agent: {
|
||||
curAgentState: AgentState.RUNNING,
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
mockStores(AgentState.RUNNING);
|
||||
|
||||
renderInteractiveChatBox({
|
||||
onSubmit: onSubmitMock,
|
||||
onStop: onStopMock,
|
||||
});
|
||||
|
||||
// The stop button should be available when agent is running
|
||||
const stopButton = screen.getByTestId("stop-button");
|
||||
expect(stopButton).toBeInTheDocument();
|
||||
|
||||
@ -297,33 +272,12 @@ describe("InteractiveChatBox", () => {
|
||||
const onSubmit = vi.fn();
|
||||
const onStop = vi.fn();
|
||||
|
||||
const { rerender } = renderInteractiveChatBox(
|
||||
{
|
||||
onSubmit: onSubmit,
|
||||
onStop: onStop,
|
||||
isWaitingForUserInput: true,
|
||||
hasSubstantiveAgentActions: true,
|
||||
optimisticUserMessage: false,
|
||||
},
|
||||
{
|
||||
preloadedState: {
|
||||
agent: {
|
||||
curAgentState: AgentState.AWAITING_USER_INPUT,
|
||||
},
|
||||
conversation: {
|
||||
isRightPanelShown: true,
|
||||
shouldStopConversation: false,
|
||||
shouldStartConversation: false,
|
||||
images: [],
|
||||
files: [],
|
||||
loadingFiles: [],
|
||||
loadingImages: [],
|
||||
messageToSend: null,
|
||||
shouldShownAgentLoading: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
mockStores(AgentState.AWAITING_USER_INPUT);
|
||||
|
||||
const { rerender } = renderInteractiveChatBox({
|
||||
onSubmit: onSubmit,
|
||||
onStop: onStop,
|
||||
});
|
||||
|
||||
// Verify text input has the initial value
|
||||
const textarea = screen.getByTestId("chat-input");
|
||||
|
||||
@ -1,25 +1,23 @@
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { Provider } from "react-redux";
|
||||
import { configureStore } from "@reduxjs/toolkit";
|
||||
import { JupyterEditor } from "#/components/features/jupyter/jupyter";
|
||||
import { useJupyterStore } from "#/state/jupyter-store";
|
||||
import { vi, describe, it, expect, beforeEach } from "vitest";
|
||||
import { AgentState } from "#/types/agent-state";
|
||||
import { useAgentStore } from "#/stores/agent-store";
|
||||
import { useJupyterStore } from "#/state/jupyter-store";
|
||||
|
||||
// Mock the agent store
|
||||
vi.mock("#/stores/agent-store", () => ({
|
||||
useAgentStore: vi.fn(),
|
||||
}));
|
||||
|
||||
// Mock react-i18next
|
||||
vi.mock("react-i18next", () => ({
|
||||
useTranslation: () => ({
|
||||
t: (key: string) => key,
|
||||
}),
|
||||
}));
|
||||
|
||||
describe("JupyterEditor", () => {
|
||||
const mockStore = configureStore({
|
||||
reducer: {
|
||||
fileState: () => ({}),
|
||||
initalQuery: () => ({}),
|
||||
browser: () => ({}),
|
||||
chat: () => ({}),
|
||||
code: () => ({}),
|
||||
cmd: () => ({}),
|
||||
agent: () => ({}),
|
||||
securityAnalyzer: () => ({}),
|
||||
status: () => ({}),
|
||||
},
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
// Reset the Zustand store before each test
|
||||
useJupyterStore.setState({
|
||||
@ -32,12 +30,17 @@ describe("JupyterEditor", () => {
|
||||
});
|
||||
|
||||
it("should have a scrollable container", () => {
|
||||
// Mock agent store to return RUNNING state (not in RUNTIME_INACTIVE_STATES)
|
||||
vi.mocked(useAgentStore).mockReturnValue({
|
||||
curAgentState: AgentState.RUNNING,
|
||||
setCurrentAgentState: vi.fn(),
|
||||
reset: vi.fn(),
|
||||
});
|
||||
|
||||
render(
|
||||
<Provider store={mockStore}>
|
||||
<div style={{ height: "100vh" }}>
|
||||
<JupyterEditor maxWidth={800} />
|
||||
</div>
|
||||
</Provider>,
|
||||
<div style={{ height: "100vh" }}>
|
||||
<JupyterEditor maxWidth={800} />
|
||||
</div>,
|
||||
);
|
||||
|
||||
const container = screen.getByTestId("jupyter-container");
|
||||
|
||||
@ -1,23 +1,21 @@
|
||||
import { screen } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import { renderWithProviders } from "test-utils";
|
||||
import { renderWithQueryAndI18n } from "test-utils";
|
||||
import { MicroagentsModal } from "#/components/features/conversation-panel/microagents-modal";
|
||||
import ConversationService from "#/api/conversation-service/conversation-service.api";
|
||||
import { AgentState } from "#/types/agent-state";
|
||||
import { useAgentStore } from "#/stores/agent-store";
|
||||
|
||||
vi.mock("react-redux", async () => {
|
||||
const actual = await vi.importActual("react-redux");
|
||||
return {
|
||||
...actual,
|
||||
useDispatch: () => vi.fn(),
|
||||
useSelector: () => ({
|
||||
agent: {
|
||||
curAgentState: AgentState.AWAITING_USER_INPUT,
|
||||
},
|
||||
}),
|
||||
};
|
||||
});
|
||||
// Mock the agent store
|
||||
vi.mock("#/stores/agent-store", () => ({
|
||||
useAgentStore: vi.fn(),
|
||||
}));
|
||||
|
||||
// Mock the conversation ID hook
|
||||
vi.mock("#/hooks/use-conversation-id", () => ({
|
||||
useConversationId: () => ({ conversationId: "test-conversation-id" }),
|
||||
}));
|
||||
|
||||
describe("MicroagentsModal - Refresh Button", () => {
|
||||
const mockOnClose = vi.fn();
|
||||
@ -47,10 +45,17 @@ describe("MicroagentsModal - Refresh Button", () => {
|
||||
// Reset all mocks before each test
|
||||
vi.clearAllMocks();
|
||||
|
||||
// Setup default mock for getUserConversations
|
||||
// Setup default mock for getMicroagents
|
||||
vi.spyOn(ConversationService, "getMicroagents").mockResolvedValue({
|
||||
microagents: mockMicroagents,
|
||||
});
|
||||
|
||||
// Mock the agent store to return a ready state
|
||||
vi.mocked(useAgentStore).mockReturnValue({
|
||||
curAgentState: AgentState.AWAITING_USER_INPUT,
|
||||
setCurrentAgentState: vi.fn(),
|
||||
reset: vi.fn(),
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@ -58,10 +63,11 @@ describe("MicroagentsModal - Refresh Button", () => {
|
||||
});
|
||||
|
||||
describe("Refresh Button Rendering", () => {
|
||||
it("should render the refresh button with correct text and test ID", () => {
|
||||
renderWithProviders(<MicroagentsModal {...defaultProps} />);
|
||||
it("should render the refresh button with correct text and test ID", async () => {
|
||||
renderWithQueryAndI18n(<MicroagentsModal {...defaultProps} />);
|
||||
|
||||
const refreshButton = screen.getByTestId("refresh-microagents");
|
||||
// Wait for the component to load and render the refresh button
|
||||
const refreshButton = await screen.findByTestId("refresh-microagents");
|
||||
expect(refreshButton).toBeInTheDocument();
|
||||
expect(refreshButton).toHaveTextContent("BUTTON$REFRESH");
|
||||
});
|
||||
@ -71,11 +77,12 @@ describe("MicroagentsModal - Refresh Button", () => {
|
||||
it("should call refetch when refresh button is clicked", async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
renderWithProviders(<MicroagentsModal {...defaultProps} />);
|
||||
renderWithQueryAndI18n(<MicroagentsModal {...defaultProps} />);
|
||||
|
||||
const refreshSpy = vi.spyOn(ConversationService, "getMicroagents");
|
||||
|
||||
const refreshButton = screen.getByTestId("refresh-microagents");
|
||||
// Wait for the component to load and render the refresh button
|
||||
const refreshButton = await screen.findByTestId("refresh-microagents");
|
||||
await user.click(refreshButton);
|
||||
|
||||
expect(refreshSpy).toHaveBeenCalledTimes(1);
|
||||
|
||||
@ -3,7 +3,8 @@ import { afterEach } from "node:test";
|
||||
import { useTerminal } from "#/hooks/use-terminal";
|
||||
import { Command, useCommandStore } from "#/state/command-store";
|
||||
import { AgentState } from "#/types/agent-state";
|
||||
import { renderWithProviders } from "../../test-utils";
|
||||
import { renderWithQueryAndI18n } from "../../test-utils";
|
||||
import { useAgentStore } from "#/stores/agent-store";
|
||||
|
||||
// Mock the WsClient context
|
||||
vi.mock("#/context/ws-client-provider", () => ({
|
||||
@ -22,6 +23,8 @@ interface TestTerminalComponentProps {
|
||||
function TestTerminalComponent({ commands }: TestTerminalComponentProps) {
|
||||
// Set commands in Zustand store
|
||||
useCommandStore.setState({ commands });
|
||||
// Set agent state in Zustand store
|
||||
useAgentStore.setState({ curAgentState: AgentState.RUNNING });
|
||||
const ref = useTerminal();
|
||||
return <div ref={ref} />;
|
||||
}
|
||||
@ -57,11 +60,7 @@ describe("useTerminal", () => {
|
||||
});
|
||||
|
||||
it("should render", () => {
|
||||
renderWithProviders(<TestTerminalComponent commands={[]} />, {
|
||||
preloadedState: {
|
||||
agent: { curAgentState: AgentState.RUNNING },
|
||||
},
|
||||
});
|
||||
renderWithQueryAndI18n(<TestTerminalComponent commands={[]} />);
|
||||
});
|
||||
|
||||
it("should render the commands in the terminal", () => {
|
||||
@ -70,11 +69,7 @@ describe("useTerminal", () => {
|
||||
{ content: "hello", type: "output" },
|
||||
];
|
||||
|
||||
renderWithProviders(<TestTerminalComponent commands={commands} />, {
|
||||
preloadedState: {
|
||||
agent: { curAgentState: AgentState.RUNNING },
|
||||
},
|
||||
});
|
||||
renderWithQueryAndI18n(<TestTerminalComponent commands={commands} />);
|
||||
|
||||
expect(mockTerminal.writeln).toHaveBeenNthCalledWith(1, "echo hello");
|
||||
expect(mockTerminal.writeln).toHaveBeenNthCalledWith(2, "hello");
|
||||
@ -92,11 +87,7 @@ describe("useTerminal", () => {
|
||||
{ content: secret, type: "output" },
|
||||
];
|
||||
|
||||
renderWithProviders(<TestTerminalComponent commands={commands} />, {
|
||||
preloadedState: {
|
||||
agent: { curAgentState: AgentState.RUNNING },
|
||||
},
|
||||
});
|
||||
renderWithQueryAndI18n(<TestTerminalComponent commands={commands} />);
|
||||
|
||||
// This test is no longer relevant as secrets filtering has been removed
|
||||
});
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import { useSelector } from "react-redux";
|
||||
import React from "react";
|
||||
import posthog from "posthog-js";
|
||||
import { useParams } from "react-router";
|
||||
@ -7,7 +6,6 @@ import { convertImageToBase64 } from "#/utils/convert-image-to-base-64";
|
||||
import { TrajectoryActions } from "../trajectory/trajectory-actions";
|
||||
import { createChatMessage } from "#/services/chat-service";
|
||||
import { InteractiveChatBox } from "./interactive-chat-box";
|
||||
import { RootState } from "#/store";
|
||||
import { AgentState } from "#/types/agent-state";
|
||||
import { isOpenHandsAction } from "#/types/core/guards";
|
||||
import { generateAgentStateChangeEvent } from "#/services/agent-state-service";
|
||||
@ -19,6 +17,7 @@ import { Messages } from "./messages";
|
||||
import { ChatSuggestions } from "./chat-suggestions";
|
||||
import { ScrollProvider } from "#/context/scroll-context";
|
||||
import { useInitialQueryStore } from "#/stores/initial-query-store";
|
||||
import { useAgentStore } from "#/stores/agent-store";
|
||||
|
||||
import { ScrollToBottomButton } from "#/components/shared/buttons/scroll-to-bottom-button";
|
||||
import { LoadingSpinner } from "#/components/shared/loading-spinner";
|
||||
@ -63,7 +62,7 @@ export function ChatInterface() {
|
||||
} = useScrollToBottom(scrollRef);
|
||||
const { data: config } = useConfig();
|
||||
|
||||
const { curAgentState } = useSelector((state: RootState) => state.agent);
|
||||
const { curAgentState } = useAgentStore();
|
||||
|
||||
const [feedbackPolarity, setFeedbackPolarity] = React.useState<
|
||||
"positive" | "negative"
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import { useSelector } from "react-redux";
|
||||
import { isFileImage } from "#/utils/is-file-image";
|
||||
import { displayErrorToast } from "#/utils/custom-toast-handlers";
|
||||
import { validateFiles } from "#/utils/file-validation";
|
||||
@ -7,8 +6,8 @@ import { AgentState } from "#/types/agent-state";
|
||||
import { useActiveConversation } from "#/hooks/query/use-active-conversation";
|
||||
import { GitControlBar } from "./git-control-bar";
|
||||
import { useConversationStore } from "#/state/conversation-store";
|
||||
import { useAgentStore } from "#/stores/agent-store";
|
||||
import { processFiles, processImages } from "#/utils/file-processing";
|
||||
import { RootState } from "#/store";
|
||||
|
||||
interface InteractiveChatBoxProps {
|
||||
onSubmit: (message: string, images: File[], files: File[]) => void;
|
||||
@ -30,9 +29,7 @@ export function InteractiveChatBox({
|
||||
addImageLoading,
|
||||
removeImageLoading,
|
||||
} = useConversationStore();
|
||||
const curAgentState = useSelector(
|
||||
(state: RootState) => state.agent.curAgentState,
|
||||
);
|
||||
const { curAgentState } = useAgentStore();
|
||||
const { data: conversation } = useActiveConversation();
|
||||
|
||||
// Helper function to validate and filter files
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useEffect } from "react";
|
||||
import { RootState } from "#/store";
|
||||
import { useStatusStore } from "#/state/status-store";
|
||||
import { useWsClient } from "#/context/ws-client-provider";
|
||||
import { useActiveConversation } from "#/hooks/query/use-active-conversation";
|
||||
@ -14,6 +12,7 @@ import { cn } from "#/utils/utils";
|
||||
import { AgentLoading } from "./agent-loading";
|
||||
import { useConversationStore } from "#/state/conversation-store";
|
||||
import CircleErrorIcon from "#/icons/circle-error.svg?react";
|
||||
import { useAgentStore } from "#/stores/agent-store";
|
||||
|
||||
export interface AgentStatusProps {
|
||||
className?: string;
|
||||
@ -30,7 +29,7 @@ export function AgentStatus({
|
||||
}: AgentStatusProps) {
|
||||
const { t } = useTranslation();
|
||||
const { setShouldShownAgentLoading } = useConversationStore();
|
||||
const { curAgentState } = useSelector((state: RootState) => state.agent);
|
||||
const { curAgentState } = useAgentStore();
|
||||
const { curStatusMessage } = useStatusStore();
|
||||
const { webSocketStatus } = useWsClient();
|
||||
const { data: conversation } = useActiveConversation();
|
||||
|
||||
@ -1,16 +1,15 @@
|
||||
import { useSelector } from "react-redux";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useState } from "react";
|
||||
import DebugStackframeDot from "#/icons/debug-stackframe-dot.svg?react";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { ConversationStatus } from "#/types/conversation-status";
|
||||
import { RootState } from "#/store";
|
||||
import { AgentState } from "#/types/agent-state";
|
||||
import { ServerStatusContextMenu } from "./server-status-context-menu";
|
||||
import { useStartConversation } from "#/hooks/mutation/use-start-conversation";
|
||||
import { useConversationId } from "#/hooks/use-conversation-id";
|
||||
import { useUserProviders } from "#/hooks/use-user-providers";
|
||||
import { useStopConversation } from "#/hooks/mutation/use-stop-conversation";
|
||||
import { useAgentStore } from "#/stores/agent-store";
|
||||
|
||||
export interface ServerStatusProps {
|
||||
className?: string;
|
||||
@ -23,7 +22,7 @@ export function ServerStatus({
|
||||
}: ServerStatusProps) {
|
||||
const [showContextMenu, setShowContextMenu] = useState(false);
|
||||
|
||||
const { curAgentState } = useSelector((state: RootState) => state.agent);
|
||||
const { curAgentState } = useAgentStore();
|
||||
const { t } = useTranslation();
|
||||
const { conversationId } = useConversationId();
|
||||
|
||||
|
||||
@ -1,17 +1,16 @@
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useSelector } from "react-redux";
|
||||
import { ModalBackdrop } from "#/components/shared/modals/modal-backdrop";
|
||||
import { ModalBody } from "#/components/shared/modals/modal-body";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { useConversationMicroagents } from "#/hooks/query/use-conversation-microagents";
|
||||
import { RootState } from "#/store";
|
||||
import { AgentState } from "#/types/agent-state";
|
||||
import { Typography } from "#/ui/typography";
|
||||
import { MicroagentsModalHeader } from "./microagents-modal-header";
|
||||
import { MicroagentsLoadingState } from "./microagents-loading-state";
|
||||
import { MicroagentsEmptyState } from "./microagents-empty-state";
|
||||
import { MicroagentItem } from "./microagent-item";
|
||||
import { useAgentStore } from "#/stores/agent-store";
|
||||
|
||||
interface MicroagentsModalProps {
|
||||
onClose: () => void;
|
||||
@ -19,7 +18,7 @@ interface MicroagentsModalProps {
|
||||
|
||||
export function MicroagentsModal({ onClose }: MicroagentsModalProps) {
|
||||
const { t } = useTranslation();
|
||||
const { curAgentState } = useSelector((state: RootState) => state.agent);
|
||||
const { curAgentState } = useAgentStore();
|
||||
const [expandedAgents, setExpandedAgents] = useState<Record<string, boolean>>(
|
||||
{},
|
||||
);
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import { useSelector } from "react-redux";
|
||||
import { FaExternalLinkAlt } from "react-icons/fa";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
@ -6,10 +5,10 @@ import { RUNTIME_INACTIVE_STATES } from "#/types/agent-state";
|
||||
import { transformVSCodeUrl } from "#/utils/vscode-url-helper";
|
||||
import { useConversationId } from "#/hooks/use-conversation-id";
|
||||
import ConversationService from "#/api/conversation-service/conversation-service.api";
|
||||
import { RootState } from "#/store";
|
||||
import { useAgentStore } from "#/stores/agent-store";
|
||||
|
||||
export function VSCodeTooltipContent() {
|
||||
const { curAgentState } = useSelector((state: RootState) => state.agent);
|
||||
const { curAgentState } = useAgentStore();
|
||||
|
||||
const { t } = useTranslation();
|
||||
const { conversationId } = useConversationId();
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
import React from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { RootState } from "#/store";
|
||||
import { useScrollToBottom } from "#/hooks/use-scroll-to-bottom";
|
||||
import { JupyterCell } from "./jupyter-cell";
|
||||
import { ScrollToBottomButton } from "#/components/shared/buttons/scroll-to-bottom-button";
|
||||
@ -9,6 +7,7 @@ import { RUNTIME_INACTIVE_STATES } from "#/types/agent-state";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import JupyterLargeIcon from "#/icons/jupyter-large.svg?react";
|
||||
import { WaitingForRuntimeMessage } from "../chat/waiting-for-runtime-message";
|
||||
import { useAgentStore } from "#/stores/agent-store";
|
||||
import { useJupyterStore } from "#/state/jupyter-store";
|
||||
|
||||
interface JupyterEditorProps {
|
||||
@ -16,8 +15,9 @@ interface JupyterEditorProps {
|
||||
}
|
||||
|
||||
export function JupyterEditor({ maxWidth }: JupyterEditorProps) {
|
||||
const { curAgentState } = useAgentStore();
|
||||
|
||||
const cells = useJupyterStore((state) => state.cells);
|
||||
const { curAgentState } = useSelector((state: RootState) => state.agent);
|
||||
|
||||
const jupyterRef = React.useRef<HTMLDivElement>(null);
|
||||
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
import { useSelector } from "react-redux";
|
||||
import { RootState } from "#/store";
|
||||
import { useTerminal } from "#/hooks/use-terminal";
|
||||
import "@xterm/xterm/css/xterm.css";
|
||||
import { RUNTIME_INACTIVE_STATES } from "#/types/agent-state";
|
||||
import { cn } from "#/utils/utils";
|
||||
import { WaitingForRuntimeMessage } from "../chat/waiting-for-runtime-message";
|
||||
import { useAgentStore } from "#/stores/agent-store";
|
||||
|
||||
function Terminal() {
|
||||
const { curAgentState } = useSelector((state: RootState) => state.agent);
|
||||
const { curAgentState } = useAgentStore();
|
||||
|
||||
const isRuntimeInactive = RUNTIME_INACTIVE_STATES.includes(curAgentState);
|
||||
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useSelector } from "react-redux";
|
||||
import ConversationService from "#/api/conversation-service/conversation-service.api";
|
||||
import { useConversationId } from "../use-conversation-id";
|
||||
import { RootState } from "#/store";
|
||||
import { AgentState } from "#/types/agent-state";
|
||||
import { useAgentStore } from "#/stores/agent-store";
|
||||
|
||||
export const useConversationMicroagents = () => {
|
||||
const { conversationId } = useConversationId();
|
||||
const { curAgentState } = useSelector((state: RootState) => state.agent);
|
||||
const { curAgentState } = useAgentStore();
|
||||
|
||||
return useQuery({
|
||||
queryKey: ["conversation", conversationId, "microagents"],
|
||||
|
||||
@ -1,10 +1,8 @@
|
||||
import { useSelector } from "react-redux";
|
||||
import { RootState } from "#/store";
|
||||
|
||||
import { RUNTIME_INACTIVE_STATES } from "#/types/agent-state";
|
||||
import { useAgentStore } from "#/stores/agent-store";
|
||||
|
||||
export const useHandleRuntimeActive = () => {
|
||||
const { curAgentState } = useSelector((state: RootState) => state.agent);
|
||||
const { curAgentState } = useAgentStore();
|
||||
|
||||
const runtimeActive = !RUNTIME_INACTIVE_STATES.includes(curAgentState);
|
||||
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import { useSelector } from "react-redux";
|
||||
import { RootState } from "#/store";
|
||||
import { RUNTIME_INACTIVE_STATES } from "#/types/agent-state";
|
||||
import { useActiveConversation } from "./query/use-active-conversation";
|
||||
import { useAgentStore } from "#/stores/agent-store";
|
||||
|
||||
/**
|
||||
* Hook to determine if the runtime is ready for operations
|
||||
@ -10,7 +9,7 @@ import { useActiveConversation } from "./query/use-active-conversation";
|
||||
*/
|
||||
export const useRuntimeIsReady = (): boolean => {
|
||||
const { data: conversation } = useActiveConversation();
|
||||
const { curAgentState } = useSelector((state: RootState) => state.agent);
|
||||
const { curAgentState } = useAgentStore();
|
||||
|
||||
return (
|
||||
conversation?.status === "RUNNING" &&
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
import { FitAddon } from "@xterm/addon-fit";
|
||||
import { Terminal } from "@xterm/xterm";
|
||||
import React from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { Command, useCommandStore } from "#/state/command-store";
|
||||
import { RUNTIME_INACTIVE_STATES } from "#/types/agent-state";
|
||||
import { useWsClient } from "#/context/ws-client-provider";
|
||||
import { getTerminalCommand } from "#/services/terminal-service";
|
||||
import { parseTerminalOutput } from "#/utils/parse-terminal-output";
|
||||
import { RootState } from "#/store";
|
||||
import { useAgentStore } from "#/stores/agent-store";
|
||||
|
||||
/*
|
||||
NOTE: Tests for this hook are indirectly covered by the tests for the XTermTerminal component.
|
||||
@ -38,7 +37,7 @@ const persistentLastCommandIndex = { current: 0 };
|
||||
|
||||
export const useTerminal = () => {
|
||||
const { send } = useWsClient();
|
||||
const { curAgentState } = useSelector((state: RootState) => state.agent);
|
||||
const { curAgentState } = useAgentStore();
|
||||
const commands = useCommandStore((state) => state.commands);
|
||||
const terminal = React.useRef<Terminal | null>(null);
|
||||
const fitAddon = React.useRef<FitAddon | null>(null);
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useSelector } from "react-redux";
|
||||
import React from "react";
|
||||
import { FileDiffViewer } from "#/components/features/diff-viewer/file-diff-viewer";
|
||||
import { retrieveAxiosErrorMessage } from "#/utils/retrieve-axios-error-message";
|
||||
import { useGetGitChanges } from "#/hooks/query/use-get-git-changes";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { RootState } from "#/store";
|
||||
import { RUNTIME_INACTIVE_STATES } from "#/types/agent-state";
|
||||
import { RandomTip } from "#/components/features/tips/random-tip";
|
||||
import { useAgentStore } from "#/stores/agent-store";
|
||||
|
||||
// Error message patterns
|
||||
const GIT_REPO_ERROR_PATTERN = /not a git repository/i;
|
||||
@ -34,7 +33,7 @@ function GitChanges() {
|
||||
null,
|
||||
);
|
||||
|
||||
const { curAgentState } = useSelector((state: RootState) => state.agent);
|
||||
const { curAgentState } = useAgentStore();
|
||||
const runtimeIsActive = !RUNTIME_INACTIVE_STATES.includes(curAgentState);
|
||||
|
||||
const isNotGitRepoError =
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import React from "react";
|
||||
import { useNavigate } from "react-router";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { useConversationId } from "#/hooks/use-conversation-id";
|
||||
@ -8,7 +7,7 @@ import { useCommandStore } from "#/state/command-store";
|
||||
import { useEffectOnce } from "#/hooks/use-effect-once";
|
||||
import { useJupyterStore } from "#/state/jupyter-store";
|
||||
import { useConversationStore } from "#/state/conversation-store";
|
||||
import { setCurrentAgentState } from "#/state/agent-slice";
|
||||
import { useAgentStore } from "#/stores/agent-store";
|
||||
import { AgentState } from "#/types/agent-state";
|
||||
|
||||
import { useBatchFeedback } from "#/hooks/query/use-batch-feedback";
|
||||
@ -39,9 +38,11 @@ function AppContent() {
|
||||
const { data: isAuthed } = useIsAuthed();
|
||||
const { providers } = useUserProviders();
|
||||
const { resetConversationState } = useConversationStore();
|
||||
const dispatch = useDispatch();
|
||||
const navigate = useNavigate();
|
||||
const clearTerminal = useCommandStore((state) => state.clearTerminal);
|
||||
const setCurrentAgentState = useAgentStore(
|
||||
(state) => state.setCurrentAgentState,
|
||||
);
|
||||
const clearJupyter = useJupyterStore((state) => state.clearJupyter);
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
@ -89,14 +90,19 @@ function AppContent() {
|
||||
clearTerminal();
|
||||
clearJupyter();
|
||||
resetConversationState();
|
||||
dispatch(setCurrentAgentState(AgentState.LOADING));
|
||||
}, [conversationId, clearTerminal, resetConversationState]);
|
||||
setCurrentAgentState(AgentState.LOADING);
|
||||
}, [
|
||||
conversationId,
|
||||
clearTerminal,
|
||||
setCurrentAgentState,
|
||||
resetConversationState,
|
||||
]);
|
||||
|
||||
useEffectOnce(() => {
|
||||
clearTerminal();
|
||||
clearJupyter();
|
||||
resetConversationState();
|
||||
dispatch(setCurrentAgentState(AgentState.LOADING));
|
||||
setCurrentAgentState(AgentState.LOADING);
|
||||
});
|
||||
|
||||
return (
|
||||
|
||||
@ -1,17 +1,16 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useSelector } from "react-redux";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { RootState } from "#/store";
|
||||
import { RUNTIME_INACTIVE_STATES } from "#/types/agent-state";
|
||||
import { useVSCodeUrl } from "#/hooks/query/use-vscode-url";
|
||||
import { VSCODE_IN_NEW_TAB } from "#/utils/feature-flags";
|
||||
import { WaitingForRuntimeMessage } from "#/components/features/chat/waiting-for-runtime-message";
|
||||
import { useAgentStore } from "#/stores/agent-store";
|
||||
|
||||
function VSCodeTab() {
|
||||
const { t } = useTranslation();
|
||||
const { data, isLoading, error } = useVSCodeUrl();
|
||||
const { curAgentState } = useSelector((state: RootState) => state.agent);
|
||||
const { curAgentState } = useAgentStore();
|
||||
const isRuntimeInactive = RUNTIME_INACTIVE_STATES.includes(curAgentState);
|
||||
const iframeRef = React.useRef<HTMLIFrameElement>(null);
|
||||
const [isCrossProtocol, setIsCrossProtocol] = useState(false);
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { setCurrentAgentState } from "#/state/agent-slice";
|
||||
import store from "#/store";
|
||||
import { ObservationMessage } from "#/types/message";
|
||||
import { useJupyterStore } from "#/state/jupyter-store";
|
||||
import { useCommandStore } from "#/state/command-store";
|
||||
import ObservationType from "#/types/observation-type";
|
||||
import { useBrowserStore } from "#/stores/browser-store";
|
||||
import { useAgentStore } from "#/stores/agent-store";
|
||||
import { AgentState } from "#/types/agent-state";
|
||||
|
||||
export function handleObservationMessage(message: ObservationMessage) {
|
||||
switch (message.observation) {
|
||||
@ -43,7 +43,11 @@ export function handleObservationMessage(message: ObservationMessage) {
|
||||
}
|
||||
break;
|
||||
case ObservationType.AGENT_STATE_CHANGED:
|
||||
store.dispatch(setCurrentAgentState(message.extras.agent_state));
|
||||
if (typeof message.extras.agent_state === "string") {
|
||||
useAgentStore
|
||||
.getState()
|
||||
.setCurrentAgentState(message.extras.agent_state as AgentState);
|
||||
}
|
||||
break;
|
||||
case ObservationType.DELEGATE:
|
||||
case ObservationType.READ:
|
||||
|
||||
@ -1,18 +0,0 @@
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
import { AgentState } from "#/types/agent-state";
|
||||
|
||||
export const agentSlice = createSlice({
|
||||
name: "agent",
|
||||
initialState: {
|
||||
curAgentState: AgentState.LOADING,
|
||||
},
|
||||
reducers: {
|
||||
setCurrentAgentState: (state, action) => {
|
||||
state.curAgentState = action.payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { setCurrentAgentState } = agentSlice.actions;
|
||||
|
||||
export default agentSlice.reducer;
|
||||
@ -1,9 +1,6 @@
|
||||
import { combineReducers, configureStore } from "@reduxjs/toolkit";
|
||||
import agentReducer from "./state/agent-slice";
|
||||
|
||||
export const rootReducer = combineReducers({
|
||||
agent: agentReducer,
|
||||
});
|
||||
export const rootReducer = combineReducers({});
|
||||
|
||||
const store = configureStore({
|
||||
reducer: rootReducer,
|
||||
|
||||
21
frontend/src/stores/agent-store.ts
Normal file
21
frontend/src/stores/agent-store.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { create } from "zustand";
|
||||
import { AgentState } from "#/types/agent-state";
|
||||
|
||||
interface AgentStateData {
|
||||
curAgentState: AgentState;
|
||||
}
|
||||
|
||||
interface AgentStore extends AgentStateData {
|
||||
setCurrentAgentState: (state: AgentState) => void;
|
||||
reset: () => void;
|
||||
}
|
||||
|
||||
const initialState: AgentStateData = {
|
||||
curAgentState: AgentState.LOADING,
|
||||
};
|
||||
|
||||
export const useAgentStore = create<AgentStore>((set) => ({
|
||||
...initialState,
|
||||
setCurrentAgentState: (state: AgentState) => set({ curAgentState: state }),
|
||||
reset: () => set(initialState),
|
||||
}));
|
||||
Loading…
x
Reference in New Issue
Block a user