mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
chore(frontend): Remove Jupyter tab and features (#11563)
This commit is contained in:
parent
a196881ab0
commit
fab48fe864
@ -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(
|
|
||||||
<div style={{ height: "100vh" }}>
|
|
||||||
<JupyterEditor maxWidth={800} />
|
|
||||||
</div>,
|
|
||||||
);
|
|
||||||
|
|
||||||
const container = screen.getByTestId("jupyter-container");
|
|
||||||
expect(container).toHaveClass("flex-1 overflow-y-auto");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -5,7 +5,6 @@ import { ActionMessage } from "#/types/message";
|
|||||||
// Mock the store and actions
|
// Mock the store and actions
|
||||||
const mockDispatch = vi.fn();
|
const mockDispatch = vi.fn();
|
||||||
const mockAppendInput = vi.fn();
|
const mockAppendInput = vi.fn();
|
||||||
const mockAppendJupyterInput = vi.fn();
|
|
||||||
|
|
||||||
vi.mock("#/store", () => ({
|
vi.mock("#/store", () => ({
|
||||||
default: {
|
default: {
|
||||||
@ -21,14 +20,6 @@ vi.mock("#/state/command-store", () => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock("#/state/jupyter-store", () => ({
|
|
||||||
useJupyterStore: {
|
|
||||||
getState: () => ({
|
|
||||||
appendJupyterInput: mockAppendJupyterInput,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
vi.mock("#/state/metrics-slice", () => ({
|
vi.mock("#/state/metrics-slice", () => ({
|
||||||
setMetrics: vi.fn(),
|
setMetrics: vi.fn(),
|
||||||
}));
|
}));
|
||||||
@ -63,10 +54,9 @@ describe("handleActionMessage", () => {
|
|||||||
// Check that appendInput was called with the command
|
// Check that appendInput was called with the command
|
||||||
expect(mockAppendInput).toHaveBeenCalledWith("ls -la");
|
expect(mockAppendInput).toHaveBeenCalledWith("ls -la");
|
||||||
expect(mockDispatch).not.toHaveBeenCalled();
|
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 { handleActionMessage } = await import("#/services/actions");
|
||||||
|
|
||||||
const ipythonAction: ActionMessage = {
|
const ipythonAction: ActionMessage = {
|
||||||
@ -84,10 +74,7 @@ describe("handleActionMessage", () => {
|
|||||||
// Handle the action
|
// Handle the action
|
||||||
handleActionMessage(ipythonAction);
|
handleActionMessage(ipythonAction);
|
||||||
|
|
||||||
// Check that appendJupyterInput was called with the code
|
// Jupyter functionality has been removed, so nothing should be called
|
||||||
expect(mockAppendJupyterInput).toHaveBeenCalledWith(
|
|
||||||
"print('Hello from Jupyter!')",
|
|
||||||
);
|
|
||||||
expect(mockAppendInput).not.toHaveBeenCalled();
|
expect(mockAppendInput).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -112,6 +99,5 @@ describe("handleActionMessage", () => {
|
|||||||
// Check that nothing was dispatched or called
|
// Check that nothing was dispatched or called
|
||||||
expect(mockDispatch).not.toHaveBeenCalled();
|
expect(mockDispatch).not.toHaveBeenCalled();
|
||||||
expect(mockAppendInput).not.toHaveBeenCalled();
|
expect(mockAppendInput).not.toHaveBeenCalled();
|
||||||
expect(mockAppendJupyterInput).not.toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -12,7 +12,6 @@ import { useConversationStore } from "#/state/conversation-store";
|
|||||||
// Lazy load all tab components
|
// Lazy load all tab components
|
||||||
const EditorTab = lazy(() => import("#/routes/changes-tab"));
|
const EditorTab = lazy(() => import("#/routes/changes-tab"));
|
||||||
const BrowserTab = lazy(() => import("#/routes/browser-tab"));
|
const BrowserTab = lazy(() => import("#/routes/browser-tab"));
|
||||||
const JupyterTab = lazy(() => import("#/routes/jupyter-tab"));
|
|
||||||
const ServedTab = lazy(() => import("#/routes/served-tab"));
|
const ServedTab = lazy(() => import("#/routes/served-tab"));
|
||||||
const VSCodeTab = lazy(() => import("#/routes/vscode-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
|
// Determine which tab is active based on the current path
|
||||||
const isEditorActive = selectedTab === "editor";
|
const isEditorActive = selectedTab === "editor";
|
||||||
const isBrowserActive = selectedTab === "browser";
|
const isBrowserActive = selectedTab === "browser";
|
||||||
const isJupyterActive = selectedTab === "jupyter";
|
|
||||||
const isServedActive = selectedTab === "served";
|
const isServedActive = selectedTab === "served";
|
||||||
const isVSCodeActive = selectedTab === "vscode";
|
const isVSCodeActive = selectedTab === "vscode";
|
||||||
const isTerminalActive = selectedTab === "terminal";
|
const isTerminalActive = selectedTab === "terminal";
|
||||||
@ -37,11 +35,6 @@ export function ConversationTabContent() {
|
|||||||
component: BrowserTab,
|
component: BrowserTab,
|
||||||
isActive: isBrowserActive,
|
isActive: isBrowserActive,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
key: "jupyter",
|
|
||||||
component: JupyterTab,
|
|
||||||
isActive: isJupyterActive,
|
|
||||||
},
|
|
||||||
{ key: "served", component: ServedTab, isActive: isServedActive },
|
{ key: "served", component: ServedTab, isActive: isServedActive },
|
||||||
{ key: "vscode", component: VSCodeTab, isActive: isVSCodeActive },
|
{ key: "vscode", component: VSCodeTab, isActive: isVSCodeActive },
|
||||||
{
|
{
|
||||||
@ -58,9 +51,6 @@ export function ConversationTabContent() {
|
|||||||
if (isBrowserActive) {
|
if (isBrowserActive) {
|
||||||
return t(I18nKey.COMMON$BROWSER);
|
return t(I18nKey.COMMON$BROWSER);
|
||||||
}
|
}
|
||||||
if (isJupyterActive) {
|
|
||||||
return t(I18nKey.COMMON$JUPYTER);
|
|
||||||
}
|
|
||||||
if (isServedActive) {
|
if (isServedActive) {
|
||||||
return t(I18nKey.COMMON$APP);
|
return t(I18nKey.COMMON$APP);
|
||||||
}
|
}
|
||||||
@ -74,7 +64,6 @@ export function ConversationTabContent() {
|
|||||||
}, [
|
}, [
|
||||||
isEditorActive,
|
isEditorActive,
|
||||||
isBrowserActive,
|
isBrowserActive,
|
||||||
isJupyterActive,
|
|
||||||
isServedActive,
|
isServedActive,
|
||||||
isVSCodeActive,
|
isVSCodeActive,
|
||||||
isTerminalActive,
|
isTerminalActive,
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useLocalStorage } from "@uidotdev/usehooks";
|
import { useLocalStorage } from "@uidotdev/usehooks";
|
||||||
import JupyterIcon from "#/icons/jupyter.svg?react";
|
|
||||||
import TerminalIcon from "#/icons/terminal.svg?react";
|
import TerminalIcon from "#/icons/terminal.svg?react";
|
||||||
import GlobeIcon from "#/icons/globe.svg?react";
|
import GlobeIcon from "#/icons/globe.svg?react";
|
||||||
import ServerIcon from "#/icons/server.svg?react";
|
import ServerIcon from "#/icons/server.svg?react";
|
||||||
@ -108,13 +107,6 @@ export function ConversationTabs() {
|
|||||||
tooltipContent: t(I18nKey.COMMON$TERMINAL),
|
tooltipContent: t(I18nKey.COMMON$TERMINAL),
|
||||||
tooltipAriaLabel: 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"),
|
isActive: isTabActive("served"),
|
||||||
icon: ServerIcon,
|
icon: ServerIcon,
|
||||||
|
|||||||
@ -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 (
|
|
||||||
<div className="rounded-lg bg-gray-800 dark:bg-gray-900 p-2 text-xs">
|
|
||||||
<div className="mb-1 text-gray-400">EXECUTE</div>
|
|
||||||
<pre
|
|
||||||
className="scrollbar-custom scrollbar-thumb-gray-500 hover:scrollbar-thumb-gray-400 dark:scrollbar-thumb-white/10 dark:hover:scrollbar-thumb-white/20 overflow-auto px-5"
|
|
||||||
style={{ padding: 0, marginBottom: 0, fontSize: "0.75rem" }}
|
|
||||||
>
|
|
||||||
<SyntaxHighlighter language="python" style={atomOneDark} wrapLongLines>
|
|
||||||
{code}
|
|
||||||
</SyntaxHighlighter>
|
|
||||||
</pre>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -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 (
|
|
||||||
<div className="rounded-lg bg-gray-800 dark:bg-gray-900 p-2 text-xs">
|
|
||||||
<div className="mb-1 text-gray-400">
|
|
||||||
{t(I18nKey.JUPYTER$OUTPUT_LABEL)}
|
|
||||||
</div>
|
|
||||||
<pre
|
|
||||||
className="scrollbar-custom scrollbar-thumb-gray-500 hover:scrollbar-thumb-gray-400 dark:scrollbar-thumb-white/10 dark:hover:scrollbar-thumb-white/20 overflow-auto px-5 max-h-[60vh] bg-gray-800"
|
|
||||||
style={{ padding: 0, marginBottom: 0, fontSize: "0.75rem" }}
|
|
||||||
>
|
|
||||||
{/* 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 (
|
|
||||||
<div key={index}>
|
|
||||||
<Markdown
|
|
||||||
components={{
|
|
||||||
p: paragraph,
|
|
||||||
}}
|
|
||||||
urlTransform={(value: string) => value}
|
|
||||||
>
|
|
||||||
{imageMarkdown}
|
|
||||||
</Markdown>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div key={index}>
|
|
||||||
<SyntaxHighlighter language="plaintext" style={atomOneDark}>
|
|
||||||
{line.content}
|
|
||||||
</SyntaxHighlighter>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</pre>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -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<JupyterLine[]>([]);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
setLines(parseCellContent(cell.content, cell.imageUrls));
|
|
||||||
}, [cell.content, cell.imageUrls]);
|
|
||||||
|
|
||||||
if (cell.type === "input") {
|
|
||||||
return <JupytrerCellInput code={cell.content} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <JupyterCellOutput lines={lines} />;
|
|
||||||
}
|
|
||||||
@ -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<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const isRuntimeInactive = RUNTIME_INACTIVE_STATES.includes(curAgentState);
|
|
||||||
|
|
||||||
const { hitBottom, scrollDomToBottom, onChatBodyScroll } =
|
|
||||||
useScrollToBottom(jupyterRef);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{isRuntimeInactive && <WaitingForRuntimeMessage />}
|
|
||||||
{!isRuntimeInactive && cells.length > 0 && (
|
|
||||||
<div className="flex-1 h-full flex flex-col" style={{ maxWidth }}>
|
|
||||||
<div
|
|
||||||
data-testid="jupyter-container"
|
|
||||||
className="flex-1 overflow-y-auto fast-smooth-scroll custom-scrollbar-always rounded-xl"
|
|
||||||
ref={jupyterRef}
|
|
||||||
onScroll={(e) => onChatBodyScroll(e.currentTarget)}
|
|
||||||
>
|
|
||||||
{cells.map((cell, index) => (
|
|
||||||
<JupyterCell key={index} cell={cell} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
{!hitBottom && (
|
|
||||||
<div className="sticky bottom-2 flex items-center justify-center">
|
|
||||||
<ScrollToBottomButton onClick={scrollDomToBottom} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{!isRuntimeInactive && cells.length === 0 && (
|
|
||||||
<div className="flex flex-col items-center justify-center w-full h-full p-10 gap-4">
|
|
||||||
<JupyterLargeIcon width={113} height={113} color="#A1A1A1" />
|
|
||||||
<span className="text-[#8D95A9] text-[19px] font-normal leading-5">
|
|
||||||
{t(I18nKey.COMMON$JUPYTER_EMPTY_MESSAGE)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="113" height="113" viewBox="0 0 113 113" fill="none">
|
|
||||||
<path d="M57.9521 83.6306C57.2282 83.6659 56.3772 83.6871 55.5226 83.6871C41.334 83.6871 28.5015 77.8853 19.2672 68.5204L19.2602 68.5134C22.177 76.7765 27.3821 83.6271 34.1162 88.4967L34.2362 88.5814C40.8185 93.3522 49.0569 96.216 57.9627 96.216C66.8685 96.216 75.1034 93.3557 81.8022 88.5038L81.6821 88.585C88.5327 83.6306 93.7378 76.7835 96.5663 68.81L96.6546 68.5204C87.4062 77.8888 74.5631 83.6907 60.3675 83.6907C59.5164 83.6907 58.6689 83.6695 57.8285 83.6271L57.9521 83.6306ZM57.9486 24.9624C58.676 24.9236 59.527 24.9024 60.3851 24.9024C74.5702 24.9024 87.4027 30.7043 96.6334 40.0656L96.6405 40.0727C93.7237 31.8095 88.5186 24.9589 81.788 20.0893L81.668 20.0046C75.0857 15.2374 66.8473 12.3806 57.9415 12.3806C49.0357 12.3806 40.8008 15.2374 34.0985 20.0893L34.2186 20.0081C27.3644 24.9589 22.1593 31.8095 19.3308 39.7831L19.2425 40.0727C28.5015 30.7078 41.3517 24.9059 55.5579 24.9059C56.3983 24.9059 57.2388 24.9271 58.0686 24.966L57.9486 24.9624ZM25.5776 18.2672C25.5776 19.549 25.0585 20.7108 24.2216 21.5548C23.3882 22.3952 22.2335 22.9178 20.9587 22.9178C19.6839 22.9178 18.5257 22.3952 17.6958 21.5548C16.8589 20.7108 16.3398 19.5455 16.3398 18.2637C16.3398 16.9818 16.8589 15.8165 17.6958 14.9725C18.5292 14.1321 19.6839 13.6095 20.9587 13.6095C22.2335 13.6095 23.3918 14.1321 24.2251 14.9761C25.062 15.82 25.5776 16.9818 25.5776 18.2672ZM94.3734 9.84516C94.3734 9.84869 94.3734 9.85222 94.3734 9.85222C94.3734 11.5861 93.6742 13.1575 92.5442 14.2981C91.4178 15.4387 89.8534 16.1449 88.1266 16.1449C86.3998 16.1449 84.8355 15.4387 83.709 14.3016C82.579 13.1575 81.8798 11.5825 81.8798 9.84516C81.8798 8.10779 82.579 6.53285 83.709 5.38872C84.8355 4.25166 86.3963 3.54541 88.1266 3.54541C89.8569 3.54541 91.4178 4.25166 92.5442 5.38872C93.6742 6.52932 94.3734 8.10072 94.3734 9.8381V9.84516ZM35.1296 101.516C35.1296 101.516 35.1296 101.52 35.1296 101.523C35.1296 103.709 34.2503 105.69 32.8237 107.131C31.4042 108.568 29.4337 109.458 27.2585 109.458C25.0832 109.458 23.1128 108.568 21.6932 107.135C20.2666 105.694 19.3873 103.709 19.3873 101.52C19.3873 99.3306 20.2666 97.3495 21.6932 95.9053C23.1128 94.468 25.0797 93.5817 27.2585 93.5817C29.4372 93.5817 31.4042 94.4716 32.8237 95.9088C34.2468 97.3495 35.1296 99.3306 35.1296 101.516C35.1296 101.52 35.1296 101.52 35.1296 101.523V101.516Z" fill="currentColor"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 2.4 KiB |
@ -1,9 +0,0 @@
|
|||||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<g transform="scale(0.85) translate(1.8, 1.8)">
|
|
||||||
<path d="M19.5 1.5C18.67 1.5 18 2.17 18 3C18 3.83 18.67 4.5 19.5 4.5C20.33 4.5 21 3.83 21 3C21 2.17 20.33 1.5 19.5 1.5Z" fill="currentColor"/>
|
|
||||||
<path d="M12 18C8.5 18 5.5 16.8 4 15C4 18.3137 7.13401 21 12 21C16.866 21 20 18.3137 20 15C18.5 16.8 15.5 18 12 18Z" fill="currentColor"/>
|
|
||||||
<path d="M12 6C15.5 6 18.5 7.2 20 9C20 5.68629 16.866 3 12 3C7.13401 3 4 5.68629 4 9C5.5 7.2 8.5 6 12 6Z" fill="currentColor"/>
|
|
||||||
<path d="M7.5 21C6.67 21 6 21.67 6 22.5C6 23.33 6.67 24 7.5 24C8.33 24 9 23.33 9 22.5C9 21.67 8.33 21 7.5 21Z" fill="currentColor"/>
|
|
||||||
<path d="M4.5 5.5C3.67 5.5 3 4.83 3 4C3 3.17 3.67 2.5 4.5 2.5C5.33 2.5 6 3.17 6 4C6 4.83 5.33 5.5 4.5 5.5Z" fill="currentColor"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 831 B |
@ -4,7 +4,6 @@ import { useTranslation } from "react-i18next";
|
|||||||
|
|
||||||
import { useConversationId } from "#/hooks/use-conversation-id";
|
import { useConversationId } from "#/hooks/use-conversation-id";
|
||||||
import { useCommandStore } from "#/state/command-store";
|
import { useCommandStore } from "#/state/command-store";
|
||||||
import { useJupyterStore } from "#/state/jupyter-store";
|
|
||||||
import { useConversationStore } from "#/state/conversation-store";
|
import { useConversationStore } from "#/state/conversation-store";
|
||||||
import { useAgentStore } from "#/stores/agent-store";
|
import { useAgentStore } from "#/stores/agent-store";
|
||||||
import { AgentState } from "#/types/agent-state";
|
import { AgentState } from "#/types/agent-state";
|
||||||
@ -53,7 +52,6 @@ function AppContent() {
|
|||||||
const setCurrentAgentState = useAgentStore(
|
const setCurrentAgentState = useAgentStore(
|
||||||
(state) => state.setCurrentAgentState,
|
(state) => state.setCurrentAgentState,
|
||||||
);
|
);
|
||||||
const clearJupyter = useJupyterStore((state) => state.clearJupyter);
|
|
||||||
const removeErrorMessage = useErrorMessageStore(
|
const removeErrorMessage = useErrorMessageStore(
|
||||||
(state) => state.removeErrorMessage,
|
(state) => state.removeErrorMessage,
|
||||||
);
|
);
|
||||||
@ -70,7 +68,6 @@ function AppContent() {
|
|||||||
// 1. Cleanup Effect - runs when navigating to a different conversation
|
// 1. Cleanup Effect - runs when navigating to a different conversation
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
clearTerminal();
|
clearTerminal();
|
||||||
clearJupyter();
|
|
||||||
resetConversationState();
|
resetConversationState();
|
||||||
setCurrentAgentState(AgentState.LOADING);
|
setCurrentAgentState(AgentState.LOADING);
|
||||||
removeErrorMessage();
|
removeErrorMessage();
|
||||||
@ -84,7 +81,6 @@ function AppContent() {
|
|||||||
}, [
|
}, [
|
||||||
conversationId,
|
conversationId,
|
||||||
clearTerminal,
|
clearTerminal,
|
||||||
clearJupyter,
|
|
||||||
resetConversationState,
|
resetConversationState,
|
||||||
setCurrentAgentState,
|
setCurrentAgentState,
|
||||||
removeErrorMessage,
|
removeErrorMessage,
|
||||||
|
|||||||
@ -1,44 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { JupyterEditor } from "#/components/features/jupyter/jupyter";
|
|
||||||
|
|
||||||
function Jupyter() {
|
|
||||||
const parentRef = React.useRef<HTMLDivElement>(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 (
|
|
||||||
<div ref={parentRef} className="h-full">
|
|
||||||
<JupyterEditor maxWidth={maxWidth} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Jupyter;
|
|
||||||
@ -8,7 +8,6 @@ import {
|
|||||||
StatusMessage,
|
StatusMessage,
|
||||||
} from "#/types/message";
|
} from "#/types/message";
|
||||||
import { handleObservationMessage } from "./observations";
|
import { handleObservationMessage } from "./observations";
|
||||||
import { useJupyterStore } from "#/state/jupyter-store";
|
|
||||||
import { useCommandStore } from "#/state/command-store";
|
import { useCommandStore } from "#/state/command-store";
|
||||||
import { queryClient } from "#/query-client-config";
|
import { queryClient } from "#/query-client-config";
|
||||||
import {
|
import {
|
||||||
@ -35,10 +34,6 @@ export function handleActionMessage(message: ActionMessage) {
|
|||||||
useCommandStore.getState().appendInput(message.args.command);
|
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) {
|
if ("args" in message && "security_risk" in message.args) {
|
||||||
useSecurityAnalyzerStore.getState().appendSecurityAnalyzerInput({
|
useSecurityAnalyzerStore.getState().appendSecurityAnalyzerInput({
|
||||||
id: message.id,
|
id: message.id,
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { ObservationMessage } from "#/types/message";
|
import { ObservationMessage } from "#/types/message";
|
||||||
import { useJupyterStore } from "#/state/jupyter-store";
|
|
||||||
import { useCommandStore } from "#/state/command-store";
|
import { useCommandStore } from "#/state/command-store";
|
||||||
import ObservationType from "#/types/observation-type";
|
import ObservationType from "#/types/observation-type";
|
||||||
import { useBrowserStore } from "#/stores/browser-store";
|
import { useBrowserStore } from "#/stores/browser-store";
|
||||||
@ -22,14 +21,6 @@ export function handleObservationMessage(message: ObservationMessage) {
|
|||||||
useCommandStore.getState().appendOutput(content);
|
useCommandStore.getState().appendOutput(content);
|
||||||
break;
|
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:
|
||||||
case ObservationType.BROWSE_INTERACTIVE:
|
case ObservationType.BROWSE_INTERACTIVE:
|
||||||
if (
|
if (
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import { devtools } from "zustand/middleware";
|
|||||||
export type ConversationTab =
|
export type ConversationTab =
|
||||||
| "editor"
|
| "editor"
|
||||||
| "browser"
|
| "browser"
|
||||||
| "jupyter"
|
|
||||||
| "served"
|
| "served"
|
||||||
| "vscode"
|
| "vscode"
|
||||||
| "terminal";
|
| "terminal";
|
||||||
|
|||||||
@ -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<JupyterState>((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: [],
|
|
||||||
})),
|
|
||||||
}));
|
|
||||||
@ -1,21 +1,10 @@
|
|||||||
enum TabOption {
|
enum TabOption {
|
||||||
PLANNER = "planner",
|
PLANNER = "planner",
|
||||||
BROWSER = "browser",
|
BROWSER = "browser",
|
||||||
JUPYTER = "jupyter",
|
|
||||||
VSCODE = "vscode",
|
VSCODE = "vscode",
|
||||||
}
|
}
|
||||||
|
|
||||||
type TabType =
|
type TabType = TabOption.PLANNER | TabOption.BROWSER | TabOption.VSCODE;
|
||||||
| TabOption.PLANNER
|
const AllTabs = [TabOption.VSCODE, TabOption.BROWSER, TabOption.PLANNER];
|
||||||
| TabOption.BROWSER
|
|
||||||
| TabOption.JUPYTER
|
|
||||||
| TabOption.VSCODE;
|
|
||||||
|
|
||||||
const AllTabs = [
|
|
||||||
TabOption.VSCODE,
|
|
||||||
TabOption.BROWSER,
|
|
||||||
TabOption.PLANNER,
|
|
||||||
TabOption.JUPYTER,
|
|
||||||
];
|
|
||||||
|
|
||||||
export { AllTabs, TabOption, type TabType };
|
export { AllTabs, TabOption, type TabType };
|
||||||
|
|||||||
@ -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;
|
|
||||||
};
|
|
||||||
Loading…
x
Reference in New Issue
Block a user