import { useEffect, useMemo, useRef, useState } from 'react' import { SCHEDULED_TAB_ID, SETTINGS_TAB_ID, TERMINAL_TAB_PREFIX, useTabStore, type TabType, } from '../stores/tabStore' import { useSessionStore } from '../stores/sessionStore' import { useChatStore } from '../stores/chatStore' import { useCLITaskStore } from '../stores/cliTaskStore' import { useTeamStore } from '../stores/teamStore' import { useWorkspacePanelStore } from '../stores/workspacePanelStore' import { TERMINAL_PANEL_DEFAULT_HEIGHT, TERMINAL_PANEL_MAX_HEIGHT, TERMINAL_PANEL_MIN_HEIGHT, useTerminalPanelStore, } from '../stores/terminalPanelStore' import { useTranslation } from '../i18n' import { MessageList } from '../components/chat/MessageList' import { ChatInput } from '../components/chat/ChatInput' import { ComputerUsePermissionModal } from '../components/chat/ComputerUsePermissionModal' import { SessionTaskBar } from '../components/chat/SessionTaskBar' import { WorkspacePanel } from '../components/workspace/WorkspacePanel' import { TeamStatusBar } from '../components/teams/TeamStatusBar' import { TerminalSettings } from './TerminalSettings' import type { SessionListItem } from '../types/session' import { useMobileViewport } from '../hooks/useMobileViewport' import { isTauriRuntime } from '../lib/desktopRuntime' import type { ActiveGoalState } from '../types/chat' import { Target } from 'lucide-react' const TASK_POLL_INTERVAL_MS = 1000 const WORKSPACE_RESIZE_STEP = 32 const TERMINAL_RESIZE_STEP = 24 const CHAT_COLUMN_WITH_WORKSPACE_CLASS = 'min-w-[320px] flex-1 border-r border-[var(--color-border)] bg-[var(--color-surface)]' function isSessionTabState(activeTabId: string | null, activeTabType: TabType | null | undefined) { if (!activeTabId) return false if (activeTabType === 'session') return true if (activeTabType) return false return activeTabId !== SETTINGS_TAB_ID && activeTabId !== SCHEDULED_TAB_ID && !activeTabId.startsWith(TERMINAL_TAB_PREFIX) } function getSessionTerminalCwd(session: SessionListItem | undefined) { if (!session) return undefined if (session.workDir && session.workDirExists !== false) return session.workDir return session.projectPath || undefined } function WorkspaceResizeHandle() { const t = useTranslation() const width = useWorkspacePanelStore((state) => state.width) const setWidth = useWorkspacePanelStore((state) => state.setWidth) const [dragState, setDragState] = useState<{ startX: number; startWidth: number } | null>(null) const dragStateRef = useRef(dragState) useEffect(() => { dragStateRef.current = dragState }, [dragState]) useEffect(() => { if (!dragState) return const handlePointerMove = (event: PointerEvent) => { const current = dragStateRef.current if (!current) return setWidth(current.startWidth + current.startX - event.clientX) } const handlePointerUp = () => { setDragState(null) } document.body.style.cursor = 'col-resize' document.body.style.userSelect = 'none' window.addEventListener('pointermove', handlePointerMove) window.addEventListener('pointerup', handlePointerUp) window.addEventListener('pointercancel', handlePointerUp) return () => { document.body.style.cursor = '' document.body.style.userSelect = '' window.removeEventListener('pointermove', handlePointerMove) window.removeEventListener('pointerup', handlePointerUp) window.removeEventListener('pointercancel', handlePointerUp) } }, [dragState, setWidth]) return (
{ if (event.button !== 0) return event.preventDefault() setDragState({ startX: event.clientX, startWidth: width }) }} onKeyDown={(event) => { if (event.key === 'ArrowLeft') { event.preventDefault() setWidth(width + WORKSPACE_RESIZE_STEP) } if (event.key === 'ArrowRight') { event.preventDefault() setWidth(width - WORKSPACE_RESIZE_STEP) } }} className="group relative z-10 flex w-2 shrink-0 cursor-col-resize items-stretch justify-center bg-[var(--color-surface)] outline-none focus-visible:bg-[var(--color-surface-container)]" >
) } function TerminalResizeHandle() { const t = useTranslation() const height = useTerminalPanelStore((state) => state.height) const setHeight = useTerminalPanelStore((state) => state.setHeight) const [dragState, setDragState] = useState<{ startY: number; startHeight: number } | null>(null) const dragStateRef = useRef(dragState) useEffect(() => { dragStateRef.current = dragState }, [dragState]) useEffect(() => { if (!dragState) return const handlePointerMove = (event: PointerEvent) => { const current = dragStateRef.current if (!current) return setHeight(current.startHeight + current.startY - event.clientY) } const handlePointerUp = () => { setDragState(null) } document.body.style.cursor = 'row-resize' document.body.style.userSelect = 'none' window.addEventListener('pointermove', handlePointerMove) window.addEventListener('pointerup', handlePointerUp) window.addEventListener('pointercancel', handlePointerUp) return () => { document.body.style.cursor = '' document.body.style.userSelect = '' window.removeEventListener('pointermove', handlePointerMove) window.removeEventListener('pointerup', handlePointerUp) window.removeEventListener('pointercancel', handlePointerUp) } }, [dragState, setHeight]) return (
{ if (event.button !== 0) return event.preventDefault() setDragState({ startY: event.clientY, startHeight: height }) }} onKeyDown={(event) => { if (event.key === 'ArrowUp') { event.preventDefault() setHeight(height + TERMINAL_RESIZE_STEP) } if (event.key === 'ArrowDown') { event.preventDefault() setHeight(height - TERMINAL_RESIZE_STEP) } if (event.key === 'Home') { event.preventDefault() setHeight(TERMINAL_PANEL_MIN_HEIGHT) } if (event.key === 'End') { event.preventDefault() setHeight(TERMINAL_PANEL_MAX_HEIGHT) } }} onDoubleClick={() => setHeight(TERMINAL_PANEL_DEFAULT_HEIGHT)} className="group flex h-2.5 shrink-0 cursor-row-resize items-center bg-[var(--color-surface)] outline-none focus-visible:bg-[var(--color-surface-container)]" >
) } function GoalStatusPanel({ goal, isRunning, compact, }: { goal: ActiveGoalState isRunning: boolean compact: boolean }) { const t = useTranslation() const stateLabel = goal.action === 'completed' ? t('chat.activeGoal.completed') : goal.action === 'paused' || goal.status === 'paused' ? t('chat.activeGoal.paused') : isRunning && goal.status !== 'complete' ? t('chat.activeGoal.running') : t('chat.activeGoal.active') return (
) } export function ActiveSession() { const isMobileLayout = useMobileViewport() && !isTauriRuntime() const activeTabId = useTabStore((s) => s.activeTabId) const activeTabType = useTabStore((s) => s.tabs.find((tab) => tab.sessionId === s.activeTabId)?.type ?? null) const sessions = useSessionStore((s) => s.sessions) const connectToSession = useChatStore((s) => s.connectToSession) const sessionState = useChatStore((s) => activeTabId ? s.sessions[activeTabId] : undefined) const pendingComputerUsePermission = sessionState?.pendingComputerUsePermission ?? null const fetchSessionTasks = useCLITaskStore((s) => s.fetchSessionTasks) const trackedTaskSessionId = useCLITaskStore((s) => s.sessionId) const hasIncompleteTasks = useCLITaskStore((s) => s.tasks.some((task) => task.status !== 'completed')) const chatState = sessionState?.chatState ?? 'idle' const activeGoal = sessionState?.activeGoal ?? null const tokenUsage = sessionState?.tokenUsage ?? { input_tokens: 0, output_tokens: 0 } const session = sessions.find((s) => s.id === activeTabId) const memberInfo = useTeamStore((s) => activeTabId ? s.getMemberBySessionId(activeTabId) : null) const activeTeam = useTeamStore((s) => s.activeTeam) const isMemberSession = !!memberInfo const showWorkspacePanel = useWorkspacePanelStore((state) => activeTabId && isSessionTabState(activeTabId, activeTabType) && !isMemberSession && !isMobileLayout ? state.isPanelOpen(activeTabId) : false, ) const showTerminalPanel = useTerminalPanelStore((state) => activeTabId && isSessionTabState(activeTabId, activeTabType) && !isMemberSession && !isMobileLayout ? state.isPanelOpen(activeTabId) : false, ) const terminalPanelHeight = useTerminalPanelStore((state) => state.height) useEffect(() => { if (activeTabId && !isMemberSession) { connectToSession(activeTabId) } }, [activeTabId, isMemberSession, connectToSession]) useEffect(() => { if (!activeTabId || isMemberSession) return const shouldPollTasks = chatState !== 'idle' || (trackedTaskSessionId === activeTabId && hasIncompleteTasks) if (!shouldPollTasks) return void fetchSessionTasks(activeTabId) const timer = setInterval(() => { void fetchSessionTasks(activeTabId) }, TASK_POLL_INTERVAL_MS) return () => clearInterval(timer) }, [ activeTabId, isMemberSession, chatState, trackedTaskSessionId, hasIncompleteTasks, fetchSessionTasks, ]) const t = useTranslation() const messages = sessionState?.messages ?? [] const streamingText = sessionState?.streamingText ?? '' const isEmpty = messages.length === 0 && !streamingText && (session?.messageCount ?? 0) === 0 const isActive = chatState !== 'idle' const totalTokens = tokenUsage.input_tokens + tokenUsage.output_tokens const lastUpdated = useMemo(() => { if (!session?.modifiedAt) return '' const diff = Date.now() - new Date(session.modifiedAt).getTime() if (diff < 60000) return t('session.timeJustNow') if (diff < 3600000) return t('session.timeMinutes', { n: Math.floor(diff / 60000) }) if (diff < 86400000) return t('session.timeHours', { n: Math.floor(diff / 3600000) }) return t('session.timeDays', { n: Math.floor(diff / 86400000) }) }, [session?.modifiedAt, t]) if (!activeTabId) return null return (
{isMemberSession && (
{memberInfo?.status === 'running' && ( )} {memberInfo?.status === 'completed' && ( check_circle )} smart_toy {memberInfo?.role} {activeTeam && ( @ {activeTeam.name} )}

{t('teams.memberSessionHint')}

)} {isEmpty ? (
{isMemberSession ? ( <> smart_toy

{memberInfo?.status === 'running' ? `${memberInfo.role} ${t('teams.working')}` : t('teams.noMessages')}

) : ( <> Claude Code Haha

{t('empty.title')}

{t('empty.subtitle')}

)}
) : ( <> {!isMemberSession && !isMobileLayout && (

{session?.title || t('session.untitled')}

{isActive && ( {t('session.active')} )} {totalTokens > 0 && ( <> · {totalTokens.toLocaleString()} t )} {lastUpdated && ( <> · {t('session.lastUpdated', { time: lastUpdated })} )} {!showWorkspacePanel && session?.messageCount !== undefined && session.messageCount > 0 && ( <> · {t('session.messages', { count: session.messageCount })} )}
{session?.workDirExists === false && (
warning {t('session.workspaceUnavailable', { dir: session.workDir || 'directory no longer exists' })}
)}
)} {activeGoal && ( )} )} {!isMemberSession && } {showTerminalPanel && activeTabId ? (
{ useTerminalPanelStore.getState().closePanel(activeTabId) useTabStore.getState().openTerminalTab(getSessionTerminalCwd(session)) }} onClose={() => useTerminalPanelStore.getState().closePanel(activeTabId)} />
) : null}
{showWorkspacePanel ? ( <> ) : null}
{!isMemberSession && activeTabId ? ( ) : null}
) }