qwenproxy-cli 1.0.31 → 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.
@@ -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 { addAccount, removeAccount } from "../../core/accounts.ts";
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
- this.refresh();
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
- if (col >= 2 && col <= leftW - 1 && row >= 8 && row < 8 + accounts.length) {
550
- const hoverIdx = row - 8;
551
- if (this.hoveredAccountIndex !== hoverIdx) {
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 >= 15 && row <= 20) {
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.selectedIndex = row - 8;
584
- return true;
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
- await this.handleKey({ name: "a", ctrl: false, shift: false, meta: false });
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(` ${theme.muted("Pressione ")}${theme.cyan("'A'")}${theme.muted(" ou use a opção ao lado para adicionar.")}`);
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
- accounts.forEach((acc, idx) => {
684
- const isFocused = idx === this.selectedIndex;
685
- const isHovered = idx === this.hoveredAccountIndex;
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(idx + 1) + ".", 4);
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: `Contas (${accounts.length})`,
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
- rightContent.push(` ${this.hoveredActionRow === 15 ? theme.bgHover(` ${theme.cyan("[ A ] Adicionar Conta")} `) : `${theme.cyan("[ A ]")} Adicionar Conta`}`);
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
- rightContent.push(` ${this.hoveredActionRow === 15 ? theme.bgHover(` ${theme.cyan("[ A ] Adicionar Conta")} `) : `${theme.cyan("[ A ]")} Adicionar Conta`}`);
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));