qwenproxy-cli 1.0.32 → 1.1.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/README.es.md +269 -0
- package/README.md +175 -782
- package/README.pt-BR.md +1033 -0
- package/package.json +4 -4
- package/src/api/server.ts +18 -2
- package/src/core/accounts.ts +130 -0
- package/src/core/config.ts +95 -4
- package/src/index.ts +3 -0
- package/src/routes/chat/context.ts +9 -9
- package/src/routes/chat/index.ts +15 -6
- package/src/services/qwen-chat-pool.ts +4 -5
- package/src/services/qwen.ts +2 -5
- package/src/tui/app.ts +8 -9
- package/src/tui/index.ts +3 -0
- package/src/tui/proxy-client.ts +3 -2
- package/src/tui/screen.ts +10 -3
- package/src/tui/settings.ts +2 -0
- package/src/tui/theme.ts +1 -0
- package/src/tui/types.ts +1 -0
- package/src/tui/views/accounts-view.ts +343 -23
- package/src/tui/views/chat-view.ts +209 -14
- package/src/tui/views/status-view.ts +34 -3
|
@@ -4,13 +4,18 @@
|
|
|
4
4
|
|
|
5
5
|
import type { TuiView, ProxyStatusSnapshot } from "../types.ts";
|
|
6
6
|
import type { KeyEvent } from "../screen.ts";
|
|
7
|
-
import { theme, glyphs, drawBox, pad, truncate } from "../theme.ts";
|
|
7
|
+
import { theme, glyphs, drawBox, pad, truncate, getClipboardText } from "../theme.ts";
|
|
8
8
|
import {
|
|
9
9
|
fetchProxyStatus,
|
|
10
10
|
resetAllCooldowns,
|
|
11
11
|
resetAccountCooldownById,
|
|
12
12
|
} from "../proxy-client.ts";
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
addAccount,
|
|
15
|
+
removeAccount,
|
|
16
|
+
parseBatchAccounts,
|
|
17
|
+
addAccountsBatch,
|
|
18
|
+
} from "../../core/accounts.ts";
|
|
14
19
|
import { ServerManager } from "../server-manager.ts";
|
|
15
20
|
import { config } from "../../core/config.ts";
|
|
16
21
|
export function formatCooldownReason(reason?: string | null, maxLen = 28): string {
|
|
@@ -50,16 +55,24 @@ export class AccountsView implements TuiView {
|
|
|
50
55
|
private statusMessage = "";
|
|
51
56
|
private statusMessageTimer: NodeJS.Timeout | null = null;
|
|
52
57
|
private isAddModalOpen = false;
|
|
58
|
+
private isBatchModalOpen = false;
|
|
53
59
|
private addEmailInput = "";
|
|
54
60
|
private addPasswordInput = "";
|
|
55
61
|
private addEmailCursor = 0;
|
|
56
62
|
private addPasswordCursor = 0;
|
|
57
63
|
private addActiveField: "email" | "password" = "email";
|
|
64
|
+
private batchInput = "";
|
|
65
|
+
private batchCursor = 0;
|
|
66
|
+
private batchHoveredButton: "import" | "cancel" | null = null;
|
|
67
|
+
private batchActiveButton: "import" | "cancel" | null = null;
|
|
68
|
+
private lastBatchModalLeftPad = 0;
|
|
69
|
+
private lastBatchModalStartRow = 4;
|
|
58
70
|
private hoveredActionRow: number | null = null;
|
|
59
71
|
private hoveredAccountIndex: number | null = null;
|
|
60
72
|
private modalHoveredField: "email" | "password" | "save" | "cancel" | null = null;
|
|
61
73
|
private lastModalLeftPad = 0;
|
|
62
74
|
private lastLeftW = 46;
|
|
75
|
+
private accountsScrollOffset = 0;
|
|
63
76
|
private confirmDialog: {
|
|
64
77
|
type: "remove_account" | "delete_account_chats" | "delete_all_chats";
|
|
65
78
|
title: string;
|
|
@@ -71,7 +84,9 @@ export class AccountsView implements TuiView {
|
|
|
71
84
|
private lastConfirmModalLeftPad = 0;
|
|
72
85
|
private lastConfirmModalStartRow = 0;
|
|
73
86
|
constructor() {
|
|
74
|
-
|
|
87
|
+
if (!process.env.NODE_TEST_CONTEXT) {
|
|
88
|
+
void this.refresh();
|
|
89
|
+
}
|
|
75
90
|
}
|
|
76
91
|
|
|
77
92
|
public onActivate(): void {
|
|
@@ -79,7 +94,7 @@ export class AccountsView implements TuiView {
|
|
|
79
94
|
}
|
|
80
95
|
|
|
81
96
|
public isCapturingText(): boolean {
|
|
82
|
-
return this.isAddModalOpen || this.confirmDialog !== null;
|
|
97
|
+
return this.isAddModalOpen || this.isBatchModalOpen || this.confirmDialog !== null;
|
|
83
98
|
}
|
|
84
99
|
public getShortcuts(): Array<{ key: string; label: string }> {
|
|
85
100
|
if (this.confirmDialog) {
|
|
@@ -88,6 +103,13 @@ export class AccountsView implements TuiView {
|
|
|
88
103
|
{ key: "N / Esc", label: "Cancelar" },
|
|
89
104
|
];
|
|
90
105
|
}
|
|
106
|
+
if (this.isBatchModalOpen) {
|
|
107
|
+
return [
|
|
108
|
+
{ key: "Enter", label: "Importar" },
|
|
109
|
+
{ key: "Ctrl+V", label: "Colar Lote" },
|
|
110
|
+
{ key: "Esc", label: "Cancelar" },
|
|
111
|
+
];
|
|
112
|
+
}
|
|
91
113
|
if (this.isAddModalOpen) {
|
|
92
114
|
return [
|
|
93
115
|
{ key: "↑↓/Mouse", label: "Alternar" },
|
|
@@ -97,6 +119,7 @@ export class AccountsView implements TuiView {
|
|
|
97
119
|
}
|
|
98
120
|
return [
|
|
99
121
|
{ key: "a", label: "Adicionar Conta" },
|
|
122
|
+
{ key: "b", label: "Importar em Lote" },
|
|
100
123
|
{ key: "d", label: "Remover Conta" },
|
|
101
124
|
{ key: "x", label: "Limpar Chats" },
|
|
102
125
|
{ key: "l", label: "Limpar Todos Chats" },
|
|
@@ -169,6 +192,39 @@ export class AccountsView implements TuiView {
|
|
|
169
192
|
this.setStatusMessage(theme.red(`✗ Erro ao salvar: ${err?.message || String(err)}`));
|
|
170
193
|
}
|
|
171
194
|
}
|
|
195
|
+
private async saveBatchAccounts(): Promise<void> {
|
|
196
|
+
const { entries } = parseBatchAccounts(this.batchInput);
|
|
197
|
+
if (entries.length === 0) {
|
|
198
|
+
this.setStatusMessage(theme.yellow("[!] Nenhuma conta válida detectada"));
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
const result = addAccountsBatch(entries);
|
|
204
|
+
this.isBatchModalOpen = false;
|
|
205
|
+
this.batchInput = "";
|
|
206
|
+
this.batchCursor = 0;
|
|
207
|
+
this.batchActiveButton = null;
|
|
208
|
+
this.batchHoveredButton = null;
|
|
209
|
+
await this.refresh();
|
|
210
|
+
|
|
211
|
+
const addedCount = result.added.length;
|
|
212
|
+
const skippedCount = result.skipped.length;
|
|
213
|
+
if (addedCount > 0) {
|
|
214
|
+
const msg =
|
|
215
|
+
skippedCount > 0
|
|
216
|
+
? `✓ ${addedCount} conta(s) adicionada(s)! (${skippedCount} já existiam)`
|
|
217
|
+
: `✓ ${addedCount} conta(s) adicionada(s) em lote!`;
|
|
218
|
+
this.setStatusMessage(theme.green(msg));
|
|
219
|
+
} else {
|
|
220
|
+
this.setStatusMessage(
|
|
221
|
+
theme.yellow(`[!] Todas as ${skippedCount} conta(s) já existiam no banco.`),
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
} catch (err: any) {
|
|
225
|
+
this.setStatusMessage(theme.red(`✗ Erro no lote: ${err?.message || String(err)}`));
|
|
226
|
+
}
|
|
227
|
+
}
|
|
172
228
|
public async handleKey(key: KeyEvent): Promise<boolean | void> {
|
|
173
229
|
// 0. Confirm Dialog Active
|
|
174
230
|
if (this.confirmDialog) {
|
|
@@ -249,6 +305,143 @@ export class AccountsView implements TuiView {
|
|
|
249
305
|
}
|
|
250
306
|
return true;
|
|
251
307
|
}
|
|
308
|
+
// 0.5 Batch Account Import Modal Active
|
|
309
|
+
if (this.isBatchModalOpen) {
|
|
310
|
+
if (key.name === "escape") {
|
|
311
|
+
this.isBatchModalOpen = false;
|
|
312
|
+
this.batchInput = "";
|
|
313
|
+
this.batchCursor = 0;
|
|
314
|
+
this.batchActiveButton = null;
|
|
315
|
+
this.batchHoveredButton = null;
|
|
316
|
+
return true;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Keyboard button selection navigation
|
|
320
|
+
if (key.name === "tab" || key.name === "left" || key.name === "right") {
|
|
321
|
+
if (this.batchActiveButton === null) {
|
|
322
|
+
this.batchActiveButton = "cancel";
|
|
323
|
+
} else {
|
|
324
|
+
this.batchActiveButton = this.batchActiveButton === "import" ? "cancel" : "import";
|
|
325
|
+
}
|
|
326
|
+
this.batchHoveredButton = null;
|
|
327
|
+
return true;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Mouse hover in Batch modal
|
|
331
|
+
if (key.name === "hover" && key.mouse) {
|
|
332
|
+
const { row, col } = key.mouse;
|
|
333
|
+
const btnRow = (this.lastBatchModalStartRow || 4) + 11;
|
|
334
|
+
if (row === btnRow) {
|
|
335
|
+
const leftPad = this.lastBatchModalLeftPad || 0;
|
|
336
|
+
const relCol = col - leftPad;
|
|
337
|
+
let btn: "import" | "cancel" | null = null;
|
|
338
|
+
if (relCol >= 3 && relCol <= 24) {
|
|
339
|
+
btn = "import";
|
|
340
|
+
} else if (relCol >= 25 && relCol <= 44) {
|
|
341
|
+
btn = "cancel";
|
|
342
|
+
}
|
|
343
|
+
if (this.batchHoveredButton !== btn) {
|
|
344
|
+
this.batchHoveredButton = btn;
|
|
345
|
+
return true;
|
|
346
|
+
}
|
|
347
|
+
return true;
|
|
348
|
+
}
|
|
349
|
+
if (this.batchHoveredButton !== null) {
|
|
350
|
+
this.batchHoveredButton = null;
|
|
351
|
+
return true;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Mouse click in Batch modal
|
|
356
|
+
if (key.name === "click" && key.mouse) {
|
|
357
|
+
const { row, col } = key.mouse;
|
|
358
|
+
const btnRow = (this.lastBatchModalStartRow || 4) + 11;
|
|
359
|
+
if (row === btnRow) {
|
|
360
|
+
const leftPad = this.lastBatchModalLeftPad || 0;
|
|
361
|
+
const relCol = col - leftPad;
|
|
362
|
+
if (relCol >= 3 && relCol <= 24) {
|
|
363
|
+
await this.saveBatchAccounts();
|
|
364
|
+
return true;
|
|
365
|
+
}
|
|
366
|
+
if (relCol >= 25 && relCol <= 44) {
|
|
367
|
+
this.isBatchModalOpen = false;
|
|
368
|
+
this.batchInput = "";
|
|
369
|
+
this.batchCursor = 0;
|
|
370
|
+
this.batchActiveButton = null;
|
|
371
|
+
this.batchHoveredButton = null;
|
|
372
|
+
return true;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Paste event or Ctrl+V
|
|
378
|
+
if (key.name === "paste" || (key.ctrl && (key.name === "v" || key.raw === "\x16"))) {
|
|
379
|
+
const pasted = key.name === "paste" && key.char ? key.char : getClipboardText();
|
|
380
|
+
if (pasted) {
|
|
381
|
+
this.batchInput =
|
|
382
|
+
this.batchInput.slice(0, this.batchCursor) +
|
|
383
|
+
pasted +
|
|
384
|
+
this.batchInput.slice(this.batchCursor);
|
|
385
|
+
this.batchCursor += pasted.length;
|
|
386
|
+
this.batchActiveButton = null;
|
|
387
|
+
return true;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Single Ctrl+C in batch modal clears buffer
|
|
392
|
+
if (key.ctrl && key.name === "c") {
|
|
393
|
+
this.batchInput = "";
|
|
394
|
+
this.batchCursor = 0;
|
|
395
|
+
return true;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Backspace
|
|
399
|
+
if (key.name === "backspace") {
|
|
400
|
+
if (this.batchCursor > 0) {
|
|
401
|
+
this.batchInput =
|
|
402
|
+
this.batchInput.slice(0, this.batchCursor - 1) +
|
|
403
|
+
this.batchInput.slice(this.batchCursor);
|
|
404
|
+
this.batchCursor--;
|
|
405
|
+
}
|
|
406
|
+
return true;
|
|
407
|
+
}
|
|
408
|
+
// Delete
|
|
409
|
+
if (key.name === "delete") {
|
|
410
|
+
if (this.batchCursor < this.batchInput.length) {
|
|
411
|
+
this.batchInput =
|
|
412
|
+
this.batchInput.slice(0, this.batchCursor) +
|
|
413
|
+
this.batchInput.slice(this.batchCursor + 1);
|
|
414
|
+
}
|
|
415
|
+
return true;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Enter saves or triggers focused button
|
|
419
|
+
if (key.name === "return") {
|
|
420
|
+
const target = this.batchHoveredButton || this.batchActiveButton;
|
|
421
|
+
if (target === "cancel") {
|
|
422
|
+
this.isBatchModalOpen = false;
|
|
423
|
+
this.batchInput = "";
|
|
424
|
+
this.batchCursor = 0;
|
|
425
|
+
this.batchActiveButton = null;
|
|
426
|
+
this.batchHoveredButton = null;
|
|
427
|
+
return true;
|
|
428
|
+
}
|
|
429
|
+
await this.saveBatchAccounts();
|
|
430
|
+
return true;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Type character into batch buffer (including newline)
|
|
434
|
+
if (key.char && !key.ctrl && !key.meta && key.name !== "tab") {
|
|
435
|
+
this.batchInput =
|
|
436
|
+
this.batchInput.slice(0, this.batchCursor) +
|
|
437
|
+
key.char +
|
|
438
|
+
this.batchInput.slice(this.batchCursor);
|
|
439
|
+
this.batchCursor += key.char.length;
|
|
440
|
+
return true;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
return true;
|
|
444
|
+
}
|
|
252
445
|
|
|
253
446
|
// 1. Add Account Modal Active
|
|
254
447
|
if (this.isAddModalOpen) {
|
|
@@ -352,7 +545,6 @@ export class AccountsView implements TuiView {
|
|
|
352
545
|
|
|
353
546
|
// Paste from clipboard with Ctrl+V
|
|
354
547
|
if (key.ctrl && (key.name === "v" || key.raw === "\x16")) {
|
|
355
|
-
const { getClipboardText } = require("../theme.ts");
|
|
356
548
|
const pasted = getClipboardText();
|
|
357
549
|
if (pasted) {
|
|
358
550
|
if (this.addActiveField === "email") {
|
|
@@ -460,6 +652,16 @@ export class AccountsView implements TuiView {
|
|
|
460
652
|
return true;
|
|
461
653
|
}
|
|
462
654
|
|
|
655
|
+
// Open Batch Import modal with 'b' or 'B'
|
|
656
|
+
if ((key.name === "b" || key.name === "B") && !key.ctrl) {
|
|
657
|
+
this.isBatchModalOpen = true;
|
|
658
|
+
this.batchInput = "";
|
|
659
|
+
this.batchCursor = 0;
|
|
660
|
+
this.batchActiveButton = null;
|
|
661
|
+
this.batchHoveredButton = null;
|
|
662
|
+
return true;
|
|
663
|
+
}
|
|
664
|
+
|
|
463
665
|
// Delete selected account with 'd' or 'D' (requires confirmation)
|
|
464
666
|
if ((key.name === "d" || key.name === "D") && !key.ctrl) {
|
|
465
667
|
const selected = accounts[this.selectedIndex];
|
|
@@ -546,9 +748,10 @@ export class AccountsView implements TuiView {
|
|
|
546
748
|
const leftW = this.lastLeftW || 46;
|
|
547
749
|
|
|
548
750
|
// Account list rows start at row 8 (row 4=box border, 5=blank, 6=header, 7=divider)
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
751
|
+
const availableRows = 14;
|
|
752
|
+
if (col >= 2 && col <= leftW - 1 && row >= 8 && row < 8 + Math.min(accounts.length, availableRows)) {
|
|
753
|
+
const hoverIdx = this.accountsScrollOffset + (row - 8);
|
|
754
|
+
if (this.hoveredAccountIndex !== hoverIdx && hoverIdx < accounts.length) {
|
|
552
755
|
this.hoveredAccountIndex = hoverIdx;
|
|
553
756
|
return true;
|
|
554
757
|
}
|
|
@@ -559,7 +762,13 @@ export class AccountsView implements TuiView {
|
|
|
559
762
|
|
|
560
763
|
// Right panel action buttons hover (rows 15 to 20)
|
|
561
764
|
if (col >= leftW) {
|
|
562
|
-
if (row
|
|
765
|
+
if (row === 15) {
|
|
766
|
+
const actionRow = col < leftW + 18 ? 15 : 21;
|
|
767
|
+
if (this.hoveredActionRow !== actionRow) {
|
|
768
|
+
this.hoveredActionRow = actionRow;
|
|
769
|
+
return true;
|
|
770
|
+
}
|
|
771
|
+
} else if (row >= 16 && row <= 20) {
|
|
563
772
|
if (this.hoveredActionRow !== row) {
|
|
564
773
|
this.hoveredActionRow = row;
|
|
565
774
|
return true;
|
|
@@ -578,15 +787,23 @@ export class AccountsView implements TuiView {
|
|
|
578
787
|
const { row, col } = key.mouse;
|
|
579
788
|
const leftW = this.lastLeftW || 46;
|
|
580
789
|
|
|
790
|
+
const availableRows = Math.max(4, (this.lastLeftW ? 18 : 14));
|
|
581
791
|
// Click on account row (rows 8, 9, ...)
|
|
582
|
-
if (col >= 2 && col <= leftW - 1 && row >= 8 && row < 8 + accounts.length) {
|
|
583
|
-
this.
|
|
584
|
-
|
|
792
|
+
if (col >= 2 && col <= leftW - 1 && row >= 8 && row < 8 + Math.min(accounts.length, availableRows)) {
|
|
793
|
+
const targetIdx = this.accountsScrollOffset + (row - 8);
|
|
794
|
+
if (targetIdx >= 0 && targetIdx < accounts.length) {
|
|
795
|
+
this.selectedIndex = targetIdx;
|
|
796
|
+
return true;
|
|
797
|
+
}
|
|
585
798
|
}
|
|
586
|
-
// Right panel action buttons click (rows 15, 16, 17, 18)
|
|
799
|
+
// Right panel action buttons click (rows 15, 16, 17, 18, 19, 20)
|
|
587
800
|
if (col >= leftW) {
|
|
588
801
|
if (row === 15) {
|
|
589
|
-
|
|
802
|
+
if (col < leftW + 18) {
|
|
803
|
+
await this.handleKey({ name: "a", ctrl: false, shift: false, meta: false });
|
|
804
|
+
} else {
|
|
805
|
+
await this.handleKey({ name: "b", ctrl: false, shift: false, meta: false });
|
|
806
|
+
}
|
|
590
807
|
return true;
|
|
591
808
|
}
|
|
592
809
|
if (row === 16) {
|
|
@@ -668,6 +885,11 @@ export class AccountsView implements TuiView {
|
|
|
668
885
|
const accounts = data?.accounts || [];
|
|
669
886
|
const selected = accounts[this.selectedIndex];
|
|
670
887
|
|
|
888
|
+
const availableRows = Math.max(4, contentH - 6);
|
|
889
|
+
if (this.selectedIndex >= accounts.length && accounts.length > 0) {
|
|
890
|
+
this.selectedIndex = accounts.length - 1;
|
|
891
|
+
}
|
|
892
|
+
|
|
671
893
|
// Left Panel: Accounts List Table
|
|
672
894
|
const leftContent: string[] = [
|
|
673
895
|
"",
|
|
@@ -678,13 +900,30 @@ export class AccountsView implements TuiView {
|
|
|
678
900
|
if (accounts.length === 0) {
|
|
679
901
|
leftContent.push("");
|
|
680
902
|
leftContent.push(` ${theme.yellow("Nenhuma conta configurada ainda.")}`);
|
|
681
|
-
leftContent.push(
|
|
903
|
+
leftContent.push(
|
|
904
|
+
` ${theme.muted("Pressione ")}${theme.cyan("'A'")}${theme.muted(" (individual) ou ")}${theme.cyan("'B'")}${theme.muted(" (em lote).")}`,
|
|
905
|
+
);
|
|
682
906
|
} else {
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
907
|
+
// Clamp scroll offset to keep selectedIndex inside visible window
|
|
908
|
+
if (this.selectedIndex < this.accountsScrollOffset) {
|
|
909
|
+
this.accountsScrollOffset = this.selectedIndex;
|
|
910
|
+
} else if (this.selectedIndex >= this.accountsScrollOffset + availableRows) {
|
|
911
|
+
this.accountsScrollOffset = this.selectedIndex - availableRows + 1;
|
|
912
|
+
}
|
|
913
|
+
const maxScroll = Math.max(0, accounts.length - availableRows);
|
|
914
|
+
this.accountsScrollOffset = Math.max(0, Math.min(this.accountsScrollOffset, maxScroll));
|
|
915
|
+
|
|
916
|
+
const visibleAccounts = accounts.slice(
|
|
917
|
+
this.accountsScrollOffset,
|
|
918
|
+
this.accountsScrollOffset + availableRows,
|
|
919
|
+
);
|
|
920
|
+
|
|
921
|
+
visibleAccounts.forEach((acc, visibleIdx) => {
|
|
922
|
+
const actualIdx = this.accountsScrollOffset + visibleIdx;
|
|
923
|
+
const isFocused = actualIdx === this.selectedIndex;
|
|
924
|
+
const isHovered = actualIdx === this.hoveredAccountIndex;
|
|
686
925
|
const pointer = isFocused ? theme.cyan(`${glyphs.pointer} `) : " ";
|
|
687
|
-
const num = pad(String(
|
|
926
|
+
const num = pad(String(actualIdx + 1) + ".", 4);
|
|
688
927
|
const name = pad(truncate(acc.emailOrName, 20), 22);
|
|
689
928
|
|
|
690
929
|
let status = theme.green(`${glyphs.bullet} Pronto `);
|
|
@@ -722,8 +961,13 @@ export class AccountsView implements TuiView {
|
|
|
722
961
|
});
|
|
723
962
|
}
|
|
724
963
|
|
|
964
|
+
const boxTitle =
|
|
965
|
+
accounts.length > availableRows
|
|
966
|
+
? `Contas (${this.accountsScrollOffset + 1}-${Math.min(accounts.length, this.accountsScrollOffset + availableRows)} de ${accounts.length})`
|
|
967
|
+
: `Contas (${accounts.length})`;
|
|
968
|
+
|
|
725
969
|
const leftBox = drawBox({
|
|
726
|
-
title:
|
|
970
|
+
title: boxTitle,
|
|
727
971
|
width: leftW,
|
|
728
972
|
height: contentH,
|
|
729
973
|
borderColor: theme.borderActive,
|
|
@@ -731,7 +975,6 @@ export class AccountsView implements TuiView {
|
|
|
731
975
|
footer: this.statusMessage || undefined,
|
|
732
976
|
content: leftContent,
|
|
733
977
|
});
|
|
734
|
-
|
|
735
978
|
// Right Panel: Selected Account Details
|
|
736
979
|
const rightContent: string[] = [
|
|
737
980
|
"",
|
|
@@ -747,7 +990,9 @@ export class AccountsView implements TuiView {
|
|
|
747
990
|
rightContent.push("");
|
|
748
991
|
rightContent.push("");
|
|
749
992
|
rightContent.push(` ${theme.dim("─────────────────────────────────")}`);
|
|
750
|
-
|
|
993
|
+
const btnA = this.hoveredActionRow === 15 ? theme.bgHover(` ${theme.cyan("[ A ] Adicionar")} `) : `${theme.cyan("[ A ]")} Adicionar`;
|
|
994
|
+
const btnB = this.hoveredActionRow === 21 ? theme.bgHover(` ${theme.cyan("[ B ] Em Lote")} `) : `${theme.cyan("[ B ]")} Em Lote`;
|
|
995
|
+
rightContent.push(` ${btnA} ${btnB}`);
|
|
751
996
|
} else {
|
|
752
997
|
const email = truncate(selected.emailOrName, 18);
|
|
753
998
|
rightContent.push(` ${theme.bold("Conta:")} ${theme.cyan(email)}`);
|
|
@@ -773,7 +1018,9 @@ export class AccountsView implements TuiView {
|
|
|
773
1018
|
rightContent.push("");
|
|
774
1019
|
}
|
|
775
1020
|
rightContent.push(` ${theme.dim("─────────────────────────────────")}`);
|
|
776
|
-
|
|
1021
|
+
const btnA = this.hoveredActionRow === 15 ? theme.bgHover(` ${theme.cyan("[ A ] Adicionar")} `) : `${theme.cyan("[ A ]")} Adicionar`;
|
|
1022
|
+
const btnB = this.hoveredActionRow === 21 ? theme.bgHover(` ${theme.cyan("[ B ] Em Lote")} `) : `${theme.cyan("[ B ]")} Em Lote`;
|
|
1023
|
+
rightContent.push(` ${btnA} ${btnB}`);
|
|
777
1024
|
rightContent.push(` ${this.hoveredActionRow === 16 ? theme.bgHover(` ${theme.red("[ D ] Remover Conta")} `) : `${theme.red("[ D ]")} Remover Conta`}`);
|
|
778
1025
|
rightContent.push(` ${this.hoveredActionRow === 17 ? theme.bgHover(` ${theme.yellow("[ C ] Zerar Cooldown")} `) : `${theme.yellow("[ C ]")} Zerar Cooldown`}`);
|
|
779
1026
|
rightContent.push(` ${this.hoveredActionRow === 18 ? theme.bgHover(` ${theme.green("[ Z ] Zerar Todas")} `) : `${theme.green("[ Z ]")} Zerar Todas`}`);
|
|
@@ -797,6 +1044,79 @@ export class AccountsView implements TuiView {
|
|
|
797
1044
|
const rightRow = rightBox[r] || " ".repeat(rightW);
|
|
798
1045
|
mergedLines.push(leftRow + " " + rightRow);
|
|
799
1046
|
}
|
|
1047
|
+
if (this.isBatchModalOpen) {
|
|
1048
|
+
const modalW = Math.min(width - 4, 70);
|
|
1049
|
+
this.lastBatchModalLeftPad = Math.max(0, Math.floor((width - modalW) / 2));
|
|
1050
|
+
this.lastBatchModalStartRow = 4;
|
|
1051
|
+
|
|
1052
|
+
const parsed = parseBatchAccounts(this.batchInput);
|
|
1053
|
+
const count = parsed.entries.length;
|
|
1054
|
+
const invalidCount = parsed.invalid.length;
|
|
1055
|
+
|
|
1056
|
+
const rawLines = this.batchInput.split(/\r?\n/).filter(Boolean);
|
|
1057
|
+
let displaySnippet: string[];
|
|
1058
|
+
if (rawLines.length === 0) {
|
|
1059
|
+
displaySnippet = [
|
|
1060
|
+
"",
|
|
1061
|
+
" " + theme.dim("Nenhuma conta colada ainda."),
|
|
1062
|
+
" " + theme.cyan("Pressione Ctrl+V") + " " + theme.dim("para colar suas contas aqui..."),
|
|
1063
|
+
"",
|
|
1064
|
+
];
|
|
1065
|
+
} else {
|
|
1066
|
+
const previewLines = rawLines.slice(-4);
|
|
1067
|
+
displaySnippet = previewLines.map((l) => " " + truncate(l, modalW - 8));
|
|
1068
|
+
while (displaySnippet.length < 4) {
|
|
1069
|
+
displaySnippet.unshift("");
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
const countBadge =
|
|
1074
|
+
count > 0
|
|
1075
|
+
? theme.green(`✓ ${count} conta(s) detectada(s)`)
|
|
1076
|
+
: theme.yellow("0 contas detectadas");
|
|
1077
|
+
const invalidBadge =
|
|
1078
|
+
invalidCount > 0 ? theme.red(` | ${invalidCount} formato(s) ignorado(s)`) : "";
|
|
1079
|
+
|
|
1080
|
+
const isImportHovered = this.batchHoveredButton === "import";
|
|
1081
|
+
const isCancelHovered = this.batchHoveredButton === "cancel";
|
|
1082
|
+
const isImportSelected = !this.batchHoveredButton && this.batchActiveButton === "import";
|
|
1083
|
+
const isCancelSelected = !this.batchHoveredButton && this.batchActiveButton === "cancel";
|
|
1084
|
+
|
|
1085
|
+
const importLabel = " [ Enter ] Importar ";
|
|
1086
|
+
const cancelLabel = " [ Esc ] Cancelar ";
|
|
1087
|
+
const importBtn = isImportHovered
|
|
1088
|
+
? theme.bgHover(theme.green(importLabel))
|
|
1089
|
+
: isImportSelected
|
|
1090
|
+
? theme.bgSelected(theme.green(importLabel))
|
|
1091
|
+
: theme.green(importLabel);
|
|
1092
|
+
|
|
1093
|
+
const cancelBtn = isCancelHovered
|
|
1094
|
+
? theme.bgHover(theme.red(cancelLabel))
|
|
1095
|
+
: isCancelSelected
|
|
1096
|
+
? theme.bgSelected(theme.red(cancelLabel))
|
|
1097
|
+
: theme.muted(cancelLabel);
|
|
1098
|
+
const modalContent = [
|
|
1099
|
+
"",
|
|
1100
|
+
` ${theme.bold("Cole suas contas")} ${theme.dim("(email:senha, uma por linha ou formato .env)")}:`,
|
|
1101
|
+
` ${theme.dim("────────────────────────────────────────────────────────")}`,
|
|
1102
|
+
...displaySnippet,
|
|
1103
|
+
` ${theme.dim("────────────────────────────────────────────────────────")}`,
|
|
1104
|
+
` Status: ${countBadge}${invalidBadge}`,
|
|
1105
|
+
"",
|
|
1106
|
+
` ${importBtn} ${cancelBtn} ${theme.dim("(Ctrl+V colar)")}`,
|
|
1107
|
+
];
|
|
1108
|
+
const modalBox = drawBox({
|
|
1109
|
+
title: "Importar Contas em Lote (Multi-Contas)",
|
|
1110
|
+
width: modalW,
|
|
1111
|
+
height: Math.min(contentH, 13),
|
|
1112
|
+
borderColor: theme.borderActive,
|
|
1113
|
+
titleColor: theme.cyan,
|
|
1114
|
+
content: modalContent,
|
|
1115
|
+
});
|
|
1116
|
+
|
|
1117
|
+
const padStr = " ".repeat(this.lastBatchModalLeftPad);
|
|
1118
|
+
return modalBox.map((line) => padStr + line);
|
|
1119
|
+
}
|
|
800
1120
|
if (this.isAddModalOpen) {
|
|
801
1121
|
const modalW = Math.min(width - 4, 66);
|
|
802
1122
|
this.lastModalLeftPad = Math.max(0, Math.floor((width - modalW) / 2));
|