import { useDisclosure } from "@heroui/react"; import React from "react"; import { useNavigate } from "react-router"; import { useDispatch, useSelector } from "react-redux"; import { FaServer, FaExternalLinkAlt } from "react-icons/fa"; import { useTranslation } from "react-i18next"; import { DiGit } from "react-icons/di"; import { VscCode } from "react-icons/vsc"; import { I18nKey } from "#/i18n/declaration"; import { RUNTIME_INACTIVE_STATES } from "#/types/agent-state"; import { useConversationId } from "#/hooks/use-conversation-id"; import { Controls } from "#/components/features/controls/controls"; import { clearTerminal } from "#/state/command-slice"; import { useEffectOnce } from "#/hooks/use-effect-once"; import GlobeIcon from "#/icons/globe.svg?react"; import JupyterIcon from "#/icons/jupyter.svg?react"; import TerminalIcon from "#/icons/terminal.svg?react"; import { clearJupyter } from "#/state/jupyter-slice"; import { ChatInterface } from "../components/features/chat/chat-interface"; import { WsClientProvider } from "#/context/ws-client-provider"; import { EventHandler } from "../wrapper/event-handler"; import { useConversationConfig } from "#/hooks/query/use-conversation-config"; import { Container } from "#/components/layout/container"; import { Orientation, ResizablePanel, } from "#/components/layout/resizable-panel"; import Security from "#/components/shared/modals/security/security"; import { useActiveConversation } from "#/hooks/query/use-active-conversation"; import { ServedAppLabel } from "#/components/layout/served-app-label"; import { useSettings } from "#/hooks/query/use-settings"; import { RootState } from "#/store"; import { displayErrorToast } from "#/utils/custom-toast-handlers"; import { useDocumentTitleFromState } from "#/hooks/use-document-title-from-state"; import { transformVSCodeUrl } from "#/utils/vscode-url-helper"; import OpenHands from "#/api/open-hands"; import { TabContent } from "#/components/layout/tab-content"; import { useIsAuthed } from "#/hooks/query/use-is-authed"; function AppContent() { useConversationConfig(); const { t } = useTranslation(); const { data: settings } = useSettings(); const { conversationId } = useConversationId(); const { data: conversation, isFetched, refetch } = useActiveConversation(); const { data: isAuthed } = useIsAuthed(); const { curAgentState } = useSelector((state: RootState) => state.agent); const dispatch = useDispatch(); const navigate = useNavigate(); // Set the document title to the conversation title when available useDocumentTitleFromState(); const [width, setWidth] = React.useState(window.innerWidth); React.useEffect(() => { if (isFetched && !conversation && isAuthed) { displayErrorToast( "This conversation does not exist, or you do not have permission to access it.", ); navigate("/"); } else if (conversation?.status === "STOPPED") { // start the conversation if the state is stopped on initial load OpenHands.startConversation(conversation.conversation_id).then(() => refetch(), ); } }, [conversation?.conversation_id, isFetched, isAuthed]); React.useEffect(() => { dispatch(clearTerminal()); dispatch(clearJupyter()); }, [conversationId]); useEffectOnce(() => { dispatch(clearTerminal()); dispatch(clearJupyter()); }); function handleResize() { setWidth(window.innerWidth); } React.useEffect(() => { window.addEventListener("resize", handleResize); return () => { window.removeEventListener("resize", handleResize); }; }, []); const { isOpen: securityModalIsOpen, onOpen: onSecurityModalOpen, onOpenChange: onSecurityModalOpenChange, } = useDisclosure(); function renderMain() { const basePath = `/conversations/${conversationId}`; if (width <= 640) { return (
); } return ( } secondChild={ , }, { label: (
{t(I18nKey.VSCODE$TITLE)}
), to: "vscode", icon: , rightContent: !RUNTIME_INACTIVE_STATES.includes( curAgentState, ) ? ( { e.preventDefault(); e.stopPropagation(); if (conversationId) { try { const data = await OpenHands.getVSCodeUrl(conversationId); if (data.vscode_url) { const transformedUrl = transformVSCodeUrl( data.vscode_url, ); if (transformedUrl) { window.open(transformedUrl, "_blank"); } } } catch (err) { // Silently handle the error } } }} /> ) : null, }, { label: t(I18nKey.WORKSPACE$TERMINAL_TAB_LABEL), to: "terminal", icon: , }, { label: "Jupyter", to: "jupyter", icon: }, { label: , to: "served", icon: , }, { label: (
{t(I18nKey.BROWSER$TITLE)}
), to: "browser", icon: , }, ]} > {/* Use both Outlet and TabContent */}
} /> ); } return (
{renderMain()}
{settings && ( )}
); } function App() { return ; } export default App;