66
77import type React from 'react' ;
88import { useState , useEffect , useMemo } from 'react' ;
9- import { Text , useIsScreenReaderEnabled } from 'ink' ;
9+ import { Box , Text , useIsScreenReaderEnabled } from 'ink' ;
1010import { CliSpinner } from './CliSpinner.js' ;
1111import type { SpinnerName } from 'cli-spinners' ;
1212import { Colors } from '../colors.js' ;
1313import tinygradient from 'tinygradient' ;
1414
1515const COLOR_CYCLE_DURATION_MS = 4000 ;
16+ const SPINNER_UPDATE_INTERVAL_MS = 30 ;
17+ const TMUX_UPDATE_INTERVAL_MS = 750 ;
18+ const TMUX_FRAMES = [ '.' , '..' , '...' ] as const ;
19+
20+ function isTmuxEnvironment ( ) : boolean {
21+ return (
22+ ! ! process . env [ 'TMUX' ] || ( process . env [ 'TERM' ] ?? '' ) . startsWith ( 'tmux' )
23+ ) ;
24+ }
1625
1726interface GeminiSpinnerProps {
1827 spinnerType ?: SpinnerName ;
@@ -24,7 +33,9 @@ export const GeminiSpinner: React.FC<GeminiSpinnerProps> = ({
2433 altText,
2534} ) => {
2635 const isScreenReaderEnabled = useIsScreenReaderEnabled ( ) ;
36+ const isTmux = isTmuxEnvironment ( ) ;
2737 const [ time , setTime ] = useState ( 0 ) ;
38+ const [ tmuxFrameIndex , setTmuxFrameIndex ] = useState ( 0 ) ;
2839
2940 const googleGradient = useMemo ( ( ) => {
3041 const brandColors = [
@@ -43,18 +54,31 @@ export const GeminiSpinner: React.FC<GeminiSpinnerProps> = ({
4354 return ;
4455 }
4556
46- const interval = setInterval ( ( ) => {
47- setTime ( ( prevTime ) => prevTime + 30 ) ;
48- } , 30 ) ; // ~33fps for smooth color transitions
57+ const interval = setInterval (
58+ ( ) => {
59+ if ( isTmux ) {
60+ setTmuxFrameIndex (
61+ ( prevIndex ) => ( prevIndex + 1 ) % TMUX_FRAMES . length ,
62+ ) ;
63+ return ;
64+ }
65+ setTime ( ( prevTime ) => prevTime + SPINNER_UPDATE_INTERVAL_MS ) ;
66+ } ,
67+ isTmux ? TMUX_UPDATE_INTERVAL_MS : SPINNER_UPDATE_INTERVAL_MS ,
68+ ) ;
4969
5070 return ( ) => clearInterval ( interval ) ;
51- } , [ isScreenReaderEnabled ] ) ;
71+ } , [ isScreenReaderEnabled , isTmux ] ) ;
5272
5373 const progress = ( time % COLOR_CYCLE_DURATION_MS ) / COLOR_CYCLE_DURATION_MS ;
5474 const currentColor = googleGradient . rgbAt ( progress ) . toHexString ( ) ;
5575
5676 return isScreenReaderEnabled ? (
5777 < Text > { altText } </ Text >
78+ ) : isTmux ? (
79+ < Box width = { 3 } >
80+ < Text > { TMUX_FRAMES [ tmuxFrameIndex ] } </ Text >
81+ </ Box >
5882 ) : (
5983 < Text color = { currentColor } >
6084 < CliSpinner type = { spinnerType } />
0 commit comments