qwenproxy-cli 1.0.13 → 1.0.15
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 +16 -9
- package/src/routes/chat/account.ts +16 -4
- package/src/services/playwright.ts +6 -0
- package/src/tui/views/accounts-view.ts +39 -15
- package/src/tui/views/storage-view.ts +39 -15
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qwenproxy-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.15",
|
|
4
4
|
"description": "High-performance OpenAI & Anthropic compatible API gateway for Qwen with multi-account rotation, interactive TUI, and resilient tool calling.",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"bin": {
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
"reset": "npx tsx src/reset-cooldowns.ts",
|
|
25
25
|
"purge": "npx tsx src/delete-chats.ts",
|
|
26
26
|
"test": "npm run test:mock && npm run test:live",
|
|
27
|
-
"test:mock": "tsx --test --test-concurrency=1 --env-file=.env.test src/tests/account-concurrency.test.ts src/tests/concurrency-latest-wins.test.ts src/tests/mutex.test.ts src/tests/context-truncation.test.ts src/tests/streaming-thinking-summary.test.ts src/tests/tool-call-guard.test.ts src/tests/utils-logger-full.test.ts src/tests/logger-toolcall-errors.test.ts src/tests/logger-toolcall-disabled.test.ts src/tests/utils-json-full.test.ts src/tests/utils-session-guard-full.test.ts src/tests/core-logic-full.test.ts src/tests/core-logic-accounts.test.ts src/tests/chat-validation-full.test.ts src/tests/chat-logic-full.test.ts src/tests/account-stickiness.test.ts src/tests/accounts-security.test.ts src/tests/advanced.test.ts src/tests/auth-playwright.test.ts src/tests/captcha-solver.test.ts src/tests/cache-scan-regex.test.ts src/tests/chat-cleanup.test.ts src/tests/chromium-args.test.ts src/tests/config-current.test.ts src/tests/context-meter.test.ts src/tests/concurrency.test.ts src/tests/delta.test.ts src/tests/fingerprint.test.ts src/tests/human-behavior.test.ts src/tests/index.test.ts src/tests/json-recovery.test.ts src/tests/jsonFix.test.ts src/tests/memory-cache.test.ts src/tests/model-alias.test.ts src/tests/model-registry-thinking.test.ts src/tests/models-endpoint.test.ts src/tests/multimodal.test.ts src/tests/oss-upload.test.ts src/tests/parser.test.ts src/tests/personalization-flow.test.ts src/tests/personalization-required.test.ts src/tests/retry-policy.test.ts src/tests/rotation.test.ts src/tests/server-lifecycle.test.ts src/tests/session-keeper.test.ts src/tests/session-state.test.ts src/tests/single-account-mode.test.ts src/tests/stream-retry.test.ts src/tests/tool-calls-endpoint.test.ts src/tests/tool-instructions-cache.test.ts src/tests/session-id.test.ts src/tests/watchdog-ram.test.ts src/tests/tool-recovery-t1t2.test.ts src/tests/abort-silent-t4.test.ts src/tests/t3-close-tag-escape.test.ts src/tests/t5-tool-cap.test.ts src/tests/logger-redaction.test.ts src/tests/stall-timeout-retryable.test.ts src/tests/t6-missing-quote-array-close.test.ts src/tests/watchdog-rss.test.ts src/tests/logical-thread-batch.test.ts src/tests/chat-pool-style.test.ts src/tests/queue-wait-cap.test.ts src/tests/context-eviction-order.test.ts src/tests/chat-in-progress-settle.test.ts src/tests/stream-lock-leak.test.ts src/tests/stream-semaphore.test.ts src/tests/waf-isolation.test.ts src/tests/legacy-completions.test.ts src/tests/response-format.test.ts src/tests/cors-headers.test.ts src/tests/stream-lock-terminal-cancel.test.ts src/tests/parser-truncated-tool-call.test.ts src/tests/metrics-prometheus.test.ts src/tests/context-death-cleanup.test.ts src/tests/personalization-direct-fetch.test.ts src/tests/completion-headers-parity.test.ts src/tests/parallel-escape-repro.test.ts src/tests/chat-in-progress-scale-cost.test.ts src/tests/temp-chat-mode.test.ts src/tests/profile-reset.test.ts src/tests/error-classification.test.ts src/tests/quota-cooldown.test.ts src/tests/account-ready-gate.test.ts src/tests/tool-cap-notice.test.ts src/tests/interaction-flows.test.ts src/tests/headers-gate-cooldown-fallback.test.ts src/tests/unwrapped-tool-calls.test.ts src/tests/anthropic.test.ts src/tests/sync-clients.test.ts src/tests/cli.test.ts src/tests/paths.test.ts src/tests/tool-manifest.test.ts src/tests/tui.test.ts",
|
|
28
|
-
"test:mock:fast": "tsx --test --test-concurrency=2 --env-file=.env.test src/tests/account-concurrency.test.ts src/tests/concurrency-latest-wins.test.ts src/tests/mutex.test.ts src/tests/context-truncation.test.ts src/tests/streaming-thinking-summary.test.ts src/tests/tool-call-guard.test.ts src/tests/utils-logger-full.test.ts src/tests/logger-toolcall-errors.test.ts src/tests/logger-toolcall-disabled.test.ts src/tests/utils-json-full.test.ts src/tests/utils-session-guard-full.test.ts src/tests/core-logic-full.test.ts src/tests/core-logic-accounts.test.ts src/tests/chat-validation-full.test.ts src/tests/chat-logic-full.test.ts src/tests/account-stickiness.test.ts src/tests/accounts-security.test.ts src/tests/advanced.test.ts src/tests/auth-playwright.test.ts src/tests/captcha-solver.test.ts src/tests/cache-scan-regex.test.ts src/tests/chat-cleanup.test.ts src/tests/chromium-args.test.ts src/tests/config-current.test.ts src/tests/context-meter.test.ts src/tests/concurrency.test.ts src/tests/delta.test.ts src/tests/fingerprint.test.ts src/tests/human-behavior.test.ts src/tests/index.test.ts src/tests/json-recovery.test.ts src/tests/jsonFix.test.ts src/tests/memory-cache.test.ts src/tests/model-alias.test.ts src/tests/model-registry-thinking.test.ts src/tests/models-endpoint.test.ts src/tests/multimodal.test.ts src/tests/oss-upload.test.ts src/tests/parser.test.ts src/tests/personalization-flow.test.ts src/tests/personalization-required.test.ts src/tests/retry-policy.test.ts src/tests/rotation.test.ts src/tests/server-lifecycle.test.ts src/tests/session-keeper.test.ts src/tests/session-state.test.ts src/tests/single-account-mode.test.ts src/tests/stream-retry.test.ts src/tests/tool-calls-endpoint.test.ts src/tests/tool-instructions-cache.test.ts src/tests/session-id.test.ts src/tests/watchdog-ram.test.ts src/tests/tool-recovery-t1t2.test.ts src/tests/abort-silent-t4.test.ts src/tests/t3-close-tag-escape.test.ts src/tests/t5-tool-cap.test.ts src/tests/logger-redaction.test.ts src/tests/stall-timeout-retryable.test.ts src/tests/t6-missing-quote-array-close.test.ts src/tests/watchdog-rss.test.ts src/tests/logical-thread-batch.test.ts src/tests/chat-pool-style.test.ts src/tests/queue-wait-cap.test.ts src/tests/context-eviction-order.test.ts src/tests/chat-in-progress-settle.test.ts src/tests/stream-lock-leak.test.ts src/tests/stream-semaphore.test.ts src/tests/waf-isolation.test.ts src/tests/legacy-completions.test.ts src/tests/response-format.test.ts src/tests/cors-headers.test.ts src/tests/stream-lock-terminal-cancel.test.ts src/tests/parser-truncated-tool-call.test.ts src/tests/metrics-prometheus.test.ts src/tests/context-death-cleanup.test.ts src/tests/personalization-direct-fetch.test.ts src/tests/completion-headers-parity.test.ts src/tests/parallel-escape-repro.test.ts src/tests/chat-in-progress-scale-cost.test.ts src/tests/temp-chat-mode.test.ts src/tests/profile-reset.test.ts src/tests/error-classification.test.ts src/tests/quota-cooldown.test.ts src/tests/account-ready-gate.test.ts src/tests/tool-cap-notice.test.ts src/tests/interaction-flows.test.ts src/tests/headers-gate-cooldown-fallback.test.ts src/tests/unwrapped-tool-calls.test.ts src/tests/anthropic.test.ts src/tests/sync-clients.test.ts src/tests/cli.test.ts src/tests/paths.test.ts src/tests/tool-manifest.test.ts src/tests/tui.test.ts",
|
|
27
|
+
"test:mock": "tsx --test --test-concurrency=1 --env-file=.env.test src/tests/account-concurrency.test.ts src/tests/concurrency-latest-wins.test.ts src/tests/mutex.test.ts src/tests/context-truncation.test.ts src/tests/streaming-thinking-summary.test.ts src/tests/tool-call-guard.test.ts src/tests/utils-logger-full.test.ts src/tests/logger-toolcall-errors.test.ts src/tests/logger-toolcall-disabled.test.ts src/tests/utils-json-full.test.ts src/tests/utils-session-guard-full.test.ts src/tests/core-logic-full.test.ts src/tests/core-logic-accounts.test.ts src/tests/chat-validation-full.test.ts src/tests/chat-logic-full.test.ts src/tests/account-stickiness.test.ts src/tests/accounts-security.test.ts src/tests/advanced.test.ts src/tests/auth-playwright.test.ts src/tests/captcha-solver.test.ts src/tests/cache-scan-regex.test.ts src/tests/chat-cleanup.test.ts src/tests/chromium-args.test.ts src/tests/config-current.test.ts src/tests/context-meter.test.ts src/tests/concurrency.test.ts src/tests/delta.test.ts src/tests/fingerprint.test.ts src/tests/human-behavior.test.ts src/tests/index.test.ts src/tests/json-recovery.test.ts src/tests/jsonFix.test.ts src/tests/memory-cache.test.ts src/tests/model-alias.test.ts src/tests/model-registry-thinking.test.ts src/tests/models-endpoint.test.ts src/tests/multimodal.test.ts src/tests/oss-upload.test.ts src/tests/parser.test.ts src/tests/personalization-flow.test.ts src/tests/personalization-required.test.ts src/tests/retry-policy.test.ts src/tests/rotation.test.ts src/tests/server-lifecycle.test.ts src/tests/session-keeper.test.ts src/tests/session-state.test.ts src/tests/single-account-mode.test.ts src/tests/stream-retry.test.ts src/tests/tool-calls-endpoint.test.ts src/tests/tool-instructions-cache.test.ts src/tests/session-id.test.ts src/tests/watchdog-ram.test.ts src/tests/tool-recovery-t1t2.test.ts src/tests/abort-silent-t4.test.ts src/tests/t3-close-tag-escape.test.ts src/tests/t5-tool-cap.test.ts src/tests/logger-redaction.test.ts src/tests/stall-timeout-retryable.test.ts src/tests/t6-missing-quote-array-close.test.ts src/tests/watchdog-rss.test.ts src/tests/logical-thread-batch.test.ts src/tests/chat-pool-style.test.ts src/tests/queue-wait-cap.test.ts src/tests/context-eviction-order.test.ts src/tests/chat-in-progress-settle.test.ts src/tests/stream-lock-leak.test.ts src/tests/stream-semaphore.test.ts src/tests/waf-isolation.test.ts src/tests/legacy-completions.test.ts src/tests/response-format.test.ts src/tests/cors-headers.test.ts src/tests/stream-lock-terminal-cancel.test.ts src/tests/parser-truncated-tool-call.test.ts src/tests/metrics-prometheus.test.ts src/tests/context-death-cleanup.test.ts src/tests/personalization-direct-fetch.test.ts src/tests/completion-headers-parity.test.ts src/tests/parallel-escape-repro.test.ts src/tests/chat-in-progress-scale-cost.test.ts src/tests/temp-chat-mode.test.ts src/tests/profile-reset.test.ts src/tests/error-classification.test.ts src/tests/quota-cooldown.test.ts src/tests/account-ready-gate.test.ts src/tests/tool-cap-notice.test.ts src/tests/interaction-flows.test.ts src/tests/headers-gate-cooldown-fallback.test.ts src/tests/unwrapped-tool-calls.test.ts src/tests/anthropic.test.ts src/tests/sync-clients.test.ts src/tests/cli.test.ts src/tests/paths.test.ts src/tests/tool-manifest.test.ts src/tests/tui.test.ts src/tests/personalization-deadline.test.ts",
|
|
28
|
+
"test:mock:fast": "tsx --test --test-concurrency=2 --env-file=.env.test src/tests/account-concurrency.test.ts src/tests/concurrency-latest-wins.test.ts src/tests/mutex.test.ts src/tests/context-truncation.test.ts src/tests/streaming-thinking-summary.test.ts src/tests/tool-call-guard.test.ts src/tests/utils-logger-full.test.ts src/tests/logger-toolcall-errors.test.ts src/tests/logger-toolcall-disabled.test.ts src/tests/utils-json-full.test.ts src/tests/utils-session-guard-full.test.ts src/tests/core-logic-full.test.ts src/tests/core-logic-accounts.test.ts src/tests/chat-validation-full.test.ts src/tests/chat-logic-full.test.ts src/tests/account-stickiness.test.ts src/tests/accounts-security.test.ts src/tests/advanced.test.ts src/tests/auth-playwright.test.ts src/tests/captcha-solver.test.ts src/tests/cache-scan-regex.test.ts src/tests/chat-cleanup.test.ts src/tests/chromium-args.test.ts src/tests/config-current.test.ts src/tests/context-meter.test.ts src/tests/concurrency.test.ts src/tests/delta.test.ts src/tests/fingerprint.test.ts src/tests/human-behavior.test.ts src/tests/index.test.ts src/tests/json-recovery.test.ts src/tests/jsonFix.test.ts src/tests/memory-cache.test.ts src/tests/model-alias.test.ts src/tests/model-registry-thinking.test.ts src/tests/models-endpoint.test.ts src/tests/multimodal.test.ts src/tests/oss-upload.test.ts src/tests/parser.test.ts src/tests/personalization-flow.test.ts src/tests/personalization-required.test.ts src/tests/retry-policy.test.ts src/tests/rotation.test.ts src/tests/server-lifecycle.test.ts src/tests/session-keeper.test.ts src/tests/session-state.test.ts src/tests/single-account-mode.test.ts src/tests/stream-retry.test.ts src/tests/tool-calls-endpoint.test.ts src/tests/tool-instructions-cache.test.ts src/tests/session-id.test.ts src/tests/watchdog-ram.test.ts src/tests/tool-recovery-t1t2.test.ts src/tests/abort-silent-t4.test.ts src/tests/t3-close-tag-escape.test.ts src/tests/t5-tool-cap.test.ts src/tests/logger-redaction.test.ts src/tests/stall-timeout-retryable.test.ts src/tests/t6-missing-quote-array-close.test.ts src/tests/watchdog-rss.test.ts src/tests/logical-thread-batch.test.ts src/tests/chat-pool-style.test.ts src/tests/queue-wait-cap.test.ts src/tests/context-eviction-order.test.ts src/tests/chat-in-progress-settle.test.ts src/tests/stream-lock-leak.test.ts src/tests/stream-semaphore.test.ts src/tests/waf-isolation.test.ts src/tests/legacy-completions.test.ts src/tests/response-format.test.ts src/tests/cors-headers.test.ts src/tests/stream-lock-terminal-cancel.test.ts src/tests/parser-truncated-tool-call.test.ts src/tests/metrics-prometheus.test.ts src/tests/context-death-cleanup.test.ts src/tests/personalization-direct-fetch.test.ts src/tests/completion-headers-parity.test.ts src/tests/parallel-escape-repro.test.ts src/tests/chat-in-progress-scale-cost.test.ts src/tests/temp-chat-mode.test.ts src/tests/profile-reset.test.ts src/tests/error-classification.test.ts src/tests/quota-cooldown.test.ts src/tests/account-ready-gate.test.ts src/tests/tool-cap-notice.test.ts src/tests/interaction-flows.test.ts src/tests/headers-gate-cooldown-fallback.test.ts src/tests/unwrapped-tool-calls.test.ts src/tests/anthropic.test.ts src/tests/sync-clients.test.ts src/tests/cli.test.ts src/tests/paths.test.ts src/tests/tool-manifest.test.ts src/tests/tui.test.ts src/tests/personalization-deadline.test.ts",
|
|
29
29
|
"test:live": "tsx --test src/tests/concurrentChat.test.ts src/tests/agenticStress.test.ts",
|
|
30
30
|
"benchmark:proxy": "npx tsx src/benchmarks/proxy-baseline.ts",
|
|
31
31
|
"typecheck": "tsc --noEmit"
|
package/src/api/server.ts
CHANGED
|
@@ -650,15 +650,20 @@ export async function startServer(options?: {
|
|
|
650
650
|
const BATCH_SIZE = config.playwright.initBatchSize;
|
|
651
651
|
|
|
652
652
|
if (accounts.length > 0) {
|
|
653
|
-
let readyAccountId: string | null = null;
|
|
654
653
|
const totalAccounts = accounts.length;
|
|
655
654
|
|
|
656
655
|
// Warm accounts in priority order (recently successful accounts first),
|
|
657
|
-
// skipping accounts still on cooldown
|
|
658
|
-
//
|
|
656
|
+
// skipping accounts still on cooldown. Warm up to maxActiveContexts
|
|
657
|
+
// accounts (default 2: main + reserve) so an immediate failover has a
|
|
658
|
+
// live browser ready instead of incurring a cold start.
|
|
659
659
|
const warmOrder = getAccountsByPriority(accounts).filter(
|
|
660
660
|
(account) => !getAccountCooldownInfo(account.id),
|
|
661
661
|
);
|
|
662
|
+
const targetWarmCount = Math.min(
|
|
663
|
+
warmOrder.length,
|
|
664
|
+
Math.max(1, config.playwright.maxActiveContexts),
|
|
665
|
+
);
|
|
666
|
+
const readyAccountIds = new Set<string>();
|
|
662
667
|
|
|
663
668
|
for (let i = 0; i < warmOrder.length; i++) {
|
|
664
669
|
const ok = await prepareAccountRuntime(
|
|
@@ -669,24 +674,26 @@ export async function startServer(options?: {
|
|
|
669
674
|
warmQwenChatPool,
|
|
670
675
|
);
|
|
671
676
|
if (ok) {
|
|
677
|
+
readyAccountIds.add(warmOrder[i].id);
|
|
672
678
|
console.log(
|
|
673
|
-
`✅ [Server] Account ready (${
|
|
679
|
+
`✅ [Server] Account ready (${readyAccountIds.size}/${totalAccounts}): ${maskEmail(warmOrder[i].email)}`,
|
|
674
680
|
);
|
|
675
|
-
|
|
676
|
-
|
|
681
|
+
if (readyAccountIds.size >= targetWarmCount) {
|
|
682
|
+
break;
|
|
683
|
+
}
|
|
677
684
|
}
|
|
678
685
|
}
|
|
679
686
|
|
|
680
687
|
const remainingAccounts = accounts.filter(
|
|
681
|
-
(account) => account.id
|
|
688
|
+
(account) => !readyAccountIds.has(account.id),
|
|
682
689
|
);
|
|
683
|
-
if (
|
|
690
|
+
if (readyAccountIds.size === 0) {
|
|
684
691
|
console.warn(
|
|
685
692
|
`⚠️ [Server] No account ready during startup; continuing in background`,
|
|
686
693
|
);
|
|
687
694
|
}
|
|
688
695
|
|
|
689
|
-
if (config.playwright.prepareAllOnStartup ||
|
|
696
|
+
if (config.playwright.prepareAllOnStartup || readyAccountIds.size === 0) {
|
|
690
697
|
if (config.playwright.prepareAllOnStartup && remainingAccounts.length > 0) {
|
|
691
698
|
console.log(
|
|
692
699
|
`🪶 [Server] Preparing ${remainingAccounts.length} standby account(s) in background`,
|
|
@@ -35,7 +35,7 @@ import {
|
|
|
35
35
|
type AccountLease,
|
|
36
36
|
} from "../../core/account-concurrency.ts";
|
|
37
37
|
import { isAuthMockEnabled } from "../../services/auth-playwright.ts";
|
|
38
|
-
import { refreshHeaders } from "../../services/playwright.ts";
|
|
38
|
+
import { isPlaywrightInitialized, refreshHeaders } from "../../services/playwright.ts";
|
|
39
39
|
import {
|
|
40
40
|
clearAllSessionsForAccount,
|
|
41
41
|
createQwenStream,
|
|
@@ -77,7 +77,18 @@ const MAX_ANTI_BOT_ROTATIONS = 1;
|
|
|
77
77
|
* a stuck account page (closed context / WAF) can otherwise hold each browser
|
|
78
78
|
* op for 60s and keep the personalization mutex blocked for minutes.
|
|
79
79
|
*/
|
|
80
|
-
const PERSONALIZATION_SYNC_DEADLINE_MS = 30_000;
|
|
80
|
+
export const PERSONALIZATION_SYNC_DEADLINE_MS = 30_000;
|
|
81
|
+
export const COLD_ACCOUNT_PERSONALIZATION_SYNC_DEADLINE_MS = 60_000;
|
|
82
|
+
|
|
83
|
+
export function computePersonalizationDeadlineMs(
|
|
84
|
+
accountId: string | undefined,
|
|
85
|
+
navigationTimeoutMs = config.timeouts.navigation,
|
|
86
|
+
): number {
|
|
87
|
+
if (accountId && accountId !== "global" && isPlaywrightInitialized(accountId)) {
|
|
88
|
+
return PERSONALIZATION_SYNC_DEADLINE_MS;
|
|
89
|
+
}
|
|
90
|
+
return Math.max(COLD_ACCOUNT_PERSONALIZATION_SYNC_DEADLINE_MS, navigationTimeoutMs);
|
|
91
|
+
}
|
|
81
92
|
|
|
82
93
|
/**
|
|
83
94
|
* Hard deadline for a single stream-acquire attempt (models sync + truncation
|
|
@@ -1183,15 +1194,16 @@ async function tryCreateStreamWithRetry(
|
|
|
1183
1194
|
return false;
|
|
1184
1195
|
},
|
|
1185
1196
|
);
|
|
1197
|
+
const syncDeadlineMs = computePersonalizationDeadlineMs(currentAccountId);
|
|
1186
1198
|
personalizationApplied = await Promise.race([
|
|
1187
1199
|
syncPromise,
|
|
1188
1200
|
new Promise<boolean>((resolve) => {
|
|
1189
1201
|
personalizationDeadlineTimer = setTimeout(() => {
|
|
1190
1202
|
if (!syncSettled) {
|
|
1191
|
-
syncFailure = `sync timed out after ${
|
|
1203
|
+
syncFailure = `sync timed out after ${syncDeadlineMs}ms`;
|
|
1192
1204
|
}
|
|
1193
1205
|
resolve(false);
|
|
1194
|
-
},
|
|
1206
|
+
}, syncDeadlineMs);
|
|
1195
1207
|
}),
|
|
1196
1208
|
]);
|
|
1197
1209
|
// The sync won the race: stop the deadline so it cannot keep the
|
|
@@ -3157,6 +3157,12 @@ export function registerPlaywrightAccountForTests(
|
|
|
3157
3157
|
lastAccountActivity.set(accountId, lastActivityAt);
|
|
3158
3158
|
}
|
|
3159
3159
|
|
|
3160
|
+
export function unregisterPlaywrightAccountForTests(accountId: string): void {
|
|
3161
|
+
accountPages.delete(accountId);
|
|
3162
|
+
accountContexts.delete(accountId);
|
|
3163
|
+
lastAccountActivity.delete(accountId);
|
|
3164
|
+
}
|
|
3165
|
+
|
|
3160
3166
|
// ─── Token TTL Diagnostics ───────────────────────────────────────────────────
|
|
3161
3167
|
|
|
3162
3168
|
export interface CookieDiagnostic {
|
|
@@ -145,7 +145,7 @@ export class AccountsView implements TuiView {
|
|
|
145
145
|
public async handleKey(key: KeyEvent): Promise<boolean | void> {
|
|
146
146
|
// 0. Confirm Dialog Active
|
|
147
147
|
if (this.confirmDialog) {
|
|
148
|
-
if (key.name === "s" || key.name === "S"
|
|
148
|
+
if (key.name === "s" || key.name === "S") {
|
|
149
149
|
const dialog = this.confirmDialog;
|
|
150
150
|
this.confirmDialog = null;
|
|
151
151
|
this.confirmDialogHovered = null;
|
|
@@ -158,17 +158,40 @@ export class AccountsView implements TuiView {
|
|
|
158
158
|
this.setStatusMessage(theme.muted("Ação cancelada"));
|
|
159
159
|
return true;
|
|
160
160
|
}
|
|
161
|
+
if (key.name === "enter" || key.name === "return") {
|
|
162
|
+
if (this.confirmDialogHovered === "cancel") {
|
|
163
|
+
this.confirmDialog = null;
|
|
164
|
+
this.confirmDialogHovered = null;
|
|
165
|
+
this.setStatusMessage(theme.muted("Ação cancelada"));
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
const dialog = this.confirmDialog;
|
|
169
|
+
this.confirmDialog = null;
|
|
170
|
+
this.confirmDialogHovered = null;
|
|
171
|
+
await dialog.onConfirm();
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
if (key.name === "left" || key.name === "right" || key.name === "tab") {
|
|
175
|
+
this.confirmDialogHovered = this.confirmDialogHovered === "cancel" ? "confirm" : "cancel";
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
161
178
|
if (key.name === "hover" && key.mouse) {
|
|
162
179
|
const { row, col } = key.mouse;
|
|
163
|
-
const btnRow = this.lastConfirmModalStartRow +
|
|
164
|
-
if (row === btnRow) {
|
|
165
|
-
const
|
|
166
|
-
if (
|
|
167
|
-
this.confirmDialogHovered
|
|
180
|
+
const btnRow = (this.lastConfirmModalStartRow || 4) + 5;
|
|
181
|
+
if (row === btnRow || row === btnRow - 1) {
|
|
182
|
+
const relCol = col - (this.lastConfirmModalLeftPad || 0);
|
|
183
|
+
if (relCol >= 2 && relCol <= 34) {
|
|
184
|
+
if (this.confirmDialogHovered !== "confirm") {
|
|
185
|
+
this.confirmDialogHovered = "confirm";
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
168
188
|
return true;
|
|
169
189
|
}
|
|
170
|
-
if (
|
|
171
|
-
this.confirmDialogHovered
|
|
190
|
+
if (relCol >= 35 && relCol <= 60) {
|
|
191
|
+
if (this.confirmDialogHovered !== "cancel") {
|
|
192
|
+
this.confirmDialogHovered = "cancel";
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
172
195
|
return true;
|
|
173
196
|
}
|
|
174
197
|
}
|
|
@@ -179,17 +202,17 @@ export class AccountsView implements TuiView {
|
|
|
179
202
|
}
|
|
180
203
|
if (key.name === "click" && key.mouse) {
|
|
181
204
|
const { row, col } = key.mouse;
|
|
182
|
-
const btnRow = this.lastConfirmModalStartRow +
|
|
183
|
-
if (row === btnRow) {
|
|
184
|
-
const
|
|
185
|
-
if (
|
|
205
|
+
const btnRow = (this.lastConfirmModalStartRow || 4) + 5;
|
|
206
|
+
if (row === btnRow || row === btnRow - 1) {
|
|
207
|
+
const relCol = col - (this.lastConfirmModalLeftPad || 0);
|
|
208
|
+
if (relCol >= 2 && relCol <= 34) {
|
|
186
209
|
const dialog = this.confirmDialog;
|
|
187
210
|
this.confirmDialog = null;
|
|
188
211
|
this.confirmDialogHovered = null;
|
|
189
212
|
await dialog.onConfirm();
|
|
190
213
|
return true;
|
|
191
214
|
}
|
|
192
|
-
if (
|
|
215
|
+
if (relCol >= 35 && relCol <= 60) {
|
|
193
216
|
this.confirmDialog = null;
|
|
194
217
|
this.confirmDialogHovered = null;
|
|
195
218
|
this.setStatusMessage(theme.muted("Ação cancelada"));
|
|
@@ -804,14 +827,15 @@ export class AccountsView implements TuiView {
|
|
|
804
827
|
if (this.confirmDialog) {
|
|
805
828
|
const modalW = Math.min(width - 4, 66);
|
|
806
829
|
this.lastConfirmModalLeftPad = Math.max(0, Math.floor((width - modalW) / 2));
|
|
830
|
+
this.lastConfirmModalStartRow = 4;
|
|
807
831
|
const confirmBtn =
|
|
808
832
|
this.confirmDialogHovered === "confirm"
|
|
809
833
|
? theme.bgHover(theme.red(" [ S / Enter ] Sim, Confirmar "))
|
|
810
|
-
: theme.red("[ S / Enter ] Sim, Confirmar")
|
|
834
|
+
: ` ${theme.red("[ S / Enter ] Sim, Confirmar")} `;
|
|
811
835
|
const cancelBtn =
|
|
812
836
|
this.confirmDialogHovered === "cancel"
|
|
813
837
|
? theme.bgHover(theme.green(" [ N / Esc ] Cancelar "))
|
|
814
|
-
: theme.green("[ N / Esc ] Cancelar")
|
|
838
|
+
: ` ${theme.green("[ N / Esc ] Cancelar")} `;
|
|
815
839
|
|
|
816
840
|
const modalContent = [
|
|
817
841
|
"",
|
|
@@ -126,7 +126,7 @@ export class StorageView implements TuiView {
|
|
|
126
126
|
public async handleKey(key: KeyEvent): Promise<boolean | void> {
|
|
127
127
|
// 0. Confirm Dialog Active
|
|
128
128
|
if (this.confirmDialog) {
|
|
129
|
-
if (key.name === "s" || key.name === "S"
|
|
129
|
+
if (key.name === "s" || key.name === "S") {
|
|
130
130
|
const dialog = this.confirmDialog;
|
|
131
131
|
this.confirmDialog = null;
|
|
132
132
|
this.confirmDialogHovered = null;
|
|
@@ -139,17 +139,40 @@ export class StorageView implements TuiView {
|
|
|
139
139
|
this.addLog(theme.muted("Ação cancelada"));
|
|
140
140
|
return true;
|
|
141
141
|
}
|
|
142
|
+
if (key.name === "enter" || key.name === "return") {
|
|
143
|
+
if (this.confirmDialogHovered === "cancel") {
|
|
144
|
+
this.confirmDialog = null;
|
|
145
|
+
this.confirmDialogHovered = null;
|
|
146
|
+
this.addLog(theme.muted("Ação cancelada"));
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
const dialog = this.confirmDialog;
|
|
150
|
+
this.confirmDialog = null;
|
|
151
|
+
this.confirmDialogHovered = null;
|
|
152
|
+
await dialog.onConfirm();
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
if (key.name === "left" || key.name === "right" || key.name === "tab") {
|
|
156
|
+
this.confirmDialogHovered = this.confirmDialogHovered === "cancel" ? "confirm" : "cancel";
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
142
159
|
if (key.name === "hover" && key.mouse) {
|
|
143
160
|
const { row, col } = key.mouse;
|
|
144
|
-
const btnRow = this.lastConfirmModalStartRow +
|
|
145
|
-
if (row === btnRow) {
|
|
146
|
-
const
|
|
147
|
-
if (
|
|
148
|
-
this.confirmDialogHovered
|
|
161
|
+
const btnRow = (this.lastConfirmModalStartRow || 4) + 5;
|
|
162
|
+
if (row === btnRow || row === btnRow - 1) {
|
|
163
|
+
const relCol = col - (this.lastConfirmModalLeftPad || 0);
|
|
164
|
+
if (relCol >= 2 && relCol <= 34) {
|
|
165
|
+
if (this.confirmDialogHovered !== "confirm") {
|
|
166
|
+
this.confirmDialogHovered = "confirm";
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
149
169
|
return true;
|
|
150
170
|
}
|
|
151
|
-
if (
|
|
152
|
-
this.confirmDialogHovered
|
|
171
|
+
if (relCol >= 35 && relCol <= 60) {
|
|
172
|
+
if (this.confirmDialogHovered !== "cancel") {
|
|
173
|
+
this.confirmDialogHovered = "cancel";
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
153
176
|
return true;
|
|
154
177
|
}
|
|
155
178
|
}
|
|
@@ -160,17 +183,17 @@ export class StorageView implements TuiView {
|
|
|
160
183
|
}
|
|
161
184
|
if (key.name === "click" && key.mouse) {
|
|
162
185
|
const { row, col } = key.mouse;
|
|
163
|
-
const btnRow = this.lastConfirmModalStartRow +
|
|
164
|
-
if (row === btnRow) {
|
|
165
|
-
const
|
|
166
|
-
if (
|
|
186
|
+
const btnRow = (this.lastConfirmModalStartRow || 4) + 5;
|
|
187
|
+
if (row === btnRow || row === btnRow - 1) {
|
|
188
|
+
const relCol = col - (this.lastConfirmModalLeftPad || 0);
|
|
189
|
+
if (relCol >= 2 && relCol <= 34) {
|
|
167
190
|
const dialog = this.confirmDialog;
|
|
168
191
|
this.confirmDialog = null;
|
|
169
192
|
this.confirmDialogHovered = null;
|
|
170
193
|
await dialog.onConfirm();
|
|
171
194
|
return true;
|
|
172
195
|
}
|
|
173
|
-
if (
|
|
196
|
+
if (relCol >= 35 && relCol <= 60) {
|
|
174
197
|
this.confirmDialog = null;
|
|
175
198
|
this.confirmDialogHovered = null;
|
|
176
199
|
this.addLog(theme.muted("Ação cancelada"));
|
|
@@ -409,14 +432,15 @@ export class StorageView implements TuiView {
|
|
|
409
432
|
if (this.confirmDialog) {
|
|
410
433
|
const modalW = Math.min(width - 4, 66);
|
|
411
434
|
this.lastConfirmModalLeftPad = Math.max(0, Math.floor((width - modalW) / 2));
|
|
435
|
+
this.lastConfirmModalStartRow = 4;
|
|
412
436
|
const confirmBtn =
|
|
413
437
|
this.confirmDialogHovered === "confirm"
|
|
414
438
|
? theme.bgHover(theme.red(" [ S / Enter ] Sim, Confirmar "))
|
|
415
|
-
: theme.red("[ S / Enter ] Sim, Confirmar")
|
|
439
|
+
: ` ${theme.red("[ S / Enter ] Sim, Confirmar")} `;
|
|
416
440
|
const cancelBtn =
|
|
417
441
|
this.confirmDialogHovered === "cancel"
|
|
418
442
|
? theme.bgHover(theme.green(" [ N / Esc ] Cancelar "))
|
|
419
|
-
: theme.green("[ N / Esc ] Cancelar")
|
|
443
|
+
: ` ${theme.green("[ N / Esc ] Cancelar")} `;
|
|
420
444
|
|
|
421
445
|
const modalContent = [
|
|
422
446
|
"",
|