From fab48fe864a1b0504dd71d3fdca50e7bafa92103 Mon Sep 17 00:00:00 2001
From: "sp.wack" <83104063+amanape@users.noreply.github.com>
Date: Wed, 29 Oct 2025 21:57:48 +0400
Subject: [PATCH] chore(frontend): Remove Jupyter tab and features (#11563)
---
.../components/jupyter/jupyter.test.tsx | 47 --------------
frontend/__tests__/services/actions.test.tsx | 18 +-----
.../conversation-tab-content.tsx | 11 ----
.../conversation-tabs/conversation-tabs.tsx | 8 ---
.../features/jupyter/jupyter-cell-input.tsx | 22 -------
.../features/jupyter/jupyter-cell-output.tsx | 55 ----------------
.../features/jupyter/jupyter-cell.tsx | 23 -------
.../components/features/jupyter/jupyter.tsx | 63 -------------------
frontend/src/icons/jupyter-large.svg | 3 -
frontend/src/icons/jupyter.svg | 9 ---
frontend/src/routes/conversation.tsx | 4 --
frontend/src/routes/jupyter-tab.tsx | 44 -------------
frontend/src/services/actions.ts | 5 --
frontend/src/services/observations.ts | 9 ---
frontend/src/state/conversation-store.ts | 1 -
frontend/src/state/jupyter-store.ts | 40 ------------
frontend/src/types/tab-option.tsx | 15 +----
frontend/src/utils/parse-cell-content.ts | 32 ----------
18 files changed, 4 insertions(+), 405 deletions(-)
delete mode 100644 frontend/__tests__/components/jupyter/jupyter.test.tsx
delete mode 100644 frontend/src/components/features/jupyter/jupyter-cell-input.tsx
delete mode 100644 frontend/src/components/features/jupyter/jupyter-cell-output.tsx
delete mode 100644 frontend/src/components/features/jupyter/jupyter-cell.tsx
delete mode 100644 frontend/src/components/features/jupyter/jupyter.tsx
delete mode 100644 frontend/src/icons/jupyter-large.svg
delete mode 100644 frontend/src/icons/jupyter.svg
delete mode 100644 frontend/src/routes/jupyter-tab.tsx
delete mode 100644 frontend/src/state/jupyter-store.ts
delete mode 100644 frontend/src/utils/parse-cell-content.ts
diff --git a/frontend/__tests__/components/jupyter/jupyter.test.tsx b/frontend/__tests__/components/jupyter/jupyter.test.tsx
deleted file mode 100644
index bf6c746963..0000000000
--- a/frontend/__tests__/components/jupyter/jupyter.test.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-import { render, screen } from "@testing-library/react";
-import { JupyterEditor } from "#/components/features/jupyter/jupyter";
-import { vi, describe, it, expect, beforeEach } from "vitest";
-import { AgentState } from "#/types/agent-state";
-import { useAgentState } from "#/hooks/use-agent-state";
-import { useJupyterStore } from "#/state/jupyter-store";
-
-// Mock the agent state hook
-vi.mock("#/hooks/use-agent-state", () => ({
- useAgentState: vi.fn(),
-}));
-
-// Mock react-i18next
-vi.mock("react-i18next", () => ({
- useTranslation: () => ({
- t: (key: string) => key,
- }),
-}));
-
-describe("JupyterEditor", () => {
- beforeEach(() => {
- // Reset the Zustand store before each test
- useJupyterStore.setState({
- cells: Array(20).fill({
- content: "Test cell content",
- type: "input",
- imageUrls: undefined,
- }),
- });
- });
-
- it("should have a scrollable container", () => {
- // Mock agent state to return RUNNING state (not in RUNTIME_INACTIVE_STATES)
- vi.mocked(useAgentState).mockReturnValue({
- curAgentState: AgentState.RUNNING,
- });
-
- render(
-
-
-
,
- );
-
- const container = screen.getByTestId("jupyter-container");
- expect(container).toHaveClass("flex-1 overflow-y-auto");
- });
-});
diff --git a/frontend/__tests__/services/actions.test.tsx b/frontend/__tests__/services/actions.test.tsx
index 259f0544dd..05473dcb35 100644
--- a/frontend/__tests__/services/actions.test.tsx
+++ b/frontend/__tests__/services/actions.test.tsx
@@ -5,7 +5,6 @@ import { ActionMessage } from "#/types/message";
// Mock the store and actions
const mockDispatch = vi.fn();
const mockAppendInput = vi.fn();
-const mockAppendJupyterInput = vi.fn();
vi.mock("#/store", () => ({
default: {
@@ -21,14 +20,6 @@ vi.mock("#/state/command-store", () => ({
},
}));
-vi.mock("#/state/jupyter-store", () => ({
- useJupyterStore: {
- getState: () => ({
- appendJupyterInput: mockAppendJupyterInput,
- }),
- },
-}));
-
vi.mock("#/state/metrics-slice", () => ({
setMetrics: vi.fn(),
}));
@@ -63,10 +54,9 @@ describe("handleActionMessage", () => {
// Check that appendInput was called with the command
expect(mockAppendInput).toHaveBeenCalledWith("ls -la");
expect(mockDispatch).not.toHaveBeenCalled();
- expect(mockAppendJupyterInput).not.toHaveBeenCalled();
});
- it("should handle RUN_IPYTHON actions by adding input to Jupyter", async () => {
+ it("should handle RUN_IPYTHON actions as no-op (Jupyter removed)", async () => {
const { handleActionMessage } = await import("#/services/actions");
const ipythonAction: ActionMessage = {
@@ -84,10 +74,7 @@ describe("handleActionMessage", () => {
// Handle the action
handleActionMessage(ipythonAction);
- // Check that appendJupyterInput was called with the code
- expect(mockAppendJupyterInput).toHaveBeenCalledWith(
- "print('Hello from Jupyter!')",
- );
+ // Jupyter functionality has been removed, so nothing should be called
expect(mockAppendInput).not.toHaveBeenCalled();
});
@@ -112,6 +99,5 @@ describe("handleActionMessage", () => {
// Check that nothing was dispatched or called
expect(mockDispatch).not.toHaveBeenCalled();
expect(mockAppendInput).not.toHaveBeenCalled();
- expect(mockAppendJupyterInput).not.toHaveBeenCalled();
});
});
diff --git a/frontend/src/components/features/conversation/conversation-tabs/conversation-tab-content/conversation-tab-content.tsx b/frontend/src/components/features/conversation/conversation-tabs/conversation-tab-content/conversation-tab-content.tsx
index 271e7a750a..f8c9e35887 100644
--- a/frontend/src/components/features/conversation/conversation-tabs/conversation-tab-content/conversation-tab-content.tsx
+++ b/frontend/src/components/features/conversation/conversation-tabs/conversation-tab-content/conversation-tab-content.tsx
@@ -12,7 +12,6 @@ import { useConversationStore } from "#/state/conversation-store";
// Lazy load all tab components
const EditorTab = lazy(() => import("#/routes/changes-tab"));
const BrowserTab = lazy(() => import("#/routes/browser-tab"));
-const JupyterTab = lazy(() => import("#/routes/jupyter-tab"));
const ServedTab = lazy(() => import("#/routes/served-tab"));
const VSCodeTab = lazy(() => import("#/routes/vscode-tab"));
@@ -24,7 +23,6 @@ export function ConversationTabContent() {
// Determine which tab is active based on the current path
const isEditorActive = selectedTab === "editor";
const isBrowserActive = selectedTab === "browser";
- const isJupyterActive = selectedTab === "jupyter";
const isServedActive = selectedTab === "served";
const isVSCodeActive = selectedTab === "vscode";
const isTerminalActive = selectedTab === "terminal";
@@ -37,11 +35,6 @@ export function ConversationTabContent() {
component: BrowserTab,
isActive: isBrowserActive,
},
- {
- key: "jupyter",
- component: JupyterTab,
- isActive: isJupyterActive,
- },
{ key: "served", component: ServedTab, isActive: isServedActive },
{ key: "vscode", component: VSCodeTab, isActive: isVSCodeActive },
{
@@ -58,9 +51,6 @@ export function ConversationTabContent() {
if (isBrowserActive) {
return t(I18nKey.COMMON$BROWSER);
}
- if (isJupyterActive) {
- return t(I18nKey.COMMON$JUPYTER);
- }
if (isServedActive) {
return t(I18nKey.COMMON$APP);
}
@@ -74,7 +64,6 @@ export function ConversationTabContent() {
}, [
isEditorActive,
isBrowserActive,
- isJupyterActive,
isServedActive,
isVSCodeActive,
isTerminalActive,
diff --git a/frontend/src/components/features/conversation/conversation-tabs/conversation-tabs.tsx b/frontend/src/components/features/conversation/conversation-tabs/conversation-tabs.tsx
index 7a72305c3b..818ea658a2 100644
--- a/frontend/src/components/features/conversation/conversation-tabs/conversation-tabs.tsx
+++ b/frontend/src/components/features/conversation/conversation-tabs/conversation-tabs.tsx
@@ -1,7 +1,6 @@
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { useLocalStorage } from "@uidotdev/usehooks";
-import JupyterIcon from "#/icons/jupyter.svg?react";
import TerminalIcon from "#/icons/terminal.svg?react";
import GlobeIcon from "#/icons/globe.svg?react";
import ServerIcon from "#/icons/server.svg?react";
@@ -108,13 +107,6 @@ export function ConversationTabs() {
tooltipContent: t(I18nKey.COMMON$TERMINAL),
tooltipAriaLabel: t(I18nKey.COMMON$TERMINAL),
},
- {
- isActive: isTabActive("jupyter"),
- icon: JupyterIcon,
- onClick: () => onTabSelected("jupyter"),
- tooltipContent: t(I18nKey.COMMON$JUPYTER),
- tooltipAriaLabel: t(I18nKey.COMMON$JUPYTER),
- },
{
isActive: isTabActive("served"),
icon: ServerIcon,
diff --git a/frontend/src/components/features/jupyter/jupyter-cell-input.tsx b/frontend/src/components/features/jupyter/jupyter-cell-input.tsx
deleted file mode 100644
index c69651d105..0000000000
--- a/frontend/src/components/features/jupyter/jupyter-cell-input.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import SyntaxHighlighter from "react-syntax-highlighter";
-import { atomOneDark } from "react-syntax-highlighter/dist/esm/styles/hljs";
-
-interface JupytrerCellInputProps {
- code: string;
-}
-
-export function JupytrerCellInput({ code }: JupytrerCellInputProps) {
- return (
-
-
EXECUTE
-
-
- {code}
-
-
-
- );
-}
diff --git a/frontend/src/components/features/jupyter/jupyter-cell-output.tsx b/frontend/src/components/features/jupyter/jupyter-cell-output.tsx
deleted file mode 100644
index be2c5e3a1f..0000000000
--- a/frontend/src/components/features/jupyter/jupyter-cell-output.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-import Markdown from "react-markdown";
-import SyntaxHighlighter from "react-syntax-highlighter";
-import { atomOneDark } from "react-syntax-highlighter/dist/esm/styles/hljs";
-import { useTranslation } from "react-i18next";
-import { I18nKey } from "#/i18n/declaration";
-import { JupyterLine } from "#/utils/parse-cell-content";
-import { paragraph } from "../markdown/paragraph";
-
-interface JupyterCellOutputProps {
- lines: JupyterLine[];
-}
-
-export function JupyterCellOutput({ lines }: JupyterCellOutputProps) {
- const { t } = useTranslation();
- return (
-
-
- {t(I18nKey.JUPYTER$OUTPUT_LABEL)}
-
-
- {/* display the lines as plaintext or image */}
- {lines.map((line, index) => {
- if (line.type === "image") {
- // Use markdown to display the image
- const imageMarkdown = line.url
- ? ``
- : line.content;
- return (
-
- value}
- >
- {imageMarkdown}
-
-
- );
- }
- return (
-
-
- {line.content}
-
-
- );
- })}
-
-
- );
-}
diff --git a/frontend/src/components/features/jupyter/jupyter-cell.tsx b/frontend/src/components/features/jupyter/jupyter-cell.tsx
deleted file mode 100644
index afd429f6c6..0000000000
--- a/frontend/src/components/features/jupyter/jupyter-cell.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import React from "react";
-import { Cell } from "#/state/jupyter-store";
-import { JupyterLine, parseCellContent } from "#/utils/parse-cell-content";
-import { JupytrerCellInput } from "./jupyter-cell-input";
-import { JupyterCellOutput } from "./jupyter-cell-output";
-
-interface JupyterCellProps {
- cell: Cell;
-}
-
-export function JupyterCell({ cell }: JupyterCellProps) {
- const [lines, setLines] = React.useState([]);
-
- React.useEffect(() => {
- setLines(parseCellContent(cell.content, cell.imageUrls));
- }, [cell.content, cell.imageUrls]);
-
- if (cell.type === "input") {
- return ;
- }
-
- return ;
-}
diff --git a/frontend/src/components/features/jupyter/jupyter.tsx b/frontend/src/components/features/jupyter/jupyter.tsx
deleted file mode 100644
index 5ff84c7f2f..0000000000
--- a/frontend/src/components/features/jupyter/jupyter.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-import React from "react";
-import { useTranslation } from "react-i18next";
-import { useScrollToBottom } from "#/hooks/use-scroll-to-bottom";
-import { JupyterCell } from "./jupyter-cell";
-import { ScrollToBottomButton } from "#/components/shared/buttons/scroll-to-bottom-button";
-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 { useAgentState } from "#/hooks/use-agent-state";
-import { useJupyterStore } from "#/state/jupyter-store";
-
-interface JupyterEditorProps {
- maxWidth: number;
-}
-
-export function JupyterEditor({ maxWidth }: JupyterEditorProps) {
- const { curAgentState } = useAgentState();
-
- const cells = useJupyterStore((state) => state.cells);
-
- const jupyterRef = React.useRef(null);
-
- const { t } = useTranslation();
-
- const isRuntimeInactive = RUNTIME_INACTIVE_STATES.includes(curAgentState);
-
- const { hitBottom, scrollDomToBottom, onChatBodyScroll } =
- useScrollToBottom(jupyterRef);
-
- return (
- <>
- {isRuntimeInactive && }
- {!isRuntimeInactive && cells.length > 0 && (
-
-
onChatBodyScroll(e.currentTarget)}
- >
- {cells.map((cell, index) => (
-
- ))}
-
- {!hitBottom && (
-
-
-
- )}
-
- )}
- {!isRuntimeInactive && cells.length === 0 && (
-
-
-
- {t(I18nKey.COMMON$JUPYTER_EMPTY_MESSAGE)}
-
-
- )}
- >
- );
-}
diff --git a/frontend/src/icons/jupyter-large.svg b/frontend/src/icons/jupyter-large.svg
deleted file mode 100644
index 7643ce165e..0000000000
--- a/frontend/src/icons/jupyter-large.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
diff --git a/frontend/src/icons/jupyter.svg b/frontend/src/icons/jupyter.svg
deleted file mode 100644
index 0dc18c0fd3..0000000000
--- a/frontend/src/icons/jupyter.svg
+++ /dev/null
@@ -1,9 +0,0 @@
-
diff --git a/frontend/src/routes/conversation.tsx b/frontend/src/routes/conversation.tsx
index c1d7330f41..16edacde66 100644
--- a/frontend/src/routes/conversation.tsx
+++ b/frontend/src/routes/conversation.tsx
@@ -4,7 +4,6 @@ import { useTranslation } from "react-i18next";
import { useConversationId } from "#/hooks/use-conversation-id";
import { useCommandStore } from "#/state/command-store";
-import { useJupyterStore } from "#/state/jupyter-store";
import { useConversationStore } from "#/state/conversation-store";
import { useAgentStore } from "#/stores/agent-store";
import { AgentState } from "#/types/agent-state";
@@ -53,7 +52,6 @@ function AppContent() {
const setCurrentAgentState = useAgentStore(
(state) => state.setCurrentAgentState,
);
- const clearJupyter = useJupyterStore((state) => state.clearJupyter);
const removeErrorMessage = useErrorMessageStore(
(state) => state.removeErrorMessage,
);
@@ -70,7 +68,6 @@ function AppContent() {
// 1. Cleanup Effect - runs when navigating to a different conversation
React.useEffect(() => {
clearTerminal();
- clearJupyter();
resetConversationState();
setCurrentAgentState(AgentState.LOADING);
removeErrorMessage();
@@ -84,7 +81,6 @@ function AppContent() {
}, [
conversationId,
clearTerminal,
- clearJupyter,
resetConversationState,
setCurrentAgentState,
removeErrorMessage,
diff --git a/frontend/src/routes/jupyter-tab.tsx b/frontend/src/routes/jupyter-tab.tsx
deleted file mode 100644
index 05a2caaeaf..0000000000
--- a/frontend/src/routes/jupyter-tab.tsx
+++ /dev/null
@@ -1,44 +0,0 @@
-import React from "react";
-import { JupyterEditor } from "#/components/features/jupyter/jupyter";
-
-function Jupyter() {
- const parentRef = React.useRef(null);
- const [parentWidth, setParentWidth] = React.useState(0);
-
- // This is a hack to prevent the editor from overflowing
- // Should be removed after revising the parent and containers
- // Use ResizeObserver to properly track parent width changes
- React.useEffect(() => {
- let resizeObserver: ResizeObserver | null = null;
-
- resizeObserver = new ResizeObserver((entries) => {
- for (const entry of entries) {
- // Use contentRect.width for more accurate measurements
- const { width } = entry.contentRect;
- if (width > 0) {
- setParentWidth(width);
- }
- }
- });
-
- if (parentRef.current) {
- resizeObserver.observe(parentRef.current);
- }
-
- return () => {
- resizeObserver?.disconnect();
- };
- }, []);
-
- // Provide a fallback width to prevent the editor from being hidden
- // Use parentWidth if available, otherwise use a large default
- const maxWidth = parentWidth > 0 ? parentWidth : 9999;
-
- return (
-
-
-
- );
-}
-
-export default Jupyter;
diff --git a/frontend/src/services/actions.ts b/frontend/src/services/actions.ts
index 2c40959778..986a292779 100644
--- a/frontend/src/services/actions.ts
+++ b/frontend/src/services/actions.ts
@@ -8,7 +8,6 @@ import {
StatusMessage,
} from "#/types/message";
import { handleObservationMessage } from "./observations";
-import { useJupyterStore } from "#/state/jupyter-store";
import { useCommandStore } from "#/state/command-store";
import { queryClient } from "#/query-client-config";
import {
@@ -35,10 +34,6 @@ export function handleActionMessage(message: ActionMessage) {
useCommandStore.getState().appendInput(message.args.command);
}
- if (message.action === ActionType.RUN_IPYTHON) {
- useJupyterStore.getState().appendJupyterInput(message.args.code);
- }
-
if ("args" in message && "security_risk" in message.args) {
useSecurityAnalyzerStore.getState().appendSecurityAnalyzerInput({
id: message.id,
diff --git a/frontend/src/services/observations.ts b/frontend/src/services/observations.ts
index 0994eebcd2..40cc1daa8a 100644
--- a/frontend/src/services/observations.ts
+++ b/frontend/src/services/observations.ts
@@ -1,5 +1,4 @@
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";
@@ -22,14 +21,6 @@ export function handleObservationMessage(message: ObservationMessage) {
useCommandStore.getState().appendOutput(content);
break;
}
- case ObservationType.RUN_IPYTHON:
- useJupyterStore.getState().appendJupyterOutput({
- content: message.content,
- imageUrls: Array.isArray(message.extras?.image_urls)
- ? message.extras.image_urls
- : undefined,
- });
- break;
case ObservationType.BROWSE:
case ObservationType.BROWSE_INTERACTIVE:
if (
diff --git a/frontend/src/state/conversation-store.ts b/frontend/src/state/conversation-store.ts
index 645968adfb..dc6424044f 100644
--- a/frontend/src/state/conversation-store.ts
+++ b/frontend/src/state/conversation-store.ts
@@ -4,7 +4,6 @@ import { devtools } from "zustand/middleware";
export type ConversationTab =
| "editor"
| "browser"
- | "jupyter"
| "served"
| "vscode"
| "terminal";
diff --git a/frontend/src/state/jupyter-store.ts b/frontend/src/state/jupyter-store.ts
deleted file mode 100644
index 15d8be0ad3..0000000000
--- a/frontend/src/state/jupyter-store.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import { create } from "zustand";
-
-export type Cell = {
- content: string;
- type: "input" | "output";
- imageUrls?: string[];
-};
-
-interface JupyterState {
- cells: Cell[];
- appendJupyterInput: (content: string) => void;
- appendJupyterOutput: (payload: {
- content: string;
- imageUrls?: string[];
- }) => void;
- clearJupyter: () => void;
-}
-
-export const useJupyterStore = create((set) => ({
- cells: [],
- appendJupyterInput: (content: string) =>
- set((state) => ({
- cells: [...state.cells, { content, type: "input" }],
- })),
- appendJupyterOutput: (payload: { content: string; imageUrls?: string[] }) =>
- set((state) => ({
- cells: [
- ...state.cells,
- {
- content: payload.content,
- type: "output",
- imageUrls: payload.imageUrls,
- },
- ],
- })),
- clearJupyter: () =>
- set(() => ({
- cells: [],
- })),
-}));
diff --git a/frontend/src/types/tab-option.tsx b/frontend/src/types/tab-option.tsx
index bd6d3cc9ef..0c90786448 100644
--- a/frontend/src/types/tab-option.tsx
+++ b/frontend/src/types/tab-option.tsx
@@ -1,21 +1,10 @@
enum TabOption {
PLANNER = "planner",
BROWSER = "browser",
- JUPYTER = "jupyter",
VSCODE = "vscode",
}
-type TabType =
- | TabOption.PLANNER
- | TabOption.BROWSER
- | TabOption.JUPYTER
- | TabOption.VSCODE;
-
-const AllTabs = [
- TabOption.VSCODE,
- TabOption.BROWSER,
- TabOption.PLANNER,
- TabOption.JUPYTER,
-];
+type TabType = TabOption.PLANNER | TabOption.BROWSER | TabOption.VSCODE;
+const AllTabs = [TabOption.VSCODE, TabOption.BROWSER, TabOption.PLANNER];
export { AllTabs, TabOption, type TabType };
diff --git a/frontend/src/utils/parse-cell-content.ts b/frontend/src/utils/parse-cell-content.ts
deleted file mode 100644
index faa566a05c..0000000000
--- a/frontend/src/utils/parse-cell-content.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-export type JupyterLine = {
- type: "plaintext" | "image";
- content: string;
- url?: string;
-};
-
-export const parseCellContent = (content: string, imageUrls?: string[]) => {
- const lines: JupyterLine[] = [];
- let currentText = "";
-
- // First, process the text content
- for (const line of content.split("\n")) {
- currentText += `${line}\n`;
- }
-
- if (currentText) {
- lines.push({ type: "plaintext", content: currentText });
- }
-
- // Then, add image lines if we have image URLs
- if (imageUrls && imageUrls.length > 0) {
- imageUrls.forEach((url) => {
- lines.push({
- type: "image",
- content: ``,
- url,
- });
- });
- }
-
- return lines;
-};