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.
@@ -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
- const time = new Date().toTimeString().slice(0, 8);
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 <= 16) {
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
- this.addLog(
166
- theme.green(
167
- `✓ Caches limpos: ${formatBytes(res.totalFreedBytes)} liberados em ${res.totalFreedFiles} arquivos (${res.profilesCleaned} perfis)`,
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 no SSD (${res.unusedDirs.length} versões removidas)`,
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 (SSD já otimizado)`,
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.muted("[ R ] Atualizar Disco")} `) : `${theme.muted("[ R ]")} Atualizar Disco`}`,
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
  }