@@ -24,8 +24,64 @@ interface QuotaApiResponse {
2424 model_remains : ModelRemain [ ] ;
2525}
2626
27- function formatDuration ( ms : number ) : string {
28- if ( ms <= 0 ) return 'now' ;
27+ // ── ANSI color constants (MiniMax brand palette) ──
28+
29+ const R = '\x1b[0m' ; // reset
30+ const B = '\x1b[1m' ; // bold
31+ const D = '\x1b[2m' ; // dim
32+ const MM_BLUE = '\x1b[38;2;43;82;255m' ;
33+ const MM_CYAN = '\x1b[38;2;6;184;212m' ;
34+ const WHITE = '\x1b[38;2;255;255;255m' ;
35+
36+ // Foreground colors for text (percentage label)
37+ const FG_GREEN = '\x1b[38;2;74;222;128m' ; // #4ADE80 — remaining > 50%
38+ const FG_YELLOW = '\x1b[38;2;250;204;21m' ; // #FACC15 — remaining 20-50%
39+ const FG_RED = '\x1b[38;2;248;113;113m' ; // #F87171 — remaining < 20%
40+
41+ // Background colors for battery-style bar fill
42+ const BG_GREEN = '\x1b[48;2;22;163;74m' ; // #16A34A
43+ const BG_YELLOW = '\x1b[48;2;202;138;4m' ; // #CA8A04
44+ const BG_RED = '\x1b[48;2;220;38;38m' ; // #DC2626
45+ const BG_EMPTY = '\x1b[48;2;55;65;81m' ; // #374151 — dark grey (consumed track)
46+
47+ // Usage-level colors: low usage = green (good), high usage = red (warning)
48+ function usageColors ( usedPct : number ) : [ string , string ] {
49+ if ( usedPct < 50 ) return [ FG_GREEN , BG_GREEN ] ;
50+ if ( usedPct <= 80 ) return [ FG_YELLOW , BG_YELLOW ] ;
51+ return [ FG_RED , BG_RED ] ;
52+ }
53+
54+ // ── i18n labels (CN vs Global) ──
55+
56+ interface Labels {
57+ dashboard : string ;
58+ week : string ;
59+ weekly : string ;
60+ resetsIn : string ;
61+ noData : string ;
62+ now : string ;
63+ }
64+
65+ const LABELS_EN : Labels = {
66+ dashboard : 'Quota Dashboard' ,
67+ week : 'Week' ,
68+ weekly : 'Weekly' ,
69+ resetsIn : 'Resets in' ,
70+ noData : 'No quota data available.' ,
71+ now : 'now' ,
72+ } ;
73+
74+ const LABELS_CN : Labels = {
75+ dashboard : '配额面板' ,
76+ week : '周期' ,
77+ weekly : '每周' ,
78+ resetsIn : '重置于' ,
79+ noData : '暂无配额数据' ,
80+ now : '即将' ,
81+ } ;
82+
83+ function formatDuration ( ms : number , nowLabel : string ) : string {
84+ if ( ms <= 0 ) return nowLabel ;
2985 const hours = Math . floor ( ms / 3600000 ) ;
3086 const minutes = Math . floor ( ( ms % 3600000 ) / 60000 ) ;
3187 if ( hours > 0 ) return `${ hours } h ${ minutes } m` ;
@@ -36,6 +92,80 @@ function formatDate(epochMs: number): string {
3692 return new Date ( epochMs ) . toISOString ( ) . slice ( 0 , 10 ) ;
3793}
3894
95+ // ── Terminal display-width helper (CJK chars = 2 columns) ──
96+
97+ function isCJK ( code : number ) : boolean {
98+ return (
99+ ( code >= 0x2E80 && code <= 0x9FFF ) || // CJK Radicals .. CJK Unified Ideographs
100+ ( code >= 0xF900 && code <= 0xFAFF ) || // CJK Compatibility Ideographs
101+ ( code >= 0xFE30 && code <= 0xFE4F ) || // CJK Compatibility Forms
102+ ( code >= 0xFF01 && code <= 0xFF60 ) || // Fullwidth Forms
103+ ( code >= 0x20000 && code <= 0x2FA1F ) // CJK Unified Ideographs Extension B+
104+ ) ;
105+ }
106+
107+ /** Visible column width of a plain string (ANSI-stripped, CJK = 2 cols) */
108+ function displayWidth ( s : string ) : number {
109+ const plain = s . replace ( / \x1b \[ [ 0 - 9 ; ] * m / g, '' ) ;
110+ let w = 0 ;
111+ for ( const ch of plain ) {
112+ w += isCJK ( ch . codePointAt ( 0 ) ! ) ? 2 : 1 ;
113+ }
114+ return w ;
115+ }
116+
117+ // ── Progress bar renderer (usage-style) ──
118+
119+ const BAR_WIDTH = 16 ;
120+
121+ /**
122+ * Usage bar: shows HOW MUCH quota has been consumed.
123+ * - Colored filled blocks = used portion
124+ * - Dark grey = remaining capacity
125+ * @param usedPct - used percentage (0–100)
126+ */
127+ function renderBar ( usedPct : number , color : boolean ) : string {
128+ const ratio = Math . max ( 0 , Math . min ( 100 , usedPct ) ) / 100 ;
129+ const filled = Math . round ( BAR_WIDTH * ratio ) ;
130+ const empty = BAR_WIDTH - filled ;
131+ const pctStr = `${ usedPct } %` . padStart ( 4 ) ;
132+
133+ if ( ! color ) {
134+ // Plain-text: [████............] 1%
135+ return `[${ '█' . repeat ( filled ) } ${ '.' . repeat ( empty ) } ] ${ pctStr } ` ;
136+ }
137+
138+ const [ fg , bg ] = usageColors ( usedPct ) ;
139+ // Filled = consumed portion (colored), Empty = remaining (dark grey)
140+ return (
141+ `${ bg } ${ ' ' . repeat ( filled ) } ${ R } ` +
142+ `${ BG_EMPTY } ${ ' ' . repeat ( empty ) } ${ R } ` +
143+ ` ${ fg } ${ B } ${ pctStr } ${ R } `
144+ ) ;
145+ }
146+
147+ // ── Box-drawing helpers ──
148+
149+ function line ( w : number , left : string , fill : string , right : string , color : boolean ) : string {
150+ if ( ! color ) return `+${ '-' . repeat ( w ) } +` ;
151+ return `${ D } ${ left } ${ fill . repeat ( w ) } ${ right } ${ R } ` ;
152+ }
153+
154+ function boxTop ( w : number , c : boolean ) : string { return line ( w , '╭' , '─' , '╮' , c ) ; }
155+ function boxMid ( w : number , c : boolean ) : string { return line ( w , '├' , '─' , '┤' , c ) ; }
156+ function boxBot ( w : number , c : boolean ) : string { return line ( w , '╰' , '─' , '╯' , c ) ; }
157+
158+ function boxRow ( content : string , innerW : number , visLen : number , color : boolean ) : string {
159+ const pad = Math . max ( 0 , innerW - 2 - visLen ) ;
160+ return color
161+ ? `${ D } │${ R } ${ content } ${ ' ' . repeat ( pad ) } ${ D } │${ R } `
162+ : `| ${ content } ${ ' ' . repeat ( pad ) } |` ;
163+ }
164+
165+ // visLen removed — use displayWidth() instead for CJK-safe column counting
166+
167+ // ── Command definition ──
168+
39169export default defineCommand ( {
40170 name : 'quota show' ,
41171 description : 'Display Token Plan usage and remaining quotas' ,
@@ -55,11 +185,13 @@ export default defineCommand({
55185 const models = response . model_remains || [ ] ;
56186 const format = detectOutputFormat ( config . output ) ;
57187
188+ // Step 1: Non-text formats pass through as-is
58189 if ( format !== 'text' ) {
59190 console . log ( formatOutput ( response , format ) ) ;
60191 return ;
61192 }
62193
194+ // Step 2: Quiet mode — machine-parseable TSV
63195 if ( config . quiet ) {
64196 for ( const m of models ) {
65197 const remaining = m . current_interval_total_count - m . current_interval_usage_count ;
@@ -68,27 +200,100 @@ export default defineCommand({
68200 return ;
69201 }
70202
71- if ( models . length > 0 ) {
72- const first = models [ 0 ] ! ;
73- console . log ( `week: ${ formatDate ( first . weekly_start_time ) } — ${ formatDate ( first . weekly_end_time ) } ` ) ;
74- console . log ( '' ) ;
203+ // Step 3: Rich HUD — locale + color detection
204+ const useColor = ! config . noColor && process . stdout . isTTY === true ;
205+ const L = config . region === 'cn' ? LABELS_CN : LABELS_EN ;
206+
207+ // Dynamic box width: adapt to longest model name
208+ const maxNameLen = models . length > 0
209+ ? Math . max ( ...models . map ( m => m . model_name . length ) )
210+ : 16 ;
211+ // Layout per row: name + 2 + usage(15) + 2 + bar(BAR_WIDTH) + 1 + pct(4) = name + BAR_WIDTH + 24
212+ // Box inner W = content + 2 (for "│ " and " │" padding)
213+ const W = Math . max ( 68 , maxNameLen + BAR_WIDTH + 26 ) ;
214+
215+ // ── Header row ──
216+ const weekRange = models . length > 0
217+ ? `${ formatDate ( models [ 0 ] ! . weekly_start_time ) } — ${ formatDate ( models [ 0 ] ! . weekly_end_time ) } `
218+ : '' ;
219+
220+ const titlePlain = `MINIMAX ${ L . dashboard } ` ;
221+ const weekPlain = `${ L . week } : ${ weekRange } ` ;
222+ // Use displayWidth for CJK-safe column counting
223+ const titleDW = displayWidth ( titlePlain ) ;
224+ const weekDW = displayWidth ( weekPlain ) ;
225+ const headerGap = Math . max ( 2 , W - 2 - titleDW - weekDW ) ;
226+
227+ const titleStyled = useColor
228+ ? `${ B } ${ MM_BLUE } MINIMAX${ R } ${ D } ${ L . dashboard } ${ R } `
229+ : titlePlain ;
230+ const weekStyled = useColor
231+ ? `${ D } ${ L . week } :${ R } ${ MM_CYAN } ${ weekRange } ${ R } `
232+ : weekPlain ;
233+
234+ const headerContent = `${ titleStyled } ${ ' ' . repeat ( headerGap ) } ${ weekStyled } ` ;
235+ const headerVisLen = titleDW + headerGap + weekDW ;
236+
237+ console . log ( '' ) ;
238+ console . log ( boxTop ( W , useColor ) ) ;
239+ console . log ( boxRow ( headerContent , W , headerVisLen , useColor ) ) ;
240+
241+ if ( models . length === 0 ) {
242+ console . log ( boxBot ( W , useColor ) ) ;
243+ console . log ( `\n ${ L . noData } \n` ) ;
244+ return ;
75245 }
76246
77- const tableData = models . map ( m => {
78- const used = m . current_interval_usage_count ;
247+ // ── Model rows (each wrapped inside the same box) ──
248+ for ( let i = 0 ; i < models . length ; i ++ ) {
249+ const m = models [ i ] ! ;
250+ console . log ( boxMid ( W , useColor ) ) ;
251+
252+ // API field "usage_count" is actually the REMAINING count
253+ const remaining = m . current_interval_usage_count ;
79254 const limit = m . current_interval_total_count ;
80- const weekUsed = m . current_weekly_usage_count ;
255+ const used = Math . max ( 0 , limit - remaining ) ;
256+ const usedPct = limit > 0 ? Math . round ( ( used / limit ) * 100 ) : 0 ;
257+ const weekRemaining = m . current_weekly_usage_count ;
81258 const weekLimit = m . current_weekly_total_count ;
82- const resets = formatDuration ( m . remains_time ) ;
259+ const weekUsed = Math . max ( 0 , weekLimit - weekRemaining ) ;
260+ const resets = formatDuration ( m . remains_time , L . now ) ;
261+
262+ // Line 1: Model name + used/limit fraction + battery bar + remaining %
263+ const nameStr = m . model_name . padEnd ( maxNameLen ) ;
264+ const usageFrac = `${ used . toLocaleString ( ) } / ${ limit . toLocaleString ( ) } ` ;
265+ const bar = renderBar ( usedPct , useColor ) ;
83266
84- return {
85- MODEL : m . model_name ,
86- USED : `${ used . toLocaleString ( ) } / ${ limit . toLocaleString ( ) } ` ,
87- WEEKLY : `${ weekUsed . toLocaleString ( ) } / ${ weekLimit . toLocaleString ( ) } ` ,
88- RESETS_IN : resets ,
89- } ;
90- } ) ;
267+ // Visible columns: name(padded) + gap(2) + usage(15) + gap(2) + bar(BAR_WIDTH) + gap(1) + pct(4)
268+ const line1VisLen = maxNameLen + 2 + 15 + 2 + BAR_WIDTH + 1 + 4 ;
269+
270+ let line1Styled : string ;
271+ if ( useColor ) {
272+ const [ fg ] = usageColors ( usedPct ) ;
273+ line1Styled = `${ B } ${ WHITE } ${ nameStr } ${ R } ${ fg } ${ usageFrac . padStart ( 15 ) } ${ R } ${ bar } ` ;
274+ } else {
275+ line1Styled = `${ nameStr } ${ usageFrac . padStart ( 15 ) } ${ renderBar ( usedPct , false ) } ` ;
276+ }
277+ console . log ( boxRow ( line1Styled , W , line1VisLen , useColor ) ) ;
278+
279+ // Line 2: Weekly stats (left) + reset timer (right-aligned)
280+ const weekFrac = `${ weekUsed . toLocaleString ( ) } / ${ weekLimit . toLocaleString ( ) } ` ;
281+ const subLeft = `└ ${ L . weekly } ${ weekFrac } ` ;
282+ const subRight = `${ L . resetsIn } ${ resets } ` ;
283+ const subLeftDW = displayWidth ( subLeft ) ;
284+ const subRightDW = displayWidth ( subRight ) ;
285+ // Inner width = W - 2 (box borders), minus 2 leading spaces, minus left & right content
286+ const subGap = Math . max ( 2 , ( W - 2 ) - 2 - subLeftDW - subRightDW ) ;
287+ const subPlain = ` ${ subLeft } ${ ' ' . repeat ( subGap ) } ${ subRight } ` ;
288+ const subVisLen = 2 + subLeftDW + subGap + subRightDW ;
289+
290+ const subStyled = useColor
291+ ? ` ${ D } ${ subLeft } ${ ' ' . repeat ( subGap ) } ${ subRight } ${ R } `
292+ : subPlain ;
293+ console . log ( boxRow ( subStyled , W , subVisLen , useColor ) ) ;
294+ }
91295
92- console . log ( formatTable ( tableData ) ) ;
296+ console . log ( boxBot ( W , useColor ) ) ;
297+ console . log ( '' ) ;
93298 } ,
94299} ) ;
0 commit comments