qwenproxy-cli 1.0.26 → 1.0.27
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 +3 -3
- package/src/api/server.ts +82 -4
- package/src/core/metrics.ts +6 -1
- package/src/core/paths.ts +7 -0
- package/src/core/server-log-buffer.ts +105 -0
- package/src/routes/chat/index.ts +6 -0
- package/src/services/chat-cleanup.ts +3 -0
- package/src/sync/index.ts +47 -11
- package/src/tools/parser.ts +4 -1
- package/src/tui/app.ts +29 -20
- package/src/tui/index.ts +2 -2
- package/src/tui/proxy-client.ts +58 -4
- package/src/tui/server-manager.ts +46 -1
- package/src/tui/settings.ts +100 -0
- package/src/tui/theme.ts +15 -9
- package/src/tui/types.ts +19 -0
- package/src/tui/views/accounts-view.ts +50 -3
- package/src/tui/views/chat-view.ts +41 -2
- package/src/tui/views/logs-view.ts +22 -18
- package/src/tui/views/status-view.ts +180 -41
|
@@ -8,6 +8,48 @@ import { theme, glyphs, drawBox, pad, truncate } from "../theme.ts";
|
|
|
8
8
|
import { fetchProxyStatus, resetAllCooldowns, formatUptime } from "../proxy-client.ts";
|
|
9
9
|
import { ServerManager } from "../server-manager.ts";
|
|
10
10
|
|
|
11
|
+
export function renderProgressBar(
|
|
12
|
+
pct: number,
|
|
13
|
+
width = 8,
|
|
14
|
+
colorFn: (s: string) => string = theme.cyan,
|
|
15
|
+
): string {
|
|
16
|
+
const safePct = Math.max(0, Math.min(100, isNaN(pct) ? 0 : pct));
|
|
17
|
+
const rawFilled = Math.round((safePct / 100) * width);
|
|
18
|
+
const filled = safePct > 0.05 ? Math.max(1, rawFilled) : 0;
|
|
19
|
+
const empty = Math.max(0, width - filled);
|
|
20
|
+
return colorFn("█".repeat(filled)) + theme.muted("░".repeat(empty));
|
|
21
|
+
}
|
|
22
|
+
export function getAccountReadinessScore(acc: {
|
|
23
|
+
onCooldown: boolean;
|
|
24
|
+
headersReady: boolean;
|
|
25
|
+
activeStreams?: number;
|
|
26
|
+
isInitialized?: boolean;
|
|
27
|
+
cooldownReason?: string | null;
|
|
28
|
+
}): number {
|
|
29
|
+
if (acc.onCooldown) {
|
|
30
|
+
const reason = acc.cooldownReason || "";
|
|
31
|
+
if (
|
|
32
|
+
reason.startsWith("AuthFailed") ||
|
|
33
|
+
reason.startsWith("AuthPermanentFailure") ||
|
|
34
|
+
reason.includes("login methods exhausted")
|
|
35
|
+
) {
|
|
36
|
+
return 0; // Auth Fail at the very bottom
|
|
37
|
+
}
|
|
38
|
+
return 10; // Other cooldowns (RateLimited, WafChallenge, etc.)
|
|
39
|
+
}
|
|
40
|
+
if (acc.activeStreams && acc.activeStreams > 0 && acc.headersReady) {
|
|
41
|
+
return 100; // Actively generating code
|
|
42
|
+
}
|
|
43
|
+
if (acc.headersReady) {
|
|
44
|
+
return 80; // Ready / Warm
|
|
45
|
+
}
|
|
46
|
+
if (acc.isInitialized) {
|
|
47
|
+
return 60; // Warming up
|
|
48
|
+
}
|
|
49
|
+
return 40; // Healthy standby
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
11
53
|
export class StatusView implements TuiView {
|
|
12
54
|
public readonly id = "status";
|
|
13
55
|
public readonly title = "Status";
|
|
@@ -17,8 +59,9 @@ export class StatusView implements TuiView {
|
|
|
17
59
|
private actionMessage = "";
|
|
18
60
|
private actionMessageTimeout: NodeJS.Timeout | null = null;
|
|
19
61
|
private hoveredActionRow: number | null = null;
|
|
20
|
-
private lastLeftW =
|
|
21
|
-
|
|
62
|
+
private lastLeftW = 38;
|
|
63
|
+
private lastActionRecarregarRow = 20;
|
|
64
|
+
private lastActionZerarRow = 21;
|
|
22
65
|
constructor() {
|
|
23
66
|
this.refresh();
|
|
24
67
|
}
|
|
@@ -58,8 +101,12 @@ export class StatusView implements TuiView {
|
|
|
58
101
|
// Mouse hover over quick actions
|
|
59
102
|
if (key.name === "hover" && key.mouse) {
|
|
60
103
|
const { row, col } = key.mouse;
|
|
61
|
-
const leftW = this.lastLeftW ||
|
|
62
|
-
if (
|
|
104
|
+
const leftW = this.lastLeftW || 38;
|
|
105
|
+
if (
|
|
106
|
+
col >= 2 &&
|
|
107
|
+
col <= leftW - 1 &&
|
|
108
|
+
(row === this.lastActionRecarregarRow || row === this.lastActionZerarRow)
|
|
109
|
+
) {
|
|
63
110
|
if (this.hoveredActionRow !== row) {
|
|
64
111
|
this.hoveredActionRow = row;
|
|
65
112
|
return true;
|
|
@@ -73,14 +120,14 @@ export class StatusView implements TuiView {
|
|
|
73
120
|
// Mouse click interactions
|
|
74
121
|
if (key.name === "click" && key.mouse) {
|
|
75
122
|
const { row, col } = key.mouse;
|
|
76
|
-
const leftW = this.lastLeftW ||
|
|
123
|
+
const leftW = this.lastLeftW || 38;
|
|
77
124
|
if (col >= 2 && col <= leftW - 1) {
|
|
78
|
-
if (row ===
|
|
125
|
+
if (row === this.lastActionRecarregarRow) {
|
|
79
126
|
await this.refresh();
|
|
80
127
|
this.setMessage(theme.green("✓ Status atualizado"));
|
|
81
128
|
return true;
|
|
82
129
|
}
|
|
83
|
-
if (row ===
|
|
130
|
+
if (row === this.lastActionZerarRow) {
|
|
84
131
|
const cleared = resetAllCooldowns();
|
|
85
132
|
await this.refresh();
|
|
86
133
|
this.setMessage(theme.green(`✓ Cooldowns zerados: ${cleared} conta(s) liberada(s)`));
|
|
@@ -108,12 +155,13 @@ export class StatusView implements TuiView {
|
|
|
108
155
|
const isOnline = data?.online ?? false;
|
|
109
156
|
const contentH = Math.max(10, height);
|
|
110
157
|
|
|
111
|
-
// Two-column layout
|
|
112
|
-
|
|
158
|
+
// Two-column layout: give left box 48-52 cols so rich metrics never truncate,
|
|
159
|
+
// and right box takes the remaining width (at least 38 cols).
|
|
160
|
+
const leftW = width >= 96
|
|
161
|
+
? Math.min(52, Math.max(48, Math.floor(width * 0.48)))
|
|
162
|
+
: Math.min(46, Math.max(38, Math.floor(width * 0.50)));
|
|
113
163
|
this.lastLeftW = leftW;
|
|
114
|
-
const rightW = Math.max(
|
|
115
|
-
|
|
116
|
-
// Left Column: System & Proxy Status
|
|
164
|
+
const rightW = Math.max(36, width - leftW - 1);
|
|
117
165
|
const serverState = ServerManager.getInstance().getState();
|
|
118
166
|
let onlineBadge: string;
|
|
119
167
|
if (isOnline || serverState === "online") {
|
|
@@ -128,67 +176,158 @@ export class StatusView implements TuiView {
|
|
|
128
176
|
|
|
129
177
|
const uptimeSecs = data?.uptimeSeconds || Math.floor(process.uptime());
|
|
130
178
|
const uptimeStr = formatUptime(uptimeSecs);
|
|
131
|
-
|
|
132
179
|
const baseUrl = `http://${data?.host || "127.0.0.1"}:${data?.port || 7936}/v1`;
|
|
133
180
|
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
181
|
+
const m = data?.metrics;
|
|
182
|
+
const reqsTotal = m?.requestsTotal ?? 0;
|
|
183
|
+
const reqsErrors = m?.requestsErrors ?? 0;
|
|
184
|
+
const successPct = m?.successRate ?? (reqsTotal > 0 ? Number((((reqsTotal - reqsErrors) / reqsTotal) * 100).toFixed(1)) : 100);
|
|
185
|
+
const latencyAvg = m?.latencyAvgMs ? `${m.latencyAvgMs}ms` : "–";
|
|
186
|
+
const deltasCount = m?.deltasCount ?? 0;
|
|
187
|
+
const fullCount = m?.fullReplaysCount ?? 0;
|
|
188
|
+
const deltaRatio = m?.deltaRatio != null ? `${m.deltaRatio}%` : "–";
|
|
189
|
+
const toolCalls = m?.toolCallsCount ?? 0;
|
|
190
|
+
const toolRecovered = m?.toolCallsRecovered ?? 0;
|
|
191
|
+
const captchasDetected = m?.captchasDetected ?? 0;
|
|
192
|
+
const captchasSolved = m?.captchasSolved ?? 0;
|
|
193
|
+
const chatsCleaned = m?.chatsCleaned ?? 0;
|
|
194
|
+
const lbl = (s: string) => pad(s, 13);
|
|
195
|
+
const innerLeftW = Math.max(30, leftW - 2);
|
|
196
|
+
const deltaDetail = innerLeftW < 44
|
|
197
|
+
? `(${deltasCount}d / ${fullCount}f)`
|
|
198
|
+
: `(${deltasCount} delta / ${fullCount} full)`;
|
|
199
|
+
const ramPct = data?.systemMemoryPct || 0;
|
|
200
|
+
const ramBarColor = ramPct >= 80 ? theme.red : ramPct >= 60 ? theme.yellow : theme.cyan;
|
|
201
|
+
const ramBar = renderProgressBar(ramPct, 7, ramBarColor);
|
|
202
|
+
const ramStr = `${data?.rssMb || 0} MB [${ramBar}] ${ramPct}%`;
|
|
203
|
+
|
|
204
|
+
let connsStr = "0 ativas";
|
|
205
|
+
if (data?.activeStreams && data.activeStreams > 0) {
|
|
206
|
+
connsStr = theme.yellow(`⚡ ${data.activeStreams} ativa(s)`);
|
|
207
|
+
} else {
|
|
208
|
+
connsStr = theme.dim("0 ativas");
|
|
209
|
+
}
|
|
210
|
+
if (data?.waitingStreams && data.waitingStreams > 0) {
|
|
211
|
+
connsStr += theme.peach(` (${data.waitingStreams} na fila)`);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const leftContent: string[] = [
|
|
215
|
+
` ${theme.bold(lbl("Status:"))} ${onlineBadge}`,
|
|
216
|
+
` ${theme.bold(lbl("Base URL:"))} ${theme.cyan(baseUrl)}`,
|
|
217
|
+
` ${theme.bold(lbl("Uptime:"))} ${theme.cyan(uptimeStr)}`,
|
|
218
|
+
` ${theme.bold(lbl("Memória:"))} ${theme.cyan(ramStr)}`,
|
|
219
|
+
` ${theme.bold(lbl("Conexões:"))} ${connsStr}`,
|
|
220
|
+
` ${theme.dim("───────────────────────────────────────")}`,
|
|
221
|
+
` ${theme.bold("Tráfego & Performance:")}`,
|
|
222
|
+
` ${theme.dim(lbl("Requisições:"))} ${theme.cyan(String(reqsTotal))} ${theme.green(`(${successPct}% ok)`)} · ${reqsErrors > 0 ? theme.red(`${reqsErrors} err`) : theme.dim("0 err")}`,
|
|
223
|
+
` ${theme.dim(lbl("Latência:"))} ${theme.yellow(latencyAvg)} méd`,
|
|
224
|
+
` ${theme.dim(lbl("Deltas:"))} ${theme.green(deltaRatio)} ${theme.dim(deltaDetail)}`,
|
|
225
|
+
` ${theme.dim("───────────────────────────────────────")}`,
|
|
226
|
+
` ${theme.bold("Agentes & Operações:")}`,
|
|
227
|
+
` ${theme.dim(lbl("Tool Calls:"))} ${theme.cyan(String(toolCalls))} ${toolRecovered > 0 ? theme.green(`(${toolRecovered} curadas)`) : ""}`,
|
|
228
|
+
` ${theme.dim(lbl("Captchas:"))} ${captchasSolved > 0 ? theme.green(`${captchasSolved}/${captchasDetected} resolvidos`) : theme.dim(`${captchasDetected} detectados`)}`,
|
|
229
|
+
` ${theme.dim(lbl("Chats Limpos:"))} ${theme.cyan(String(chatsCleaned))} ${theme.dim("excluídos (>24h)")}`,
|
|
230
|
+
` ${theme.dim("───────────────────────────────────────")}`,
|
|
142
231
|
` ${theme.bold("Ações:")}`,
|
|
143
|
-
` ${this.hoveredActionRow === 13 ? theme.bgHover(` ${theme.cyan("[ R ] Recarregar")} `) : `${theme.cyan("[ R ]")} Recarregar`}`,
|
|
144
|
-
` ${this.hoveredActionRow === 14 ? theme.bgHover(` ${theme.yellow("[ Z ] Zerar Cooldowns")} `) : `${theme.yellow("[ Z ]")} Zerar Cooldowns`}`,
|
|
145
|
-
"",
|
|
146
|
-
this.actionMessage ? ` ${this.actionMessage}` : "",
|
|
147
232
|
];
|
|
148
233
|
|
|
234
|
+
const recarregarIdx = leftContent.length;
|
|
235
|
+
const zerarIdx = leftContent.length + 1;
|
|
236
|
+
this.lastActionRecarregarRow = 5 + recarregarIdx;
|
|
237
|
+
this.lastActionZerarRow = 5 + zerarIdx;
|
|
238
|
+
|
|
239
|
+
leftContent.push(
|
|
240
|
+
` ${this.hoveredActionRow === this.lastActionRecarregarRow ? theme.bgHover(` ${theme.cyan("[ R ] Recarregar")} `) : ` ${theme.cyan("[ R ]")} Recarregar`}`,
|
|
241
|
+
` ${this.hoveredActionRow === this.lastActionZerarRow ? theme.bgHover(` ${theme.yellow("[ Z ] Zerar Cooldowns")} `) : ` ${theme.yellow("[ Z ]")} Zerar Cooldowns`}`,
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
const boxHeight = Math.max(contentH, leftContent.length + 2);
|
|
245
|
+
|
|
149
246
|
const leftBox = drawBox({
|
|
150
|
-
title: "Sistema",
|
|
247
|
+
title: "Sistema & Performance",
|
|
151
248
|
width: leftW,
|
|
152
|
-
height:
|
|
249
|
+
height: boxHeight,
|
|
153
250
|
borderColor: theme.borderInactive,
|
|
154
251
|
titleColor: theme.cyan,
|
|
252
|
+
footer: this.actionMessage || undefined,
|
|
155
253
|
content: leftContent,
|
|
156
254
|
});
|
|
157
|
-
|
|
158
255
|
// Right Column: Accounts Pool Status
|
|
159
|
-
const
|
|
256
|
+
const rawAccounts = data?.accounts || [];
|
|
257
|
+
const accounts = [...rawAccounts].sort((a, b) => {
|
|
258
|
+
const scoreA = getAccountReadinessScore(a);
|
|
259
|
+
const scoreB = getAccountReadinessScore(b);
|
|
260
|
+
if (scoreB !== scoreA) {
|
|
261
|
+
return scoreB - scoreA;
|
|
262
|
+
}
|
|
263
|
+
return 0;
|
|
264
|
+
});
|
|
265
|
+
const availableCount = accounts.filter((a) => !a.onCooldown).length;
|
|
160
266
|
const readyCount = accounts.filter((a) => !a.onCooldown && a.headersReady).length;
|
|
267
|
+
const standbyCount = accounts.filter((a) => !a.onCooldown && !a.headersReady).length;
|
|
268
|
+
const cooldownCount = accounts.filter((a) => a.onCooldown).length;
|
|
269
|
+
const poolPct = accounts.length > 0 ? Math.round((availableCount / accounts.length) * 100) : 0;
|
|
270
|
+
const poolColor = poolPct >= 70 ? theme.green : poolPct >= 40 ? theme.yellow : theme.red;
|
|
271
|
+
const poolBar = renderProgressBar(poolPct, 8, poolColor);
|
|
272
|
+
const innerRightW = Math.max(30, rightW - 2);
|
|
273
|
+
const emailWidth = Math.max(16, Math.min(20, innerRightW - 27));
|
|
161
274
|
const rightContent: string[] = [
|
|
162
|
-
""
|
|
163
|
-
` ${theme.dim("
|
|
164
|
-
` ${theme.dim("
|
|
275
|
+
` ${theme.bold("Disponibilidade:")} [${poolBar}] ${poolColor(`${availableCount}/${accounts.length} (${poolPct}%)`)}`,
|
|
276
|
+
` ${theme.dim("─".repeat(Math.max(32, innerRightW - 2)))}`,
|
|
277
|
+
` ${theme.dim(`# ${pad("Conta", emailWidth)} Carga Status`)}`,
|
|
278
|
+
` ${theme.dim("─".repeat(Math.max(32, innerRightW - 2)))}`,
|
|
165
279
|
];
|
|
166
280
|
|
|
167
281
|
if (accounts.length === 0) {
|
|
168
282
|
rightContent.push(` ${theme.muted("Nenhuma conta adicionada. (Vá em [5] Contas)")}`);
|
|
169
283
|
} else {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
const
|
|
284
|
+
const maxVisibleAccounts = Math.max(6, boxHeight - 7);
|
|
285
|
+
accounts.slice(0, maxVisibleAccounts).forEach((acc, idx) => {
|
|
286
|
+
const num = pad(String(idx + 1) + ".", 4);
|
|
287
|
+
const name = pad(truncate(acc.emailOrName, emailWidth - 1), emailWidth);
|
|
288
|
+
const active = acc.activeStreams || 0;
|
|
289
|
+
const limit = acc.streamLimit || 1;
|
|
290
|
+
const loadBadge = active > 0 ? theme.yellow(`[${active}/${limit}]`) : theme.dim(`[0/${limit}]`);
|
|
291
|
+
|
|
173
292
|
let status = theme.green(`${glyphs.bullet} Pronto`);
|
|
174
|
-
if (acc.onCooldown) {
|
|
175
|
-
|
|
176
|
-
|
|
293
|
+
if (active > 0 && !acc.onCooldown && acc.headersReady) {
|
|
294
|
+
status = theme.yellow(`● Gerando `);
|
|
295
|
+
} else if (acc.onCooldown) {
|
|
296
|
+
const reason = acc.cooldownReason || "";
|
|
297
|
+
if (
|
|
298
|
+
reason.startsWith("AuthFailed") ||
|
|
299
|
+
reason.startsWith("AuthPermanentFailure") ||
|
|
300
|
+
reason.includes("login methods exhausted")
|
|
301
|
+
) {
|
|
302
|
+
status = theme.red(`❌ Auth Fail`);
|
|
303
|
+
} else if (reason === "WafChallenge") {
|
|
304
|
+
status = theme.peach(`🛡️ WAF Block`);
|
|
305
|
+
} else {
|
|
306
|
+
const mins = Math.max(1, Math.round(acc.remainingCooldownMs / 60000));
|
|
307
|
+
status = theme.yellow(`⚠️ ${mins}m cd`);
|
|
308
|
+
}
|
|
177
309
|
} else if (!acc.headersReady) {
|
|
178
310
|
status = acc.isInitialized
|
|
179
311
|
? theme.yellow(`◐ Aquecendo...`)
|
|
180
312
|
: theme.muted(`○ Standby`);
|
|
181
313
|
}
|
|
182
|
-
rightContent.push(` ${num}
|
|
314
|
+
rightContent.push(` ${num}${name} ${loadBadge} ${status}`);
|
|
183
315
|
});
|
|
184
316
|
}
|
|
185
317
|
|
|
318
|
+
const summaryParts: string[] = [];
|
|
319
|
+
if (readyCount > 0) summaryParts.push(`${readyCount} warm`);
|
|
320
|
+
if (standbyCount > 0) summaryParts.push(`${standbyCount} standby`);
|
|
321
|
+
if (cooldownCount > 0) summaryParts.push(`${cooldownCount} cd`);
|
|
322
|
+
const rightFooter = summaryParts.length > 0 ? `💡 ${summaryParts.join(" · ")} · [5] Contas` : undefined;
|
|
323
|
+
|
|
186
324
|
const rightBox = drawBox({
|
|
187
|
-
title: `Contas (${
|
|
325
|
+
title: `Contas Pool (${availableCount}/${accounts.length})`,
|
|
188
326
|
width: rightW,
|
|
189
|
-
height:
|
|
327
|
+
height: boxHeight,
|
|
190
328
|
borderColor: theme.borderInactive,
|
|
191
329
|
titleColor: theme.lavender,
|
|
330
|
+
footer: rightFooter,
|
|
192
331
|
content: rightContent,
|
|
193
332
|
});
|
|
194
333
|
|