@@ -51,6 +51,7 @@ export const Build: React.FC<Props> = React.memo((props) => {
51
51
) ;
52
52
const [ error_tab , set_error_tab ] = useState < string | null > ( null ) ;
53
53
const logContainerRef = useRef < HTMLDivElement > ( null ) ;
54
+ const stderrContainerRef = useRef < HTMLDivElement > ( null ) ;
54
55
const [ shownLog , setShownLog ] = useState < string > ( "" ) ;
55
56
56
57
// Compute whether we have running jobs - this determines UI precedence
@@ -68,6 +69,9 @@ export const Build: React.FC<Props> = React.memo((props) => {
68
69
if ( logContainerRef . current ) {
69
70
logContainerRef . current . scrollTop = logContainerRef . current . scrollHeight ;
70
71
}
72
+ if ( stderrContainerRef . current ) {
73
+ stderrContainerRef . current . scrollTop = stderrContainerRef . current . scrollHeight ;
74
+ }
71
75
} , [ shownLog ] ) ;
72
76
73
77
let no_errors = true ;
@@ -86,12 +90,19 @@ export const Build: React.FC<Props> = React.memo((props) => {
86
90
87
91
function render_tab_item (
88
92
title : string ,
89
- value : string ,
93
+ stdout : string ,
94
+ stderr : string ,
90
95
error ?: boolean ,
91
96
job_info_str ?: string ,
92
97
) : AntdTabItem {
93
98
const err_style = error ? { background : COLORS . ANTD_BG_RED_L } : undefined ;
94
99
const tab_button = < div style = { err_style } > { title } </ div > ;
100
+
101
+ // Determine if stderr is informational (not actual errors)
102
+ const hasStdout = stdout . trim ( ) . length > 0 ;
103
+ const hasStderr = stderr . trim ( ) . length > 0 ;
104
+ const stderrIsInformational = hasStderr && ! error ;
105
+
95
106
return Tab ( {
96
107
key : title ,
97
108
eventKey : title ,
@@ -110,7 +121,75 @@ export const Build: React.FC<Props> = React.memo((props) => {
110
121
{ job_info_str }
111
122
</ div >
112
123
) : undefined }
113
- < Ansi > { value } </ Ansi >
124
+ < div
125
+ style = { { display : "flex" , flexDirection : "column" , height : "100%" } }
126
+ >
127
+ { hasStdout && (
128
+ < div
129
+ style = { {
130
+ flex : 1 ,
131
+ display : "flex" ,
132
+ flexDirection : "column" ,
133
+ marginBottom : hasStderr ? "10px" : "0" ,
134
+ } }
135
+ >
136
+ < div
137
+ style = { {
138
+ fontWeight : "bold" ,
139
+ color : COLORS . GRAY_M ,
140
+ borderBottom : `1px solid ${ COLORS . GRAY_LL } ` ,
141
+ paddingBottom : "5px" ,
142
+ marginBottom : "5px" ,
143
+ fontSize : `${ font_size * 0.9 } px` ,
144
+ } }
145
+ >
146
+ Standard output
147
+ </ div >
148
+ < div
149
+ style = { {
150
+ flex : 1 ,
151
+ overflowY : "auto" ,
152
+ background : COLORS . GRAY_LLL ,
153
+ padding : "5px" ,
154
+ borderRadius : "3px" ,
155
+ } }
156
+ >
157
+ < Ansi > { stdout } </ Ansi >
158
+ </ div >
159
+ </ div >
160
+ ) }
161
+ { hasStderr && (
162
+ < div
163
+ style = { { flex : 1 , display : "flex" , flexDirection : "column" } }
164
+ >
165
+ < div
166
+ style = { {
167
+ fontWeight : "bold" ,
168
+ color : error ? COLORS . ANTD_RED : COLORS . GRAY_M ,
169
+ borderBottom : `1px solid ${
170
+ error ? COLORS . ANTD_RED_WARN : COLORS . GRAY_LL
171
+ } `,
172
+ paddingBottom : "5px" ,
173
+ marginBottom : "5px" ,
174
+ fontSize : `${ font_size * 0.9 } px` ,
175
+ } }
176
+ >
177
+ { stderrIsInformational ? "Output messages" : "Error output" }
178
+ </ div >
179
+ < div
180
+ style = { {
181
+ flex : 1 ,
182
+ overflowY : "auto" ,
183
+ background : error ? COLORS . ANTD_BG_RED_L : COLORS . GRAY_LLL ,
184
+ padding : "5px" ,
185
+ borderRadius : "3px" ,
186
+ } }
187
+ >
188
+ < Ansi > { stderr } </ Ansi >
189
+ </ div >
190
+ </ div >
191
+ ) }
192
+ </ div >
114
193
</ >
115
194
) ,
116
195
} ) ;
@@ -122,8 +201,9 @@ export const Build: React.FC<Props> = React.memo((props) => {
122
201
// const y: ExecOutput | undefined = job_infos.get(stage)?.toJS();
123
202
124
203
if ( ! x ) return ;
125
- const value = ( x . stdout ?? "" ) + ( x . stderr ?? "" ) ;
126
- if ( ! value ) return ;
204
+ const stdout = x . stdout ?? "" ;
205
+ const stderr = x . stderr ?? "" ;
206
+ if ( ! stdout && ! stderr ) return ;
127
207
// const time: number | undefined = x.get("time");
128
208
// const time_str = time ? `(${(time / 1000).toFixed(1)} seconds)` : "";
129
209
let job_info_str = "" ;
@@ -147,14 +227,14 @@ export const Build: React.FC<Props> = React.memo((props) => {
147
227
set_error_tab ( title ) ;
148
228
}
149
229
}
150
- return render_tab_item ( title , value , error , job_info_str ) ;
230
+ return render_tab_item ( title , stdout , stderr , error , job_info_str ) ;
151
231
}
152
232
153
233
function render_clean ( ) : AntdTabItem | undefined {
154
234
const value = build_logs ?. getIn ( [ "clean" , "output" ] ) as any ;
155
235
if ( ! value ) return ;
156
236
const title = "Clean Auxiliary Files" ;
157
- return render_tab_item ( title , value ) ;
237
+ return render_tab_item ( title , value , "" , false ) ;
158
238
}
159
239
160
240
function render_logs ( ) : Rendered {
@@ -209,17 +289,22 @@ export const Build: React.FC<Props> = React.memo((props) => {
209
289
if ( ! build_logs ) return ;
210
290
const infos : React . JSX . Element [ ] = [ ] ;
211
291
let isLongRunning = false ;
212
- let logTail = "" ;
292
+ let stdoutTail = "" ;
293
+ let stderrTail = "" ;
213
294
214
295
build_logs . forEach ( ( infoI , key ) => {
215
296
const info : ExecuteCodeOutput = infoI ?. toJS ( ) ;
216
297
if ( ! info || info . type !== "async" || info . status !== "running" ) return ;
217
298
const stats_str = getResourceUsage ( info . stats , "last" ) ;
218
299
const start = info . start ;
219
- logTail = tail ( ( info . stdout ?? "" ) + ( info . stderr ?? "" ) , 100 ) ;
220
- // Update state for auto-scrolling effect
221
- if ( logTail !== shownLog ) {
222
- setShownLog ( logTail ) ;
300
+ stdoutTail = tail ( info . stdout ?? "" , 100 ) ;
301
+ stderrTail = tail ( info . stderr ?? "" , 100 ) ;
302
+ // Update state for auto-scrolling effect - combine for backward compatibility
303
+ const combinedLog =
304
+ stdoutTail +
305
+ ( stderrTail ? "\n--- Error Output ---\n" + stderrTail : "" ) ;
306
+ if ( combinedLog !== shownLog ) {
307
+ setShownLog ( combinedLog ) ;
223
308
}
224
309
isLongRunning ||=
225
310
typeof start === "number" &&
@@ -245,6 +330,9 @@ export const Build: React.FC<Props> = React.memo((props) => {
245
330
246
331
if ( infos . length === 0 ) return ;
247
332
333
+ const hasStdout = stdoutTail . trim ( ) . length > 0 ;
334
+ const hasStderr = stderrTail . trim ( ) . length > 0 ;
335
+
248
336
return (
249
337
< >
250
338
< div
@@ -293,14 +381,85 @@ export const Build: React.FC<Props> = React.memo((props) => {
293
381
{ status }
294
382
{ "\n" }
295
383
</ div >
296
- < div
297
- ref = { logContainerRef }
298
- style = { {
299
- overflowY : "auto" ,
300
- flexGrow : 1 ,
301
- } }
302
- >
303
- < Ansi > { shownLog } </ Ansi >
384
+ < div style = { {
385
+ flex : 1 ,
386
+ display : "flex" ,
387
+ flexDirection : "column" ,
388
+ gap : hasStdout && hasStderr ? "10px" : "0" ,
389
+ overflow : "hidden"
390
+ } } >
391
+ { hasStdout && (
392
+ < div
393
+ style = { {
394
+ flex : 1 ,
395
+ display : "flex" ,
396
+ flexDirection : "column" ,
397
+ overflow : "hidden" ,
398
+ } }
399
+ >
400
+ < div
401
+ style = { {
402
+ fontWeight : "bold" ,
403
+ color : COLORS . GRAY_M ,
404
+ borderBottom : `1px solid ${ COLORS . GRAY_LL } ` ,
405
+ paddingBottom : "5px" ,
406
+ marginBottom : "5px" ,
407
+ fontSize : `${ font_size * 0.9 } px` ,
408
+ flexShrink : 0 ,
409
+ } }
410
+ >
411
+ Standard output (stdout)
412
+ </ div >
413
+ < div
414
+ ref = { logContainerRef }
415
+ style = { {
416
+ flex : 1 ,
417
+ overflowY : "auto" ,
418
+ background : COLORS . GRAY_LLL ,
419
+ padding : "5px" ,
420
+ borderRadius : "3px" ,
421
+ } }
422
+ >
423
+ < Ansi > { stdoutTail } </ Ansi >
424
+ </ div >
425
+ </ div >
426
+ ) }
427
+ { hasStderr && (
428
+ < div
429
+ style = { {
430
+ flex : 1 ,
431
+ display : "flex" ,
432
+ flexDirection : "column" ,
433
+ overflow : "hidden" ,
434
+ } }
435
+ >
436
+ < div
437
+ style = { {
438
+ fontWeight : "bold" ,
439
+ color : COLORS . GRAY_M ,
440
+ borderBottom : `1px solid ${ COLORS . GRAY_LL } ` ,
441
+ paddingBottom : "5px" ,
442
+ marginBottom : "5px" ,
443
+ fontSize : `${ font_size * 0.9 } px` ,
444
+ flexShrink : 0 ,
445
+ } }
446
+ >
447
+ Error output (stderr)
448
+ </ div >
449
+ < div
450
+ ref = { stderrContainerRef }
451
+ style = { {
452
+ flex : 1 ,
453
+ overflowY : "auto" ,
454
+ background : COLORS . GRAY_LLL ,
455
+ padding : "5px" ,
456
+ borderRadius : "3px" ,
457
+ } }
458
+ >
459
+ < Ansi > { stderrTail } </ Ansi >
460
+ </ div >
461
+ </ div >
462
+ ) }
304
463
</ div >
305
464
</ div >
306
465
</ >
0 commit comments