qwenproxy-cli 1.0.30 → 1.0.31
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 +1 -1
- package/src/routes/chat/account.ts +12 -3
- package/src/services/playwright.ts +8 -0
- package/src/services/qwen.ts +22 -23
- package/src/sync/cline.ts +1 -1
- package/src/sync/index.ts +1 -1
- package/src/sync-clients.ts +2 -2
- package/src/tui/views/sync-view.ts +53 -47
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qwenproxy-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.31",
|
|
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": {
|
|
@@ -35,7 +35,11 @@ import {
|
|
|
35
35
|
type AccountLease,
|
|
36
36
|
} from "../../core/account-concurrency.ts";
|
|
37
37
|
import { isAuthMockEnabled } from "../../services/auth-playwright.ts";
|
|
38
|
-
import {
|
|
38
|
+
import {
|
|
39
|
+
isPlaywrightInitialized,
|
|
40
|
+
isAccountRecentlyActive,
|
|
41
|
+
refreshHeaders,
|
|
42
|
+
} from "../../services/playwright.ts";
|
|
39
43
|
import { enqueueOrphanChatDeletion } from "../../services/chat-cleanup.ts";
|
|
40
44
|
import {
|
|
41
45
|
clearAllSessionsForAccount,
|
|
@@ -78,14 +82,19 @@ const MAX_ANTI_BOT_ROTATIONS = 1;
|
|
|
78
82
|
* a stuck account page (closed context / WAF) can otherwise hold each browser
|
|
79
83
|
* op for 60s and keep the personalization mutex blocked for minutes.
|
|
80
84
|
*/
|
|
81
|
-
export const PERSONALIZATION_SYNC_DEADLINE_MS =
|
|
85
|
+
export const PERSONALIZATION_SYNC_DEADLINE_MS = 45_000;
|
|
82
86
|
export const COLD_ACCOUNT_PERSONALIZATION_SYNC_DEADLINE_MS = 60_000;
|
|
83
87
|
|
|
84
88
|
export function computePersonalizationDeadlineMs(
|
|
85
89
|
accountId: string | undefined,
|
|
86
90
|
navigationTimeoutMs = config.timeouts.navigation,
|
|
87
91
|
): number {
|
|
88
|
-
if (
|
|
92
|
+
if (
|
|
93
|
+
accountId &&
|
|
94
|
+
accountId !== "global" &&
|
|
95
|
+
isPlaywrightInitialized(accountId) &&
|
|
96
|
+
isAccountRecentlyActive(accountId, 5 * 60 * 1000)
|
|
97
|
+
) {
|
|
89
98
|
return PERSONALIZATION_SYNC_DEADLINE_MS;
|
|
90
99
|
}
|
|
91
100
|
return Math.max(COLD_ACCOUNT_PERSONALIZATION_SYNC_DEADLINE_MS, navigationTimeoutMs);
|
|
@@ -3342,6 +3342,14 @@ export function isPlaywrightInitialized(accountId: string): boolean {
|
|
|
3342
3342
|
return accountPages.has(accountId);
|
|
3343
3343
|
}
|
|
3344
3344
|
|
|
3345
|
+
export function isAccountRecentlyActive(
|
|
3346
|
+
accountId: string,
|
|
3347
|
+
maxIdleMs = 300_000,
|
|
3348
|
+
): boolean {
|
|
3349
|
+
const last = lastAccountActivity.get(accountId) ?? 0;
|
|
3350
|
+
return last > 0 && Date.now() - last < maxIdleMs;
|
|
3351
|
+
}
|
|
3352
|
+
|
|
3345
3353
|
/**
|
|
3346
3354
|
* Register an account as if it had been initialized, with a chosen last
|
|
3347
3355
|
* activity timestamp. Lets the idle/keep-alive selection be exercised without
|
package/src/services/qwen.ts
CHANGED
|
@@ -2993,19 +2993,6 @@ async function createQwenStreamInternal(
|
|
|
2993
2993
|
const preview = await readResponsePreview(response);
|
|
2994
2994
|
const htmlBody = isHtmlResponseBody(preview);
|
|
2995
2995
|
const antiBotChallenge = isWafChallengeResponse(preview);
|
|
2996
|
-
logger.warn(
|
|
2997
|
-
htmlBody || isHtmlResponseContentType(responseContentType)
|
|
2998
|
-
? "[Qwen] Completion returned HTML instead of SSE"
|
|
2999
|
-
: "[Qwen] Completion returned a non-SSE body",
|
|
3000
|
-
{
|
|
3001
|
-
accountId: accountId ?? "global",
|
|
3002
|
-
chatId: chatSessionId ?? "new",
|
|
3003
|
-
status: response.status,
|
|
3004
|
-
contentType: responseContentType,
|
|
3005
|
-
antiBotChallenge,
|
|
3006
|
-
previewBytes: Buffer.byteLength(preview, "utf8"),
|
|
3007
|
-
},
|
|
3008
|
-
);
|
|
3009
2996
|
|
|
3010
2997
|
if (
|
|
3011
2998
|
antiBotChallenge &&
|
|
@@ -3026,6 +3013,19 @@ async function createQwenStreamInternal(
|
|
|
3026
3013
|
continue;
|
|
3027
3014
|
}
|
|
3028
3015
|
|
|
3016
|
+
logger.warn(
|
|
3017
|
+
htmlBody || isHtmlResponseContentType(responseContentType)
|
|
3018
|
+
? "[Qwen] Completion returned HTML instead of SSE"
|
|
3019
|
+
: "[Qwen] Completion returned a non-SSE body",
|
|
3020
|
+
{
|
|
3021
|
+
accountId: accountId ?? "global",
|
|
3022
|
+
chatId: chatSessionId ?? "new",
|
|
3023
|
+
status: response.status,
|
|
3024
|
+
contentType: responseContentType,
|
|
3025
|
+
antiBotChallenge,
|
|
3026
|
+
previewBytes: Buffer.byteLength(preview, "utf8"),
|
|
3027
|
+
},
|
|
3028
|
+
);
|
|
3029
3029
|
throw withCreatedChatMetadata(
|
|
3030
3030
|
new QwenUpstreamError(
|
|
3031
3031
|
antiBotChallenge
|
|
@@ -3070,16 +3070,6 @@ async function createQwenStreamInternal(
|
|
|
3070
3070
|
const htmlResponse = isHtmlResponseBody(errText);
|
|
3071
3071
|
const antiBotChallenge = isWafChallengeResponse(errText);
|
|
3072
3072
|
if (antiBotChallenge || htmlResponse) {
|
|
3073
|
-
logger.warn(
|
|
3074
|
-
"[Qwen] Completion returned an HTML or anti-bot challenge body instead of SSE.",
|
|
3075
|
-
{
|
|
3076
|
-
accountId: accountId ?? "global",
|
|
3077
|
-
chatId: chatSessionId ?? "new",
|
|
3078
|
-
antiBotChallenge,
|
|
3079
|
-
previewBytes: Buffer.byteLength(errText, "utf8"),
|
|
3080
|
-
},
|
|
3081
|
-
);
|
|
3082
|
-
|
|
3083
3073
|
if (
|
|
3084
3074
|
antiBotChallenge &&
|
|
3085
3075
|
(await retryAfterCaptchaRecovery(
|
|
@@ -3099,6 +3089,15 @@ async function createQwenStreamInternal(
|
|
|
3099
3089
|
continue;
|
|
3100
3090
|
}
|
|
3101
3091
|
|
|
3092
|
+
logger.warn(
|
|
3093
|
+
"[Qwen] Completion returned an HTML or anti-bot challenge body instead of SSE.",
|
|
3094
|
+
{
|
|
3095
|
+
accountId: accountId ?? "global",
|
|
3096
|
+
chatId: chatSessionId ?? "new",
|
|
3097
|
+
antiBotChallenge,
|
|
3098
|
+
previewBytes: Buffer.byteLength(errText, "utf8"),
|
|
3099
|
+
},
|
|
3100
|
+
);
|
|
3102
3101
|
throw withCreatedChatMetadata(
|
|
3103
3102
|
new QwenUpstreamError(
|
|
3104
3103
|
antiBotChallenge
|
package/src/sync/cline.ts
CHANGED
|
@@ -84,7 +84,7 @@ export function syncCline(options: SyncOptions): ClientSyncResult {
|
|
|
84
84
|
backupPath,
|
|
85
85
|
success: true,
|
|
86
86
|
action: backupPath ? "updated" : "created",
|
|
87
|
-
message: `Configured Cline
|
|
87
|
+
message: `Configured Cline in state.vscdb with model ${model} and reasoning effort ${reasoningEffort}`,
|
|
88
88
|
};
|
|
89
89
|
} catch (err: any) {
|
|
90
90
|
return {
|
package/src/sync/index.ts
CHANGED
|
@@ -544,7 +544,7 @@ export function syncAllClients(options: SyncAllOptions = {}): SyncAllResult {
|
|
|
544
544
|
}
|
|
545
545
|
}
|
|
546
546
|
|
|
547
|
-
// 8. Cline
|
|
547
|
+
// 8. Cline
|
|
548
548
|
if (shouldSync("cline")) {
|
|
549
549
|
const clineExisted = fs.existsSync(paths.cline);
|
|
550
550
|
const clineRes = syncCline({
|
package/src/sync-clients.ts
CHANGED
|
@@ -67,7 +67,7 @@ Exemplos:
|
|
|
67
67
|
npm run sync claude # Sincroniza apenas o Claude Code
|
|
68
68
|
npm run sync openclaw # Sincroniza apenas o OpenClaw
|
|
69
69
|
npm run sync kilo # Sincroniza apenas o Kilo Code
|
|
70
|
-
npm run sync cline # Sincroniza
|
|
70
|
+
npm run sync cline # Sincroniza apenas o Cline
|
|
71
71
|
npm run sync omp # Sincroniza apenas o OMP (Oh My Pi)
|
|
72
72
|
npm run sync codex # Sincroniza apenas o Codex CLI
|
|
73
73
|
npm run sync zed # Sincroniza apenas o Zed Editor
|
|
@@ -108,7 +108,7 @@ async function main() {
|
|
|
108
108
|
{ id: "claude-code", name: "3. Claude Code", path: defaultPaths.claudeCode },
|
|
109
109
|
{ id: "openclaw", name: "4. OpenClaw", path: defaultPaths.openClaw },
|
|
110
110
|
{ id: "kilo", name: "5. Kilo Code", path: defaultPaths.kilo },
|
|
111
|
-
{ id: "cline", name: "6. Cline
|
|
111
|
+
{ id: "cline", name: "6. Cline", path: defaultPaths.cline },
|
|
112
112
|
{ id: "omp", name: "7. OMP (Oh My Pi)", path: defaultPaths.omp },
|
|
113
113
|
{ id: "codex", name: "8. Codex CLI", path: defaultPaths.codex },
|
|
114
114
|
{ id: "zed", name: "9. Zed Editor", path: defaultPaths.zed },
|
|
@@ -11,10 +11,11 @@ import {
|
|
|
11
11
|
getDefaultPaths,
|
|
12
12
|
inspectClientSyncStatus,
|
|
13
13
|
} from "../../sync/index.ts";
|
|
14
|
+
import type { SyncClientName } from "../../sync/types.ts";
|
|
14
15
|
import { fetchLiveModels } from "../proxy-client.ts";
|
|
15
16
|
|
|
16
17
|
interface ClientOption {
|
|
17
|
-
id:
|
|
18
|
+
id: SyncClientName;
|
|
18
19
|
name: string;
|
|
19
20
|
path: string;
|
|
20
21
|
selected: boolean;
|
|
@@ -27,7 +28,7 @@ export class SyncView implements TuiView {
|
|
|
27
28
|
public readonly title = "Sync";
|
|
28
29
|
public readonly tabNumber = 3;
|
|
29
30
|
private clients: ClientOption[] = [];
|
|
30
|
-
private selectedRowIndex = 0; // 0..
|
|
31
|
+
private selectedRowIndex = 0; // 0..9 for clients, 10 for model, 11 for scope, 12 for sync, 13 for restore
|
|
31
32
|
private hoveredActionRow: number | null = null;
|
|
32
33
|
private availableModels = [
|
|
33
34
|
"qwen3.8-max",
|
|
@@ -60,11 +61,17 @@ export class SyncView implements TuiView {
|
|
|
60
61
|
|
|
61
62
|
private detectClients(): void {
|
|
62
63
|
const paths = getDefaultPaths();
|
|
63
|
-
const defs: Array<{ id:
|
|
64
|
-
{ id: "
|
|
65
|
-
{ id: "codex", name: "OpenAI Codex", path: paths.codex },
|
|
64
|
+
const defs: Array<{ id: SyncClientName; name: string; path: string }> = [
|
|
65
|
+
{ id: "hermes", name: "Hermes Agent", path: paths.hermes },
|
|
66
66
|
{ id: "opencode", name: "OpenCode", path: paths.openCode },
|
|
67
|
+
{ id: "claude-code", name: "Claude Code", path: paths.claudeCode },
|
|
68
|
+
{ id: "openclaw", name: "OpenClaw", path: paths.openClaw },
|
|
69
|
+
{ id: "kilo", name: "Kilo Code", path: paths.kilo },
|
|
70
|
+
{ id: "cline", name: "Cline", path: paths.cline },
|
|
67
71
|
{ id: "omp", name: "OMP (Oh My Pi)", path: paths.omp },
|
|
72
|
+
{ id: "codex", name: "Codex CLI", path: paths.codex },
|
|
73
|
+
{ id: "zed", name: "Zed Editor", path: paths.zed },
|
|
74
|
+
{ id: "aider", name: "Aider", path: paths.aider },
|
|
68
75
|
];
|
|
69
76
|
|
|
70
77
|
this.clients = defs.map((d) => {
|
|
@@ -96,23 +103,23 @@ export class SyncView implements TuiView {
|
|
|
96
103
|
const { row, col } = key.mouse;
|
|
97
104
|
const leftW = this.lastLeftW || 46;
|
|
98
105
|
if (col >= 2 && col <= leftW - 1) {
|
|
99
|
-
if (row >= 8 && row <=
|
|
106
|
+
if (row >= 8 && row <= 17) {
|
|
100
107
|
const targetRow = row - 8;
|
|
101
108
|
if (this.selectedRowIndex !== targetRow) {
|
|
102
109
|
this.selectedRowIndex = targetRow;
|
|
103
110
|
return true;
|
|
104
111
|
}
|
|
105
|
-
} else if (row === 14) {
|
|
106
|
-
if (this.selectedRowIndex !==
|
|
107
|
-
this.selectedRowIndex =
|
|
112
|
+
} else if (row === 20 || row === 14) {
|
|
113
|
+
if (this.selectedRowIndex !== 10) {
|
|
114
|
+
this.selectedRowIndex = 10;
|
|
108
115
|
return true;
|
|
109
116
|
}
|
|
110
|
-
} else if (row === 15) {
|
|
111
|
-
if (this.selectedRowIndex !==
|
|
112
|
-
this.selectedRowIndex =
|
|
117
|
+
} else if (row === 21 || row === 15) {
|
|
118
|
+
if (this.selectedRowIndex !== 11) {
|
|
119
|
+
this.selectedRowIndex = 11;
|
|
113
120
|
return true;
|
|
114
121
|
}
|
|
115
|
-
} else if (row === 18 || row === 19) {
|
|
122
|
+
} else if (row === 24 || row === 18 || row === 25 || row === 19) {
|
|
116
123
|
if (this.hoveredActionRow !== row) {
|
|
117
124
|
this.hoveredActionRow = row;
|
|
118
125
|
return true;
|
|
@@ -132,8 +139,8 @@ export class SyncView implements TuiView {
|
|
|
132
139
|
const { row, col } = key.mouse;
|
|
133
140
|
const leftW = this.lastLeftW || 46;
|
|
134
141
|
if (col >= 2 && col <= leftW - 1) {
|
|
135
|
-
// Rows 8
|
|
136
|
-
if (row >= 8 && row <=
|
|
142
|
+
// Rows 8..17: Toggle client
|
|
143
|
+
if (row >= 8 && row <= 17) {
|
|
137
144
|
const client = this.clients[row - 8];
|
|
138
145
|
if (client) {
|
|
139
146
|
client.selected = !client.selected;
|
|
@@ -141,27 +148,27 @@ export class SyncView implements TuiView {
|
|
|
141
148
|
return true;
|
|
142
149
|
}
|
|
143
150
|
}
|
|
144
|
-
//
|
|
145
|
-
if (row === 14) {
|
|
151
|
+
// Model selector
|
|
152
|
+
if (row === 20 || row === 14) {
|
|
146
153
|
this.modelIndex = (this.modelIndex + 1) % this.availableModels.length;
|
|
147
|
-
this.selectedRowIndex =
|
|
154
|
+
this.selectedRowIndex = 10;
|
|
148
155
|
return true;
|
|
149
156
|
}
|
|
150
|
-
//
|
|
151
|
-
if (row === 15) {
|
|
157
|
+
// Scope selector
|
|
158
|
+
if (row === 21 || row === 15) {
|
|
152
159
|
this.syncAllModels = !this.syncAllModels;
|
|
153
|
-
this.selectedRowIndex =
|
|
160
|
+
this.selectedRowIndex = 11;
|
|
154
161
|
return true;
|
|
155
162
|
}
|
|
156
|
-
//
|
|
157
|
-
if (row === 18) {
|
|
158
|
-
this.selectedRowIndex =
|
|
163
|
+
// Sincronizar button
|
|
164
|
+
if (row === 24 || row === 18) {
|
|
165
|
+
this.selectedRowIndex = 12;
|
|
159
166
|
this.executeSync();
|
|
160
167
|
return true;
|
|
161
168
|
}
|
|
162
|
-
//
|
|
163
|
-
if (row === 19) {
|
|
164
|
-
this.selectedRowIndex =
|
|
169
|
+
// Restaurar button
|
|
170
|
+
if (row === 25 || row === 19) {
|
|
171
|
+
this.selectedRowIndex = 13;
|
|
165
172
|
this.executeRollback();
|
|
166
173
|
return true;
|
|
167
174
|
}
|
|
@@ -174,28 +181,28 @@ export class SyncView implements TuiView {
|
|
|
174
181
|
return true;
|
|
175
182
|
}
|
|
176
183
|
if (key.name === "down" || key.name === "wheeldown" || (key.name === "j" && !key.ctrl)) {
|
|
177
|
-
this.selectedRowIndex = Math.min(
|
|
184
|
+
this.selectedRowIndex = Math.min(13, this.selectedRowIndex + 1);
|
|
178
185
|
return true;
|
|
179
186
|
}
|
|
180
187
|
|
|
181
188
|
// Toggle client selection with Space
|
|
182
189
|
if (key.name === "space") {
|
|
183
|
-
if (this.selectedRowIndex <
|
|
190
|
+
if (this.selectedRowIndex < this.clients.length) {
|
|
184
191
|
const client = this.clients[this.selectedRowIndex];
|
|
185
192
|
if (client) {
|
|
186
193
|
client.selected = !client.selected;
|
|
187
194
|
}
|
|
188
|
-
} else if (this.selectedRowIndex ===
|
|
195
|
+
} else if (this.selectedRowIndex === 10) {
|
|
189
196
|
// Cycle model with space
|
|
190
197
|
this.modelIndex = (this.modelIndex + 1) % this.availableModels.length;
|
|
191
|
-
} else if (this.selectedRowIndex ===
|
|
198
|
+
} else if (this.selectedRowIndex === 11) {
|
|
192
199
|
this.syncAllModels = !this.syncAllModels;
|
|
193
200
|
}
|
|
194
201
|
return true;
|
|
195
202
|
}
|
|
196
203
|
|
|
197
|
-
// Cycle model left/right on row
|
|
198
|
-
if (this.selectedRowIndex ===
|
|
204
|
+
// Cycle model left/right on row 10
|
|
205
|
+
if (this.selectedRowIndex === 10 && (key.name === "left" || key.name === "right")) {
|
|
199
206
|
if (key.name === "left") {
|
|
200
207
|
this.modelIndex =
|
|
201
208
|
(this.modelIndex - 1 + this.availableModels.length) %
|
|
@@ -205,7 +212,6 @@ export class SyncView implements TuiView {
|
|
|
205
212
|
}
|
|
206
213
|
return true;
|
|
207
214
|
}
|
|
208
|
-
|
|
209
215
|
// Toggle all with 'a'
|
|
210
216
|
if (key.name === "a" && !key.ctrl) {
|
|
211
217
|
const allSelected = this.clients.every((c) => c.selected);
|
|
@@ -226,7 +232,7 @@ export class SyncView implements TuiView {
|
|
|
226
232
|
|
|
227
233
|
// Confirm action on Enter
|
|
228
234
|
if (key.name === "return") {
|
|
229
|
-
if (this.selectedRowIndex ===
|
|
235
|
+
if (this.selectedRowIndex === 13) {
|
|
230
236
|
this.executeRollback();
|
|
231
237
|
} else {
|
|
232
238
|
this.executeSync();
|
|
@@ -289,8 +295,8 @@ export class SyncView implements TuiView {
|
|
|
289
295
|
}
|
|
290
296
|
|
|
291
297
|
public render(width: number, height: number): string[] {
|
|
292
|
-
const contentH = Math.max(
|
|
293
|
-
const leftW = Math.max(
|
|
298
|
+
const contentH = Math.max(22, height);
|
|
299
|
+
const leftW = Math.max(48, Math.floor(width * 0.52));
|
|
294
300
|
this.lastLeftW = leftW;
|
|
295
301
|
const rightW = Math.max(30, width - leftW - 1);
|
|
296
302
|
|
|
@@ -323,8 +329,8 @@ export class SyncView implements TuiView {
|
|
|
323
329
|
leftContent.push("");
|
|
324
330
|
leftContent.push(` ${theme.bold("Modelo:")}`);
|
|
325
331
|
|
|
326
|
-
// Row index
|
|
327
|
-
const isModelFocused = this.selectedRowIndex ===
|
|
332
|
+
// Row index 10: Model Selector
|
|
333
|
+
const isModelFocused = this.selectedRowIndex === 10;
|
|
328
334
|
const modelPointer = isModelFocused ? theme.cyan(`${glyphs.pointer} `) : " ";
|
|
329
335
|
const currentModel = this.availableModels[this.modelIndex] || "qwen3.8-max";
|
|
330
336
|
const modelText = `${currentModel} (${this.modelIndex + 1}/${this.availableModels.length})`;
|
|
@@ -335,23 +341,23 @@ export class SyncView implements TuiView {
|
|
|
335
341
|
|
|
336
342
|
leftContent.push(isModelFocused ? theme.bgSelected(modelLine) : modelLine);
|
|
337
343
|
|
|
338
|
-
// Row index
|
|
339
|
-
const isScopeFocused = this.selectedRowIndex ===
|
|
344
|
+
// Row index 11: Scope Selector
|
|
345
|
+
const isScopeFocused = this.selectedRowIndex === 11;
|
|
340
346
|
const scopePointer = isScopeFocused ? theme.cyan(`${glyphs.pointer} `) : " ";
|
|
341
347
|
const scopeCheck = this.syncAllModels ? theme.green(glyphs.radioOn) : theme.muted(glyphs.radioOff);
|
|
342
348
|
const scopeLine = `${scopePointer}${scopeCheck} Registrar todos os modelos`;
|
|
343
349
|
leftContent.push(isScopeFocused ? theme.bgSelected(scopeLine) : scopeLine);
|
|
344
350
|
leftContent.push("");
|
|
345
351
|
leftContent.push(` ${theme.bold("Ações:")}`);
|
|
346
|
-
// Row
|
|
347
|
-
const isSyncFocused = this.selectedRowIndex ===
|
|
348
|
-
const isSyncHovered = this.hoveredActionRow === 18;
|
|
352
|
+
// Row index 12: Sincronizar
|
|
353
|
+
const isSyncFocused = this.selectedRowIndex === 12;
|
|
354
|
+
const isSyncHovered = this.hoveredActionRow === 24 || this.hoveredActionRow === 18;
|
|
349
355
|
const syncLine = ` ${isSyncHovered || isSyncFocused ? theme.bgHover(` ${theme.cyan("[ Enter ] Sincronizar")} `) : `${theme.cyan("[ Enter ]")} Sincronizar`}`;
|
|
350
356
|
leftContent.push(syncLine);
|
|
351
357
|
|
|
352
|
-
// Row
|
|
353
|
-
const isRestoreFocused = this.selectedRowIndex ===
|
|
354
|
-
const isRestoreHovered = this.hoveredActionRow === 19;
|
|
358
|
+
// Row index 13: Restaurar
|
|
359
|
+
const isRestoreFocused = this.selectedRowIndex === 13;
|
|
360
|
+
const isRestoreHovered = this.hoveredActionRow === 25 || this.hoveredActionRow === 19;
|
|
355
361
|
const restoreLine = ` ${isRestoreHovered || isRestoreFocused ? theme.bgHover(` ${theme.yellow("[ R ] Restaurar")} `) : `${theme.yellow("[ R ]")} Restaurar`}`;
|
|
356
362
|
leftContent.push(restoreLine);
|
|
357
363
|
|