diff --git a/multimodal/tarko/agent-web-ui/src/common/state/atoms/ui.ts b/multimodal/tarko/agent-web-ui/src/common/state/atoms/ui.ts index e6c321a128..180fd71360 100644 --- a/multimodal/tarko/agent-web-ui/src/common/state/atoms/ui.ts +++ b/multimodal/tarko/agent-web-ui/src/common/state/atoms/ui.ts @@ -1,5 +1,5 @@ import { atom } from 'jotai'; -import { AgentProcessingPhase, AgentStatusInfo, SessionItemInfo } from '@tarko/interface'; +import { AgentProcessingPhase, AgentStatusInfo, SessionItemInfo, LayoutMode } from '@tarko/interface'; import { ConnectionStatus, PanelContent, @@ -64,3 +64,40 @@ export const isProcessingAtom = atom( * Atom for offline mode state (view-only when disconnected) */ export const offlineModeAtom = atom(false); + +/** + * Atom for layout mode with localStorage persistence + */ +export const layoutModeAtom = atom( + 'default', + (get, set, newValue: LayoutMode) => { + set(layoutModeAtom, newValue); + // Persist to localStorage + try { + localStorage.setItem('tarko-layout-mode', newValue); + } catch (error) { + console.warn('Failed to save layout mode to localStorage:', error); + } + }, +); + +/** + * Initialize layout mode from localStorage or agent config + */ +export const initializeLayoutModeAtom = atom(null, (get, set) => { + try { + const agentOptions = get(agentOptionsAtom); + const defaultLayout = agentOptions.webui?.layout?.defaultLayout || 'default'; + + // Try to get from localStorage first + const savedLayout = localStorage.getItem('tarko-layout-mode') as LayoutMode; + if (savedLayout && (savedLayout === 'default' || savedLayout === 'narrow-chat')) { + set(layoutModeAtom, savedLayout); + } else { + set(layoutModeAtom, defaultLayout); + } + } catch (error) { + console.warn('Failed to initialize layout mode:', error); + set(layoutModeAtom, 'default'); + } +}); diff --git a/multimodal/tarko/agent-web-ui/src/standalone/app/Layout/index.tsx b/multimodal/tarko/agent-web-ui/src/standalone/app/Layout/index.tsx index ea34966f09..383322fa96 100644 --- a/multimodal/tarko/agent-web-ui/src/standalone/app/Layout/index.tsx +++ b/multimodal/tarko/agent-web-ui/src/standalone/app/Layout/index.tsx @@ -1,10 +1,12 @@ -import React from 'react'; +import React, { useEffect } from 'react'; +import { useAtom, useSetAtom } from 'jotai'; import { Sidebar } from '@/standalone/sidebar'; import { Navbar } from '@/standalone/navbar'; import { ChatPanel } from '@/standalone/chat/ChatPanel'; import { WorkspacePanel } from '@/standalone/workspace/WorkspacePanel'; import { useSession } from '@/common/hooks/useSession'; import { useReplayMode } from '@/common/hooks/useReplayMode'; +import { layoutModeAtom, initializeLayoutModeAtom } from '@/common/state/atoms/ui'; import { Shell } from './Shell'; import './Layout.css'; import classNames from 'classnames'; @@ -26,9 +28,16 @@ interface LayoutProps { */ export const Layout: React.FC = ({ isReplayMode: propIsReplayMode }) => { const { isReplayMode: contextIsReplayMode } = useReplayMode(); + const [layoutMode] = useAtom(layoutModeAtom); + const initializeLayoutMode = useSetAtom(initializeLayoutModeAtom); const isReplayMode = propIsReplayMode !== undefined ? propIsReplayMode : contextIsReplayMode; + // Initialize layout mode on mount + useEffect(() => { + initializeLayoutMode(); + }, [initializeLayoutMode]); + return (
@@ -43,13 +52,19 @@ export const Layout: React.FC = ({ isReplayMode: propIsReplayMode } > {/* Desktop layout: horizontal split */}
-
+
-
+
diff --git a/multimodal/tarko/agent-web-ui/src/standalone/sidebar/LayoutSwitchButton.tsx b/multimodal/tarko/agent-web-ui/src/standalone/sidebar/LayoutSwitchButton.tsx new file mode 100644 index 0000000000..b4bb83da9b --- /dev/null +++ b/multimodal/tarko/agent-web-ui/src/standalone/sidebar/LayoutSwitchButton.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import { motion } from 'framer-motion'; +import { useAtom } from 'jotai'; +import { FiColumns, FiSidebar } from 'react-icons/fi'; +import { layoutModeAtom } from '@/common/state/atoms/ui'; +import { LayoutMode } from '@tarko/interface'; + +/** + * LayoutSwitchButton Component - Toggle between layout modes + * + * Design principles: + * - Consistent with toolbar button styling + * - Clear visual indication of current mode + * - Smooth animations for state transitions + */ +export const LayoutSwitchButton: React.FC = () => { + const [layoutMode, setLayoutMode] = useAtom(layoutModeAtom); + + const toggleLayout = () => { + const newMode: LayoutMode = layoutMode === 'default' ? 'narrow-chat' : 'default'; + setLayoutMode(newMode); + }; + + const isNarrowChat = layoutMode === 'narrow-chat'; + + return ( + + {isNarrowChat ? : } + + ); +}; diff --git a/multimodal/tarko/agent-web-ui/src/standalone/sidebar/ToolBar.tsx b/multimodal/tarko/agent-web-ui/src/standalone/sidebar/ToolBar.tsx index 81386f38b7..1bdfba2c50 100644 --- a/multimodal/tarko/agent-web-ui/src/standalone/sidebar/ToolBar.tsx +++ b/multimodal/tarko/agent-web-ui/src/standalone/sidebar/ToolBar.tsx @@ -1,11 +1,14 @@ import React, { useCallback, useState } from 'react'; import { motion } from 'framer-motion'; +import { useAtomValue } from 'jotai'; import { FiPlus, FiHome, FiSettings } from 'react-icons/fi'; import { useNavigate } from 'react-router-dom'; import { useSession } from '@/common/hooks/useSession'; import { useReplayMode } from '@/common/hooks/useReplayMode'; +import { agentOptionsAtom } from '@/common/state/atoms/ui'; import { AgentConfigViewer } from './AgentConfigViewer'; +import { LayoutSwitchButton } from './LayoutSwitchButton'; /** * ToolBar Component - Vertical toolbar inspired by modern IDE designs @@ -20,9 +23,12 @@ export const ToolBar: React.FC = () => { const navigate = useNavigate(); const { isReplayMode } = useReplayMode(); const { createSession, connectionStatus } = useSession(); + const agentOptions = useAtomValue(agentOptionsAtom); const [isConfigViewerOpen, setIsConfigViewerOpen] = useState(false); const [isCreatingSession, setIsCreatingSession] = useState(false); + const enableLayoutSwitchButton = agentOptions.webui?.layout?.enableLayoutSwitchButton ?? false; + // Create new session const handleNewSession = useCallback(async () => { if (isCreatingSession || !connectionStatus.connected) return; @@ -118,6 +124,9 @@ export const ToolBar: React.FC = () => { {/* Bottom tool buttons */}
+ {/* Layout switch button */} + {!isReplayMode && enableLayoutSwitchButton && } + {/* Agent config button */} {!isReplayMode && (