1- import { useEffect , useRef , useState } from "react" ;
1+ import { useCallback , useEffect , useRef , useState } from "react" ;
22import { useVirtualizer } from "@tanstack/react-virtual" ;
33import { useI18n , LOCALES , type TFn } from "../i18n/shared" ;
44import { formatTokens } from "../format-tokens" ;
55import { statusCodeInfo } from "../status-codes" ;
66import { IconX } from "../icons" ;
77import { modelLabel } from "../model-display" ;
8- import { EmptyState } from "../ui" ;
8+ import { EmptyState , Notice } from "../ui" ;
99import Debug from "./Debug" ;
1010
1111type LogsTab = "logs" | "debug" ;
@@ -251,6 +251,8 @@ function modelTitle(log: LogEntry): string {
251251export default function Logs ( { apiBase } : { apiBase : string } ) {
252252 const { t, locale } = useI18n ( ) ;
253253 const [ logs , setLogs ] = useState < LogEntry [ ] > ( [ ] ) ;
254+ const [ loading , setLoading ] = useState ( true ) ;
255+ const [ error , setError ] = useState < string | null > ( null ) ;
254256 const [ autoRefresh , setAutoRefresh ] = useState ( true ) ;
255257 const [ detail , setDetail ] = useState < LogEntry | null > ( null ) ;
256258 const [ surfaceFilter , setSurfaceFilter ] = useState < "all" | "claude" | "codex" > ( "all" ) ;
@@ -275,19 +277,28 @@ export default function Logs({ apiBase }: { apiBase: string }) {
275277 else if ( e . key === "ArrowRight" || e . key === "End" ) { e . preventDefault ( ) ; selectTab ( "debug" ) ; document . getElementById ( "logs-tab-debug" ) ?. focus ( ) ; }
276278 } ;
277279
280+ const fetchLogs = useCallback ( async ( ) => {
281+ setLoading ( true ) ;
282+ setError ( null ) ;
283+ try {
284+ const res = await fetch ( `${ apiBase } /api/logs` ) ;
285+ if ( ! res . ok ) throw new Error ( `${ res . status } ${ res . statusText } ` . trim ( ) ) ;
286+ setLogs ( await res . json ( ) ) ;
287+ } catch ( cause ) {
288+ const detail = cause instanceof Error ? cause . message : "" ;
289+ setError ( detail ? `${ t ( "logs.loadError" ) } ${ detail } ` : t ( "logs.loadError" ) ) ;
290+ } finally {
291+ setLoading ( false ) ;
292+ }
293+ } , [ apiBase , t ] ) ;
294+
278295 useEffect ( ( ) => {
279296 if ( tab !== "logs" ) return ;
280- const fetchLogs = async ( ) => {
281- try {
282- const res = await fetch ( `${ apiBase } /api/logs` ) ;
283- setLogs ( await res . json ( ) ) ;
284- } catch { /* ignore */ }
285- } ;
286- fetchLogs ( ) ;
297+ void fetchLogs ( ) ;
287298 if ( ! autoRefresh ) return ;
288- const interval = setInterval ( fetchLogs , 2000 ) ;
299+ const interval = setInterval ( ( ) => void fetchLogs ( ) , 2000 ) ;
289300 return ( ) => clearInterval ( interval ) ;
290- } , [ apiBase , autoRefresh , tab ] ) ;
301+ } , [ autoRefresh , fetchLogs , tab ] ) ;
291302
292303 const detailInfo = detail ? statusCodeInfo ( detail . status , locale ) : null ;
293304 const filteredLogs = logs . filter ( log => (
@@ -378,9 +389,20 @@ export default function Logs({ apiBase }: { apiBase: string }) {
378389 </ div >
379390 </ div >
380391
381- { filteredLogs . length === 0 ? (
392+ { error ? (
393+ < Notice tone = "err" >
394+ { error } { " " }
395+ < button type = "button" className = "btn btn-ghost btn-sm" onClick = { ( ) => void fetchLogs ( ) } disabled = { loading } >
396+ { t ( "common.retry" ) }
397+ </ button >
398+ </ Notice >
399+ ) : loading && logs . length === 0 ? (
400+ < EmptyState title = { t ( "common.loading" ) } />
401+ ) : filteredLogs . length === 0 ? (
382402 < EmptyState title = { t ( "logs.noRequests" ) } />
383403 ) : (
404+ < >
405+ { loading && < div className = "row muted" role = "status" > < span className = "spin" /> { t ( "common.loading" ) } </ div > }
384406 < div ref = { scrollContainerRef } className = "tbl-wrap" style = { { overflowY : "auto" , maxHeight : "calc(100vh - 260px)" } } >
385407 < table className = "tbl logs-table" >
386408 < thead style = { { position : "sticky" , top : 0 , zIndex : 1 , background : "var(--surface)" } } >
@@ -481,6 +503,7 @@ export default function Logs({ apiBase }: { apiBase: string }) {
481503 </ tbody >
482504 </ table >
483505 </ div >
506+ </ >
484507 ) }
485508
486509 { detail && (
0 commit comments