Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 38 additions & 1 deletion multimodal/tarko/agent-web-ui/src/common/state/atoms/ui.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -64,3 +64,40 @@ export const isProcessingAtom = atom(
* Atom for offline mode state (view-only when disconnected)
*/
export const offlineModeAtom = atom<boolean>(false);

/**
* Atom for layout mode with localStorage persistence
*/
export const layoutModeAtom = atom<LayoutMode>(
'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');
}
});
21 changes: 18 additions & 3 deletions multimodal/tarko/agent-web-ui/src/standalone/app/Layout/index.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -26,9 +28,16 @@ interface LayoutProps {
*/
export const Layout: React.FC<LayoutProps> = ({ 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 (
<div className="flex flex-col h-screen bg-[#F2F3F5] dark:bg-gray-900 text-gray-900 dark:text-gray-100 overflow-hidden">
<Navbar />
Expand All @@ -43,13 +52,19 @@ export const Layout: React.FC<LayoutProps> = ({ isReplayMode: propIsReplayMode }
>
{/* Desktop layout: horizontal split */}
<div className="hidden md:flex gap-3 flex-1 min-h-0">
<div className="flex-1 flex flex-col overflow-hidden">
<div className={classNames('flex flex-col overflow-hidden', {
'flex-1': layoutMode === 'default',
'flex-[1_1_33.333%]': layoutMode === 'narrow-chat',
})}>
<Shell className="h-full rounded-xl shadow-lg shadow-gray-200/50 dark:shadow-gray-950/20">
<ChatPanel />
</Shell>
</div>

<div className="flex-1 flex flex-col overflow-hidden">
<div className={classNames('flex flex-col overflow-hidden', {
'flex-1': layoutMode === 'default',
'flex-[2_1_66.667%]': layoutMode === 'narrow-chat',
})}>
<Shell className="h-full rounded-xl shadow-lg shadow-gray-200/50 dark:shadow-gray-950/20">
<WorkspacePanel />
</Shell>
Expand Down
Original file line number Diff line number Diff line change
@@ -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 (
<motion.button
whileHover={{
scale: 1.08,
}}
whileTap={{ scale: 0.97 }}
transition={{ type: 'spring', stiffness: 400, damping: 17 }}
onClick={toggleLayout}
className="w-8 h-8 rounded-lg flex items-center justify-center bg-white dark:bg-gray-800 text-black dark:text-white hover:shadow-md"
title={isNarrowChat ? 'Switch to Equal Layout' : 'Switch to Narrow Chat Layout'}
>
{isNarrowChat ? <FiColumns size={16} /> : <FiSidebar size={16} />}
</motion.button>
);
};
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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;
Expand Down Expand Up @@ -118,6 +124,9 @@ export const ToolBar: React.FC = () => {

{/* Bottom tool buttons */}
<div className="flex flex-col items-center gap-4 pb-4">
{/* Layout switch button */}
{!isReplayMode && enableLayoutSwitchButton && <LayoutSwitchButton />}

{/* Agent config button */}
{!isReplayMode && (
<motion.button
Expand Down
25 changes: 25 additions & 0 deletions multimodal/tarko/interface/src/web-ui-implementation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,27 @@ export interface WorkspaceNavItem {
link: string;
}

/**
* Layout mode configuration
*/
export type LayoutMode = 'default' | 'narrow-chat';

/**
* Layout configuration options
*/
export interface LayoutConfig {
/**
* Default layout mode
* @defaultValue 'default'
*/
defaultLayout?: LayoutMode;
/**
* Enable layout switch button in toolbar
* @defaultValue false
*/
enableLayoutSwitchButton?: boolean;
}

/**
* Workspace configuration options
*/
Expand Down Expand Up @@ -85,6 +106,10 @@ export interface BaseAgentWebUIImplementation {
* Workspace configuration
*/
workspace?: WorkspaceConfig;
/**
* Layout configuration
*/
layout?: LayoutConfig;
}

/**
Expand Down
Loading