From 61566d8a4b5180f8c2b72fb0ef0672df5319d845 Mon Sep 17 00:00:00 2001 From: James Sukanto Date: Tue, 27 Jan 2026 17:34:02 +0800 Subject: [PATCH 1/8] refactor: extract demo logs fetching into useDemoLogs hook Move demo logs preview fetching logic from Dashboard component into a reusable custom hook for better separation of concerns. --- visualizer/src/components/Dashboard.tsx | 57 ++--------------- visualizer/src/hooks/use-demo-logs.ts | 81 +++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 52 deletions(-) create mode 100644 visualizer/src/hooks/use-demo-logs.ts diff --git a/visualizer/src/components/Dashboard.tsx b/visualizer/src/components/Dashboard.tsx index 79a9e00e2..77d726307 100644 --- a/visualizer/src/components/Dashboard.tsx +++ b/visualizer/src/components/Dashboard.tsx @@ -4,70 +4,23 @@ import { useState, useCallback, useEffect } from 'react'; import { Card, CardContent } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { ScrollArea } from '@/components/ui/scroll-area'; +import { useDemoLogs } from '@/hooks/use-demo-logs'; import { FileUploader } from './FileUploader'; import { LogViewer } from './LogViewer'; import { AsciiRLM } from './AsciiGlobe'; import { ThemeToggle } from './ThemeToggle'; -import { parseLogFile, extractContextVariable } from '@/lib/parse-logs'; +import { parseLogFile } from '@/lib/parse-logs'; import { RLMLogFile } from '@/lib/types'; import { cn } from '@/lib/utils'; -interface DemoLogInfo { - fileName: string; - contextPreview: string | null; - hasFinalAnswer: boolean; - iterations: number; -} - export function Dashboard() { const [logFiles, setLogFiles] = useState([]); const [selectedLog, setSelectedLog] = useState(null); - const [demoLogs, setDemoLogs] = useState([]); - const [loadingDemos, setLoadingDemos] = useState(true); - // Load demo log previews on mount - fetches latest 10 from API - useEffect(() => { - async function loadDemoPreviews() { - try { - // Fetch list of log files from API - const listResponse = await fetch('/api/logs'); - if (!listResponse.ok) { - throw new Error('Failed to fetch log list'); - } - const { files } = await listResponse.json(); - - const previews: DemoLogInfo[] = []; - - for (const fileName of files) { - try { - const response = await fetch(`/logs/${fileName}`); - if (!response.ok) continue; - const content = await response.text(); - const parsed = parseLogFile(fileName, content); - const contextVar = extractContextVariable(parsed.iterations); - - previews.push({ - fileName, - contextPreview: contextVar, - hasFinalAnswer: !!parsed.metadata.finalAnswer, - iterations: parsed.metadata.totalIterations, - }); - } catch (e) { - console.error('Failed to load demo preview:', fileName, e); - } - } - - setDemoLogs(previews); - } catch (e) { - console.error('Failed to load demo logs:', e); - } finally { - setLoadingDemos(false); - } - } - - loadDemoPreviews(); - }, []); + // Preview cards for "Recent Traces" section: log files from server that users can click to load. + const { demoLogs, loadingDemos } = useDemoLogs(); + // When a full log file is loaded (upload or demo), parse and register it. const handleFileLoaded = useCallback((fileName: string, content: string) => { const parsed = parseLogFile(fileName, content); setLogFiles(prev => { diff --git a/visualizer/src/hooks/use-demo-logs.ts b/visualizer/src/hooks/use-demo-logs.ts new file mode 100644 index 000000000..d4408eff3 --- /dev/null +++ b/visualizer/src/hooks/use-demo-logs.ts @@ -0,0 +1,81 @@ +import { useCallback, useEffect, useState } from 'react'; + +import { extractContextVariable, parseLogFile } from '@/lib/parse-logs'; + +// Lightweight summary of a log file for the "Recent Traces" list +export interface DemoLogInfo { + fileName: string; + contextPreview: string | null; + hasFinalAnswer: boolean; + iterations: number; +} + +interface UseDemoLogsResult { + demoLogs: DemoLogInfo[]; + loadingDemos: boolean; + refetch: () => Promise; +} + +// Encapsulates fetching + parsing of demo log previews from the backend +export function useDemoLogs(): UseDemoLogsResult { + const [demoLogs, setDemoLogs] = useState([]); + const [loadingDemos, setLoadingDemos] = useState(true); + + const loadDemoPreviews = useCallback(async () => { + setLoadingDemos(true); + + try { + // 1) Ask API for the list of available log files + const listResponse = await fetch('/api/logs'); + if (!listResponse.ok) { + throw new Error('Failed to fetch log list'); + } + + const { files } = await listResponse.json() as { files: string[] }; + + const previews: DemoLogInfo[] = []; + + // 2) For each file, fetch the JSONL content and build a small preview object + for (const fileName of files) { + try { + const response = await fetch(`/logs/${fileName}`); + if (!response.ok) continue; + + const content = await response.text(); + const parsed = parseLogFile(fileName, content); + const contextVar = extractContextVariable(parsed.iterations); + + previews.push({ + fileName, + contextPreview: contextVar, + hasFinalAnswer: !!parsed.metadata.finalAnswer, + iterations: parsed.metadata.totalIterations, + }); + } catch (error) { + // If one file fails, log it and keep going with the rest + // eslint-disable-next-line no-console + console.error('Failed to load demo preview:', fileName, error); + } + } + + setDemoLogs(previews); + } catch (error) { + // eslint-disable-next-line no-console + console.error('Failed to load demo logs:', error); + } finally { + setLoadingDemos(false); + } + }, []); + + // Load once on mount so Dashboard just consumes the data. + useEffect(() => { + void loadDemoPreviews(); + }, [loadDemoPreviews]); + + return { + demoLogs, + loadingDemos, + refetch: loadDemoPreviews, + }; +} + From 5b84a2e7637a5d453ead6e73deecb46d02d0fd95 Mon Sep 17 00:00:00 2001 From: James Sukanto Date: Tue, 27 Jan 2026 18:26:45 +0800 Subject: [PATCH 2/8] refactor: extract Dashboard layout into smaller components --- visualizer/src/components/Dashboard.tsx | 483 ++++++++++++++---------- 1 file changed, 277 insertions(+), 206 deletions(-) diff --git a/visualizer/src/components/Dashboard.tsx b/visualizer/src/components/Dashboard.tsx index 77d726307..f58de32ff 100644 --- a/visualizer/src/components/Dashboard.tsx +++ b/visualizer/src/components/Dashboard.tsx @@ -4,7 +4,7 @@ import { useState, useCallback, useEffect } from 'react'; import { Card, CardContent } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { ScrollArea } from '@/components/ui/scroll-area'; -import { useDemoLogs } from '@/hooks/use-demo-logs'; +import { useDemoLogs, DemoLogInfo } from '@/hooks/use-demo-logs'; import { FileUploader } from './FileUploader'; import { LogViewer } from './LogViewer'; import { AsciiRLM } from './AsciiGlobe'; @@ -17,7 +17,7 @@ export function Dashboard() { const [logFiles, setLogFiles] = useState([]); const [selectedLog, setSelectedLog] = useState(null); - // Preview cards for "Recent Traces" section: log files from server that users can click to load. + // Log files from server that users can load ("Recent Traces" section in UI) const { demoLogs, loadingDemos } = useDemoLogs(); // When a full log file is loaded (upload or demo), parse and register it. @@ -25,240 +25,311 @@ export function Dashboard() { const parsed = parseLogFile(fileName, content); setLogFiles(prev => { if (prev.some(f => f.fileName === fileName)) { - return prev.map(f => f.fileName === fileName ? parsed : f); + return prev.map(f => (f.fileName === fileName ? parsed : f)); } return [...prev, parsed]; }); setSelectedLog(parsed); }, []); - const loadDemoLog = useCallback(async (fileName: string) => { - try { - const response = await fetch(`/logs/${fileName}`); - if (!response.ok) throw new Error('Failed to load demo log'); - const content = await response.text(); - handleFileLoaded(fileName, content); - } catch (error) { - console.error('Error loading demo log:', error); - alert('Failed to load demo log. Make sure the log files are in the public/logs folder.'); - } - }, [handleFileLoaded]); + const loadDemoLog = useCallback( + async (fileName: string) => { + try { + const response = await fetch(`/logs/${fileName}`); + if (!response.ok) throw new Error('Failed to load demo log'); + const content = await response.text(); + handleFileLoaded(fileName, content); + } catch (error) { + console.error('Error loading demo log:', error); + alert('Failed to load demo log. Make sure the log files are in the public/logs folder.'); + } + }, + [handleFileLoaded], + ); if (selectedLog) { return ( - setSelectedLog(null)} + setSelectedLog(null)} /> ); } return (
- {/* Background effects */} -
-
-
- + +
- {/* Header */} -
-
-
-
-

- RLM - Visualizer -

-

- Debug recursive language model execution traces -

-
-
- -
- - - READY - -
-
-
-
-
+ - {/* Main Content */}
- {/* Left Column - Upload & ASCII Art */} +
- {/* Upload Section */} -
-

- 01 - Upload Log File -

- -
- - {/* ASCII Architecture Diagram */} -
-

- ◈ - RLM Architecture -

-
- -
-
+ +
- {/* Right Column - Demo Logs & Loaded Files */}
- {/* Demo Logs Section */} -
-

- 02 - Recent Traces - (latest 10) -

- - {loadingDemos ? ( - - -
- Loading traces... -
-
-
- ) : demoLogs.length === 0 ? ( - - - No log files found in /public/logs/ - - - ) : ( - -
- {demoLogs.map((demo) => ( - loadDemoLog(demo.fileName)} - className={cn( - 'cursor-pointer transition-all hover:scale-[1.01]', - 'hover:border-primary/50 hover:bg-primary/5' - )} - > - -
- {/* Status indicator */} -
-
- {demo.hasFinalAnswer && ( -
- )} -
- - {/* Content */} -
-
- - {demo.fileName} - - - {demo.iterations} iter - -
- {demo.contextPreview && ( -

- {demo.contextPreview.length > 80 - ? demo.contextPreview.slice(0, 80) + '...' - : demo.contextPreview} -

- )} -
-
- - - ))} -
- - )} -
- - {/* Loaded Files Section */} - {logFiles.length > 0 && ( -
-

- 03 - Loaded Files -

- -
- {logFiles.map((log) => ( - setSelectedLog(log)} - > - -
-
-
- {log.metadata.finalAnswer && ( -
- )} -
-
-
- - {log.fileName} - - - {log.metadata.totalIterations} iter - -
-

- {log.metadata.contextQuestion} -

-
-
- - - ))} -
- -
- )} + +
+
- {/* Footer */} -
-
-

