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
-              ? `![image](${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: `![image](${url})`, - url, - }); - }); - } - - return lines; -};