qwenproxy-cli 1.0.0 → 1.0.2
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/LICENSE +14 -14
- package/README.md +906 -906
- package/bin/qwenproxy.js +5 -1
- package/package.json +77 -78
- package/src/api/error-classifier.ts +159 -159
- package/src/api/error-helpers.ts +118 -118
- package/src/api/models.ts +261 -261
- package/src/api/server.ts +860 -859
- package/src/cache/memory-cache.ts +385 -385
- package/src/clean-cache.ts +204 -204
- package/src/core/account-concurrency.ts +671 -671
- package/src/core/account-manager.ts +301 -297
- package/src/core/account-priority.ts +163 -163
- package/src/core/accounts.ts +186 -186
- package/src/core/config.ts +383 -383
- package/src/core/crypto-utils.ts +79 -79
- package/src/core/database.ts +276 -276
- package/src/core/errors.ts +118 -118
- package/src/core/logger.ts +269 -269
- package/src/core/memory-usage.ts +84 -84
- package/src/core/metrics.ts +291 -291
- package/src/core/model-alias.ts +77 -77
- package/src/core/model-registry.ts +544 -544
- package/src/core/mutex.ts +119 -119
- package/src/core/paths.ts +199 -199
- package/src/core/prompt-limits.ts +214 -214
- package/src/core/reasoning-effort.ts +102 -102
- package/src/core/stream-registry.ts +96 -96
- package/src/core/waf-isolation.ts +117 -117
- package/src/core/watchdog.ts +195 -195
- package/src/delete-chats.ts +23 -23
- package/src/index.ts +65 -64
- package/src/login.ts +147 -147
- package/src/reset-cooldowns.ts +11 -11
- package/src/routes/anthropic/index.ts +355 -355
- package/src/routes/anthropic/translate.ts +522 -522
- package/src/routes/anthropic/types.ts +154 -154
- package/src/routes/anthropic/validation.ts +144 -144
- package/src/routes/chat/account.ts +1817 -1817
- package/src/routes/chat/context.ts +241 -241
- package/src/routes/chat/errors.ts +85 -85
- package/src/routes/chat/helpers.ts +268 -268
- package/src/routes/chat/index.ts +618 -618
- package/src/routes/chat/media.ts +285 -285
- package/src/routes/chat/retry-policy.ts +754 -754
- package/src/routes/chat/stop.ts +98 -98
- package/src/routes/chat/streaming.ts +2710 -2710
- package/src/routes/chat/validation.ts +526 -526
- package/src/routes/chat.ts +2 -2
- package/src/routes/completions.ts +290 -290
- package/src/routes/images.ts +139 -139
- package/src/routes/responses/adapter.ts +503 -503
- package/src/routes/responses/index.ts +405 -405
- package/src/routes/responses/state.ts +230 -230
- package/src/routes/responses/streaming.ts +528 -528
- package/src/routes/responses/types.ts +285 -285
- package/src/routes/responses/validation.ts +202 -202
- package/src/routes/upload.ts +731 -731
- package/src/routes/videos.ts +214 -214
- package/src/services/auth-playwright.ts +173 -173
- package/src/services/captcha-coordinator.ts +161 -161
- package/src/services/captcha-solver.ts +553 -553
- package/src/services/chat-cleanup.ts +80 -80
- package/src/services/context-meter.ts +317 -317
- package/src/services/fingerprint.ts +242 -242
- package/src/services/human-behavior.ts +173 -173
- package/src/services/media-generation.ts +1748 -1748
- package/src/services/playwright.ts +2878 -2800
- package/src/services/qwen-chat-pool.ts +345 -345
- package/src/services/qwen-errors.ts +133 -133
- package/src/services/qwen-headers.ts +79 -79
- package/src/services/qwen-thread-state.ts +393 -393
- package/src/services/qwen-url.ts +19 -19
- package/src/services/qwen.ts +3126 -3126
- package/src/services/session-keeper.ts +88 -88
- package/src/services/token-estimation-metrics.ts +118 -118
- package/src/sync/claude-code.ts +75 -75
- package/src/sync/codex.ts +123 -123
- package/src/sync/index.ts +362 -362
- package/src/sync/omp.ts +105 -105
- package/src/sync/opencode.ts +214 -214
- package/src/sync/types.ts +53 -53
- package/src/sync/utils.ts +27 -27
- package/src/sync-clients.ts +189 -189
- package/src/tools/instructions.ts +137 -137
- package/src/tools/manifest.ts +81 -81
- package/src/tools/parser.ts +2989 -2989
- package/src/tools/toolcall-tags.ts +142 -142
- package/src/tui/app.ts +259 -264
- package/src/tui/index.ts +61 -61
- package/src/tui/markdown.ts +258 -258
- package/src/tui/proxy-client.ts +331 -326
- package/src/tui/screen.ts +294 -278
- package/src/tui/server-manager.ts +270 -270
- package/src/tui/theme.ts +432 -432
- package/src/tui/types.ts +33 -33
- package/src/tui/views/accounts-view.ts +656 -656
- package/src/tui/views/chat-view.ts +1018 -823
- package/src/tui/views/logs-view.ts +479 -413
- package/src/tui/views/status-view.ts +204 -204
- package/src/tui/views/storage-view.ts +304 -291
- package/src/tui/views/sync-view.ts +409 -409
- package/src/types/ali-oss.d.ts +32 -32
- package/src/update-cli.ts +121 -0
- package/src/utils/context-truncation.ts +84 -84
- package/src/utils/json.ts +380 -380
- package/src/utils/session-id.ts +37 -37
- package/src/utils/tool-call-guard.ts +84 -84
- package/src/utils/types.ts +109 -109
package/src/tui/screen.ts
CHANGED
|
@@ -1,278 +1,294 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* QwenProxy TUI - Screen Buffer, Resize Listener & Raw Keyboard Engine
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import readline from "node:readline";
|
|
6
|
-
import fs from "node:fs";
|
|
7
|
-
import { ANSI, theme, pad } from "./theme.ts";
|
|
8
|
-
import { ServerManager } from "./server-manager.ts";
|
|
9
|
-
export interface MouseInfo {
|
|
10
|
-
type: "click" | "scroll-up" | "scroll-down" | "hover";
|
|
11
|
-
button?: "left" | "right";
|
|
12
|
-
col: number;
|
|
13
|
-
row: number;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface KeyEvent {
|
|
17
|
-
name: string;
|
|
18
|
-
ctrl: boolean;
|
|
19
|
-
shift: boolean;
|
|
20
|
-
meta: boolean;
|
|
21
|
-
raw?: string;
|
|
22
|
-
char?: string;
|
|
23
|
-
mouse?: MouseInfo;
|
|
24
|
-
}
|
|
25
|
-
export type KeyHandler = (key: KeyEvent) => void;
|
|
26
|
-
export type ResizeHandler = (cols: number, rows: number) => void;
|
|
27
|
-
export class Screen {
|
|
28
|
-
private active = false;
|
|
29
|
-
private keyListeners: Set<KeyHandler> = new Set();
|
|
30
|
-
private resizeListeners: Set<ResizeHandler> = new Set();
|
|
31
|
-
private keypressHandler: ((ch: string | undefined, key: any) => void) | null = null;
|
|
32
|
-
private resizeHandler: (() => void) | null = null;
|
|
33
|
-
private exitHandler: (() => void) | null = null;
|
|
34
|
-
private prevRenderedRows: string[] = [];
|
|
35
|
-
private originalStdinEmit: typeof process.stdin.emit | null = null;
|
|
36
|
-
|
|
37
|
-
constructor() {
|
|
38
|
-
this.exitHandler = () => this.stop();
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
public start(): boolean {
|
|
42
|
-
if (this.active) return true;
|
|
43
|
-
if (!process.stdout.isTTY || !process.stdin.isTTY) {
|
|
44
|
-
return false;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
this.active = true;
|
|
48
|
-
this.prevRenderedRows = [];
|
|
49
|
-
// Switch to alternate screen buffer, clear screen, hide cursor, and enable mouse tracking
|
|
50
|
-
ServerManager.getInstance().withTuiRendering(() => {
|
|
51
|
-
process.stdout.write(
|
|
52
|
-
ANSI.enterAltScreen + "\x1b[2J" + ANSI.cursorHome + ANSI.hideCursor + ANSI.enableMouse,
|
|
53
|
-
);
|
|
54
|
-
});
|
|
55
|
-
// Setup raw keyboard input
|
|
56
|
-
if (process.stdin.setRawMode) {
|
|
57
|
-
process.stdin.setRawMode(true);
|
|
58
|
-
}
|
|
59
|
-
process.stdin.resume();
|
|
60
|
-
readline.emitKeypressEvents(process.stdin);
|
|
61
|
-
|
|
62
|
-
// Intercept stdin 'data' to capture SGR mouse wheel sequences before readline
|
|
63
|
-
this.originalStdinEmit = process.stdin.emit.bind(process.stdin);
|
|
64
|
-
const self = this;
|
|
65
|
-
process.stdin.emit = function (event: string, ...args: any[]) {
|
|
66
|
-
if (event === "data" && args[0] && self.active) {
|
|
67
|
-
const str = typeof args[0] === "string" ? args[0] : args[0].toString();
|
|
68
|
-
const mouseRegex = /\x1b\[<(\d+);(\d+);(\d+)([mM])/g;
|
|
69
|
-
let match: RegExpExecArray | null;
|
|
70
|
-
let handled = false;
|
|
71
|
-
while ((match = mouseRegex.exec(str)) !== null) {
|
|
72
|
-
handled = true;
|
|
73
|
-
const btn = parseInt(match[1], 10);
|
|
74
|
-
const col = parseInt(match[2], 10);
|
|
75
|
-
const row = parseInt(match[3], 10);
|
|
76
|
-
const isRelease = match[4] === "m";
|
|
77
|
-
|
|
78
|
-
if (btn === 64) {
|
|
79
|
-
self.dispatchKey({
|
|
80
|
-
name: "wheelup",
|
|
81
|
-
ctrl: false,
|
|
82
|
-
shift: false,
|
|
83
|
-
meta: false,
|
|
84
|
-
mouse: { type: "scroll-up", col, row },
|
|
85
|
-
});
|
|
86
|
-
} else if (btn === 65) {
|
|
87
|
-
self.dispatchKey({
|
|
88
|
-
name: "wheeldown",
|
|
89
|
-
ctrl: false,
|
|
90
|
-
shift: false,
|
|
91
|
-
meta: false,
|
|
92
|
-
mouse: { type: "scroll-down", col, row },
|
|
93
|
-
});
|
|
94
|
-
} else if (
|
|
95
|
-
self.dispatchKey({
|
|
96
|
-
name: "
|
|
97
|
-
ctrl: false,
|
|
98
|
-
shift: false,
|
|
99
|
-
meta: false,
|
|
100
|
-
mouse: { type: "
|
|
101
|
-
});
|
|
102
|
-
} else if (btn ===
|
|
103
|
-
self.dispatchKey({
|
|
104
|
-
name: "
|
|
105
|
-
ctrl: false,
|
|
106
|
-
shift: false,
|
|
107
|
-
meta: false,
|
|
108
|
-
mouse: { type: "
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
this.
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
if (
|
|
192
|
-
process.stdin.
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
if (this.
|
|
196
|
-
process.
|
|
197
|
-
this.
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
public
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
1
|
+
/**
|
|
2
|
+
* QwenProxy TUI - Screen Buffer, Resize Listener & Raw Keyboard Engine
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import readline from "node:readline";
|
|
6
|
+
import fs from "node:fs";
|
|
7
|
+
import { ANSI, theme, pad } from "./theme.ts";
|
|
8
|
+
import { ServerManager } from "./server-manager.ts";
|
|
9
|
+
export interface MouseInfo {
|
|
10
|
+
type: "click" | "release" | "drag" | "scroll-up" | "scroll-down" | "hover";
|
|
11
|
+
button?: "left" | "right";
|
|
12
|
+
col: number;
|
|
13
|
+
row: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface KeyEvent {
|
|
17
|
+
name: string;
|
|
18
|
+
ctrl: boolean;
|
|
19
|
+
shift: boolean;
|
|
20
|
+
meta: boolean;
|
|
21
|
+
raw?: string;
|
|
22
|
+
char?: string;
|
|
23
|
+
mouse?: MouseInfo;
|
|
24
|
+
}
|
|
25
|
+
export type KeyHandler = (key: KeyEvent) => void;
|
|
26
|
+
export type ResizeHandler = (cols: number, rows: number) => void;
|
|
27
|
+
export class Screen {
|
|
28
|
+
private active = false;
|
|
29
|
+
private keyListeners: Set<KeyHandler> = new Set();
|
|
30
|
+
private resizeListeners: Set<ResizeHandler> = new Set();
|
|
31
|
+
private keypressHandler: ((ch: string | undefined, key: any) => void) | null = null;
|
|
32
|
+
private resizeHandler: (() => void) | null = null;
|
|
33
|
+
private exitHandler: (() => void) | null = null;
|
|
34
|
+
private prevRenderedRows: string[] = [];
|
|
35
|
+
private originalStdinEmit: typeof process.stdin.emit | null = null;
|
|
36
|
+
|
|
37
|
+
constructor() {
|
|
38
|
+
this.exitHandler = () => this.stop();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public start(): boolean {
|
|
42
|
+
if (this.active) return true;
|
|
43
|
+
if (!process.stdout.isTTY || !process.stdin.isTTY) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
this.active = true;
|
|
48
|
+
this.prevRenderedRows = [];
|
|
49
|
+
// Switch to alternate screen buffer, clear screen, hide cursor, and enable mouse tracking
|
|
50
|
+
ServerManager.getInstance().withTuiRendering(() => {
|
|
51
|
+
process.stdout.write(
|
|
52
|
+
ANSI.enterAltScreen + "\x1b[2J" + ANSI.cursorHome + ANSI.hideCursor + ANSI.enableMouse,
|
|
53
|
+
);
|
|
54
|
+
});
|
|
55
|
+
// Setup raw keyboard input
|
|
56
|
+
if (process.stdin.setRawMode) {
|
|
57
|
+
process.stdin.setRawMode(true);
|
|
58
|
+
}
|
|
59
|
+
process.stdin.resume();
|
|
60
|
+
readline.emitKeypressEvents(process.stdin);
|
|
61
|
+
|
|
62
|
+
// Intercept stdin 'data' to capture SGR mouse wheel sequences before readline
|
|
63
|
+
this.originalStdinEmit = process.stdin.emit.bind(process.stdin);
|
|
64
|
+
const self = this;
|
|
65
|
+
process.stdin.emit = function (event: string, ...args: any[]) {
|
|
66
|
+
if (event === "data" && args[0] && self.active) {
|
|
67
|
+
const str = typeof args[0] === "string" ? args[0] : args[0].toString();
|
|
68
|
+
const mouseRegex = /\x1b\[<(\d+);(\d+);(\d+)([mM])/g;
|
|
69
|
+
let match: RegExpExecArray | null;
|
|
70
|
+
let handled = false;
|
|
71
|
+
while ((match = mouseRegex.exec(str)) !== null) {
|
|
72
|
+
handled = true;
|
|
73
|
+
const btn = parseInt(match[1], 10);
|
|
74
|
+
const col = parseInt(match[2], 10);
|
|
75
|
+
const row = parseInt(match[3], 10);
|
|
76
|
+
const isRelease = match[4] === "m";
|
|
77
|
+
|
|
78
|
+
if (btn === 64) {
|
|
79
|
+
self.dispatchKey({
|
|
80
|
+
name: "wheelup",
|
|
81
|
+
ctrl: false,
|
|
82
|
+
shift: false,
|
|
83
|
+
meta: false,
|
|
84
|
+
mouse: { type: "scroll-up", col, row },
|
|
85
|
+
});
|
|
86
|
+
} else if (btn === 65) {
|
|
87
|
+
self.dispatchKey({
|
|
88
|
+
name: "wheeldown",
|
|
89
|
+
ctrl: false,
|
|
90
|
+
shift: false,
|
|
91
|
+
meta: false,
|
|
92
|
+
mouse: { type: "scroll-down", col, row },
|
|
93
|
+
});
|
|
94
|
+
} else if (isRelease) {
|
|
95
|
+
self.dispatchKey({
|
|
96
|
+
name: "release",
|
|
97
|
+
ctrl: false,
|
|
98
|
+
shift: false,
|
|
99
|
+
meta: false,
|
|
100
|
+
mouse: { type: "release", button: btn === 0 ? "left" : "right", col, row },
|
|
101
|
+
});
|
|
102
|
+
} else if (btn === 0 && !isRelease) {
|
|
103
|
+
self.dispatchKey({
|
|
104
|
+
name: "click",
|
|
105
|
+
ctrl: false,
|
|
106
|
+
shift: false,
|
|
107
|
+
meta: false,
|
|
108
|
+
mouse: { type: "click", button: "left", col, row },
|
|
109
|
+
});
|
|
110
|
+
} else if (btn === 32) {
|
|
111
|
+
self.dispatchKey({
|
|
112
|
+
name: "drag",
|
|
113
|
+
ctrl: false,
|
|
114
|
+
shift: false,
|
|
115
|
+
meta: false,
|
|
116
|
+
mouse: { type: "drag", button: "left", col, row },
|
|
117
|
+
});
|
|
118
|
+
} else if (btn === 35) {
|
|
119
|
+
self.dispatchKey({
|
|
120
|
+
name: "hover",
|
|
121
|
+
ctrl: false,
|
|
122
|
+
shift: false,
|
|
123
|
+
meta: false,
|
|
124
|
+
mouse: { type: "hover", col, row },
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (handled) {
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return self.originalStdinEmit!.apply(process.stdin, [event, ...args] as any);
|
|
133
|
+
} as any;
|
|
134
|
+
|
|
135
|
+
this.keypressHandler = (_ch: string | undefined, key: any) => {
|
|
136
|
+
if (!this.active || !key) return;
|
|
137
|
+
|
|
138
|
+
let name = key.name || "";
|
|
139
|
+
let shift = Boolean(key.shift);
|
|
140
|
+
if (key.sequence === "\x1b[Z") {
|
|
141
|
+
name = "tab";
|
|
142
|
+
shift = true;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Suppress control characters from event.char
|
|
146
|
+
const isControlChar =
|
|
147
|
+
_ch && (_ch === "\t" || _ch === "\r" || _ch === "\n" || _ch < " ");
|
|
148
|
+
const char = isControlChar ? undefined : _ch;
|
|
149
|
+
|
|
150
|
+
const event: KeyEvent = {
|
|
151
|
+
name,
|
|
152
|
+
ctrl: Boolean(key.ctrl),
|
|
153
|
+
shift,
|
|
154
|
+
meta: Boolean(key.meta),
|
|
155
|
+
raw: key.sequence,
|
|
156
|
+
char,
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
// Direct Ctrl+C fallback to exit cleanly
|
|
160
|
+
this.dispatchKey(event);
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
this.resizeHandler = () => {
|
|
164
|
+
if (!this.active) return;
|
|
165
|
+
const { cols, rows } = this.getSize();
|
|
166
|
+
for (const listener of this.resizeListeners) {
|
|
167
|
+
listener(cols, rows);
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
process.stdin.on("keypress", this.keypressHandler);
|
|
172
|
+
process.stdout.on("resize", this.resizeHandler);
|
|
173
|
+
process.on("SIGINT", this.exitHandler!);
|
|
174
|
+
process.on("SIGTERM", this.exitHandler!);
|
|
175
|
+
process.on("SIGBREAK", this.exitHandler!);
|
|
176
|
+
process.on("exit", this.exitHandler!);
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private dispatchKey(event: KeyEvent): void {
|
|
181
|
+
if (!this.active) return;
|
|
182
|
+
for (const listener of this.keyListeners) {
|
|
183
|
+
listener(event);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
public stop(): void {
|
|
188
|
+
if (!this.active) return;
|
|
189
|
+
this.active = false;
|
|
190
|
+
|
|
191
|
+
if (this.keypressHandler) {
|
|
192
|
+
process.stdin.removeListener("keypress", this.keypressHandler);
|
|
193
|
+
this.keypressHandler = null;
|
|
194
|
+
}
|
|
195
|
+
if (this.resizeHandler) {
|
|
196
|
+
process.stdout.removeListener("resize", this.resizeHandler);
|
|
197
|
+
this.resizeHandler = null;
|
|
198
|
+
}
|
|
199
|
+
if (this.exitHandler) {
|
|
200
|
+
process.removeListener("SIGINT", this.exitHandler);
|
|
201
|
+
process.removeListener("SIGTERM", this.exitHandler);
|
|
202
|
+
process.removeListener("SIGBREAK", this.exitHandler);
|
|
203
|
+
process.removeListener("exit", this.exitHandler);
|
|
204
|
+
this.exitHandler = null;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (process.stdin.setRawMode) {
|
|
208
|
+
process.stdin.setRawMode(false);
|
|
209
|
+
}
|
|
210
|
+
process.stdin.pause();
|
|
211
|
+
if (this.originalStdinEmit) {
|
|
212
|
+
process.stdin.emit = this.originalStdinEmit;
|
|
213
|
+
this.originalStdinEmit = null;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Restore original screen buffer, cursor, and disable all mouse tracking modes synchronously
|
|
217
|
+
const restoreSeq = ANSI.disableMouse + ANSI.exitAltScreen + ANSI.showCursor + ANSI.reset;
|
|
218
|
+
try {
|
|
219
|
+
fs.writeSync(1, restoreSeq);
|
|
220
|
+
} catch {
|
|
221
|
+
try {
|
|
222
|
+
ServerManager.getInstance().withTuiRendering(() => {
|
|
223
|
+
process.stdout.write(restoreSeq);
|
|
224
|
+
});
|
|
225
|
+
} catch {}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
public onKey(handler: KeyHandler): () => void {
|
|
230
|
+
this.keyListeners.add(handler);
|
|
231
|
+
return () => this.keyListeners.delete(handler);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
public onResize(handler: ResizeHandler): () => void {
|
|
235
|
+
this.prevRenderedRows = [];
|
|
236
|
+
this.resizeListeners.add(handler);
|
|
237
|
+
return () => this.resizeListeners.delete(handler);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
public getSize(): { cols: number; rows: number } {
|
|
241
|
+
return {
|
|
242
|
+
cols: process.stdout.columns || 100,
|
|
243
|
+
rows: process.stdout.rows || 30,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Renders the frame atomically with zero flicker.
|
|
249
|
+
*/
|
|
250
|
+
public render(lines: string[]): void {
|
|
251
|
+
if (!this.active) return;
|
|
252
|
+
const { cols, rows } = this.getSize();
|
|
253
|
+
|
|
254
|
+
if (cols < 70 || rows < 18) {
|
|
255
|
+
// Terminal size warning
|
|
256
|
+
const warnLines = [
|
|
257
|
+
"",
|
|
258
|
+
theme.yellow(" ⚠️ Terminal muito pequeno para a interface QwenProxy."),
|
|
259
|
+
theme.muted(` Tamanho atual: ${cols}x${rows} | Mínimo recomendado: 80x24`),
|
|
260
|
+
theme.cyan(" Por favor, expanda a janela do seu terminal."),
|
|
261
|
+
"",
|
|
262
|
+
theme.muted(" Pressione Ctrl+C duas vezes para sair."),
|
|
263
|
+
];
|
|
264
|
+
process.stdout.write(ANSI.cursorHome + warnLines.join("\n") + "\n");
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
const currentRows: string[] = [];
|
|
268
|
+
for (let r = 0; r < rows; r++) {
|
|
269
|
+
const rawLine = r < lines.length ? lines[r] : "";
|
|
270
|
+
currentRows.push(pad(rawLine, cols));
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
let diffBuffer = "";
|
|
274
|
+
let changed = 0;
|
|
275
|
+
const isFirstRender = this.prevRenderedRows.length !== rows;
|
|
276
|
+
|
|
277
|
+
for (let r = 0; r < rows; r++) {
|
|
278
|
+
if (isFirstRender || this.prevRenderedRows[r] !== currentRows[r]) {
|
|
279
|
+
diffBuffer += `\x1b[${r + 1};1H${currentRows[r]}`;
|
|
280
|
+
changed++;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (changed === 0) {
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
this.prevRenderedRows = currentRows;
|
|
289
|
+
|
|
290
|
+
ServerManager.getInstance().withTuiRendering(() => {
|
|
291
|
+
process.stdout.write(ANSI.hideCursor + diffBuffer + ANSI.cursorHome);
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
}
|