@@ -16,23 +16,34 @@ const BG_YELLOW = '\x1b[48;2;202;138;4m';
1616const BG_RED = '\x1b[48;2;220;38;38m' ;
1717const BG_EMPTY = '\x1b[48;2;55;65;81m' ;
1818
19- function usageColors ( usedPct : number ) : [ string , string ] {
20- if ( usedPct < 50 ) return [ FG_GREEN , BG_GREEN ] ;
21- if ( usedPct <= 80 ) return [ FG_YELLOW , BG_YELLOW ] ;
19+ function remainingColors ( remainingPct : number ) : [ string , string ] {
20+ if ( remainingPct >= 50 ) return [ FG_GREEN , BG_GREEN ] ;
21+ if ( remainingPct >= 20 ) return [ FG_YELLOW , BG_YELLOW ] ;
2222 return [ FG_RED , BG_RED ] ;
2323}
2424
2525interface Labels {
2626 dashboard : string ;
2727 week : string ;
28+ current : string ;
2829 weekly : string ;
2930 resetsIn : string ;
3031 noData : string ;
3132 now : string ;
3233}
3334
34- const LABELS_EN : Labels = { dashboard : 'TokenPlan Quota' , week : 'Week' , weekly : 'Weekly' , resetsIn : 'Resets in' , noData : 'No quota data available.' , now : 'now' } ;
35- const LABELS_CN : Labels = { dashboard : 'TokenPlan 配额面板' , week : '周期' , weekly : '每周' , resetsIn : '重置于' , noData : '暂无配额数据' , now : '即将' } ;
35+ const LABELS_EN : Labels = { dashboard : 'TokenPlan Quota' , week : 'Week' , current : 'Left' , weekly : 'Wk left' , resetsIn : 'Reset' , noData : 'No quota data available.' , now : 'now' } ;
36+ const LABELS_CN : Labels = { dashboard : 'TokenPlan 配额面板' , week : '周期' , current : '剩余' , weekly : '周剩余' , resetsIn : '重置' , noData : '暂无配额数据' , now : '即将' } ;
37+
38+ const MODEL_NAME_CN : Record < string , string > = {
39+ 'general' : '通用' ,
40+ 'video' : '视频' ,
41+ } ;
42+
43+ function displayModelName ( name : string , region : string ) : string {
44+ if ( region !== 'cn' ) return name ;
45+ return MODEL_NAME_CN [ name ] ?? name ;
46+ }
3647
3748function formatDuration ( ms : number , nowLabel : string ) : string {
3849 if ( ms <= 0 ) return nowLabel ;
@@ -60,15 +71,47 @@ function displayWidth(s: string): number {
6071}
6172
6273const BAR_WIDTH = 16 ;
74+ const COMPACT_BAR_WIDTH = 10 ;
6375
64- function renderBar ( usedPct : number , color : boolean ) : string {
65- const ratio = Math . max ( 0 , Math . min ( 100 , usedPct ) ) / 100 ;
66- const filled = Math . round ( BAR_WIDTH * ratio ) ;
67- const empty = BAR_WIDTH - filled ;
68- const pctStr = `${ usedPct } %` . padStart ( 4 ) ;
69- if ( ! color ) return `[${ '█' . repeat ( filled ) } ${ '.' . repeat ( empty ) } ] ${ pctStr } ` ;
70- const [ fg , bg ] = usageColors ( usedPct ) ;
71- return `${ bg } ${ ' ' . repeat ( filled ) } ${ R } ${ BG_EMPTY } ${ ' ' . repeat ( empty ) } ${ R } ${ fg } ${ B } ${ pctStr } ${ R } ` ;
76+ function clampPct ( value : number ) : number {
77+ return Math . max ( 0 , Math . min ( 100 , Math . round ( value ) ) ) ;
78+ }
79+
80+ function remainingPct ( percent : number | undefined | null , remaining : number , total : number ) : number {
81+ return percent !== undefined && percent !== null
82+ ? clampPct ( percent )
83+ : total > 0 ? clampPct ( ( remaining / total ) * 100 ) : 0 ;
84+ }
85+
86+ function renderBar ( remainingPct : number , color : boolean , barWidth : number = BAR_WIDTH , showPct : boolean = true ) : string {
87+ const pct = clampPct ( remainingPct ) ;
88+ const ratio = pct / 100 ;
89+ const filled = Math . round ( barWidth * ratio ) ;
90+ const empty = barWidth - filled ;
91+ const pctStr = `${ pct } %` . padStart ( 4 ) ;
92+ if ( ! color ) {
93+ const bar = `[${ '█' . repeat ( filled ) } ${ '.' . repeat ( empty ) } ]` ;
94+ return showPct ? `${ bar } ${ pctStr } ` : bar ;
95+ }
96+ const [ fg , bg ] = remainingColors ( pct ) ;
97+ const bar = `${ bg } ${ ' ' . repeat ( filled ) } ${ R } ${ BG_EMPTY } ${ ' ' . repeat ( empty ) } ${ R } ` ;
98+ return showPct ? `${ bar } ${ fg } ${ B } ${ pctStr } ${ R } ` : bar ;
99+ }
100+
101+ function renderMetric (
102+ label : string ,
103+ remaining : number ,
104+ total : number ,
105+ percent : number | undefined | null ,
106+ color : boolean ,
107+ ) : string {
108+ const pct = remainingPct ( percent , remaining , total ) ;
109+ const bar = renderBar ( pct , color , COMPACT_BAR_WIDTH , total <= 0 ) ;
110+ if ( total > 0 ) {
111+ const count = `${ remaining . toLocaleString ( ) } / ${ total . toLocaleString ( ) } ` ;
112+ return color ? `${ D } ${ label } ${ R } ${ bar } ${ remainingColors ( pct ) [ 0 ] } ${ count } ${ R } ` : `${ label } ${ bar } ${ count } ` ;
113+ }
114+ return `${ label } ${ bar } ` ;
72115}
73116
74117function boxLine ( w : number , l : string , f : string , r : string , c : boolean ) : string {
@@ -84,9 +127,31 @@ export function renderQuotaTable(models: QuotaModelRemain[], config: Config): vo
84127 const useColor = ! config . noColor && process . stdout . isTTY === true ;
85128 const L = config . region === 'cn' ? LABELS_CN : LABELS_EN ;
86129
87- const maxNameLen = models . length > 0 ? Math . max ( ...models . map ( m => m . model_name . length ) ) : 16 ;
88- const barVisLen = useColor ? BAR_WIDTH + 5 : BAR_WIDTH + 7 ;
89- const W = Math . max ( 68 , maxNameLen + 2 + 15 + 2 + barVisLen + 2 ) ;
130+ const rows = models . map ( ( m ) => {
131+ const displayName = displayModelName ( m . model_name , config . region ) ;
132+ const current = renderMetric (
133+ L . current ,
134+ m . current_interval_usage_count ,
135+ m . current_interval_total_count ,
136+ m . current_interval_remaining_percent ,
137+ useColor ,
138+ ) ;
139+ const weekly = renderMetric (
140+ L . weekly ,
141+ m . current_weekly_usage_count ,
142+ m . current_weekly_total_count ,
143+ m . current_weekly_remaining_percent ,
144+ useColor ,
145+ ) ;
146+ const reset = `${ L . resetsIn } ${ formatDuration ( m . remains_time , L . now ) } ` ;
147+ return { displayName, current, weekly, reset } ;
148+ } ) ;
149+
150+ const nameWidth = Math . max ( 6 , ...rows . map ( r => displayWidth ( r . displayName ) ) ) ;
151+ const currentWidth = Math . max ( ...rows . map ( r => displayWidth ( r . current ) ) , 18 ) ;
152+ const weeklyWidth = Math . max ( ...rows . map ( r => displayWidth ( r . weekly ) ) , 18 ) ;
153+ const resetWidth = Math . max ( ...rows . map ( r => displayWidth ( r . reset ) ) , 10 ) ;
154+ const W = Math . max ( 72 , nameWidth + 2 + currentWidth + 2 + weeklyWidth + 2 + resetWidth + 4 ) ;
90155
91156 const weekRange = models . length > 0
92157 ? `${ formatDate ( models [ 0 ] ! . weekly_start_time ) } — ${ formatDate ( models [ 0 ] ! . weekly_end_time ) } `
@@ -110,34 +175,15 @@ export function renderQuotaTable(models: QuotaModelRemain[], config: Config): vo
110175 return ;
111176 }
112177
113- for ( const m of models ) {
178+ for ( const row of rows ) {
114179 console . log ( boxLine ( W , '├' , '─' , '┤' , useColor ) ) ;
115180
116- const used = m . current_interval_usage_count ;
117- const limit = m . current_interval_total_count ;
118- const usedPct = limit > 0 ? Math . round ( ( used / limit ) * 100 ) : 0 ;
119- const weekUsed = m . current_weekly_usage_count ;
120- const weekLimit = m . current_weekly_total_count ;
121- const resets = formatDuration ( m . remains_time , L . now ) ;
122-
123- const nameStr = m . model_name . padEnd ( maxNameLen ) ;
124- const usageFrac = `${ used . toLocaleString ( ) } / ${ limit . toLocaleString ( ) } ` ;
125- const bar = renderBar ( usedPct , useColor ) ;
126- const line1VisLen = maxNameLen + 2 + 15 + 2 + barVisLen ;
127-
128- const line1 = useColor
129- ? `${ B } ${ nameStr } ${ R } ${ usageColors ( usedPct ) [ 0 ] } ${ usageFrac . padStart ( 15 ) } ${ R } ${ bar } `
130- : `${ nameStr } ${ usageFrac . padStart ( 15 ) } ${ renderBar ( usedPct , false ) } ` ;
131- console . log ( boxRow ( line1 , W , line1VisLen , useColor ) ) ;
132-
133- const subLeft = `└ ${ L . weekly } ${ weekUsed . toLocaleString ( ) } / ${ weekLimit . toLocaleString ( ) } ` ;
134- const subRight = `${ L . resetsIn } ${ resets } ` ;
135- const subGap = Math . max ( 2 , ( W - 2 ) - 2 - displayWidth ( subLeft ) - displayWidth ( subRight ) ) ;
136- const subVisLen = 2 + displayWidth ( subLeft ) + subGap + displayWidth ( subRight ) ;
137- const sub = useColor
138- ? ` ${ D } ${ subLeft } ${ ' ' . repeat ( subGap ) } ${ subRight } ${ R } `
139- : ` ${ subLeft } ${ ' ' . repeat ( subGap ) } ${ subRight } ` ;
140- console . log ( boxRow ( sub , W , subVisLen , useColor ) ) ;
181+ const name = useColor ? `${ B } ${ row . displayName } ${ R } ` : row . displayName ;
182+ const line = `${ name } ${ ' ' . repeat ( Math . max ( 1 , nameWidth - displayWidth ( row . displayName ) + 2 ) ) } ` +
183+ `${ row . current } ${ ' ' . repeat ( Math . max ( 1 , currentWidth - displayWidth ( row . current ) + 2 ) ) } ` +
184+ `${ row . weekly } ${ ' ' . repeat ( Math . max ( 1 , weeklyWidth - displayWidth ( row . weekly ) + 2 ) ) } ` +
185+ row . reset ;
186+ console . log ( boxRow ( line , W , displayWidth ( line ) , useColor ) ) ;
141187 }
142188
143189 console . log ( boxLine ( W , '╰' , '─' , '╯' , useColor ) ) ;
0 commit comments