Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 50 additions & 7 deletions src/output/quota-table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,19 +73,42 @@ function displayWidth(s: string): number {
const BAR_WIDTH = 16;
const COMPACT_BAR_WIDTH = 10;

// Display ceiling. Server returns base percent (0–100) plus a `weekly_boost_permille`
// multiplier; a typical boosted plan shows up to 150%, so cap the rendered value
// at 200% to leave headroom and keep the bar/text readable.
const MAX_DISPLAY_PCT = 200;

// Weekly quota is unlimited when the server reports `current_weekly_status: 3`
// (per the status enum: 1=normal, 2=exhausted, 3=unlimited).
function isUnweekly(status: number | undefined | null): boolean {
return status === 3;
}

function clampPct(value: number): number {
return Math.max(0, Math.min(100, Math.round(value)));
return Math.max(0, Math.min(MAX_DISPLAY_PCT, Math.round(value)));
}

function remainingPct(percent: number | undefined | null, remaining: number, total: number): number {
return percent !== undefined && percent !== null
? clampPct(percent)
: total > 0 ? clampPct((remaining / total) * 100) : 0;
function boostFactor(boostPermille: number | undefined | null): number {
if (boostPermille === undefined || boostPermille === null) return 1;
return Math.max(0, boostPermille) / 1000;
}

function remainingPct(
percent: number | undefined | null,
remaining: number,
total: number,
boostPermille?: number | null,
): number {
const factor = boostFactor(boostPermille);
if (percent !== undefined && percent !== null) {
return clampPct(percent * factor);
}
return total > 0 ? clampPct((remaining / total) * 100 * factor) : 0;
}

function renderBar(remainingPct: number, color: boolean, barWidth: number = BAR_WIDTH, showPct: boolean = true): string {
const pct = clampPct(remainingPct);
const ratio = pct / 100;
const ratio = Math.min(1, pct / 100);
const filled = Math.round(barWidth * ratio);
const empty = barWidth - filled;
const pctStr = `${pct}%`.padStart(4);
Expand All @@ -98,14 +121,31 @@ function renderBar(remainingPct: number, color: boolean, barWidth: number = BAR_
return showPct ? `${bar} ${fg}${B}${pctStr}${R}` : bar;
}

const UNLIMITED_SYMBOL = '∞';
const UNLIMITED_LABEL_CN = '无限';
const UNLIMITED_LABEL_EN = 'unlimited';

function renderMetric(
label: string,
remaining: number,
total: number,
percent: number | undefined | null,
color: boolean,
boostPermille?: number | null,
unlimited?: boolean,
unlimitedLabel?: string,
): string {
const pct = remainingPct(percent, remaining, total);
if (unlimited) {
const ul = unlimitedLabel ?? UNLIMITED_SYMBOL;
const ulStr = ul.padStart(4);
if (color) {
const bar = `${BG_GREEN}${' '.repeat(COMPACT_BAR_WIDTH)}${R}`;
return `${D}${label}${R} ${bar} ${FG_GREEN}${B}${ulStr}${R}`;
}
const bar = `[${'█'.repeat(COMPACT_BAR_WIDTH)}]`;
return `${label} ${bar} ${ulStr}`;
}
const pct = remainingPct(percent, remaining, total, boostPermille);
const bar = renderBar(pct, color, COMPACT_BAR_WIDTH, total <= 0);
if (total > 0) {
const count = `${remaining.toLocaleString()} / ${total.toLocaleString()}`;
Expand Down Expand Up @@ -142,6 +182,9 @@ export function renderQuotaTable(models: QuotaModelRemain[], config: Config): vo
m.current_weekly_total_count,
m.current_weekly_remaining_percent,
useColor,
m.weekly_boost_permille,
isUnweekly(m.current_weekly_status),
config.region === 'cn' ? UNLIMITED_LABEL_CN : UNLIMITED_LABEL_EN,
);
const reset = `${L.resetsIn} ${formatDuration(m.remains_time, L.now)}`;
return { displayName, current, weekly, reset };
Expand Down
7 changes: 7 additions & 0 deletions src/types/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,9 +256,16 @@ export interface QuotaModelRemain {
current_weekly_total_count: number;
current_weekly_usage_count: number;
current_weekly_remaining_percent?: number;
// Server-side status. 1 = normal (limited), 2 = exhausted, 3 = unlimited.
current_interval_status?: number;
current_weekly_status?: number;
weekly_start_time: number;
weekly_end_time: number;
weekly_remains_time: number;
// Weekly display multiplier in permille (1/1000). The server returns the
// base weekly remaining percent and a separate boost factor; the rendered
// weekly value is base × (boost_permille / 1000). 1500 ⇒ display up to 150%.
weekly_boost_permille?: number;
}

// ---- File ----
Expand Down
124 changes: 124 additions & 0 deletions test/output/quota-table.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,4 +129,128 @@ describe('renderQuotaTable', () => {
expect(output).toContain('21 / 21');
expect(output).not.toContain('0 / 3');
});

it('applies weekly_boost_permille (1500 ⇒ up to 150%) when rendering weekly percent', () => {
const lines: string[] = [];
const originalLog = console.log;

console.log = (message?: unknown) => {
lines.push(String(message ?? ''));
};

try {
renderQuotaTable(
[
{
...createModel(),
current_weekly_total_count: 0,
current_weekly_usage_count: 0,
current_weekly_remaining_percent: 100,
weekly_boost_permille: 1500,
},
],
{ ...createConfig(), noColor: true },
);
} finally {
console.log = originalLog;
}

const output = lines.join('\n');
expect(output).toContain('Wk left [██████████] 150%');
});

it('clamps boosted weekly percent at MAX_DISPLAY_PCT (200)', () => {
const lines: string[] = [];
const originalLog = console.log;

console.log = (message?: unknown) => {
lines.push(String(message ?? ''));
};

try {
renderQuotaTable(
[
{
...createModel(),
current_weekly_total_count: 0,
current_weekly_usage_count: 0,
current_weekly_remaining_percent: 100,
weekly_boost_permille: 3000,
},
],
{ ...createConfig(), noColor: true },
);
} finally {
console.log = originalLog;
}

const output = lines.join('\n');
expect(output).toContain('200%');
expect(output).not.toContain('300%');
});

it('renders "无限" for weekly when status=3 (CN region)', () => {
const lines: string[] = [];
const originalLog = console.log;

console.log = (message?: unknown) => {
lines.push(String(message ?? ''));
};

try {
renderQuotaTable(
[
{
...createModel(),
current_weekly_total_count: 0,
current_weekly_usage_count: 0,
current_weekly_remaining_percent: 100,
current_weekly_status: 3,
weekly_boost_permille: 1500,
},
],
{ ...createConfig(), region: 'cn', noColor: true },
);
} finally {
console.log = originalLog;
}

const output = lines.join('\n');
expect(output).toContain('[██████████]');
expect(output).toContain('周剩余');
expect(output).toContain('无限');
expect(output).not.toContain('150%');
});

it('renders "unlimited" for weekly when status=3 (global region)', () => {
const lines: string[] = [];
const originalLog = console.log;

console.log = (message?: unknown) => {
lines.push(String(message ?? ''));
};

try {
renderQuotaTable(
[
{
...createModel(),
current_weekly_total_count: 0,
current_weekly_usage_count: 0,
current_weekly_remaining_percent: 100,
current_weekly_status: 3,
},
],
{ ...createConfig(), noColor: true },
);
} finally {
console.log = originalLog;
}

const output = lines.join('\n');
expect(output).toContain('[██████████]');
expect(output).toContain('Wk left');
expect(output).toContain('unlimited');
expect(output).not.toContain('100%');
});
});
Loading