qwenproxy-cli 1.0.3 → 1.0.5
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/README.md +34 -23
- package/bin/qwenproxy.js +34 -13
- package/package.json +1 -1
- package/src/api/server.ts +4 -1
- package/src/clean-cache.ts +17 -11
- package/src/core/config.ts +3 -3
- package/src/index.ts +5 -1
- package/src/login.ts +5 -0
- package/src/routes/chat/index.ts +6 -1
- package/src/services/chat-cleanup.ts +25 -10
- package/src/services/playwright.ts +105 -8
- package/src/services/qwen-chat-pool.ts +3 -2
- package/src/services/qwen.ts +4 -1
- package/src/tui/app.ts +1 -1
- package/src/tui/proxy-client.ts +4 -1
- package/src/tui/screen.ts +47 -7
- package/src/tui/server-manager.ts +19 -11
- package/src/tui/theme.ts +41 -23
- package/src/tui/views/accounts-view.ts +197 -18
- package/src/tui/views/status-view.ts +4 -4
- package/src/tui/views/storage-view.ts +153 -14
|
@@ -7,7 +7,7 @@ import path from "node:path";
|
|
|
7
7
|
import type { TuiView } from "../types.ts";
|
|
8
8
|
import type { KeyEvent } from "../screen.ts";
|
|
9
9
|
import { theme, glyphs, drawBox, pad } from "../theme.ts";
|
|
10
|
-
import { pruneAllPlaywrightProfiles } from "../../services/playwright.ts";
|
|
10
|
+
import { pruneAllPlaywrightProfiles, cleanupOrphanProfiles } from "../../services/playwright.ts";
|
|
11
11
|
import { getProfilesDir } from "../../core/paths.ts";
|
|
12
12
|
import {
|
|
13
13
|
formatBytes,
|
|
@@ -34,8 +34,7 @@ export class StorageView implements TuiView {
|
|
|
34
34
|
private isScanning = false;
|
|
35
35
|
|
|
36
36
|
private addLog(message: string): void {
|
|
37
|
-
|
|
38
|
-
this.actionLogs.push(`${theme.dim(`[${time}]`)} ${message}`);
|
|
37
|
+
this.actionLogs.push(message);
|
|
39
38
|
if (this.actionLogs.length > 50) {
|
|
40
39
|
this.actionLogs.shift();
|
|
41
40
|
}
|
|
@@ -46,20 +45,43 @@ export class StorageView implements TuiView {
|
|
|
46
45
|
}
|
|
47
46
|
private hoveredActionRow: number | null = null;
|
|
48
47
|
private lastLeftW = 48;
|
|
48
|
+
private confirmDialog: {
|
|
49
|
+
title: string;
|
|
50
|
+
message: string;
|
|
51
|
+
detail: string;
|
|
52
|
+
onConfirm: () => Promise<void>;
|
|
53
|
+
} | null = null;
|
|
54
|
+
private confirmDialogHovered: "confirm" | "cancel" | null = null;
|
|
55
|
+
private lastConfirmModalLeftPad = 0;
|
|
56
|
+
private lastConfirmModalStartRow = 0;
|
|
57
|
+
|
|
58
|
+
public isCapturingText(): boolean {
|
|
59
|
+
return this.confirmDialog !== null;
|
|
60
|
+
}
|
|
61
|
+
|
|
49
62
|
public getShortcuts(): Array<{ key: string; label: string }> {
|
|
63
|
+
if (this.confirmDialog) {
|
|
64
|
+
return [
|
|
65
|
+
{ key: "S / Enter", label: "Confirmar" },
|
|
66
|
+
{ key: "N / Esc", label: "Cancelar" },
|
|
67
|
+
];
|
|
68
|
+
}
|
|
50
69
|
return [
|
|
51
70
|
{ key: "p", label: "Podar Caches" },
|
|
52
71
|
{ key: "b", label: "Limpar Navegadores" },
|
|
53
72
|
{ key: "z", label: "Zerar Cooldowns" },
|
|
73
|
+
{ key: "l", label: "Limpar Chats Qwen" },
|
|
54
74
|
{ key: "r", label: "Atualizar Disco" },
|
|
55
75
|
];
|
|
56
76
|
}
|
|
57
|
-
|
|
58
77
|
public async refresh(): Promise<void> {
|
|
59
78
|
if (this.isScanning) return;
|
|
60
79
|
this.isScanning = true;
|
|
61
80
|
|
|
62
81
|
try {
|
|
82
|
+
// 0. Auto-prune orphan directories from removed accounts
|
|
83
|
+
cleanupOrphanProfiles();
|
|
84
|
+
|
|
63
85
|
// 1. Scan profiles
|
|
64
86
|
const profilesDir = getProfilesDir();
|
|
65
87
|
let totalBytes = 0;
|
|
@@ -102,11 +124,68 @@ export class StorageView implements TuiView {
|
|
|
102
124
|
}
|
|
103
125
|
|
|
104
126
|
public async handleKey(key: KeyEvent): Promise<boolean | void> {
|
|
127
|
+
// 0. Confirm Dialog Active
|
|
128
|
+
if (this.confirmDialog) {
|
|
129
|
+
if (key.name === "s" || key.name === "S" || key.name === "enter" || key.name === "return") {
|
|
130
|
+
const dialog = this.confirmDialog;
|
|
131
|
+
this.confirmDialog = null;
|
|
132
|
+
this.confirmDialogHovered = null;
|
|
133
|
+
await dialog.onConfirm();
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
if (key.name === "escape" || key.name === "n" || key.name === "N") {
|
|
137
|
+
this.confirmDialog = null;
|
|
138
|
+
this.confirmDialogHovered = null;
|
|
139
|
+
this.addLog(theme.muted("Ação cancelada"));
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
if (key.name === "hover" && key.mouse) {
|
|
143
|
+
const { row, col } = key.mouse;
|
|
144
|
+
const btnRow = this.lastConfirmModalStartRow + 4;
|
|
145
|
+
if (row === btnRow) {
|
|
146
|
+
const startCol = this.lastConfirmModalLeftPad + 2;
|
|
147
|
+
if (col >= startCol && col <= startCol + 25) {
|
|
148
|
+
this.confirmDialogHovered = "confirm";
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
if (col >= startCol + 26 && col <= startCol + 50) {
|
|
152
|
+
this.confirmDialogHovered = "cancel";
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (this.confirmDialogHovered !== null) {
|
|
157
|
+
this.confirmDialogHovered = null;
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (key.name === "click" && key.mouse) {
|
|
162
|
+
const { row, col } = key.mouse;
|
|
163
|
+
const btnRow = this.lastConfirmModalStartRow + 4;
|
|
164
|
+
if (row === btnRow) {
|
|
165
|
+
const startCol = this.lastConfirmModalLeftPad + 2;
|
|
166
|
+
if (col >= startCol && col <= startCol + 25) {
|
|
167
|
+
const dialog = this.confirmDialog;
|
|
168
|
+
this.confirmDialog = null;
|
|
169
|
+
this.confirmDialogHovered = null;
|
|
170
|
+
await dialog.onConfirm();
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
if (col >= startCol + 26 && col <= startCol + 50) {
|
|
174
|
+
this.confirmDialog = null;
|
|
175
|
+
this.confirmDialogHovered = null;
|
|
176
|
+
this.addLog(theme.muted("Ação cancelada"));
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
|
|
105
184
|
// Mouse hover over quick actions
|
|
106
185
|
if (key.name === "hover" && key.mouse) {
|
|
107
186
|
const { row, col } = key.mouse;
|
|
108
187
|
const leftW = this.lastLeftW || 48;
|
|
109
|
-
if (col >= 2 && col <= leftW - 1 && row >= 13 && row <=
|
|
188
|
+
if (col >= 2 && col <= leftW - 1 && row >= 13 && row <= 17) {
|
|
110
189
|
if (this.hoveredActionRow !== row) {
|
|
111
190
|
this.hoveredActionRow = row;
|
|
112
191
|
return true;
|
|
@@ -135,12 +214,15 @@ export class StorageView implements TuiView {
|
|
|
135
214
|
return true;
|
|
136
215
|
}
|
|
137
216
|
if (row === 16) {
|
|
217
|
+
this.handleKey({ name: "l", ctrl: false, shift: false, meta: false });
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
if (row === 17) {
|
|
138
221
|
this.handleKey({ name: "r", ctrl: false, shift: false, meta: false });
|
|
139
222
|
return true;
|
|
140
223
|
}
|
|
141
224
|
}
|
|
142
225
|
}
|
|
143
|
-
|
|
144
226
|
// Reset cooldowns with 'z'
|
|
145
227
|
if ((key.name === "z" || key.name === "Z") && !key.ctrl) {
|
|
146
228
|
const cleared = resetAllCooldowns();
|
|
@@ -162,11 +244,12 @@ export class StorageView implements TuiView {
|
|
|
162
244
|
if ((key.name === "p" || key.name === "P") && !key.ctrl) {
|
|
163
245
|
try {
|
|
164
246
|
const res = pruneAllPlaywrightProfiles();
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
)
|
|
169
|
-
|
|
247
|
+
const orphanRes = cleanupOrphanProfiles();
|
|
248
|
+
let logMsg = `✓ Caches limpos: ${formatBytes(res.totalFreedBytes)} liberados em ${res.totalFreedFiles} arquivos (${res.profilesCleaned} perfis)`;
|
|
249
|
+
if (orphanRes.removedCount > 0) {
|
|
250
|
+
logMsg += ` + ${orphanRes.removedCount} perfil(is) órfão(s) removido(s)`;
|
|
251
|
+
}
|
|
252
|
+
this.addLog(theme.green(logMsg));
|
|
170
253
|
await this.refresh();
|
|
171
254
|
} catch (err: any) {
|
|
172
255
|
this.addLog(theme.red(`✗ Falha ao limpar perfis: ${err?.message || String(err)}`));
|
|
@@ -181,13 +264,13 @@ export class StorageView implements TuiView {
|
|
|
181
264
|
if (res.freedBytes > 0 || res.unusedDirs.length > 0) {
|
|
182
265
|
this.addLog(
|
|
183
266
|
theme.green(
|
|
184
|
-
`✓ Navegadores limpos: ${formatBytes(res.freedBytes)} recuperados
|
|
267
|
+
`✓ Navegadores limpos: ${formatBytes(res.freedBytes)} recuperados em disco (${res.unusedDirs.length} versões removidas)`,
|
|
185
268
|
),
|
|
186
269
|
);
|
|
187
270
|
} else {
|
|
188
271
|
this.addLog(
|
|
189
272
|
theme.green(
|
|
190
|
-
`✓ Navegadores verificados: nenhum navegador antigo encontrado
|
|
273
|
+
`✓ Navegadores verificados: nenhum navegador antigo encontrado`,
|
|
191
274
|
),
|
|
192
275
|
);
|
|
193
276
|
}
|
|
@@ -199,6 +282,29 @@ export class StorageView implements TuiView {
|
|
|
199
282
|
}
|
|
200
283
|
return true;
|
|
201
284
|
}
|
|
285
|
+
// Delete all remote chats with 'l' or 'L' (requires confirmation)
|
|
286
|
+
if ((key.name === "l" || key.name === "L") && !key.ctrl) {
|
|
287
|
+
this.confirmDialog = {
|
|
288
|
+
title: "⚠️ Confirmar Exclusão de Chats Remotos",
|
|
289
|
+
message: "Apagar TODOS os chats remotos de TODAS as contas no Qwen?",
|
|
290
|
+
detail: "Esta ação apagará permanentemente todas as conversas em chat.qwen.ai.",
|
|
291
|
+
onConfirm: async () => {
|
|
292
|
+
this.addLog(theme.yellow("⏳ Apagando chats no Qwen de todas as contas..."));
|
|
293
|
+
try {
|
|
294
|
+
const { deleteChatsForConfiguredAccounts } = await import("../../services/chat-cleanup.ts");
|
|
295
|
+
const res = await deleteChatsForConfiguredAccounts(true);
|
|
296
|
+
this.addLog(
|
|
297
|
+
theme.green(
|
|
298
|
+
`✓ Todos os chats remotos foram apagados no Qwen (${res.succeeded}/${res.attempted} contas)`,
|
|
299
|
+
),
|
|
300
|
+
);
|
|
301
|
+
} catch (err: any) {
|
|
302
|
+
this.addLog(theme.red(`✗ Falha ao apagar chats: ${err?.message || String(err)}`));
|
|
303
|
+
}
|
|
304
|
+
},
|
|
305
|
+
};
|
|
306
|
+
return true;
|
|
307
|
+
}
|
|
202
308
|
}
|
|
203
309
|
public render(width: number, height: number): string[] {
|
|
204
310
|
const contentH = Math.max(12, height);
|
|
@@ -233,7 +339,8 @@ export class StorageView implements TuiView {
|
|
|
233
339
|
` ${this.hoveredActionRow === 13 ? theme.bgHover(` ${theme.cyan("[ P ] Podar Caches")} `) : `${theme.cyan("[ P ]")} Podar Caches`}`,
|
|
234
340
|
` ${this.hoveredActionRow === 14 ? theme.bgHover(` ${theme.yellow("[ B ] Limpar Navegadores")} `) : `${theme.yellow("[ B ]")} Limpar Navegadores`}`,
|
|
235
341
|
` ${this.hoveredActionRow === 15 ? theme.bgHover(` ${theme.green("[ Z ] Zerar Todos os Cooldowns")} `) : `${theme.green("[ Z ]")} Zerar Todos os Cooldowns`}`,
|
|
236
|
-
` ${this.hoveredActionRow === 16 ? theme.bgHover(` ${theme.
|
|
342
|
+
` ${this.hoveredActionRow === 16 ? theme.bgHover(` ${theme.red("[ L ] Limpar Todos os Chats (Qwen)")} `) : `${theme.red("[ L ]")} Limpar Todos os Chats (Qwen)`}`,
|
|
343
|
+
` ${this.hoveredActionRow === 17 ? theme.bgHover(` ${theme.muted("[ R ] Atualizar Disco")} `) : `${theme.muted("[ R ]")} Atualizar Disco`}`,
|
|
237
344
|
"",
|
|
238
345
|
];
|
|
239
346
|
|
|
@@ -299,6 +406,38 @@ export class StorageView implements TuiView {
|
|
|
299
406
|
mergedLines.push(leftRow + " " + rightRow);
|
|
300
407
|
}
|
|
301
408
|
|
|
409
|
+
if (this.confirmDialog) {
|
|
410
|
+
const modalW = Math.min(width - 4, 66);
|
|
411
|
+
this.lastConfirmModalLeftPad = Math.max(0, Math.floor((width - modalW) / 2));
|
|
412
|
+
const confirmBtn =
|
|
413
|
+
this.confirmDialogHovered === "confirm"
|
|
414
|
+
? theme.bgHover(theme.red(" [ S / Enter ] Sim, Confirmar "))
|
|
415
|
+
: theme.red("[ S / Enter ] Sim, Confirmar");
|
|
416
|
+
const cancelBtn =
|
|
417
|
+
this.confirmDialogHovered === "cancel"
|
|
418
|
+
? theme.bgHover(theme.green(" [ N / Esc ] Cancelar "))
|
|
419
|
+
: theme.green("[ N / Esc ] Cancelar");
|
|
420
|
+
|
|
421
|
+
const modalContent = [
|
|
422
|
+
"",
|
|
423
|
+
` ${theme.bold(this.confirmDialog.message)}`,
|
|
424
|
+
` ${theme.muted(this.confirmDialog.detail)}`,
|
|
425
|
+
"",
|
|
426
|
+
` ${confirmBtn} ${cancelBtn}`,
|
|
427
|
+
];
|
|
428
|
+
|
|
429
|
+
const modalBox = drawBox({
|
|
430
|
+
title: this.confirmDialog.title,
|
|
431
|
+
width: modalW,
|
|
432
|
+
height: Math.min(contentH, 8),
|
|
433
|
+
borderColor: theme.red,
|
|
434
|
+
titleColor: theme.red,
|
|
435
|
+
content: modalContent,
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
const padStr = " ".repeat(this.lastConfirmModalLeftPad);
|
|
439
|
+
return modalBox.map((line) => padStr + line);
|
|
440
|
+
}
|
|
302
441
|
return mergedLines;
|
|
303
442
|
}
|
|
304
443
|
}
|