@@ -12,7 +12,7 @@ import chalk from 'chalk';
1212import { effectiveModelAlias } from '@pymodel/pythinker-code-sdk' ;
1313
1414import { ALL_TIPS , type ToolbarTip } from '#/tui/constant/tips' ;
15- import { isRainbowDancing , renderDanceFooterModel } from '#/tui/easter-eggs/dance ' ;
15+ import { isRainbowHatching , renderHatchFooterModel } from '#/tui/easter-eggs/hatch ' ;
1616import { currentTheme } from '#/tui/theme' ;
1717import type { ColorPalette } from '#/tui/theme/colors' ;
1818import type { AppState } from '#/tui/types' ;
@@ -195,6 +195,7 @@ export class FooterComponent implements Component {
195195 private gitCacheWorkDir : string ;
196196 private transientHint : string | null = null ;
197197 private warningHint : string | null = null ;
198+ private streamSpeedTps : number | null = null ;
198199 private goalSnapshotKey : string | null = null ;
199200 private goalObservedAtMs = Date . now ( ) ;
200201 private goalTimer : ReturnType < typeof setInterval > | null = null ;
@@ -245,6 +246,17 @@ export class FooterComponent implements Component {
245246 }
246247 }
247248
249+ /**
250+ * Decode speed of the most recently completed step (tokens/s), shown next
251+ * to the context readout on line 2. `null` hides it (turn end, or a step
252+ * too short to measure — see `computeDecodeTps`).
253+ */
254+ setStreamSpeed ( tps : number | null ) : void {
255+ if ( this . streamSpeedTps === tps ) return ;
256+ this . streamSpeedTps = tps ;
257+ this . onRefresh ( ) ;
258+ }
259+
248260 /**
249261 * Short-lived hint that replaces the rotating toolbar tips on line 1.
250262 * Used by the exit-confirmation double-tap flow to show "Press Ctrl+C
@@ -337,28 +349,34 @@ export class FooterComponent implements Component {
337349 }
338350 }
339351
340- // ── Line 2: hint (bottom-left) + context (right) ──
352+ // ── Line 2: hint (bottom-left) + context + stream speed (right) ──
341353 const contextText = formatContextStatus (
342354 state . contextUsage ,
343355 state . contextTokens ,
344356 state . maxContextTokens ,
345357 ) ;
346- const contextWidth = visibleWidth ( contextText ) ;
358+ const speedSuffix =
359+ this . streamSpeedTps === null ? '' : ` · ${ this . streamSpeedTps . toFixed ( 1 ) } t/s` ;
360+ const rightWidth = visibleWidth ( contextText ) + visibleWidth ( speedSuffix ) ;
347361 let line2 : string ;
348362 const hint = this . transientHint ?? this . warningHint ;
349363 if ( hint ) {
350- const maxHintWidth = Math . max ( 0 , width - contextWidth - 1 ) ;
364+ const maxHintWidth = Math . max ( 0 , width - rightWidth - 1 ) ;
351365 const shownHint =
352366 visibleWidth ( hint ) <= maxHintWidth ? hint : truncateToWidth ( hint , maxHintWidth , '…' ) ;
353367 const hintWidth = visibleWidth ( shownHint ) ;
354- const pad = Math . max ( 0 , width - hintWidth - contextWidth ) ;
368+ const pad = Math . max ( 0 , width - hintWidth - rightWidth ) ;
355369 line2 =
356370 chalk . hex ( colors . warning ) . bold ( shownHint ) +
357371 ' ' . repeat ( pad ) +
358- chalk . hex ( colors . text ) ( contextText ) ;
372+ chalk . hex ( colors . text ) ( contextText ) +
373+ chalk . hex ( colors . textDim ) ( speedSuffix ) ;
359374 } else {
360- const leftPad = Math . max ( 0 , width - contextWidth ) ;
361- line2 = ' ' . repeat ( leftPad ) + chalk . hex ( colors . text ) ( contextText ) ;
375+ const leftPad = Math . max ( 0 , width - rightWidth ) ;
376+ line2 =
377+ ' ' . repeat ( leftPad ) +
378+ chalk . hex ( colors . text ) ( contextText ) +
379+ chalk . hex ( colors . textDim ) ( speedSuffix ) ;
362380 }
363381
364382 return [ truncateToWidth ( line1 , width ) , truncateToWidth ( line2 , width ) ] ;
@@ -413,8 +431,8 @@ export class FooterComponent implements Component {
413431 : '' ;
414432 const modelLabel = `${ model } ${ thinkingLabel } ` ;
415433 let renderedModelLabel = chalk . hex ( colors . text ) ( modelLabel ) ;
416- if ( isRainbowDancing ( ) ) {
417- renderedModelLabel = renderDanceFooterModel ( modelLabel ) ;
434+ if ( isRainbowHatching ( ) ) {
435+ renderedModelLabel = renderHatchFooterModel ( modelLabel ) ;
418436 }
419437 slots [ 'model' ] = [ renderedModelLabel ] ;
420438 }
0 commit comments