diff --git a/flowsint-app/src/components/layout/log-panel.tsx b/flowsint-app/src/components/layout/log-panel.tsx index 94ccce5..f2c722b 100644 --- a/flowsint-app/src/components/layout/log-panel.tsx +++ b/flowsint-app/src/components/layout/log-panel.tsx @@ -1,117 +1,15 @@ -import { Badge } from '../ui/badge' import { logService } from '@/api/log-service' import { useConfirm } from '../use-confirm-dialog' import { useParams } from '@tanstack/react-router' -import { - Info, - AlertTriangle, - AlertCircle, - CheckCircle, - Zap, - Clock, - RotateCcw, - PartyPopper, - BarChart3, - FileText, - Trash2 -} from 'lucide-react' -import { useEffect, useRef, memo } from 'react' -import { CopyButton } from '../copy' -import { EventLevel } from '@/types' -import { cn } from '@/lib/utils' -import { Button } from '../ui/button' +import { memo } from 'react' import { useEvents } from '@/hooks/use-events' import { useMutation, useQueryClient } from '@tanstack/react-query' import { queryKeys } from '@/api/query-keys' - -const formatTime = (date: string) => { - return new Date(date).toLocaleTimeString('en-US', { - hour12: false, - hour: '2-digit', - minute: '2-digit', - second: '2-digit' - }) -} - -const logLevelConfig = { - [EventLevel.INFO]: { - icon: Info, - color: 'dark:text-blue-400 text-blue-600', - bg: 'bg-blue-500/10', - badge: 'dark:bg-blue-500/20 dark:text-blue-300 bg-blue-500/20 text-blue-600', - emoji: 'ℹ️' - }, - [EventLevel.WARNING]: { - icon: AlertTriangle, - color: 'dark:text-yellow-400 text-yellow-600', - bg: 'bg-yellow-500/10', - badge: 'dark:bg-yellow-500/20 dark:text-yellow-300 bg-yellow-500/20 text-yellow-600', - emoji: '⚠️' - }, - [EventLevel.FAILED]: { - icon: AlertCircle, - color: 'dark:text-red-400 text-red-600', - bg: 'bg-red-500/10', - badge: 'dark:bg-red-500/20 dark:text-red-300 bg-red-500/20 text-red-600', - emoji: '❌' - }, - [EventLevel.SUCCESS]: { - icon: CheckCircle, - color: 'dark:text-green-400 text-green-600', - bg: 'bg-green-500/10', - badge: 'dark:bg-green-500/20 dark:text-green-300 bg-green-500/20 text-green-600', - emoji: '✅' - }, - [EventLevel.DEBUG]: { - icon: Zap, - color: 'dark:text-purple-400 text-purple-600', - bg: 'bg-purple-500/10', - badge: 'dark:bg-purple-500/20 dark:text-purple-300 bg-purple-500/20 text-purple-600', - emoji: '🐛' - }, - [EventLevel.PENDING]: { - icon: Clock, - color: 'dark:text-orange-400 text-orange-600', - bg: 'bg-orange-500/10', - badge: 'dark:bg-orange-500/20 dark:text-orange-300 bg-orange-500/20 text-orange-600', - emoji: '⏳' - }, - [EventLevel.RUNNING]: { - icon: RotateCcw, - color: 'dark:text-blue-400 text-blue-600', - bg: 'bg-blue-500/10', - badge: 'dark:bg-blue-500/20 dark:text-blue-300 bg-blue-500/20 text-blue-600', - emoji: '🔄' - }, - [EventLevel.COMPLETED]: { - icon: PartyPopper, - color: 'dark:text-green-400 text-green-600', - bg: 'bg-green-500/10', - badge: 'dark:bg-green-500/20 dark:text-green-300 bg-green-500/20 text-green-600', - emoji: '🎉' - }, - [EventLevel.GRAPH_APPEND]: { - icon: BarChart3, - color: 'dark:text-purple-400 text-purple-600', - bg: 'bg-purple-500/10', - badge: 'dark:bg-purple-500/20 dark:text-purple-300 bg-purple-500/20 text-purple-600', - emoji: '📊' - } -} -// Fallback config for any missing event levels -const defaultConfig = { - icon: FileText, - color: 'text-gray-400', - bg: 'bg-gray-500/10', - badge: 'bg-gray-500/20 text-gray-300', - emoji: '📝' -} +import { TerminalLogViewer } from '../terminal' export const LogPanel = memo(() => { const { id: sketch_id } = useParams({ strict: false }) const { confirm } = useConfirm() - const bottomRef = useRef(null) - const scrollAreaRef = useRef(null) const { logs, refetch } = useEvents(sketch_id as string) const queryClient = useQueryClient() @@ -132,12 +30,6 @@ export const LogPanel = memo(() => { } }) - useEffect(() => { - if (bottomRef.current) { - bottomRef.current.scrollIntoView({ behavior: 'smooth' }) - } - }, [logs]) - const handleDeleteLogs = async () => { if (!sketch_id) return if ( @@ -151,65 +43,12 @@ export const LogPanel = memo(() => { } return ( -
-
-
- {logs.length === 0 ? ( -
-

- Waiting for investigation - activity -

-

Events will appear here as they happen

-
- ) : ( - logs.map((log, i) => { - //@ts-ignore - const config = logLevelConfig[log.type] || defaultConfig - const Icon = config.icon - - return ( -
-
- - - {formatTime(log.created_at)} - - - {log.type} - -
-
-

{log.payload.message}

-
- -
- ) - }) - )} -
-
-
-
- - -
+
+
) }) diff --git a/flowsint-app/src/components/layout/persistent-log-panel.tsx b/flowsint-app/src/components/layout/persistent-log-panel.tsx new file mode 100644 index 0000000..c4cda04 --- /dev/null +++ b/flowsint-app/src/components/layout/persistent-log-panel.tsx @@ -0,0 +1,79 @@ +import { useLayoutStore } from '@/stores/layout-store' +import { useParams } from '@tanstack/react-router' +import { LogPanel } from './log-panel' +import { StatusBar } from './status-bar' +import { ResizableHandle, ResizablePanel } from '../ui/resizable' +import { useMemo } from 'react' + +/** + * Wrapper component that keeps LogPanel mounted even when console is closed + * to preserve terminal state. Uses a stable key to ensure React reuses the + * same instance. + */ +export const PersistentLogPanel = () => { + const isOpenConsole = useLayoutStore((s) => s.isOpenConsole) + const { id } = useParams({ strict: false }) + + // Create a memoized LogPanel that persists across re-renders + // This ensures the same instance is used whether console is open or closed + const logPanel = useMemo(() => { + if (!id) return null + return + }, [id]) + + // Don't render anything if there's no id + if (!id) { + return ( +
+ +
+ ) + } + + return ( + <> + {isOpenConsole ? ( + <> + + +
+
+ +
+
+ {/* Show LogPanel when console is open */} + {logPanel} +
+
+
+ + ) : ( +
+ + {/* Keep LogPanel mounted but hidden when console is closed */} +
+ {/* Same LogPanel instance rendered off-screen */} + {logPanel} +
+
+ )} + + ) +} diff --git a/flowsint-app/src/components/layout/root.layout.tsx b/flowsint-app/src/components/layout/root.layout.tsx index ca5ba98..1b8e590 100644 --- a/flowsint-app/src/components/layout/root.layout.tsx +++ b/flowsint-app/src/components/layout/root.layout.tsx @@ -1,11 +1,10 @@ import { type ReactNode, useMemo } from 'react' import { Sidebar } from './sidebar' import { TopNavbar } from './top-navbar' -import { StatusBar } from './status-bar' import SecondaryNavigation from './secondary-navigation' import { ConfirmContextProvider } from '@/components/use-confirm-dialog' import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '../ui/resizable' -import { LogPanel } from './log-panel' +import { PersistentLogPanel } from './persistent-log-panel' import { useLayoutStore } from '@/stores/layout-store' import NotesPanel from '../analyses/notes-panel' import { useKeyboardShortcut } from '@/hooks/use-keyboard-shortcut' @@ -156,32 +155,8 @@ export default function RootLayout({ children }: LayoutProps) { - {/* Console panel with status bar - only shown when isOpen is true */} - {isOpenConsole && id ? ( - <> - - -
-
- -
-
- -
-
-
- - ) : ( -
- -
- )} + {/* Console panel with status bar - uses PersistentLogPanel to maintain state */} +