|
4 | 4 | * Layout: |
5 | 5 | * Line 1: [yolo] [plan] <model> <cwd> <git-badge> <shortcut hints> |
6 | 6 | * Line 2: context: XX.X% (tokens/max) |
7 | | - * Line 3+: aligned quota rows: "label (used%/100%)" under context parens |
| 7 | + * Line 3+: right-aligned quota rows: "label: XX% (reset in ...)" |
8 | 8 | */ |
9 | 9 |
|
10 | 10 | import type { Component } from '@earendil-works/pi-tui'; |
@@ -220,36 +220,52 @@ function hslToHex(h: number, s: number, l: number): string { |
220 | 220 | return `#${f(0)}${f(8)}${f(4)}`; |
221 | 221 | } |
222 | 222 |
|
| 223 | +function formatResetHint(hint: string | undefined): string { |
| 224 | + if (hint === undefined) return ''; |
| 225 | + if (hint === 'reset') return '(reset)'; |
| 226 | + if (hint.startsWith('resets in ')) { |
| 227 | + const duration = hint.slice('resets in '.length).replace(/ /g, ', '); |
| 228 | + return `(${duration})`; |
| 229 | + } |
| 230 | + return `(${hint})`; |
| 231 | +} |
| 232 | + |
223 | 233 | function formatQuotaLines( |
224 | 234 | quotas: readonly QuotaInfo[] | undefined, |
225 | | - contextText: string, |
226 | | - contextWidth: number, |
227 | 235 | width: number, |
228 | 236 | colors: ColorPalette, |
229 | 237 | ): string[] { |
230 | 238 | if (quotas === undefined || quotas.length === 0) return []; |
231 | 239 |
|
232 | | - const parenIndex = contextText.indexOf('('); |
233 | | - const parenCol = |
234 | | - parenIndex >= 0 ? Math.max(0, width - contextWidth + parenIndex) : width; |
| 240 | + const rows = quotas |
| 241 | + .filter((quota) => quota.limit > 0) |
| 242 | + .map((quota) => { |
| 243 | + const usedRatio = Math.max(0, Math.min(quota.used / quota.limit, 1)); |
| 244 | + return { |
| 245 | + label: `${quota.label.toLowerCase()}:`, |
| 246 | + percent: `${Math.round(usedRatio * 100)}%`, |
| 247 | + reset: formatResetHint(quota.resetHint), |
| 248 | + ratio: usedRatio, |
| 249 | + }; |
| 250 | + }); |
| 251 | + if (rows.length === 0) return []; |
| 252 | + |
| 253 | + const labelColWidth = Math.max(...rows.map((r) => visibleWidth(r.label))); |
| 254 | + const percentColWidth = Math.max(...rows.map((r) => visibleWidth(r.percent))); |
| 255 | + const resetColWidth = Math.max(...rows.map((r) => visibleWidth(r.reset))); |
| 256 | + const gap = 3; |
| 257 | + const blockWidth = labelColWidth + gap + percentColWidth + gap + resetColWidth; |
235 | 258 |
|
236 | 259 | const lines: string[] = []; |
237 | | - for (const quota of quotas) { |
238 | | - if (quota.limit <= 0) continue; |
239 | | - const usedRatio = Math.max(0, Math.min(quota.used / quota.limit, 1)); |
240 | | - const usedPct = Math.round(usedRatio * 100); |
241 | | - // Gradient from dark green (0%) to red (100%). |
242 | | - const numberColor = chalk.hex(hslToHex(Math.round((1 - usedRatio) * 120), 80, 40)); |
243 | | - const prefix = `${quota.label.toLowerCase()} `; |
244 | | - const prefixWidth = visibleWidth(prefix); |
245 | | - const pad = Math.max(0, parenCol - prefixWidth); |
246 | | - const line = |
247 | | - ' '.repeat(pad) + |
248 | | - chalk.hex(colors.text)(prefix) + |
249 | | - chalk.hex(colors.text)('(') + |
250 | | - numberColor(String(usedPct)) + |
251 | | - chalk.hex(colors.text)('/100%)'); |
252 | | - lines.push(truncateToWidth(line, width)); |
| 260 | + for (const row of rows) { |
| 261 | + const numberColor = chalk.hex(hslToHex(Math.round((1 - row.ratio) * 120), 80, 40)); |
| 262 | + const content = |
| 263 | + row.label.padEnd(labelColWidth + gap) + |
| 264 | + numberColor(row.percent.padStart(percentColWidth)) + |
| 265 | + ' '.repeat(gap) + |
| 266 | + chalk.hex(colors.text)(row.reset.padStart(resetColWidth)); |
| 267 | + const leftPad = Math.max(0, width - blockWidth); |
| 268 | + lines.push(truncateToWidth(' '.repeat(leftPad) + content, width)); |
253 | 269 | } |
254 | 270 | return lines; |
255 | 271 | } |
@@ -424,13 +440,7 @@ export class FooterComponent implements Component { |
424 | 440 | line2 = ' '.repeat(leftPad) + chalk.hex(colors.text)(contextText); |
425 | 441 | } |
426 | 442 |
|
427 | | - const quotaLines = formatQuotaLines( |
428 | | - state.quotas, |
429 | | - contextText, |
430 | | - contextWidth, |
431 | | - width, |
432 | | - colors, |
433 | | - ); |
| 443 | + const quotaLines = formatQuotaLines(state.quotas, width, colors); |
434 | 444 | if (quotaLines.length > 0) { |
435 | 445 | return [ |
436 | 446 | truncateToWidth(line1, width), |
|
0 commit comments