- RLM Visualizer • Recursive Language Models -

-

- Prompt → [LM ↔ REPL] → Answer + +

+
+ ); +} + +function BackgroundEffects() { + return ( + <> +
+
+
+ + ); +} + +function DashboardHeader() { + return ( +
+
+
+
+

+ RLM + Visualizer +

+

+ Debug recursive language model execution traces

- +
+ +
+ + + READY + +
+
+
+
+ ); +} + +interface UploadSectionProps { + onFileLoaded: (fileName: string, content: string) => void; +} + +function UploadSection({ onFileLoaded }: UploadSectionProps) { + return ( +
+

+ 01 + Upload Log File +

+ +
+ ); +} + +function AsciiSection() { + return ( +
+

+ ◈ + RLM Architecture +

+
+ +
+
+ ); +} + +interface RecentTracesSectionProps { + demoLogs: DemoLogInfo[]; + loading: boolean; + onSelectDemo: (fileName: string) => void; +} + +function RecentTracesSection({ + demoLogs, + loading, + onSelectDemo, +}: RecentTracesSectionProps) { + return ( +
+

+ 02 + Recent Traces + (latest 10) +

+ + {loading ? ( + + +
+ Loading traces... +
+
+
+ ) : demoLogs.length === 0 ? ( + + + No log files found in /public/logs/ + + + ) : ( + +
+ {demoLogs.map((demo) => ( + onSelectDemo(demo.fileName)} + className={cn( + 'cursor-pointer transition-all hover:scale-[1.01]', + 'hover:border-primary/50 hover:bg-primary/5', + )} + > + +
+
+
+ {demo.hasFinalAnswer && ( +
+ )} +
+ +
+
+ + {demo.fileName} + + + {demo.iterations} iter + +
+ {demo.contextPreview && ( +

+ {demo.contextPreview.length > 80 + ? `${demo.contextPreview.slice(0, 80)}...` + : demo.contextPreview} +

+ )} +
+
+ + + ))} +
+ + )} +
+ ); +} + +interface LoadedFilesSectionProps { + logFiles: RLMLogFile[]; + onSelectLog: (log: RLMLogFile) => void; +} + +function LoadedFilesSection({ logFiles, onSelectLog }: LoadedFilesSectionProps) { + if (logFiles.length === 0) { + return null; + } + + return ( +
+

+ 03 + Loaded Files +

+ +
+ {logFiles.map((log) => ( + onSelectLog(log)} + > + +
+
+
+ {log.metadata.finalAnswer && ( +
+ )} +
+
+
+ + {log.fileName} + + + {log.metadata.totalIterations} iter + +
+

+ {log.metadata.contextQuestion} +

+
+
+ + + ))} +
+
); } + +function DashboardFooter() { + return ( +
+
+

+ RLM Visualizer • Recursive Language Models +

+

+ Prompt → [LM ↔ REPL] → Answer +

+
+
+ ); +} From c7557b04349498b751ee6c3ad4e6b8bfb0184100 Mon Sep 17 00:00:00 2001 From: James Sukanto Date: Tue, 27 Jan 2026 21:00:43 +0800 Subject: [PATCH 3/8] refactor: split LogViewer into smaller components --- visualizer/src/components/LogViewer.tsx | 317 ++++++++++++++---------- 1 file changed, 187 insertions(+), 130 deletions(-) diff --git a/visualizer/src/components/LogViewer.tsx b/visualizer/src/components/LogViewer.tsx index cdfd751a9..98d6f6b6e 100644 --- a/visualizer/src/components/LogViewer.tsx +++ b/visualizer/src/components/LogViewer.tsx @@ -47,150 +47,207 @@ export function LogViewer({ logFile, onBack }: LogViewerProps) { return (
- {/* Top Bar - Compact header */} -
-
-
-
- -
-
-

- ◈ - {logFile.fileName} -

-

- {config.root_model ?? 'Unknown model'} • {config.backend ?? 'Unknown backend'} • {config.environment_type ?? 'Unknown env'} -

-
-
-
- {metadata.hasErrors && ( - Has Errors - )} - {metadata.finalAnswer && ( - - Completed - - )} - -
-
-
-
- - {/* Question & Answer + Stats Row */} -
-
- {/* Question & Answer Summary */} - - -
-
-

- Context / Question -

-

- {metadata.contextQuestion} -

-
-
-

- Final Answer -

-

- {metadata.finalAnswer || 'Not yet completed'} -

-
-
-
-
- - {/* Quick Stats */} -
- - - - -
-
-
+ + + - {/* Iteration Timeline - Full width scrollable row */} - {/* Main Content - Resizable Split View */} -
- - {/* Left Panel - Prompt & Response */} - -
- -
-
+ + + +
+ ); +} - +interface LogViewerHeaderProps { + fileName: string; + config: RLMLogFile['config']; + metadata: RLMLogFile['metadata']; + onBack: () => void; +} - {/* Right Panel - Code Execution & Sub-LM Calls */} - -
- +function LogViewerHeader({ + fileName, + config, + metadata, + onBack, +}: LogViewerHeaderProps) { + return ( +
+
+
+
+ +
+
+

+ ◈ + {fileName} +

+

+ {config.root_model ?? 'Unknown model'} •{' '} + {config.backend ?? 'Unknown backend'} •{' '} + {config.environment_type ?? 'Unknown env'} +

- - +
+
+ {metadata.hasErrors && ( + + Has Errors + + )} + {metadata.finalAnswer && ( + + Completed + + )} + +
+
+
+ ); +} + +interface LogViewerSummaryProps { + metadata: RLMLogFile['metadata']; +} - {/* Keyboard hint footer */} -
-
- - ← - → - Navigate - - - Esc - Back - +function LogViewerSummary({ metadata }: LogViewerSummaryProps) { + return ( +
+
+ + +
+
+

+ Context / Question +

+

+ {metadata.contextQuestion} +

+
+
+

+ Final Answer +

+

+ {metadata.finalAnswer || 'Not yet completed'} +

+
+
+
+
+ +
+ + + +
); } + +interface LogViewerMainContentProps { + iterations: RLMLogFile['iterations']; + selectedIteration: number; + onSelectIteration: (index: number) => void; +} + +function LogViewerMainContent({ + iterations, + selectedIteration, + onSelectIteration, +}: LogViewerMainContentProps) { + return ( +
+ + +
+ +
+
+ + + + +
+ +
+
+
+
+ ); +} + +function LogViewerFooter() { + return ( +
+
+ + ← + → + Navigate + + + Esc + Back + +
+
+ ); +} From a5c690136e42babf2f97cbdde65e3471b33bd07c Mon Sep 17 00:00:00 2001 From: James Sukanto Date: Wed, 28 Jan 2026 14:33:41 +0800 Subject: [PATCH 4/8] fix: improve layout and scrolling in LogViewer - Fix CodeBlock horizontal scrolling by constraining CollapsibleContent expansion with width: 0; minWidth: 100% wrapper - Fix LogViewerMainContent cutoff by replacing h-screen with flex-1 to properly account for header/footer height - Ensure ResizablePanelGroup fills available vertical space --- visualizer/src/components/CodeBlock.tsx | 12 ++- .../src/components/CodeWithLineNumbers.tsx | 4 +- visualizer/src/components/ExecutionPanel.tsx | 4 +- visualizer/src/components/LogViewer.tsx | 86 ++++++++++--------- visualizer/src/components/ui/collapsible.tsx | 2 +- 5 files changed, 59 insertions(+), 49 deletions(-) diff --git a/visualizer/src/components/CodeBlock.tsx b/visualizer/src/components/CodeBlock.tsx index 376a82fd0..0d4334195 100644 --- a/visualizer/src/components/CodeBlock.tsx +++ b/visualizer/src/components/CodeBlock.tsx @@ -44,7 +44,7 @@ export function CodeBlock({ block, index }: CodeBlockProps) {
{executionTime && ( - {executionTime}s + {executionTime} )} {hasError && ( @@ -66,7 +66,8 @@ export function CodeBlock({ block, index }: CodeBlockProps) { - +
+ {/* Code */}
@@ -74,7 +75,7 @@ export function CodeBlock({ block, index }: CodeBlockProps) { Python
-
+
@@ -97,7 +98,7 @@ export function CodeBlock({ block, index }: CodeBlockProps) { {/* Errors */} {hasError && ( -
+
stderr @@ -178,8 +179,11 @@ export function CodeBlock({ block, index }: CodeBlockProps) {
)} +
+ + ); } diff --git a/visualizer/src/components/CodeWithLineNumbers.tsx b/visualizer/src/components/CodeWithLineNumbers.tsx index 6133475b6..8af98f040 100644 --- a/visualizer/src/components/CodeWithLineNumbers.tsx +++ b/visualizer/src/components/CodeWithLineNumbers.tsx @@ -19,7 +19,7 @@ export function CodeWithLineNumbers({ return (
{/* Line numbers */} -
+
{lines.map((_, idx) => (
{/* Code */} -
+
diff --git a/visualizer/src/components/ExecutionPanel.tsx b/visualizer/src/components/ExecutionPanel.tsx index e7457dfdf..7e3908737 100644 --- a/visualizer/src/components/ExecutionPanel.tsx +++ b/visualizer/src/components/ExecutionPanel.tsx @@ -33,7 +33,7 @@ export function ExecutionPanel({ iteration }: ExecutionPanelProps) { ); return ( -
+
{/* Header */}
@@ -84,7 +84,7 @@ export function ExecutionPanel({ iteration }: ExecutionPanelProps) {
-
+
{iteration.code_blocks.length > 0 ? ( iteration.code_blocks.map((block, idx) => ( diff --git a/visualizer/src/components/LogViewer.tsx b/visualizer/src/components/LogViewer.tsx index 98d6f6b6e..245b58c69 100644 --- a/visualizer/src/components/LogViewer.tsx +++ b/visualizer/src/components/LogViewer.tsx @@ -53,21 +53,28 @@ export function LogViewer({ logFile, onBack }: LogViewerProps) { metadata={metadata} onBack={onBack} /> - +
+ +
+ +
- - - +
+ + +
+
+
); @@ -137,16 +144,16 @@ interface LogViewerSummaryProps { function LogViewerSummary({ metadata }: LogViewerSummaryProps) { return ( -
-
- +
+
+ -
+

Context / Question

-

+

{metadata.contextQuestion}

@@ -154,7 +161,7 @@ function LogViewerSummary({ metadata }: LogViewerSummaryProps) {

Final Answer

-

+

{metadata.finalAnswer || 'Not yet completed'}

@@ -162,7 +169,7 @@ function LogViewerSummary({ metadata }: LogViewerSummaryProps) { -
+
- - -
- -
-
+ + + +
+ +
+
- + - -
- -
-
+ + +
); diff --git a/visualizer/src/components/ui/collapsible.tsx b/visualizer/src/components/ui/collapsible.tsx index ae9fad04a..6f6ed3ad4 100644 --- a/visualizer/src/components/ui/collapsible.tsx +++ b/visualizer/src/components/ui/collapsible.tsx @@ -5,7 +5,7 @@ import * as CollapsiblePrimitive from "@radix-ui/react-collapsible" function Collapsible({ ...props }: React.ComponentProps) { - return + return } function CollapsibleTrigger({ From d01699c14bc75266316a7eae7acdab30f787eeeb Mon Sep 17 00:00:00 2001 From: James Sukanto Date: Wed, 28 Jan 2026 15:12:52 +0800 Subject: [PATCH 5/8] refactor: extract StatsRow component from LogViewer - Create reusable StatsRow component for label-value pairs - Replace duplicated stat rows in LogViewer with StatsRow - Follow StatsCard naming convention for consistency --- visualizer/src/components/LogViewer.tsx | 85 +++++++++++-------------- visualizer/src/components/StatsRow.tsx | 22 +++++++ 2 files changed, 58 insertions(+), 49 deletions(-) create mode 100644 visualizer/src/components/StatsRow.tsx diff --git a/visualizer/src/components/LogViewer.tsx b/visualizer/src/components/LogViewer.tsx index 245b58c69..e022ea553 100644 --- a/visualizer/src/components/LogViewer.tsx +++ b/visualizer/src/components/LogViewer.tsx @@ -5,7 +5,7 @@ import { Card, CardContent } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '@/components/ui/resizable'; -import { StatsCard } from './StatsCard'; +import { StatsRow } from './StatsRow'; import { TrajectoryPanel } from './TrajectoryPanel'; import { ExecutionPanel } from './ExecutionPanel'; import { IterationTimeline } from './IterationTimeline'; @@ -56,7 +56,7 @@ export function LogViewer({ logFile, onBack }: LogViewerProps) {
-
+
@@ -146,6 +146,18 @@ function LogViewerSummary({ metadata }: LogViewerSummaryProps) { return (
+ + + +
+ + + + +
+
+
+
@@ -153,15 +165,15 @@ function LogViewerSummary({ metadata }: LogViewerSummaryProps) {

Context / Question

-

+

{metadata.contextQuestion}

-

+

Final Answer

-

+

{metadata.finalAnswer || 'Not yet completed'}

@@ -169,32 +181,6 @@ function LogViewerSummary({ metadata }: LogViewerSummaryProps) {
-
- - - - -
); @@ -215,26 +201,27 @@ function LogViewerMainContent({
- -
- -
-
- - + +
+ +
+
- - - + + + + +
); diff --git a/visualizer/src/components/StatsRow.tsx b/visualizer/src/components/StatsRow.tsx new file mode 100644 index 000000000..ea3dbb544 --- /dev/null +++ b/visualizer/src/components/StatsRow.tsx @@ -0,0 +1,22 @@ +'use client'; + +import * as React from 'react'; + +interface StatsRowProps { + label: string; + value: string | number; + valueClassName?: string; +} + +export function StatsRow({ label, value, valueClassName }: StatsRowProps) { + return ( +
+

+ {label} +

+

+ {value} +

+
+ ); +} From 70f7ba494e62546840d79af55d5dd19580a23e57 Mon Sep 17 00:00:00 2001 From: James Sukanto Date: Wed, 28 Jan 2026 15:30:03 +0800 Subject: [PATCH 6/8] Improve keyboard navigation hints: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Hide LogViewerFooter. Currently, its only job is to display navigation hints. - Use kbd elements for better visual styling - Change 'to navigate' → 'prev/next' and 'back' → 'back to files' --- visualizer/src/components/Dashboard.tsx | 2 +- visualizer/src/components/IterationTimeline.tsx | 17 +++++++++++++---- visualizer/src/components/LogViewer.tsx | 2 +- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/visualizer/src/components/Dashboard.tsx b/visualizer/src/components/Dashboard.tsx index f58de32ff..193b5be5b 100644 --- a/visualizer/src/components/Dashboard.tsx +++ b/visualizer/src/components/Dashboard.tsx @@ -196,7 +196,7 @@ function RecentTracesSection({ ) : ( -
+
ssss {demoLogs.map((demo) => (
- Recursive Language Model Trajectory + Recursive Iterations ({iterations.length} total)
- - ← scroll → - +
+ + ← + → + prev / next iteration + + • + + Esc + back to files + +
diff --git a/visualizer/src/components/LogViewer.tsx b/visualizer/src/components/LogViewer.tsx index e022ea553..40bd57c55 100644 --- a/visualizer/src/components/LogViewer.tsx +++ b/visualizer/src/components/LogViewer.tsx @@ -75,7 +75,7 @@ export function LogViewer({ logFile, onBack }: LogViewerProps) {
- + {/* */}
); } From dc70f2a6205d4ff193d285c7bf2369e9e73cd863 Mon Sep 17 00:00:00 2001 From: James Sukanto Date: Wed, 28 Jan 2026 15:57:53 +0800 Subject: [PATCH 7/8] feat: add SelectedIterationInfo component for iteration stats --- visualizer/src/components/LogViewer.tsx | 90 ++++++++++++++++++++++++- 1 file changed, 89 insertions(+), 1 deletion(-) diff --git a/visualizer/src/components/LogViewer.tsx b/visualizer/src/components/LogViewer.tsx index 40bd57c55..a810c773f 100644 --- a/visualizer/src/components/LogViewer.tsx +++ b/visualizer/src/components/LogViewer.tsx @@ -10,7 +10,7 @@ import { TrajectoryPanel } from './TrajectoryPanel'; import { ExecutionPanel } from './ExecutionPanel'; import { IterationTimeline } from './IterationTimeline'; import { ThemeToggle } from './ThemeToggle'; -import { RLMLogFile } from '@/lib/types'; +import { RLMLogFile, RLMIteration } from '@/lib/types'; interface LogViewerProps { logFile: RLMLogFile; @@ -66,6 +66,12 @@ export function LogViewer({ logFile, onBack }: LogViewerProps) { selectedIteration={selectedIteration} onSelectIteration={setSelectedIteration} /> + + + m.content).join(''); + const estimatedInputTokens = Math.round(promptText.length / 4); + const estimatedOutputTokens = Math.round(iteration.response.length / 4); + + const codeBlocks = iteration.code_blocks.length; + const inputTokensK = (estimatedInputTokens / 1000).toFixed(1); + const outputTokensK = (estimatedOutputTokens / 1000).toFixed(1); + + return ( +
+ {/* Section header and stats */} +
+
+ + + +
+ Iteration {iterationNumber} +
+
+ {codeBlocks > 0 && ( +
+ Code Blocks + + {codeBlocks} + +
+ )} + + {totalSubCalls > 0 && ( +
+ Sub-LM Calls + + {totalSubCalls} + +
+ )} + +
+ Time + {execTime.toFixed(2)}s +
+ +
+ Estimated Tokens + + {inputTokensK}k + → + {outputTokensK}k + +
+
+
+
+ ); +} + function LogViewerFooter() { return (
From 281420724d072f88ab181c41cfec9a7d3471420d Mon Sep 17 00:00:00 2001 From: James Sukanto Date: Wed, 28 Jan 2026 17:01:57 +0800 Subject: [PATCH 8/8] refactor: streamline RLM visualizer UX --- visualizer/src/components/ExecutionPanel.tsx | 38 +-------- .../src/components/IterationTimeline.tsx | 40 ++++----- visualizer/src/components/LogViewer.tsx | 82 +++++++------------ visualizer/src/components/StatsRow.tsx | 2 +- visualizer/src/components/TrajectoryPanel.tsx | 12 +-- 5 files changed, 51 insertions(+), 123 deletions(-) diff --git a/visualizer/src/components/ExecutionPanel.tsx b/visualizer/src/components/ExecutionPanel.tsx index 7e3908737..5ed16fb8a 100644 --- a/visualizer/src/components/ExecutionPanel.tsx +++ b/visualizer/src/components/ExecutionPanel.tsx @@ -34,46 +34,12 @@ export function ExecutionPanel({ iteration }: ExecutionPanelProps) { return (
- {/* Header */} -
-
-
-
- ⟨⟩ -
-
-

Code & Sub-LM Calls

-

- Iteration {iteration.iteration} • {new Date(iteration.timestamp).toLocaleString()} -

-
-
-
- - {/* Quick stats */} -
- - {iteration.code_blocks.length} code block{iteration.code_blocks.length !== 1 ? 's' : ''} - - {totalSubCalls > 0 && ( - - {totalSubCalls} sub-LM call{totalSubCalls !== 1 ? 's' : ''} - - )} - {iteration.final_answer && ( - - Has Final Answer - - )} -
-
- {/* Tabs - Code Execution and Sub-LM Calls only */} -
+
- Code Execution + Code Blocks ({iteration.code_blocks.length}) Sub-LM Calls ({totalSubCalls}) diff --git a/visualizer/src/components/IterationTimeline.tsx b/visualizer/src/components/IterationTimeline.tsx index 3d499944c..0c5246533 100644 --- a/visualizer/src/components/IterationTimeline.tsx +++ b/visualizer/src/components/IterationTimeline.tsx @@ -68,14 +68,8 @@ export function IterationTimeline({
{/* Section header */}
-
- - - -
- Recursive Iterations - ({iterations.length} total) + All Iterations ({iterations.length} total)
@@ -156,8 +150,14 @@ export function IterationTimeline({ {stats.subCalls} sub )} - + {stats.execTime.toFixed(2)}s + • + + {(stats.inputTokens / 1000).toFixed(1)}k + → + {(stats.outputTokens / 1000).toFixed(1)}k +
@@ -166,22 +166,14 @@ export function IterationTimeline({ {responseSnippet}{iteration.response.length > 60 ? '...' : ''}

- {/* Bottom row: tokens */} -
- - {(stats.inputTokens / 1000).toFixed(1)}k - → - {(stats.outputTokens / 1000).toFixed(1)}k - - {stats.hasFinal && finalAnswer && ( - <> - │ - - = {finalAnswer} - - - )} -
+ {/* Bottom row: final answer */} + {stats.hasFinal && finalAnswer && ( +
+ + = {finalAnswer} + +
+ )}
diff --git a/visualizer/src/components/LogViewer.tsx b/visualizer/src/components/LogViewer.tsx index a810c773f..fe5d2a533 100644 --- a/visualizer/src/components/LogViewer.tsx +++ b/visualizer/src/components/LogViewer.tsx @@ -56,7 +56,7 @@ export function LogViewer({ logFile, onBack }: LogViewerProps) {
-
+
@@ -151,41 +151,28 @@ interface LogViewerSummaryProps { function LogViewerSummary({ metadata }: LogViewerSummaryProps) { return (
-
- - -
- - - - -
-
-
+
+

+ Overview +

+ +
+ + + + +
- - -
-
-

- Context / Question -

-

- {metadata.contextQuestion} -

-
-
-

- Final Answer -

-

- {metadata.finalAnswer || 'Not yet completed'} -

-
-
-
-
+
+ +

{metadata.contextQuestion}

+
+ +
+ +

{metadata.finalAnswer || 'Not yet completed'}

+
@@ -278,37 +265,24 @@ function SelectedIterationInfo({ iteration, iterationNumber }: SelectedIteration Iteration {iterationNumber}
- {codeBlocks > 0 && ( -
- Code Blocks - - {codeBlocks} - -
- )} - - {totalSubCalls > 0 && ( -
- Sub-LM Calls - - {totalSubCalls} - -
- )} -
- Time {execTime.toFixed(2)}s
- Estimated Tokens + Est. Tokens {inputTokensK}k → {outputTokensK}k
+ +
+ + {new Date(iteration.timestamp).toLocaleString()} + +
diff --git a/visualizer/src/components/StatsRow.tsx b/visualizer/src/components/StatsRow.tsx index ea3dbb544..ee71b6bc1 100644 --- a/visualizer/src/components/StatsRow.tsx +++ b/visualizer/src/components/StatsRow.tsx @@ -11,7 +11,7 @@ interface StatsRowProps { export function StatsRow({ label, value, valueClassName }: StatsRowProps) { return (
-

+

{label}

diff --git a/visualizer/src/components/TrajectoryPanel.tsx b/visualizer/src/components/TrajectoryPanel.tsx index 86a061f11..2d08b8d15 100644 --- a/visualizer/src/components/TrajectoryPanel.tsx +++ b/visualizer/src/components/TrajectoryPanel.tsx @@ -77,22 +77,18 @@ export function TrajectoryPanel({ {/* Header */}

-
- ◈ -
-

Conversation

-

- Iteration {selectedIteration + 1} of {iterations.length} +

+ Conversation

- {currentIteration?.code_blocks.length > 0 && ( + {/* {currentIteration?.code_blocks.length > 0 && ( {currentIteration.code_blocks.length} code - )} + )} */} {currentIteration?.final_answer && ( ✓ Answer