11"use client" ;
2- import { useState , useEffect , useRef } from 'react' ;
2+ import { useState , useEffect , useRef , useMemo } from 'react' ;
33import useWebSocket , { ReadyState } from 'react-use-websocket' ;
44import { useAuth } from '@/hooks/use-auth' ;
55import { Problem , Submission , Container } from '@/lib/types' ;
@@ -8,25 +8,41 @@ import useSWR from 'swr';
88import api from '@/lib/api' ;
99import { Skeleton } from '../ui/skeleton' ;
1010
11+ interface LogMessage {
12+ stream : 'stdout' | 'stderr' | 'info' | 'error' ;
13+ data : string ;
14+ }
15+
1116// --- Sub-component for displaying static logs of finished containers ---
1217const StaticLogViewer = ( { submissionId, containerId } : { submissionId : string , containerId : string } ) => {
13- // A simple text fetcher for SWR, as the log API returns plain text.
1418 const textFetcher = ( url : string ) => api . get ( url , { responseType : 'text' } ) . then ( res => res . data ) ;
15-
1619 const { data : logText , error, isLoading } = useSWR ( `/submissions/${ submissionId } /containers/${ containerId } /log` , textFetcher ) ;
17-
1820 const logContainerRef = useRef < HTMLDivElement > ( null ) ;
1921
20- // Scroll to bottom on initial load
22+ const messages : LogMessage [ ] = useMemo ( ( ) => {
23+ if ( ! logText ) return [ ] ;
24+ return logText
25+ . split ( '\n' )
26+ . filter ( ( line : string ) => line . trim ( ) !== '' )
27+ . map ( ( line : string ) => {
28+ try {
29+ return JSON . parse ( line ) as LogMessage ;
30+ } catch ( e ) {
31+ console . error ( "Failed to parse log line as JSON:" , line ) ;
32+ return { stream : 'stdout' , data : line } ;
33+ }
34+ } ) ;
35+ } , [ logText ] ) ;
36+
2137 useEffect ( ( ) => {
2238 if ( logContainerRef . current ) {
2339 logContainerRef . current . scrollTop = logContainerRef . current . scrollHeight ;
2440 }
25- } , [ logText ] ) ;
41+ } , [ messages ] ) ;
2642
2743 return (
2844 < div className = "relative" >
29- < div className = "absolute top-2 right-2 text-xs font-semibold flex items-center gap-2 z-10" >
45+ < div className = "absolute top-2 right-6 text-xs font-semibold flex items-center gap-2 z-10" >
3046 < span className = "h-2 w-2 rounded-full bg-gray-400" > </ span >
3147 Finished
3248 </ div >
@@ -36,18 +52,24 @@ const StaticLogViewer = ({ submissionId, containerId }: { submissionId: string,
3652 >
3753 { isLoading && < Skeleton className = "h-full w-full" /> }
3854 { error && < p className = "text-red-400" > Failed to load log.</ p > }
39- { logText && < pre className = "whitespace-pre-wrap break-all" > { logText } </ pre > }
55+ { messages . length > 0 && messages . map ( ( msg , index ) => (
56+ < span key = { index } className = "whitespace-pre-wrap break-all" >
57+ { msg . stream === 'stderr' || msg . stream === 'error' ? (
58+ < span className = "text-red-400" > { msg . data } </ span >
59+ ) : msg . stream === 'info' ? (
60+ < span className = "text-blue-400" > { msg . data } </ span >
61+ ) : (
62+ < span className = "text-foreground" > { msg . data } </ span >
63+ ) }
64+ </ span >
65+ ) ) }
4066 </ div >
4167 </ div >
4268 ) ;
4369} ;
4470
45- // --- Sub-component for streaming real-time logs via WebSocket ---
46- interface LogMessage {
47- stream : 'stdout' | 'stderr' | 'info' | 'error' ;
48- data : string ;
49- }
5071
72+ // --- Sub-component for streaming real-time logs via WebSocket ---
5173const RealtimeLogViewer = ( { wsUrl, onStatusUpdate } : { wsUrl : string | null , onStatusUpdate : ( ) => void } ) => {
5274 const [ messages , setMessages ] = useState < LogMessage [ ] > ( [ ] ) ;
5375 const logContainerRef = useRef < HTMLDivElement > ( null ) ;
@@ -92,7 +114,7 @@ const RealtimeLogViewer = ({ wsUrl, onStatusUpdate }: { wsUrl: string | null, on
92114
93115 return (
94116 < div className = "relative" >
95- < div className = "absolute top-2 right-2 text-xs font-semibold flex items-center gap-2 z-10" >
117+ < div className = "absolute top-2 right-6 text-xs font-semibold flex items-center gap-2 z-10" >
96118 < span className = { `h-2 w-2 rounded-full ${ connectionStatus . color } ` } > </ span >
97119 { connectionStatus . text }
98120 </ div >
@@ -102,15 +124,15 @@ const RealtimeLogViewer = ({ wsUrl, onStatusUpdate }: { wsUrl: string | null, on
102124 >
103125 { messages . length === 0 && < p className = "text-muted-foreground" > Waiting for judge output...</ p > }
104126 { messages . map ( ( msg , index ) => (
105- < div key = { index } className = "whitespace-pre-wrap break-all" >
127+ < span key = { index } className = "whitespace-pre-wrap break-all" >
106128 { msg . stream === 'stderr' || msg . stream === 'error' ? (
107129 < span className = "text-red-400" > { msg . data } </ span >
108130 ) : msg . stream === 'info' ? (
109131 < span className = "text-blue-400" > { msg . data } </ span >
110132 ) : (
111133 < span className = "text-foreground" > { msg . data } </ span >
112134 ) }
113- </ div >
135+ </ span >
114136 ) ) }
115137 </ div >
116138 </ div >
0 commit comments