@aliou/pi-synthetic 0.10.2 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -19,10 +19,19 @@ export function registerQuotasCommand(pi: ExtensionAPI): void {
|
|
|
19
19
|
|
|
20
20
|
const result = await ctx.ui.custom<null>((tui, theme, _kb, done) => {
|
|
21
21
|
const controller = new AbortController();
|
|
22
|
-
const component = new QuotasComponent(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
const component = new QuotasComponent(
|
|
23
|
+
theme,
|
|
24
|
+
tui,
|
|
25
|
+
() => {
|
|
26
|
+
controller.abort();
|
|
27
|
+
done(null);
|
|
28
|
+
},
|
|
29
|
+
() => {
|
|
30
|
+
component.setState({ type: "loading" });
|
|
31
|
+
tui.requestRender();
|
|
32
|
+
void loadQuotas();
|
|
33
|
+
},
|
|
34
|
+
);
|
|
26
35
|
|
|
27
36
|
async function loadQuotas(): Promise<void> {
|
|
28
37
|
const fetchResult = await fetchQuotas(key, controller.signal);
|
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
import type { Theme } from "@mariozechner/pi-coding-agent";
|
|
2
2
|
import { DynamicBorder } from "@mariozechner/pi-coding-agent";
|
|
3
3
|
import type { Component, TUI } from "@mariozechner/pi-tui";
|
|
4
|
-
import {
|
|
5
|
-
Loader,
|
|
6
|
-
matchesKey,
|
|
7
|
-
truncateToWidth,
|
|
8
|
-
visibleWidth,
|
|
9
|
-
} from "@mariozechner/pi-tui";
|
|
4
|
+
import { Loader, matchesKey, truncateToWidth } from "@mariozechner/pi-tui";
|
|
10
5
|
import type { QuotasResponse } from "../../../types/quotas";
|
|
11
6
|
|
|
12
7
|
type QuotasState =
|
|
@@ -21,10 +16,12 @@ interface QuotaWindow {
|
|
|
21
16
|
windowSeconds: number;
|
|
22
17
|
usedValue: number;
|
|
23
18
|
limitValue: number;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
19
|
+
isCurrency?: boolean;
|
|
20
|
+
showPace?: boolean;
|
|
21
|
+
paceScale?: number;
|
|
22
|
+
limited?: boolean;
|
|
23
|
+
nextAmount?: string;
|
|
24
|
+
nextLabel?: string;
|
|
28
25
|
}
|
|
29
26
|
|
|
30
27
|
/** Safely compute percentage, guarding against division by zero */
|
|
@@ -42,80 +39,65 @@ function parseCurrency(value: string): number {
|
|
|
42
39
|
function toWindows(quotas: QuotasResponse): QuotaWindow[] {
|
|
43
40
|
const windows: QuotaWindow[] = [];
|
|
44
41
|
|
|
45
|
-
// Weekly token limit (credits-based)
|
|
46
42
|
if (quotas.weeklyTokenLimit) {
|
|
47
43
|
const { weeklyTokenLimit } = quotas;
|
|
48
44
|
const limitValue = parseCurrency(weeklyTokenLimit.maxCredits);
|
|
49
45
|
const remainingValue = parseCurrency(weeklyTokenLimit.remainingCredits);
|
|
50
46
|
windows.push({
|
|
51
|
-
label: "Credits",
|
|
47
|
+
label: "Credits / week",
|
|
52
48
|
usedPercent: Math.max(
|
|
53
49
|
0,
|
|
54
50
|
Math.min(100, 100 - weeklyTokenLimit.percentRemaining),
|
|
55
51
|
),
|
|
56
52
|
resetsAt: new Date(weeklyTokenLimit.nextRegenAt),
|
|
57
|
-
windowSeconds:
|
|
53
|
+
windowSeconds: 24 * 60 * 60,
|
|
58
54
|
usedValue: limitValue - remainingValue,
|
|
59
55
|
limitValue,
|
|
60
|
-
|
|
61
|
-
|
|
56
|
+
isCurrency: true,
|
|
57
|
+
showPace: true,
|
|
58
|
+
paceScale: 1 / 7,
|
|
59
|
+
nextAmount: `+${weeklyTokenLimit.nextRegenCredits}`,
|
|
60
|
+
nextLabel: "Next regen",
|
|
62
61
|
});
|
|
63
62
|
}
|
|
64
63
|
|
|
65
|
-
// Rolling 5-hour limit (request-based)
|
|
66
64
|
if (quotas.rollingFiveHourLimit && quotas.rollingFiveHourLimit.max > 0) {
|
|
67
65
|
const { rollingFiveHourLimit } = quotas;
|
|
66
|
+
const used = rollingFiveHourLimit.max - rollingFiveHourLimit.remaining;
|
|
67
|
+
const tickAmount =
|
|
68
|
+
rollingFiveHourLimit.tickPercent * rollingFiveHourLimit.max;
|
|
68
69
|
windows.push({
|
|
69
|
-
label: "5h",
|
|
70
|
-
usedPercent: safePercent(
|
|
71
|
-
rollingFiveHourLimit.max - rollingFiveHourLimit.remaining,
|
|
72
|
-
rollingFiveHourLimit.max,
|
|
73
|
-
),
|
|
70
|
+
label: "Requests / 5h",
|
|
71
|
+
usedPercent: safePercent(used, rollingFiveHourLimit.max),
|
|
74
72
|
resetsAt: new Date(rollingFiveHourLimit.nextTickAt),
|
|
75
73
|
windowSeconds: 5 * 60 * 60,
|
|
76
|
-
usedValue:
|
|
74
|
+
usedValue: Math.round(used),
|
|
77
75
|
limitValue: rollingFiveHourLimit.max,
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
// Legacy subscription (fallback if rollingFiveHourLimit not available)
|
|
84
|
-
if (
|
|
85
|
-
!quotas.rollingFiveHourLimit &&
|
|
86
|
-
quotas.subscription?.limit &&
|
|
87
|
-
quotas.subscription.limit > 0
|
|
88
|
-
) {
|
|
89
|
-
windows.push({
|
|
90
|
-
label: "Completions",
|
|
91
|
-
usedPercent: safePercent(
|
|
92
|
-
quotas.subscription.requests,
|
|
93
|
-
quotas.subscription.limit,
|
|
94
|
-
),
|
|
95
|
-
resetsAt: new Date(quotas.subscription.renewsAt),
|
|
96
|
-
windowSeconds: 5 * 60 * 60,
|
|
97
|
-
usedValue: quotas.subscription.requests,
|
|
98
|
-
limitValue: quotas.subscription.limit,
|
|
76
|
+
showPace: false,
|
|
77
|
+
limited: rollingFiveHourLimit.limited,
|
|
78
|
+
nextAmount: `+${tickAmount.toFixed(1)}`,
|
|
79
|
+
nextLabel: "Next tick",
|
|
99
80
|
});
|
|
100
81
|
}
|
|
101
82
|
|
|
102
83
|
if (quotas.search?.hourly?.limit && quotas.search.hourly.limit > 0) {
|
|
84
|
+
const { hourly } = quotas.search;
|
|
103
85
|
windows.push({
|
|
104
|
-
label: "Search",
|
|
105
|
-
usedPercent: safePercent(
|
|
106
|
-
|
|
107
|
-
quotas.search.hourly.limit,
|
|
108
|
-
),
|
|
109
|
-
resetsAt: new Date(quotas.search.hourly.renewsAt),
|
|
86
|
+
label: "Search / hour",
|
|
87
|
+
usedPercent: safePercent(hourly.requests, hourly.limit),
|
|
88
|
+
resetsAt: new Date(hourly.renewsAt),
|
|
110
89
|
windowSeconds: 60 * 60,
|
|
111
|
-
usedValue:
|
|
112
|
-
limitValue:
|
|
90
|
+
usedValue: hourly.requests,
|
|
91
|
+
limitValue: hourly.limit,
|
|
92
|
+
showPace: true,
|
|
93
|
+
paceScale: 1,
|
|
94
|
+
nextLabel: "Resets",
|
|
113
95
|
});
|
|
114
96
|
}
|
|
115
97
|
|
|
116
98
|
if (quotas.freeToolCalls?.limit && quotas.freeToolCalls.limit > 0) {
|
|
117
99
|
windows.push({
|
|
118
|
-
label: "Free Tool Calls",
|
|
100
|
+
label: "Free Tool Calls / day",
|
|
119
101
|
usedPercent: safePercent(
|
|
120
102
|
quotas.freeToolCalls.requests,
|
|
121
103
|
quotas.freeToolCalls.limit,
|
|
@@ -124,6 +106,9 @@ function toWindows(quotas: QuotasResponse): QuotaWindow[] {
|
|
|
124
106
|
windowSeconds: 24 * 60 * 60,
|
|
125
107
|
usedValue: quotas.freeToolCalls.requests,
|
|
126
108
|
limitValue: quotas.freeToolCalls.limit,
|
|
109
|
+
showPace: true,
|
|
110
|
+
paceScale: 1,
|
|
111
|
+
nextLabel: "Resets",
|
|
127
112
|
});
|
|
128
113
|
}
|
|
129
114
|
|
|
@@ -168,29 +153,28 @@ function getSeverity(
|
|
|
168
153
|
return "success";
|
|
169
154
|
}
|
|
170
155
|
|
|
171
|
-
function
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
hour12: true,
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
if (isToday) {
|
|
185
|
-
return `today ${timeStr}`;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
const dateStr = date.toLocaleDateString("en-US", {
|
|
189
|
-
month: "short",
|
|
190
|
-
day: "numeric",
|
|
191
|
-
});
|
|
156
|
+
function formatTimeRemaining(date: Date): string {
|
|
157
|
+
const ms = date.getTime() - Date.now();
|
|
158
|
+
if (ms <= 0) return "now";
|
|
159
|
+
const totalMins = Math.ceil(ms / (1000 * 60));
|
|
160
|
+
const hours = Math.floor(totalMins / 60);
|
|
161
|
+
const mins = totalMins % 60;
|
|
162
|
+
if (hours >= 1) return mins > 0 ? `${hours}h${mins}m` : `${hours}h`;
|
|
163
|
+
const totalSecs = Math.ceil(ms / 1000);
|
|
164
|
+
return totalMins >= 1 ? `${totalMins}m` : `${totalSecs}s`;
|
|
165
|
+
}
|
|
192
166
|
|
|
193
|
-
|
|
167
|
+
/**
|
|
168
|
+
* Convert a foreground ANSI escape to its background equivalent.
|
|
169
|
+
* Handles truecolor (38;2), 256-color (38;5), and basic (3X) escapes.
|
|
170
|
+
*/
|
|
171
|
+
function fgAnsiToBg(fgAnsi: string): string {
|
|
172
|
+
// Convert fg escape sequences to bg equivalents by replacing the
|
|
173
|
+
// discriminating digit: 38 (truecolor/256) → 48, 3X (basic) → 4X.
|
|
174
|
+
return fgAnsi
|
|
175
|
+
.split("[38;")
|
|
176
|
+
.join("[48;")
|
|
177
|
+
.replace(/\[3([0-9])m/g, "[4$1m");
|
|
194
178
|
}
|
|
195
179
|
|
|
196
180
|
function renderProgressBar(
|
|
@@ -202,45 +186,40 @@ function renderProgressBar(
|
|
|
202
186
|
): string {
|
|
203
187
|
const clamped = Math.max(0, Math.min(100, Math.round(percent)));
|
|
204
188
|
const filled = Math.round((clamped / 100) * width);
|
|
205
|
-
const paceIndex =
|
|
206
|
-
pacePercent === null || pacePercent === undefined || pacePercent <= percent
|
|
207
|
-
? null
|
|
208
|
-
: Math.round((Math.max(0, Math.min(100, pacePercent)) / 100) * width);
|
|
209
189
|
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
190
|
+
const showPace =
|
|
191
|
+
pacePercent !== null &&
|
|
192
|
+
pacePercent !== undefined &&
|
|
193
|
+
pacePercent >= 5 &&
|
|
194
|
+
Math.abs(pacePercent - percent) >= 5;
|
|
195
|
+
const paceIndex = showPace
|
|
196
|
+
? Math.min(
|
|
197
|
+
width - 1,
|
|
198
|
+
Math.round(
|
|
199
|
+
(Math.max(0, Math.min(100, pacePercent ?? 0)) / 100) * width,
|
|
200
|
+
),
|
|
201
|
+
)
|
|
202
|
+
: null;
|
|
220
203
|
|
|
221
|
-
|
|
222
|
-
}
|
|
204
|
+
const reset = "\x1b[0m";
|
|
223
205
|
|
|
224
|
-
function renderSimpleIndicatorBar(
|
|
225
|
-
usedPercent: number,
|
|
226
|
-
width: number,
|
|
227
|
-
theme: Theme,
|
|
228
|
-
severity: "success" | "warning" | "error",
|
|
229
|
-
): string {
|
|
230
|
-
const clampedPercent = Math.max(0, Math.min(100, usedPercent));
|
|
231
|
-
// Clamp to width - 1 to avoid off-by-one when usedPercent === 100
|
|
232
|
-
const usedIndex = Math.min(
|
|
233
|
-
Math.round((clampedPercent / 100) * width),
|
|
234
|
-
width - 1,
|
|
235
|
-
);
|
|
236
206
|
const parts: string[] = [];
|
|
237
|
-
|
|
238
|
-
// Hide marker when within 5% of edges
|
|
239
|
-
const showMarker = clampedPercent >= 5 && clampedPercent <= 95;
|
|
240
|
-
|
|
241
207
|
for (let idx = 0; idx < width; idx++) {
|
|
242
|
-
if (
|
|
243
|
-
|
|
208
|
+
if (paceIndex !== null && idx === paceIndex) {
|
|
209
|
+
// Inside fill = ahead of pace: accent. Outside = behind pace: severity.
|
|
210
|
+
const markerColor = idx < filled ? "accent" : fillColor;
|
|
211
|
+
// Inside fill: set bg to fill color so `|` doesn't expose the panel bg
|
|
212
|
+
// through the thin character. Outside fill: ░ uses terminal bg naturally,
|
|
213
|
+
// so leave bg unset to match.
|
|
214
|
+
if (idx < filled) {
|
|
215
|
+
const bgAnsi = fgAnsiToBg(theme.getFgAnsi(fillColor));
|
|
216
|
+
const fgAnsi = theme.getFgAnsi(markerColor);
|
|
217
|
+
parts.push(`${bgAnsi}${fgAnsi}|${reset}`);
|
|
218
|
+
} else {
|
|
219
|
+
parts.push(theme.fg(markerColor, "|"));
|
|
220
|
+
}
|
|
221
|
+
} else if (idx < filled) {
|
|
222
|
+
parts.push(theme.fg(fillColor, "█"));
|
|
244
223
|
} else {
|
|
245
224
|
parts.push(theme.fg("dim", "░"));
|
|
246
225
|
}
|
|
@@ -254,12 +233,19 @@ export class QuotasComponent implements Component {
|
|
|
254
233
|
private theme: Theme;
|
|
255
234
|
private tui: TUI;
|
|
256
235
|
private onClose: () => void;
|
|
236
|
+
private onRefetch: () => void;
|
|
257
237
|
private loader: Loader | null = null;
|
|
258
238
|
|
|
259
|
-
constructor(
|
|
239
|
+
constructor(
|
|
240
|
+
theme: Theme,
|
|
241
|
+
tui: TUI,
|
|
242
|
+
onClose: () => void,
|
|
243
|
+
onRefetch: () => void,
|
|
244
|
+
) {
|
|
260
245
|
this.theme = theme;
|
|
261
246
|
this.tui = tui;
|
|
262
247
|
this.onClose = onClose;
|
|
248
|
+
this.onRefetch = onRefetch;
|
|
263
249
|
this.startLoader();
|
|
264
250
|
}
|
|
265
251
|
|
|
@@ -278,7 +264,10 @@ export class QuotasComponent implements Component {
|
|
|
278
264
|
}
|
|
279
265
|
|
|
280
266
|
setState(state: QuotasState): void {
|
|
281
|
-
if (
|
|
267
|
+
if (state.type === "loading") {
|
|
268
|
+
this.loader?.stop();
|
|
269
|
+
this.startLoader();
|
|
270
|
+
} else if (this.state.type === "loading") {
|
|
282
271
|
this.loader?.stop();
|
|
283
272
|
this.loader = null;
|
|
284
273
|
}
|
|
@@ -290,6 +279,10 @@ export class QuotasComponent implements Component {
|
|
|
290
279
|
this.onClose();
|
|
291
280
|
return true;
|
|
292
281
|
}
|
|
282
|
+
if (data === "r") {
|
|
283
|
+
this.onRefetch();
|
|
284
|
+
return true;
|
|
285
|
+
}
|
|
293
286
|
return false;
|
|
294
287
|
}
|
|
295
288
|
|
|
@@ -326,7 +319,7 @@ export class QuotasComponent implements Component {
|
|
|
326
319
|
}
|
|
327
320
|
|
|
328
321
|
lines.push("");
|
|
329
|
-
lines.push(this.theme.fg("dim", " q/Esc to close"));
|
|
322
|
+
lines.push(this.theme.fg("dim", " r to refresh q/Esc to close"));
|
|
330
323
|
lines.push(...border.render(width));
|
|
331
324
|
|
|
332
325
|
return lines;
|
|
@@ -362,134 +355,51 @@ export class QuotasComponent implements Component {
|
|
|
362
355
|
const lines: string[] = [];
|
|
363
356
|
const theme = this.theme;
|
|
364
357
|
|
|
365
|
-
const
|
|
358
|
+
const rawPace = window.showPace ? getPacePercent(window) : null;
|
|
359
|
+
const pacePercent =
|
|
360
|
+
rawPace !== null ? rawPace * (window.paceScale ?? 1) : null;
|
|
366
361
|
const projectedPercent = getProjectedPercent(
|
|
367
362
|
window.usedPercent,
|
|
368
363
|
pacePercent,
|
|
369
364
|
);
|
|
370
|
-
|
|
365
|
+
let severity = getSeverity(projectedPercent, pacePercent);
|
|
366
|
+
if (window.limited) severity = "error";
|
|
371
367
|
|
|
372
368
|
// Label
|
|
373
369
|
lines.push(
|
|
374
370
|
truncateToWidth(` ${theme.fg("accent", window.label)}`, maxWidth),
|
|
375
371
|
);
|
|
376
372
|
|
|
377
|
-
//
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
maxWidth,
|
|
396
|
-
),
|
|
397
|
-
);
|
|
398
|
-
} else {
|
|
399
|
-
// Traditional progress bar for legacy quota types
|
|
400
|
-
const bar = renderProgressBar(
|
|
401
|
-
window.usedPercent,
|
|
402
|
-
barWidth,
|
|
403
|
-
theme,
|
|
404
|
-
severity,
|
|
405
|
-
pacePercent,
|
|
406
|
-
);
|
|
407
|
-
const usedStr = `${window.usedValue.toLocaleString()}/${window.limitValue.toLocaleString()} (${Math.round(window.usedPercent)}%)`;
|
|
408
|
-
lines.push(
|
|
409
|
-
truncateToWidth(` ${bar} ${theme.fg(severity, usedStr)}`, maxWidth),
|
|
410
|
-
);
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
// Metadata: estimated + pace left, reset time right
|
|
414
|
-
const leftParts: string[] = [];
|
|
415
|
-
|
|
416
|
-
// Show tick info for rolling window
|
|
417
|
-
if (window.tickPercent !== undefined) {
|
|
418
|
-
const now = Date.now();
|
|
419
|
-
const remainingMs = window.resetsAt.getTime() - now;
|
|
420
|
-
const remainingMins = Math.ceil(remainingMs / (1000 * 60));
|
|
421
|
-
const remainingSecs = Math.ceil(remainingMs / 1000);
|
|
422
|
-
const timeStr =
|
|
423
|
-
remainingMs <= 0
|
|
424
|
-
? "now"
|
|
425
|
-
: remainingMins >= 1
|
|
426
|
-
? `${remainingMins}m`
|
|
427
|
-
: `${remainingSecs}s`;
|
|
428
|
-
const tickValue = (window.tickPercent / 100) * window.limitValue;
|
|
429
|
-
const tickStr = `+${tickValue.toFixed(1)} in ${timeStr}`;
|
|
430
|
-
leftParts.push(theme.fg("dim", tickStr));
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
// Show next regen credits for weekly token limit
|
|
434
|
-
if (window.nextRegenCredits !== undefined) {
|
|
435
|
-
const now = Date.now();
|
|
436
|
-
const remainingMs = window.resetsAt.getTime() - now;
|
|
437
|
-
const remainingHours = Math.ceil(remainingMs / (1000 * 60 * 60));
|
|
438
|
-
const remainingMins = Math.ceil(remainingMs / (1000 * 60));
|
|
439
|
-
const timeStr =
|
|
440
|
-
remainingMs <= 0
|
|
441
|
-
? "now"
|
|
442
|
-
: remainingHours >= 1
|
|
443
|
-
? `${remainingHours}h`
|
|
444
|
-
: `${remainingMins}m`;
|
|
445
|
-
const regenStr = `+${window.nextRegenCredits} in ${timeStr}`;
|
|
446
|
-
leftParts.push(theme.fg("dim", regenStr));
|
|
447
|
-
}
|
|
373
|
+
// Bar + usage
|
|
374
|
+
const bar = renderProgressBar(
|
|
375
|
+
window.usedPercent,
|
|
376
|
+
barWidth,
|
|
377
|
+
theme,
|
|
378
|
+
severity,
|
|
379
|
+
pacePercent,
|
|
380
|
+
);
|
|
381
|
+
const usedStr = window.isCurrency
|
|
382
|
+
? `${Math.round(window.usedPercent)}%/$${window.limitValue.toFixed(2)}`
|
|
383
|
+
: `${Math.round(window.usedPercent)}%/${window.limitValue}`;
|
|
384
|
+
const limitedBadge = window.limited ? theme.fg("error", " LIMITED") : "";
|
|
385
|
+
lines.push(
|
|
386
|
+
truncateToWidth(
|
|
387
|
+
` ${bar} ${theme.fg(severity, usedStr)}${limitedBadge}`,
|
|
388
|
+
maxWidth,
|
|
389
|
+
),
|
|
390
|
+
);
|
|
448
391
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
window.
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
? theme.fg(severity, estStr)
|
|
458
|
-
: theme.fg("dim", estStr),
|
|
392
|
+
// Subtitle: next event info
|
|
393
|
+
if (window.nextLabel) {
|
|
394
|
+
const timeStr = formatTimeRemaining(window.resetsAt);
|
|
395
|
+
const subtitleStr = window.nextAmount
|
|
396
|
+
? `${window.nextAmount} in ${timeStr}`
|
|
397
|
+
: `${window.nextLabel} in ${timeStr}`;
|
|
398
|
+
lines.push(
|
|
399
|
+
truncateToWidth(` ${theme.fg("dim", subtitleStr)}`, maxWidth),
|
|
459
400
|
);
|
|
460
401
|
}
|
|
461
402
|
|
|
462
|
-
if (
|
|
463
|
-
pacePercent !== null &&
|
|
464
|
-
window.tickPercent === undefined &&
|
|
465
|
-
window.nextRegenCredits === undefined
|
|
466
|
-
) {
|
|
467
|
-
const paceDiff = window.usedPercent - pacePercent;
|
|
468
|
-
if (Math.abs(paceDiff) > 5) {
|
|
469
|
-
if (paceDiff > 0) {
|
|
470
|
-
leftParts.push(
|
|
471
|
-
theme.fg("warning", `${Math.round(Math.abs(paceDiff))}% ahead`),
|
|
472
|
-
);
|
|
473
|
-
} else {
|
|
474
|
-
leftParts.push(
|
|
475
|
-
theme.fg("success", `${Math.round(Math.abs(paceDiff))}% behind`),
|
|
476
|
-
);
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
const leftStr = leftParts.join(" ");
|
|
482
|
-
const resetStr = formatResetDateTime(window.resetsAt);
|
|
483
|
-
const rightStr = theme.fg("dim", resetStr);
|
|
484
|
-
|
|
485
|
-
const leftW = visibleWidth(leftStr);
|
|
486
|
-
const rightW = visibleWidth(rightStr);
|
|
487
|
-
const gap = Math.max(2, barWidth - leftW - rightW);
|
|
488
|
-
|
|
489
|
-
lines.push(
|
|
490
|
-
truncateToWidth(` ${leftStr}${" ".repeat(gap)}${rightStr}`, maxWidth),
|
|
491
|
-
);
|
|
492
|
-
|
|
493
403
|
return lines;
|
|
494
404
|
}
|
|
495
405
|
|