@cloudbase/cloudbase-mcp 2.15.2 → 2.15.3

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.
@@ -0,0 +1,4273 @@
1
+ export const __webpack_id__ = 341;
2
+ export const __webpack_ids__ = [341];
3
+ export const __webpack_modules__ = {
4
+
5
+ /***/ 2341:
6
+ /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
7
+
8
+
9
+ var __importDefault = (this && this.__importDefault) || function (mod) {
10
+ return (mod && mod.__esModule) ? mod : { "default": mod };
11
+ };
12
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
13
+ exports.InteractiveServer = void 0;
14
+ exports.getInteractiveServer = getInteractiveServer;
15
+ exports.resetInteractiveServer = resetInteractiveServer;
16
+ exports.getInteractiveServerSafe = getInteractiveServerSafe;
17
+ const express_1 = __importDefault(__webpack_require__(2674));
18
+ const http_1 = __importDefault(__webpack_require__(3782));
19
+ const child_process_1 = __webpack_require__(5814);
20
+ const open_1 = __importDefault(__webpack_require__(1622));
21
+ const ws_1 = __webpack_require__(6220);
22
+ const index_js_1 = __webpack_require__(9529);
23
+ const logger_js_1 = __webpack_require__(3039);
24
+ function isVSCodeEnvironment() {
25
+ return Boolean(process.env.VSCODE_IPC_HOOK_CLI ||
26
+ process.env.VSCODE_PID ||
27
+ process.env.TERM_PROGRAM === "vscode");
28
+ }
29
+ function parseBrowserCommand(browser) {
30
+ const parts = browser.match(/(?:[^\s"]+|"[^"]*")+/g) || [];
31
+ return parts.map((item) => item.replace(/^"(.*)"$/, "$1"));
32
+ }
33
+ async function openUrlByBrowserEnv(url) {
34
+ const browser = process.env.BROWSER?.trim();
35
+ if (!browser) {
36
+ return false;
37
+ }
38
+ const [command, ...args] = parseBrowserCommand(browser);
39
+ if (!command) {
40
+ return false;
41
+ }
42
+ const finalArgs = args.map((arg) => (arg === "%s" ? url : arg));
43
+ if (!args.includes("%s")) {
44
+ finalArgs.push(url);
45
+ }
46
+ return new Promise((resolve) => {
47
+ (0, child_process_1.execFile)(command, finalArgs, (execError) => {
48
+ resolve(!execError);
49
+ });
50
+ });
51
+ }
52
+ function shouldUseBrowserEnvFallback() {
53
+ return process.platform === "linux" && isVSCodeEnvironment();
54
+ }
55
+ // Dynamic open behavior with IDE awareness and VSCode/Linux fallback
56
+ async function openUrl(url, options, mcpServer) {
57
+ // mcpServer 是 ExtendedMcpServer 实例,它有 server 和 ide 属性
58
+ // server 属性是 MCP server 的内部 server 实例,有 sendLoggingMessage 方法
59
+ const currentIde = mcpServer?.ide || process.env.INTEGRATION_IDE;
60
+ const internalServer = mcpServer?.server; // 内部的 server 实例
61
+ (0, logger_js_1.debug)(`[openUrl] Checking IDE: ${currentIde}`);
62
+ (0, logger_js_1.debug)(`[openUrl] mcpServer type: ${typeof mcpServer}, has server: ${!!mcpServer?.server}, has ide: ${!!mcpServer?.ide}`);
63
+ if (internalServer) {
64
+ (0, logger_js_1.debug)(`[openUrl] internalServer type: ${typeof internalServer}, has sendLoggingMessage: ${typeof internalServer.sendLoggingMessage === 'function'}`);
65
+ }
66
+ // 检查是否为 CodeBuddy IDE (优先使用 mcpServer.ide,回退到环境变量)
67
+ if (currentIde === "CodeBuddy" && internalServer && typeof internalServer.sendLoggingMessage === 'function') {
68
+ try {
69
+ // internalServer 是 MCP server 的内部 server 实例,有 sendLoggingMessage 方法
70
+ internalServer.sendLoggingMessage({
71
+ level: "notice",
72
+ data: {
73
+ type: "tcb",
74
+ url: url,
75
+ },
76
+ });
77
+ (0, logger_js_1.info)(`CodeBuddy IDE: 已发送网页打开通知 - ${url}`);
78
+ return;
79
+ }
80
+ catch (err) {
81
+ (0, logger_js_1.error)(`Failed to send logging message for ${url}: ${err instanceof Error ? err.message : err}`, err instanceof Error ? err : new Error(String(err)));
82
+ // 如果发送通知失败,在 CodeBuddy IDE 中不打开网页,直接返回
83
+ (0, logger_js_1.warn)(`CodeBuddy IDE: 发送通知失败,不打开网页 - ${url}`);
84
+ return;
85
+ }
86
+ }
87
+ // 默认行为:直接打开网页(带 VSCode/Linux 降级逻辑)
88
+ (0, logger_js_1.debug)(`[openUrl] Opening URL in browser: ${url}`);
89
+ try {
90
+ const openOptions = options ? { url: true, ...options } : { url: true };
91
+ const child = await (0, open_1.default)(url, openOptions);
92
+ if (child?.once) {
93
+ child.once("error", async () => {
94
+ if (shouldUseBrowserEnvFallback()) {
95
+ (0, logger_js_1.debug)("[openUrl] open() failed, trying BROWSER fallback");
96
+ await openUrlByBrowserEnv(url);
97
+ }
98
+ });
99
+ }
100
+ return;
101
+ }
102
+ catch (err) {
103
+ (0, logger_js_1.error)(`Failed to open ${url} ${options} ${err instanceof Error ? err.message : err} `, err instanceof Error ? err : new Error(String(err)));
104
+ if (shouldUseBrowserEnvFallback()) {
105
+ (0, logger_js_1.debug)("[openUrl] open() threw, trying BROWSER fallback");
106
+ await openUrlByBrowserEnv(url);
107
+ return;
108
+ }
109
+ (0, logger_js_1.warn)(`Please manually open: ${url}`);
110
+ }
111
+ }
112
+ class InteractiveServer {
113
+ app;
114
+ server;
115
+ wss;
116
+ port = 0;
117
+ isRunning = false;
118
+ currentResolver = null;
119
+ sessionData = new Map();
120
+ _mcpServer = null; // 保存 MCP server 实例引用
121
+ // 公共 getter 和 setter
122
+ get mcpServer() {
123
+ return this._mcpServer;
124
+ }
125
+ set mcpServer(server) {
126
+ this._mcpServer = server;
127
+ }
128
+ DEFAULT_PORT = 3721;
129
+ FALLBACK_PORTS = [
130
+ 3722, 3723, 3724, 3725, 3726, 3727, 3728, 3729, 3730, 3731, 3732, 3733,
131
+ 3734, 3735,
132
+ ];
133
+ /** Idle timeout for HTTP/WS connections (e.g. long login). Avoids "connection disconnected" after ~1 min. */
134
+ static SERVER_IDLE_MS = 30 * 60 * 1000; // 30 minutes
135
+ /** WebSocket ping interval to keep connection alive past proxies/firewalls. */
136
+ static WS_PING_INTERVAL_MS = 25 * 1000; // 25 seconds
137
+ constructor(mcpServer) {
138
+ this._mcpServer = mcpServer;
139
+ this.app = (0, express_1.default)();
140
+ this.server = http_1.default.createServer(this.app);
141
+ this.applyServerTimeouts();
142
+ this.wss = new ws_1.WebSocketServer({ server: this.server });
143
+ this.setupExpress();
144
+ this.setupWebSocket();
145
+ process.on("exit", () => this.cleanup());
146
+ process.on("SIGINT", () => this.cleanup());
147
+ process.on("SIGTERM", () => this.cleanup());
148
+ }
149
+ cleanup() {
150
+ if (this.isRunning) {
151
+ (0, logger_js_1.debug)("Cleaning up interactive server resources...");
152
+ this.server.close();
153
+ this.wss.close();
154
+ this.isRunning = false;
155
+ }
156
+ }
157
+ /** Apply timeouts so long-lived login does not cause "connection disconnected". */
158
+ applyServerTimeouts() {
159
+ this.server.timeout = 0;
160
+ if (typeof this.server.keepAliveTimeout === "number") {
161
+ this.server.keepAliveTimeout = InteractiveServer.SERVER_IDLE_MS;
162
+ }
163
+ if (typeof this.server.headersTimeout === "number") {
164
+ this.server.headersTimeout = Math.max(this.server.headersTimeout || 0, InteractiveServer.SERVER_IDLE_MS);
165
+ }
166
+ }
167
+ setupExpress() {
168
+ this.app.use(express_1.default.json());
169
+ this.app.get("/env-setup/:sessionId", (req, res) => {
170
+ const { sessionId } = req.params;
171
+ const sessionData = this.sessionData.get(sessionId);
172
+ if (!sessionData) {
173
+ res.status(404).send("会话不存在或已过期");
174
+ return;
175
+ }
176
+ res.send(this.getEnvSetupHTML(sessionData.envs, sessionData.accountInfo, sessionData.errorContext, // Pass error context
177
+ sessionId // Pass sessionId for retry functionality
178
+ ));
179
+ });
180
+ this.app.get("/clarification/:sessionId", (req, res) => {
181
+ const { sessionId } = req.params;
182
+ const sessionData = this.sessionData.get(sessionId);
183
+ if (!sessionData) {
184
+ res.status(404).send("会话不存在或已过期");
185
+ return;
186
+ }
187
+ res.send(this.getClarificationHTML(sessionData.message, sessionData.options));
188
+ });
189
+ this.app.post("/api/submit", (req, res) => {
190
+ const { type, data } = req.body;
191
+ (0, logger_js_1.debug)("Received submit request", { type, data });
192
+ if (this.currentResolver) {
193
+ (0, logger_js_1.info)("Resolving with user data");
194
+ this.currentResolver({ type, data });
195
+ this.currentResolver = null;
196
+ }
197
+ else {
198
+ (0, logger_js_1.warn)("No resolver waiting for response");
199
+ }
200
+ res.json({ success: true });
201
+ });
202
+ this.app.post("/api/cancel", (req, res) => {
203
+ (0, logger_js_1.info)("Received cancel request");
204
+ if (this.currentResolver) {
205
+ (0, logger_js_1.info)("Resolving with cancelled status");
206
+ this.currentResolver({
207
+ type: "clarification",
208
+ data: null,
209
+ cancelled: true,
210
+ });
211
+ this.currentResolver = null;
212
+ }
213
+ else {
214
+ (0, logger_js_1.warn)("No resolver waiting for cancellation");
215
+ }
216
+ res.json({ success: true });
217
+ });
218
+ this.app.post("/api/switch", (req, res) => {
219
+ (0, logger_js_1.info)("Received switch account request");
220
+ if (this.currentResolver) {
221
+ (0, logger_js_1.info)("Resolving with switch status");
222
+ this.currentResolver({
223
+ type: "envId",
224
+ data: null,
225
+ switch: true,
226
+ });
227
+ this.currentResolver = null;
228
+ }
229
+ else {
230
+ (0, logger_js_1.warn)("No resolver waiting for switch");
231
+ }
232
+ res.json({ success: true });
233
+ });
234
+ this.app.post("/api/retry-init-tcb", (req, res) => {
235
+ const { sessionId } = req.body;
236
+ (0, logger_js_1.info)("Received retry InitTcb request", { sessionId });
237
+ // Mark session for retry
238
+ const sessionData = this.sessionData.get(sessionId);
239
+ if (sessionData) {
240
+ sessionData.retryInitTcb = true;
241
+ (0, logger_js_1.debug)("Marked session for InitTcb retry", { sessionId });
242
+ }
243
+ res.json({ success: true, message: "重试请求已提交,页面将刷新" });
244
+ });
245
+ // Universal URL opener API - ensures proper handling in different IDEs (e.g., CodeBuddy)
246
+ this.app.post("/api/open-url", async (req, res) => {
247
+ const { url } = req.body;
248
+ if (!url) {
249
+ res.status(400).json({ success: false, error: "URL is required" });
250
+ return;
251
+ }
252
+ (0, logger_js_1.info)("Received open URL request", { url });
253
+ try {
254
+ // Pass mcpServer directly - it's an ExtendedMcpServer instance with server property
255
+ await openUrl(url, undefined, this._mcpServer);
256
+ res.json({ success: true });
257
+ }
258
+ catch (err) {
259
+ (0, logger_js_1.error)("Failed to open URL", err instanceof Error ? err : new Error(String(err)));
260
+ res.status(500).json({
261
+ success: false,
262
+ error: err instanceof Error ? err.message : String(err)
263
+ });
264
+ }
265
+ });
266
+ }
267
+ setupWebSocket() {
268
+ this.wss.on("connection", (ws) => {
269
+ (0, logger_js_1.debug)("WebSocket client connected");
270
+ // Keep connection alive during long login so proxies/firewalls do not close it
271
+ const pingInterval = setInterval(() => {
272
+ if (ws.readyState === ws_1.WebSocket.OPEN) {
273
+ ws.ping();
274
+ }
275
+ }, InteractiveServer.WS_PING_INTERVAL_MS);
276
+ ws.on("close", () => {
277
+ clearInterval(pingInterval);
278
+ (0, logger_js_1.debug)("WebSocket client disconnected");
279
+ });
280
+ ws.on("message", async (message) => {
281
+ try {
282
+ const data = JSON.parse(message.toString());
283
+ (0, logger_js_1.debug)("WebSocket message received", data);
284
+ // Handle session registration
285
+ if (data.type === 'registerSession' && data.sessionId) {
286
+ (0, logger_js_1.debug)("Registering WebSocket for session:", data.sessionId);
287
+ const sessionData = this.sessionData.get(data.sessionId);
288
+ if (sessionData) {
289
+ sessionData.ws = ws;
290
+ (0, logger_js_1.debug)("WebSocket registered successfully for session:", data.sessionId);
291
+ }
292
+ else {
293
+ (0, logger_js_1.debug)("Session not found:", data.sessionId);
294
+ }
295
+ return;
296
+ }
297
+ // Handle refresh environment list request
298
+ if (data.type === 'refreshEnvList') {
299
+ (0, logger_js_1.debug)("Handling refreshEnvList request");
300
+ try {
301
+ // Find the session ID for this WebSocket connection
302
+ let targetSessionId = null;
303
+ for (const [sessionId, sessionData] of this.sessionData.entries()) {
304
+ if (sessionData.ws === ws) {
305
+ targetSessionId = sessionId;
306
+ break;
307
+ }
308
+ }
309
+ if (targetSessionId) {
310
+ const sessionData = this.sessionData.get(targetSessionId);
311
+ if (sessionData && sessionData.manager) {
312
+ // Re-fetch environment list using the same API and parameters as initial query
313
+ let envResult;
314
+ try {
315
+ // Use DescribeEnvs with filter parameters (same as initial query)
316
+ const queryParams = {
317
+ EnvTypes: ["weda", "baas"],
318
+ IsVisible: false,
319
+ Channels: ["dcloud", "iotenable", "tem", "scene_module"],
320
+ };
321
+ envResult = await sessionData.manager.commonService("tcb", "2018-06-08").call({
322
+ Action: "DescribeEnvs",
323
+ Param: queryParams,
324
+ });
325
+ // Transform response format to match original listEnvs() format
326
+ if (envResult && envResult.EnvList) {
327
+ envResult = { EnvList: envResult.EnvList };
328
+ }
329
+ else if (envResult && envResult.Data && envResult.Data.EnvList) {
330
+ envResult = { EnvList: envResult.Data.EnvList };
331
+ }
332
+ else {
333
+ // Fallback to listEnvs if format is unexpected
334
+ (0, logger_js_1.debug)("Unexpected response format, falling back to listEnvs()");
335
+ envResult = await sessionData.manager.env.listEnvs();
336
+ }
337
+ }
338
+ catch (error) {
339
+ (0, logger_js_1.debug)("DescribeEnvs failed, falling back to listEnvs():", error instanceof Error ? error : new Error(String(error)));
340
+ // Fallback to original method on error
341
+ envResult = await sessionData.manager.env.listEnvs();
342
+ }
343
+ const envs = envResult?.EnvList || [];
344
+ // Update session data
345
+ sessionData.envs = envs;
346
+ // Send updated environment list to client
347
+ ws.send(JSON.stringify({
348
+ type: 'envListRefreshed',
349
+ envs: envs,
350
+ success: true
351
+ }));
352
+ (0, logger_js_1.info)(`Environment list refreshed, found ${envs.length} environments`);
353
+ }
354
+ else {
355
+ ws.send(JSON.stringify({
356
+ type: 'envListRefreshed',
357
+ success: false,
358
+ error: '无法获取环境管理器'
359
+ }));
360
+ }
361
+ }
362
+ else {
363
+ ws.send(JSON.stringify({
364
+ type: 'envListRefreshed',
365
+ success: false,
366
+ error: '会话不存在'
367
+ }));
368
+ }
369
+ }
370
+ catch (err) {
371
+ (0, logger_js_1.error)("Failed to refresh environment list", err instanceof Error ? err : new Error(String(err)));
372
+ ws.send(JSON.stringify({
373
+ type: 'envListRefreshed',
374
+ success: false,
375
+ error: err instanceof Error ? err.message : '刷新失败'
376
+ }));
377
+ }
378
+ return;
379
+ }
380
+ if (this.currentResolver) {
381
+ this.currentResolver(data);
382
+ this.currentResolver = null;
383
+ }
384
+ }
385
+ catch (err) {
386
+ (0, logger_js_1.error)("WebSocket message parsing error", err instanceof Error ? err : new Error(String(err)));
387
+ }
388
+ });
389
+ });
390
+ }
391
+ async start() {
392
+ if (this.isRunning) {
393
+ (0, logger_js_1.debug)(`Interactive server already running on port ${this.port}`);
394
+ return this.port;
395
+ }
396
+ return new Promise((resolve, reject) => {
397
+ (0, logger_js_1.info)("Starting interactive server...");
398
+ const tryPorts = [this.DEFAULT_PORT, ...this.FALLBACK_PORTS];
399
+ let currentIndex = 0;
400
+ const tryNextPort = () => {
401
+ if (currentIndex >= tryPorts.length) {
402
+ const err = new Error(`All ${tryPorts.length} ports are in use (${tryPorts.join(", ")}), failed to start server`);
403
+ (0, logger_js_1.error)("Server start failed", err);
404
+ reject(err);
405
+ return;
406
+ }
407
+ const portToTry = tryPorts[currentIndex];
408
+ currentIndex++;
409
+ (0, logger_js_1.debug)(`Trying to start server on port ${portToTry} (attempt ${currentIndex}/${tryPorts.length})`);
410
+ tryPort(portToTry);
411
+ };
412
+ const tryPort = (portToTry) => {
413
+ // 清除之前的所有监听器
414
+ this.server.removeAllListeners("error");
415
+ this.server.removeAllListeners("listening");
416
+ // 设置错误处理
417
+ const errorHandler = (err) => {
418
+ if (err.code === "EADDRINUSE") {
419
+ (0, logger_js_1.warn)(`Port ${portToTry} is in use, trying next port...`);
420
+ // 清理当前尝试
421
+ this.server.removeAllListeners("error");
422
+ this.server.removeAllListeners("listening");
423
+ tryNextPort();
424
+ }
425
+ else {
426
+ (0, logger_js_1.error)("Server error", err);
427
+ reject(err);
428
+ }
429
+ };
430
+ // Host: default 0.0.0.0 so Cloud IDE / VSCode Remote port-forward can connect; set INTERACTIVE_SERVER_HOST=127.0.0.1 for local-only
431
+ const host = process.env.INTERACTIVE_SERVER_HOST ?? "0.0.0.0";
432
+ // 设置成功监听处理
433
+ const listeningHandler = () => {
434
+ const address = this.server.address();
435
+ if (address && typeof address === "object") {
436
+ this.port = address.port;
437
+ this.isRunning = true;
438
+ (0, logger_js_1.info)(`Interactive server started successfully on http://${host}:${this.port}`);
439
+ // 移除临时监听器
440
+ this.server.removeListener("error", errorHandler);
441
+ this.server.removeListener("listening", listeningHandler);
442
+ resolve(this.port);
443
+ }
444
+ else {
445
+ const err = new Error("Failed to get server address");
446
+ (0, logger_js_1.error)("Server start error", err);
447
+ reject(err);
448
+ }
449
+ };
450
+ this.server.once("error", errorHandler);
451
+ this.server.once("listening", listeningHandler);
452
+ try {
453
+ this.server.listen(portToTry, host);
454
+ }
455
+ catch (err) {
456
+ (0, logger_js_1.error)(`Failed to bind to port ${portToTry}:`, err instanceof Error ? err : new Error(String(err)));
457
+ tryNextPort();
458
+ }
459
+ };
460
+ tryNextPort();
461
+ });
462
+ }
463
+ async stop() {
464
+ if (!this.isRunning) {
465
+ (0, logger_js_1.debug)("Interactive server is not running, nothing to stop");
466
+ return;
467
+ }
468
+ (0, logger_js_1.info)("Stopping interactive server...");
469
+ return new Promise((resolve, reject) => {
470
+ // 设置超时,防止无限等待
471
+ const timeout = setTimeout(() => {
472
+ (0, logger_js_1.warn)("Server close timeout, forcing cleanup");
473
+ this.isRunning = false;
474
+ this.port = 0;
475
+ resolve();
476
+ }, 30000);
477
+ try {
478
+ // 首先关闭WebSocket服务器,等待其完全关闭
479
+ this.wss.close(() => {
480
+ (0, logger_js_1.debug)("WebSocket server closed");
481
+ // WebSocket关闭后,再关闭HTTP服务器
482
+ this.server.close((err) => {
483
+ clearTimeout(timeout);
484
+ if (err) {
485
+ (0, logger_js_1.error)("Error closing server:", err);
486
+ reject(err);
487
+ }
488
+ else {
489
+ (0, logger_js_1.info)("Interactive server stopped successfully");
490
+ this.isRunning = false;
491
+ this.port = 0;
492
+ // 重新创建整个服务器实例以便下次使用
493
+ this.server = http_1.default.createServer(this.app);
494
+ this.applyServerTimeouts();
495
+ this.wss = new ws_1.WebSocketServer({ server: this.server });
496
+ this.setupWebSocket();
497
+ (0, logger_js_1.debug)("HTTP and WebSocket servers recreated for next use");
498
+ resolve();
499
+ }
500
+ });
501
+ });
502
+ }
503
+ catch (err) {
504
+ clearTimeout(timeout);
505
+ (0, logger_js_1.error)("Error stopping server:", err instanceof Error ? err : new Error(String(err)));
506
+ this.isRunning = false;
507
+ this.port = 0;
508
+ reject(err);
509
+ }
510
+ });
511
+ }
512
+ async collectEnvId(availableEnvs, accountInfo, errorContext, // EnvSetupContext
513
+ manager, // CloudBase manager instance for refreshing env list
514
+ mcpServer) {
515
+ try {
516
+ // CRITICAL: Clean up any existing unresolved request to prevent hanging
517
+ if (this.currentResolver) {
518
+ (0, logger_js_1.warn)("[collectEnvId] Found existing unresolved request, cleaning up...");
519
+ const oldResolver = this.currentResolver;
520
+ this.currentResolver = null;
521
+ // Resolve the old request as cancelled to prevent it from hanging forever
522
+ oldResolver({ type: "envId", data: null, cancelled: true });
523
+ }
524
+ // Update mcpServer if provided, or use existing this._mcpServer as fallback
525
+ (0, logger_js_1.debug)(`[collectEnvId] Received mcpServer parameter: type=${typeof mcpServer}, is null=${mcpServer === null}, is undefined=${mcpServer === undefined}, has server=${!!mcpServer?.server}, has ide=${!!mcpServer?.ide}`);
526
+ (0, logger_js_1.debug)(`[collectEnvId] Current this._mcpServer before update: type=${typeof this._mcpServer}, is null=${this._mcpServer === null}, is undefined=${this._mcpServer === undefined}, has server=${!!this._mcpServer?.server}, has ide=${!!this._mcpServer?.ide}`);
527
+ // Use mcpServer parameter if provided, otherwise keep existing this._mcpServer
528
+ const effectiveMcpServer = mcpServer || this._mcpServer;
529
+ if (mcpServer) {
530
+ this._mcpServer = mcpServer;
531
+ (0, logger_js_1.debug)(`[collectEnvId] Updated mcpServer from parameter, has server: ${!!mcpServer?.server}, has ide: ${!!mcpServer?.ide}`);
532
+ }
533
+ else if (this._mcpServer) {
534
+ (0, logger_js_1.debug)(`[collectEnvId] mcpServer parameter is falsy, using existing this._mcpServer`);
535
+ }
536
+ else {
537
+ (0, logger_js_1.warn)(`[collectEnvId] WARNING: Both mcpServer parameter and this._mcpServer are undefined! IDE detection will fail.`);
538
+ }
539
+ (0, logger_js_1.debug)(`[collectEnvId] Effective mcpServer: type=${typeof effectiveMcpServer}, has server=${!!effectiveMcpServer?.server}, has ide=${!!effectiveMcpServer?.ide}`);
540
+ (0, logger_js_1.info)("Starting environment ID collection...");
541
+ (0, logger_js_1.debug)(`Available environments: ${availableEnvs.length}`);
542
+ (0, logger_js_1.debug)(`Account info:`, accountInfo);
543
+ (0, logger_js_1.debug)(`Error context:`, {
544
+ hasInitTcbError: !!errorContext?.initTcbError,
545
+ hasCreateEnvError: !!errorContext?.createEnvError,
546
+ initTcbError: errorContext?.initTcbError,
547
+ createEnvError: errorContext?.createEnvError
548
+ });
549
+ const port = await this.start();
550
+ const sessionId = Math.random().toString(36).substring(2, 15);
551
+ this.sessionData.set(sessionId, {
552
+ envs: availableEnvs,
553
+ accountInfo,
554
+ errorContext, // Store error context
555
+ manager, // Store manager for refreshing env list
556
+ ws: null // Will be set when WebSocket connects
557
+ });
558
+ (0, logger_js_1.debug)(`Created session: ${sessionId}`);
559
+ setTimeout(() => {
560
+ this.sessionData.delete(sessionId);
561
+ (0, logger_js_1.debug)(`Session ${sessionId} expired`);
562
+ }, 5 * 60 * 1000);
563
+ const url = `http://localhost:${port}/env-setup/${sessionId}`;
564
+ (0, logger_js_1.info)(`Opening browser: ${url}`);
565
+ // Check if this is CodeBuddy IDE and notification was sent (no browser opened)
566
+ const isCodeBuddy = effectiveMcpServer?.ide === "CodeBuddy" || process.env.INTEGRATION_IDE === "CodeBuddy";
567
+ let notificationSent = false;
568
+ try {
569
+ // Use effectiveMcpServer (from parameter or this._mcpServer) for openUrl
570
+ (0, logger_js_1.debug)(`[collectEnvId] Using effectiveMcpServer for openUrl: type=${typeof effectiveMcpServer}, has server=${!!effectiveMcpServer?.server}, has ide=${!!effectiveMcpServer?.ide}`);
571
+ (0, logger_js_1.debug)(`[collectEnvId] effectiveMcpServer keys: ${effectiveMcpServer ? Object.keys(effectiveMcpServer).slice(0, 10).join(', ') : 'null'}`);
572
+ // Store the original openUrl to check if it returns early (notification sent)
573
+ const openUrlResult = await openUrl(url, { wait: false }, effectiveMcpServer);
574
+ if (isCodeBuddy && openUrlResult === undefined) {
575
+ notificationSent = true;
576
+ (0, logger_js_1.debug)("[collectEnvId] CodeBuddy notification sent, no browser opened");
577
+ }
578
+ (0, logger_js_1.info)("Browser opened successfully");
579
+ }
580
+ catch (browserError) {
581
+ (0, logger_js_1.error)("Failed to open browser", browserError instanceof Error ? browserError : new Error(String(browserError)));
582
+ (0, logger_js_1.warn)(`Please manually open: ${url}`);
583
+ }
584
+ (0, logger_js_1.info)("Waiting for user selection...");
585
+ // Use same 10 minutes for all IDEs so long login (re-auth, switch account) does not close the server
586
+ const timeoutDuration = 10 * 60 * 1000;
587
+ (0, logger_js_1.debug)(`[collectEnvId] Using timeout duration: ${timeoutDuration / 1000} seconds`);
588
+ return new Promise((resolve) => {
589
+ this.currentResolver = (result) => {
590
+ // 用户选择完成后,关闭服务器
591
+ this.currentResolver = null;
592
+ this.stop().catch((err) => {
593
+ (0, logger_js_1.debug)("Error stopping server after user selection:", err);
594
+ });
595
+ resolve(result);
596
+ };
597
+ const timeoutId = setTimeout(() => {
598
+ if (this.currentResolver) {
599
+ (0, logger_js_1.warn)(`Request timeout after ${timeoutDuration / 1000} seconds, resolving with cancelled`);
600
+ this.currentResolver = null;
601
+ // 超时后也关闭服务器
602
+ this.stop().catch((err) => {
603
+ (0, logger_js_1.debug)("Error stopping server after timeout:", err);
604
+ });
605
+ resolve({ type: "envId", data: null, cancelled: true, timeout: true, timeoutDuration });
606
+ }
607
+ }, timeoutDuration);
608
+ // Store timeout ID so we can clear it if resolved early
609
+ // Note: We can't clear it here, but the timeout will be cleared when the promise resolves
610
+ // The timeout will be automatically cleared when the promise resolves or rejects
611
+ });
612
+ }
613
+ catch (err) {
614
+ // Clean up currentResolver on error
615
+ if (this.currentResolver) {
616
+ this.currentResolver = null;
617
+ }
618
+ (0, logger_js_1.error)("Error in collectEnvId", err instanceof Error ? err : new Error(String(err)));
619
+ throw err;
620
+ }
621
+ }
622
+ async clarifyRequest(message, options) {
623
+ const port = await this.start();
624
+ // 生成会话ID并存储数据
625
+ const sessionId = Math.random().toString(36).substring(2, 15);
626
+ this.sessionData.set(sessionId, { message, options });
627
+ // 设置会话过期时间(5分钟)
628
+ setTimeout(() => {
629
+ this.sessionData.delete(sessionId);
630
+ }, 5 * 60 * 1000);
631
+ const url = `http://localhost:${port}/clarification/${sessionId}`;
632
+ // Pass mcpServer directly - it's an ExtendedMcpServer instance with server property
633
+ await openUrl(url, undefined, this._mcpServer);
634
+ return new Promise((resolve) => {
635
+ this.currentResolver = (result) => {
636
+ // 用户选择完成后,关闭服务器
637
+ this.stop().catch((err) => {
638
+ (0, logger_js_1.debug)("Error stopping server after user selection:", err);
639
+ });
640
+ resolve(result);
641
+ };
642
+ });
643
+ }
644
+ escapeHtml(text) {
645
+ if (!text)
646
+ return '';
647
+ return text
648
+ .replace(/&/g, '&')
649
+ .replace(/</g, '&lt;')
650
+ .replace(/>/g, '&gt;')
651
+ .replace(/"/g, '&quot;')
652
+ .replace(/'/g, '&#039;');
653
+ }
654
+ getEnvSetupHTML(envs, accountInfo, errorContext, // EnvSetupContext
655
+ sessionId) {
656
+ // Extract error information
657
+ const initTcbError = errorContext?.initTcbError;
658
+ const createEnvError = errorContext?.createEnvError;
659
+ (0, logger_js_1.debug)("getEnvSetupHTML called with:", {
660
+ envCount: envs?.length || 0,
661
+ hasInitTcbError: !!initTcbError,
662
+ hasCreateEnvError: !!createEnvError,
663
+ hasAccountInfo: !!accountInfo?.uin,
664
+ sessionId
665
+ });
666
+ // Get current IDE information
667
+ const currentIde = this._mcpServer?.ide || process.env.INTEGRATION_IDE;
668
+ // Use new template system
669
+ return (0, index_js_1.renderEnvSetupPage)({
670
+ envs,
671
+ accountInfo,
672
+ errorContext,
673
+ sessionId,
674
+ wsPort: this.port,
675
+ ide: currentIde
676
+ });
677
+ }
678
+ // Keep the old implementation for reference (can be removed later)
679
+ getEnvSetupHTML_OLD(envs, accountInfo, errorContext, sessionId) {
680
+ const accountDisplay = accountInfo?.uin ? `UIN: ${accountInfo.uin}` : "";
681
+ const hasAccountInfo = !!accountInfo?.uin;
682
+ const hasEnvs = (envs || []).length > 0;
683
+ // Extract error information
684
+ const initTcbError = errorContext?.initTcbError;
685
+ const createEnvError = errorContext?.createEnvError;
686
+ const hasErrors = !!(initTcbError || createEnvError);
687
+ const hasInitError = !!initTcbError;
688
+ return `
689
+ <!DOCTYPE html>
690
+ <html lang="zh-CN">
691
+ <head>
692
+ <meta charset="UTF-8">
693
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
694
+ <title>CloudBase AI Toolkit - 环境配置</title>
695
+ <style>
696
+ @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&display=swap');
697
+
698
+ * { margin: 0; padding: 0; box-sizing: border-box; }
699
+ :root {
700
+ --primary-color: #1a1a1a;
701
+ --primary-hover: #000000;
702
+ --accent-color: #67E9E9;
703
+ --accent-hover: #2BCCCC;
704
+ --text-primary: #ffffff;
705
+ --text-secondary: #a0a0a0;
706
+ --border-color: rgba(255, 255, 255, 0.15);
707
+ --bg-secondary: rgba(255, 255, 255, 0.08);
708
+ --bg-glass: rgba(26, 26, 26, 0.95);
709
+ --shadow: 0 25px 50px rgba(0, 0, 0, 0.3), 0 10px 20px rgba(0, 0, 0, 0.2);
710
+ --font-mono: 'JetBrains Mono', 'SF Mono', 'Monaco', monospace;
711
+ --header-bg: linear-gradient(135deg, #0a0a0a 0%, #1a1a1a 50%, #0d1117 100%);
712
+ }
713
+
714
+ body {
715
+ font-family: var(--font-mono);
716
+ background: linear-gradient(135deg, #0a0a0a 0%, #1a1a1a 100%);
717
+ min-height: 100vh;
718
+ display: flex;
719
+ align-items: center;
720
+ justify-content: center;
721
+ padding: 20px;
722
+ position: relative;
723
+ overflow-x: hidden;
724
+ overflow-y: auto;
725
+ }
726
+
727
+ /* Custom scrollbar styles */
728
+ ::-webkit-scrollbar {
729
+ width: 8px;
730
+ }
731
+
732
+ ::-webkit-scrollbar-track {
733
+ background: rgba(255, 255, 255, 0.05);
734
+ border-radius: 4px;
735
+ }
736
+
737
+ ::-webkit-scrollbar-thumb {
738
+ background: var(--accent-color);
739
+ border-radius: 4px;
740
+ }
741
+
742
+ ::-webkit-scrollbar-thumb:hover {
743
+ background: var(--accent-hover);
744
+ }
745
+
746
+ body::before {
747
+ content: '';
748
+ position: fixed;
749
+ top: 0; left: 0; right: 0; bottom: 0;
750
+ background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="grid" width="20" height="20" patternUnits="userSpaceOnUse"><path d="M 20 0 L 0 0 0 20" fill="none" stroke="rgba(255,255,255,0.02)" stroke-width="1"/></pattern></defs><rect width="100" height="100" fill="url(%23grid)"/></svg>') repeat;
751
+ pointer-events: none;
752
+ z-index: -1;
753
+ }
754
+
755
+ body::after {
756
+ content: '';
757
+ position: fixed;
758
+ top: 50%; left: 50%;
759
+ width: 500px; height: 500px;
760
+ background: radial-gradient(circle, rgba(103, 233, 233, 0.05) 0%, transparent 70%);
761
+ transform: translate(-50%, -50%);
762
+ pointer-events: none;
763
+ z-index: -1;
764
+ animation: pulse 8s ease-in-out infinite;
765
+ }
766
+
767
+ @keyframes pulse {
768
+ 0%, 100% { opacity: 0.3; transform: translate(-50%, -50%) scale(1); }
769
+ 50% { opacity: 0.6; transform: translate(-50%, -50%) scale(1.1); }
770
+ }
771
+
772
+ .modal {
773
+ background: var(--bg-glass);
774
+ backdrop-filter: blur(20px);
775
+ border-radius: 20px;
776
+ box-shadow: var(--shadow);
777
+ border: 2px solid var(--border-color);
778
+ width: 100%;
779
+ max-width: 520px;
780
+ overflow: hidden;
781
+ animation: modalIn 0.6s cubic-bezier(0.175, 0.885, 0.32, 1.275);
782
+ position: relative;
783
+ }
784
+
785
+ .modal::before {
786
+ content: '';
787
+ position: absolute;
788
+ top: 0; left: 0; right: 0; bottom: 0;
789
+ background: linear-gradient(45deg, transparent 30%, rgba(255,255,255,0.02) 50%, transparent 70%);
790
+ animation: shimmer 3s infinite;
791
+ pointer-events: none;
792
+ }
793
+
794
+ @keyframes shimmer {
795
+ 0% { transform: translateX(-100%); }
796
+ 100% { transform: translateX(100%); }
797
+ }
798
+
799
+ @keyframes modalIn {
800
+ from {
801
+ opacity: 0;
802
+ transform: scale(0.9) translateY(-20px);
803
+ }
804
+ to {
805
+ opacity: 1;
806
+ transform: scale(1) translateY(0);
807
+ }
808
+ }
809
+
810
+ .header {
811
+ background: var(--header-bg);
812
+ color: var(--text-primary);
813
+ padding: 24px 28px;
814
+ display: flex;
815
+ align-items: center;
816
+ justify-content: space-between;
817
+ position: relative;
818
+ overflow: hidden;
819
+ }
820
+
821
+ .header::before {
822
+ content: '';
823
+ position: absolute;
824
+ top: 0; left: 0; right: 0; bottom: 0;
825
+ background: linear-gradient(45deg, transparent 30%, rgba(255,255,255,0.03) 50%, transparent 70%);
826
+ animation: headerShimmer 4s infinite;
827
+ pointer-events: none;
828
+ }
829
+
830
+ @keyframes headerShimmer {
831
+ 0% { transform: translateX(-100%); }
832
+ 100% { transform: translateX(100%); }
833
+ }
834
+
835
+ .header-left {
836
+ display: flex;
837
+ align-items: center;
838
+ gap: 16px;
839
+ z-index: 1;
840
+ }
841
+
842
+ .logo {
843
+ width: 32px;
844
+ height: 32px;
845
+ filter: drop-shadow(0 4px 8px rgba(0,0,0,0.2));
846
+ animation: logoFloat 3s ease-in-out infinite;
847
+ }
848
+
849
+ @keyframes logoFloat {
850
+ 0%, 100% { transform: translateY(0px); }
851
+ 50% { transform: translateY(-3px); }
852
+ }
853
+
854
+ .title {
855
+ font-size: 20px;
856
+ font-weight: 700;
857
+ text-shadow: 0 2px 4px rgba(0,0,0,0.1);
858
+ }
859
+
860
+ .github-link {
861
+ color: var(--text-primary);
862
+ text-decoration: none;
863
+ display: flex;
864
+ align-items: center;
865
+ gap: 8px;
866
+ font-size: 14px;
867
+ background: rgba(255,255,255,0.08);
868
+ border: 1px solid rgba(255, 255, 255, 0.12);
869
+ backdrop-filter: blur(10px);
870
+ padding: 8px 16px;
871
+ border-radius: 8px;
872
+ font-weight: 500;
873
+ z-index: 1;
874
+ transition: all 0.3s ease;
875
+ }
876
+
877
+ .github-link:hover {
878
+ background: rgba(255,255,255,0.15);
879
+ transform: translateY(-1px);
880
+ box-shadow: 0 4px 12px rgba(0,0,0,0.3);
881
+ }
882
+
883
+ .content {
884
+ padding: 32px 24px;
885
+ position: relative;
886
+ }
887
+
888
+ .content-title {
889
+ font-size: 24px;
890
+ font-weight: 700;
891
+ color: var(--text-primary);
892
+ margin-bottom: 8px;
893
+ animation: fadeInUp 0.8s ease-out 0.2s both;
894
+ }
895
+
896
+ .content-subtitle {
897
+ color: var(--text-secondary);
898
+ margin-bottom: 16px;
899
+ line-height: 1.5;
900
+ animation: fadeInUp 0.8s ease-out 0.4s both;
901
+ }
902
+
903
+ .account-bar {
904
+ display: flex;
905
+ align-items: center;
906
+ justify-content: space-between;
907
+ padding: 10px 14px;
908
+ background: rgba(103, 233, 233, 0.06);
909
+ border: 1px solid rgba(103, 233, 233, 0.15);
910
+ border-radius: 8px;
911
+ margin-bottom: 20px;
912
+ animation: fadeInUp 0.8s ease-out 0.5s both;
913
+ }
914
+
915
+ .account-info {
916
+ display: flex;
917
+ align-items: center;
918
+ gap: 10px;
919
+ font-size: 13px;
920
+ color: var(--accent-color);
921
+ }
922
+
923
+ .account-info svg {
924
+ flex-shrink: 0;
925
+ opacity: 0.8;
926
+ }
927
+
928
+ .account-info span {
929
+ font-family: var(--font-mono);
930
+ }
931
+
932
+ .btn-switch {
933
+ display: flex;
934
+ align-items: center;
935
+ gap: 6px;
936
+ padding: 6px 12px;
937
+ font-size: 12px;
938
+ background: transparent;
939
+ border: 1px solid rgba(103, 233, 233, 0.3);
940
+ border-radius: 6px;
941
+ color: var(--accent-color);
942
+ cursor: pointer;
943
+ transition: all 0.2s ease;
944
+ font-family: var(--font-mono);
945
+ }
946
+
947
+ .btn-switch:hover {
948
+ background: rgba(103, 233, 233, 0.1);
949
+ border-color: var(--accent-color);
950
+ }
951
+
952
+ .btn-switch svg {
953
+ flex-shrink: 0;
954
+ }
955
+
956
+ @keyframes fadeInUp {
957
+ from {
958
+ opacity: 0;
959
+ transform: translateY(20px);
960
+ }
961
+ to {
962
+ opacity: 1;
963
+ transform: translateY(0);
964
+ }
965
+ }
966
+
967
+ .env-list {
968
+ border: 1px solid var(--border-color);
969
+ border-radius: 12px;
970
+ margin-bottom: 24px;
971
+ max-height: 300px;
972
+ overflow-y: auto;
973
+ overflow-x: hidden;
974
+ background: rgba(255, 255, 255, 0.03);
975
+ animation: fadeInUp 0.8s ease-out 0.6s both;
976
+ }
977
+
978
+ .env-item {
979
+ padding: 16px 20px;
980
+ border-bottom: 1px solid var(--border-color);
981
+ cursor: pointer;
982
+ transition: all 0.3s ease;
983
+ display: flex;
984
+ align-items: center;
985
+ gap: 14px;
986
+ position: relative;
987
+ overflow: hidden;
988
+ color: var(--text-primary);
989
+ }
990
+
991
+ .env-item::before {
992
+ content: '';
993
+ position: absolute;
994
+ left: 0; top: 0; bottom: 0;
995
+ width: 0;
996
+ background: var(--accent-color);
997
+ transition: width 0.3s ease;
998
+ }
999
+
1000
+ .env-item:last-child {
1001
+ border-bottom: none;
1002
+ }
1003
+
1004
+ .env-item:hover {
1005
+ background: var(--bg-secondary);
1006
+ transform: translateX(5px);
1007
+ }
1008
+
1009
+ .env-item:hover::before {
1010
+ width: 4px;
1011
+ }
1012
+
1013
+ .env-item.selected {
1014
+ background: rgba(103, 233, 233, 0.1);
1015
+ border-left: 4px solid var(--accent-color);
1016
+ transform: translateX(5px);
1017
+ }
1018
+
1019
+ .env-icon {
1020
+ width: 20px;
1021
+ height: 20px;
1022
+ color: var(--accent-color);
1023
+ flex-shrink: 0;
1024
+ animation: iconGlow 2s ease-in-out infinite;
1025
+ }
1026
+
1027
+ @keyframes iconGlow {
1028
+ 0%, 100% { filter: drop-shadow(0 0 2px rgba(103, 233, 233, 0.3)); }
1029
+ 50% { filter: drop-shadow(0 0 8px rgba(103, 233, 233, 0.6)); }
1030
+ }
1031
+
1032
+ .env-info {
1033
+ flex: 1;
1034
+ }
1035
+
1036
+ .env-name {
1037
+ font-weight: 600;
1038
+ color: var(--text-primary);
1039
+ margin-bottom: 4px;
1040
+ }
1041
+
1042
+ .env-alias {
1043
+ color: var(--text-secondary);
1044
+ font-size: 14px;
1045
+ }
1046
+
1047
+ .empty-state {
1048
+ display: flex;
1049
+ flex-direction: column;
1050
+ align-items: center;
1051
+ justify-content: center;
1052
+ padding: 60px 20px;
1053
+ text-align: center;
1054
+ animation: fadeIn 0.8s ease-out;
1055
+ }
1056
+
1057
+ .empty-icon {
1058
+ margin-bottom: 24px;
1059
+ color: var(--text-secondary);
1060
+ opacity: 0.6;
1061
+ }
1062
+
1063
+ .empty-title {
1064
+ font-size: 20px;
1065
+ font-weight: 600;
1066
+ color: var(--text-primary);
1067
+ margin-bottom: 12px;
1068
+ }
1069
+
1070
+ .empty-message {
1071
+ font-size: 14px;
1072
+ color: var(--text-secondary);
1073
+ line-height: 1.6;
1074
+ margin-bottom: 32px;
1075
+ max-width: 400px;
1076
+ }
1077
+
1078
+ .error-banner {
1079
+ margin-bottom: 24px;
1080
+ padding: 16px;
1081
+ background: rgba(255, 193, 7, 0.1);
1082
+ border: 1px solid rgba(255, 193, 7, 0.3);
1083
+ border-radius: 12px;
1084
+ animation: fadeIn 0.5s ease-out;
1085
+ }
1086
+
1087
+ .error-item {
1088
+ margin-bottom: 16px;
1089
+ }
1090
+
1091
+ .error-item:last-child {
1092
+ margin-bottom: 0;
1093
+ }
1094
+
1095
+ .error-header {
1096
+ display: flex;
1097
+ align-items: center;
1098
+ gap: 8px;
1099
+ margin-bottom: 10px;
1100
+ color: #ffc107;
1101
+ }
1102
+
1103
+ .error-title {
1104
+ font-size: 15px;
1105
+ font-weight: 600;
1106
+ color: #ffc107;
1107
+ }
1108
+
1109
+ .error-message {
1110
+ font-size: 14px;
1111
+ color: var(--text-primary);
1112
+ line-height: 1.6;
1113
+ margin-bottom: 12px;
1114
+ padding-left: 24px;
1115
+ word-wrap: break-word;
1116
+ word-break: break-word;
1117
+ overflow-wrap: break-word;
1118
+ max-width: 100%;
1119
+ }
1120
+
1121
+ .error-action {
1122
+ padding-left: 24px;
1123
+ margin-top: 12px;
1124
+ }
1125
+
1126
+ .error-link {
1127
+ display: inline-flex;
1128
+ align-items: center;
1129
+ gap: 6px;
1130
+ color: var(--accent-color);
1131
+ text-decoration: none;
1132
+ font-size: 14px;
1133
+ font-weight: 500;
1134
+ transition: color 0.2s ease;
1135
+ padding: 8px 16px;
1136
+ background: rgba(103, 233, 233, 0.1);
1137
+ border: 1px solid rgba(103, 233, 233, 0.3);
1138
+ border-radius: 6px;
1139
+ }
1140
+
1141
+ .error-link:hover {
1142
+ color: var(--accent-hover);
1143
+ background: rgba(103, 233, 233, 0.2);
1144
+ border-color: var(--accent-color);
1145
+ }
1146
+
1147
+ .error-retry-btn {
1148
+ margin-left: 12px;
1149
+ cursor: pointer;
1150
+ border: none;
1151
+ font-family: inherit;
1152
+ }
1153
+
1154
+ .error-retry-btn:hover {
1155
+ transform: rotate(180deg);
1156
+ transition: transform 0.3s ease;
1157
+ }
1158
+
1159
+ .error-action {
1160
+ display: flex;
1161
+ align-items: center;
1162
+ flex-wrap: wrap;
1163
+ gap: 8px;
1164
+ }
1165
+
1166
+ .create-env-btn {
1167
+ padding: 14px 24px;
1168
+ font-size: 15px;
1169
+ background: var(--primary-color);
1170
+ color: var(--text-primary);
1171
+ border: 1px solid var(--border-color);
1172
+ border-radius: 8px;
1173
+ cursor: pointer;
1174
+ transition: all 0.3s ease;
1175
+ display: flex;
1176
+ align-items: center;
1177
+ gap: 8px;
1178
+ font-weight: 600;
1179
+ }
1180
+
1181
+ .create-env-btn:hover {
1182
+ background: var(--primary-hover);
1183
+ transform: translateY(-2px);
1184
+ box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3);
1185
+ }
1186
+
1187
+ .actions {
1188
+ display: flex;
1189
+ gap: 12px;
1190
+ justify-content: flex-end;
1191
+ animation: fadeInUp 0.8s ease-out 0.8s both;
1192
+ }
1193
+
1194
+ .btn {
1195
+ padding: 12px 20px;
1196
+ border: none;
1197
+ border-radius: 8px;
1198
+ font-size: 14px;
1199
+ font-weight: 600;
1200
+ cursor: pointer;
1201
+ transition: all 0.3s ease;
1202
+ display: flex;
1203
+ align-items: center;
1204
+ gap: 8px;
1205
+ font-family: var(--font-mono);
1206
+ position: relative;
1207
+ overflow: hidden;
1208
+ }
1209
+
1210
+ .btn::before {
1211
+ content: '';
1212
+ position: absolute;
1213
+ top: 50%; left: 50%;
1214
+ width: 0; height: 0;
1215
+ background: rgba(255,255,255,0.2);
1216
+ border-radius: 50%;
1217
+ transition: all 0.3s ease;
1218
+ transform: translate(-50%, -50%);
1219
+ }
1220
+
1221
+ .btn:hover::before {
1222
+ width: 100px; height: 100px;
1223
+ }
1224
+
1225
+ .btn-primary {
1226
+ background: var(--primary-color);
1227
+ color: var(--text-primary);
1228
+ border: 1px solid var(--border-color);
1229
+ }
1230
+
1231
+ .btn-primary:hover:not(:disabled) {
1232
+ background: var(--primary-hover);
1233
+ transform: translateY(-2px);
1234
+ box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3);
1235
+ }
1236
+
1237
+ .btn-secondary {
1238
+ background: var(--bg-secondary);
1239
+ color: var(--text-secondary);
1240
+ border: 1px solid var(--border-color);
1241
+ }
1242
+
1243
+ .btn-secondary:hover {
1244
+ background: rgba(255, 255, 255, 0.15);
1245
+ color: var(--text-primary);
1246
+ }
1247
+
1248
+ .btn:disabled {
1249
+ opacity: 0.5;
1250
+ cursor: not-allowed;
1251
+ }
1252
+
1253
+ .loading {
1254
+ display: none;
1255
+ align-items: center;
1256
+ justify-content: center;
1257
+ gap: 8px;
1258
+ margin-top: 16px;
1259
+ color: var(--text-secondary);
1260
+ font-size: 14px;
1261
+ }
1262
+
1263
+ .spinner {
1264
+ width: 16px;
1265
+ height: 16px;
1266
+ border: 2px solid var(--border-color);
1267
+ border-top: 2px solid var(--accent-color);
1268
+ border-radius: 50%;
1269
+ animation: spin 1s linear infinite;
1270
+ }
1271
+
1272
+ @keyframes spin {
1273
+ 0% { transform: rotate(0deg); }
1274
+ 100% { transform: rotate(360deg); }
1275
+ }
1276
+
1277
+ @keyframes spin {
1278
+ 0% { transform: rotate(0deg); }
1279
+ 100% { transform: rotate(360deg); }
1280
+ }
1281
+
1282
+ .success-state {
1283
+ text-align: center;
1284
+ padding: 40px 20px;
1285
+ animation: fadeInUp 0.8s ease-out both;
1286
+ }
1287
+
1288
+ .success-icon {
1289
+ margin-bottom: 20px;
1290
+ color: var(--accent-color);
1291
+ animation: successPulse 2s ease-in-out infinite;
1292
+ }
1293
+
1294
+ @keyframes successPulse {
1295
+ 0%, 100% {
1296
+ transform: scale(1);
1297
+ filter: drop-shadow(0 0 8px rgba(103, 233, 233, 0.3));
1298
+ }
1299
+ 50% {
1300
+ transform: scale(1.1);
1301
+ filter: drop-shadow(0 0 16px rgba(103, 233, 233, 0.6));
1302
+ }
1303
+ }
1304
+
1305
+ .success-title {
1306
+ font-size: 24px;
1307
+ font-weight: 700;
1308
+ color: var(--text-primary);
1309
+ margin-bottom: 12px;
1310
+ }
1311
+
1312
+ .success-message {
1313
+ color: var(--text-secondary);
1314
+ font-size: 16px;
1315
+ line-height: 1.5;
1316
+ }
1317
+
1318
+ .selected-env-info {
1319
+ margin-top: 20px;
1320
+ padding: 16px;
1321
+ background: rgba(103, 233, 233, 0.1);
1322
+ border: 1px solid var(--accent-color);
1323
+ border-radius: 12px;
1324
+ display: flex;
1325
+ align-items: center;
1326
+ gap: 12px;
1327
+ }
1328
+
1329
+ .env-label {
1330
+ color: var(--text-secondary);
1331
+ font-size: 14px;
1332
+ font-weight: 500;
1333
+ }
1334
+
1335
+ .env-value {
1336
+ color: var(--accent-color);
1337
+ font-size: 16px;
1338
+ font-weight: 600;
1339
+ font-family: var(--font-mono);
1340
+ }
1341
+ </style>
1342
+ </head>
1343
+ <body>
1344
+ <div class="modal">
1345
+ <div class="header">
1346
+ <div class="header-left">
1347
+ <img class="logo" src="https://7463-tcb-advanced-a656fc-1257967285.tcb.qcloud.la/mcp/cloudbase-logo.svg" alt="CloudBase Logo" />
1348
+ <span class="title">CloudBase AI Toolkit</span>
1349
+ </div>
1350
+ <a href="https://github.com/TencentCloudBase/CloudBase-AI-ToolKit" target="_blank" class="github-link">
1351
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
1352
+ <path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
1353
+ </svg>
1354
+ GitHub
1355
+ </a>
1356
+ </div>
1357
+
1358
+ <div class="content">
1359
+ <h1 class="content-title">选择 CloudBase 环境</h1>
1360
+ <p class="content-subtitle">请选择您要使用的 CloudBase 环境</p>
1361
+
1362
+ ${(hasAccountInfo || hasEnvs) ? `
1363
+ ${(hasAccountInfo || hasEnvs) ? `
1364
+ <div class="account-bar">
1365
+ <div class="account-info">
1366
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1367
+ <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/>
1368
+ <circle cx="12" cy="7" r="4"/>
1369
+ </svg>
1370
+ <span>${accountDisplay ? `当前账号: ${accountDisplay}` : '未登录'}</span>
1371
+ </div>
1372
+ <button class="btn btn-switch" onclick="switchAccount()">
1373
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1374
+ <path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/>
1375
+ <circle cx="8.5" cy="7" r="4"/>
1376
+ <path d="M20 8v6M23 11h-6"/>
1377
+ </svg>
1378
+ 切换账号
1379
+ </button>
1380
+ </div>
1381
+ ` : ''}
1382
+ ` : ''}
1383
+
1384
+ ${hasErrors ? `
1385
+ <div class="error-banner" id="errorBanner">
1386
+ ${initTcbError ? `
1387
+ <div class="error-item">
1388
+ <div class="error-header">
1389
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1390
+ <circle cx="12" cy="12" r="10"/>
1391
+ <line x1="12" y1="8" x2="12" y2="12"/>
1392
+ <line x1="12" y1="16" x2="12.01" y2="16"/>
1393
+ </svg>
1394
+ <span class="error-title">CloudBase 服务初始化失败</span>
1395
+ </div>
1396
+ <div class="error-message">${this.escapeHtml(initTcbError.message)}</div>
1397
+ ${initTcbError.needRealNameAuth ? `
1398
+ <div class="error-action">
1399
+ <a href="${initTcbError.helpUrl || 'https://buy.cloud.tencent.com/lowcode?buyType=tcb&channel=mcp'}" target="_blank" class="error-link">
1400
+ 前往实名认证
1401
+ </a>
1402
+ </div>
1403
+ ` : ''}
1404
+ ${initTcbError.needCamAuth ? `
1405
+ <div class="error-action">
1406
+ <a href="${initTcbError.helpUrl || 'https://buy.cloud.tencent.com/lowcode?buyType=tcb&channel=mcp'}" target="_blank" class="error-link">
1407
+ 前往开通 CloudBase 服务
1408
+ </a>
1409
+ ${sessionId ? `
1410
+ <button class="error-link error-retry-btn" onclick="retryInitTcb('${sessionId}')">
1411
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1412
+ <path d="M23 4v6h-6M1 20v-6h6M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/>
1413
+ </svg>
1414
+ 重试
1415
+ </button>
1416
+ ` : ''}
1417
+ </div>
1418
+ ` : ''}
1419
+ ${!initTcbError.needRealNameAuth && !initTcbError.needCamAuth && initTcbError.helpUrl ? `
1420
+ <div class="error-action">
1421
+ <a href="${initTcbError.helpUrl}" target="_blank" class="error-link">
1422
+ 前往开通 CloudBase 服务
1423
+ </a>
1424
+ ${sessionId ? `
1425
+ <button class="error-link error-retry-btn" onclick="retryInitTcb('${sessionId}')">
1426
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1427
+ <path d="M23 4v6h-6M1 20v-6h6M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/>
1428
+ </svg>
1429
+ 重试
1430
+ </button>
1431
+ ` : ''}
1432
+ </div>
1433
+ ` : ''}
1434
+ </div>
1435
+ ` : ''}
1436
+ ${createEnvError ? `
1437
+ <div class="error-item">
1438
+ <div class="error-header">
1439
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1440
+ <circle cx="12" cy="12" r="10"/>
1441
+ <line x1="12" y1="8" x2="12" y2="12"/>
1442
+ <line x1="12" y1="16" x2="12.01" y2="16"/>
1443
+ </svg>
1444
+ <span class="error-title">免费环境创建失败</span>
1445
+ </div>
1446
+ <div class="error-message">${this.escapeHtml(createEnvError.message)}</div>
1447
+ ${createEnvError.helpUrl ? `
1448
+ <div class="error-action">
1449
+ <a href="${createEnvError.helpUrl}" target="_blank" class="error-link">
1450
+ 手动创建环境
1451
+ </a>
1452
+ </div>
1453
+ ` : ''}
1454
+ </div>
1455
+ ` : ''}
1456
+ </div>
1457
+ ` : ''}
1458
+
1459
+ <div class="env-list" id="envList">
1460
+ ${(envs || []).length > 0
1461
+ ? (envs || [])
1462
+ .map((env, index) => `
1463
+ <div class="env-item" onclick="selectEnv('${env.EnvId}', this)" style="animation-delay: ${index * 0.1}s;">
1464
+ <svg class="env-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1465
+ <path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/>
1466
+ </svg>
1467
+ <div class="env-info">
1468
+ <div class="env-name">${env.EnvId}</div>
1469
+ <div class="env-alias">${env.Alias || "无别名"}</div>
1470
+ </div>
1471
+ </div>
1472
+ `)
1473
+ .join("")
1474
+ : `
1475
+ <div class="empty-state">
1476
+ <h3 class="empty-title">暂无 CloudBase 环境</h3>
1477
+ ${hasInitError ? `
1478
+ <p class="empty-message">由于 CloudBase 服务初始化失败,无法创建新环境。请先解决初始化问题后重试。</p>
1479
+ ` : `
1480
+ <p class="empty-message">当前没有可用的 CloudBase 环境,请新建后重新在 AI 对话中重试</p>
1481
+ <button class="btn btn-primary create-env-btn" onclick="createNewEnv()">
1482
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1483
+ <path d="M12 5v14M5 12h14"/>
1484
+ </svg>
1485
+ 新建环境
1486
+ </button>
1487
+ `}
1488
+ </div>
1489
+ `}
1490
+ </div>
1491
+
1492
+ <div class="actions">
1493
+ <button class="btn btn-secondary" onclick="cancel()">
1494
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1495
+ <path d="M18 6L6 18M6 6l12 12"/>
1496
+ </svg>
1497
+ 取消
1498
+ </button>
1499
+ <button class="btn btn-primary" id="confirmBtn" onclick="confirm()" disabled>
1500
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1501
+ <path d="M20 6L9 17l-5-5"/>
1502
+ </svg>
1503
+ 确认选择
1504
+ </button>
1505
+ </div>
1506
+
1507
+ <div class="loading" id="loading">
1508
+ <div class="spinner"></div>
1509
+ <span>正在配置环境...</span>
1510
+ </div>
1511
+
1512
+ <div class="success-state" id="successState" style="display: none;">
1513
+ <div class="success-icon">
1514
+ <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1515
+ <path d="M20 6L9 17l-5-5"/>
1516
+ </svg>
1517
+ </div>
1518
+ <h2 class="success-title">环境配置成功!</h2>
1519
+ <p class="success-message">已成功选择 CloudBase 环境</p>
1520
+ <div class="selected-env-info">
1521
+ <span class="env-label">环境 ID:</span>
1522
+ <span class="env-value" id="selectedEnvDisplay"></span>
1523
+ </div>
1524
+ </div>
1525
+ </div>
1526
+ </div>
1527
+
1528
+ <script>
1529
+ let selectedEnvId = null;
1530
+
1531
+ function selectEnv(envId, element) {
1532
+ console.log('=== 环境选择事件触发 ===');
1533
+ console.log('传入的envId:', envId);
1534
+ console.log('传入的element:', element);
1535
+ console.log('element类名:', element ? element.className : 'null');
1536
+
1537
+ selectedEnvId = envId;
1538
+ console.log('设置selectedEnvId为:', selectedEnvId);
1539
+
1540
+ // Remove selected class from all items
1541
+ const allItems = document.querySelectorAll('.env-item');
1542
+ console.log('找到的所有环境项数量:', allItems.length);
1543
+ allItems.forEach(item => {
1544
+ item.classList.remove('selected');
1545
+ });
1546
+
1547
+ // Add selected class to current item
1548
+ if (element) {
1549
+ element.classList.add('selected');
1550
+ console.log('✅ 已添加selected样式到当前项');
1551
+ console.log('当前项的最终类名:', element.className);
1552
+ } else {
1553
+ console.error('❌ element为空,无法添加选中样式');
1554
+ }
1555
+
1556
+ // Enable confirm button
1557
+ const confirmBtn = document.getElementById('confirmBtn');
1558
+ if (confirmBtn) {
1559
+ confirmBtn.disabled = false;
1560
+ console.log('✅ 确认按钮已启用');
1561
+ } else {
1562
+ console.error('❌ 找不到确认按钮');
1563
+ }
1564
+ }
1565
+
1566
+ function confirm() {
1567
+ console.log('=== CONFIRM BUTTON CLICKED ===');
1568
+ console.log('selectedEnvId:', selectedEnvId);
1569
+
1570
+ if (!selectedEnvId) {
1571
+ console.error('❌ 没有选择环境ID!');
1572
+ alert('请先选择一个环境');
1573
+ return;
1574
+ }
1575
+
1576
+ console.log('✅ 环境ID验证通过,开始发送请求...');
1577
+ document.getElementById('loading').style.display = 'flex';
1578
+ document.getElementById('confirmBtn').disabled = true;
1579
+
1580
+ const requestBody = {
1581
+ type: 'envId',
1582
+ data: selectedEnvId
1583
+ };
1584
+
1585
+ console.log('📤 发送请求体:', JSON.stringify(requestBody, null, 2));
1586
+
1587
+ fetch('/api/submit', {
1588
+ method: 'POST',
1589
+ headers: { 'Content-Type': 'application/json' },
1590
+ body: JSON.stringify(requestBody)
1591
+ }).then(response => {
1592
+ console.log('📥 收到响应状态:', response.status);
1593
+ console.log('📥 响应头:', [...response.headers.entries()]);
1594
+ return response.json();
1595
+ }).then(data => {
1596
+ console.log('📥 响应数据:', data);
1597
+ if (data.success) {
1598
+ console.log('✅ 请求成功,展示成功提示');
1599
+ // 隐藏选择区和按钮,仅展示成功提示
1600
+ document.getElementById('envList').style.display = 'none';
1601
+ document.querySelector('.actions').style.display = 'none';
1602
+ document.getElementById('loading').style.display = 'none';
1603
+ document.getElementById('successState').style.display = 'block';
1604
+ // 显示选中的环境 ID
1605
+ document.getElementById('selectedEnvDisplay').textContent = selectedEnvId;
1606
+ window.close();
1607
+ } else {
1608
+ console.error('❌ 请求失败:', data);
1609
+ alert('选择环境失败: ' + (data.error || '未知错误'));
1610
+ document.getElementById('loading').style.display = 'none';
1611
+ document.getElementById('confirmBtn').disabled = false;
1612
+ }
1613
+ }).catch(err => {
1614
+ console.error('❌ 网络请求错误:', err);
1615
+ alert('网络请求失败: ' + err.message);
1616
+ document.getElementById('loading').style.display = 'none';
1617
+ document.getElementById('confirmBtn').disabled = false;
1618
+ });
1619
+ }
1620
+
1621
+ function createNewEnv() {
1622
+ const integrationIde = '${process.env.INTEGRATION_IDE || "AI Toolkit"}';
1623
+ const url = \`http://tcb.cloud.tencent.com/dev?from=\${encodeURIComponent(integrationIde)}\`;
1624
+ location.href = url;
1625
+ }
1626
+
1627
+ function cancel() {
1628
+ fetch('/api/cancel', {
1629
+ method: 'POST',
1630
+ headers: { 'Content-Type': 'application/json' }
1631
+ }).then(() => {
1632
+ window.close();
1633
+ });
1634
+ }
1635
+
1636
+ function switchAccount() {
1637
+ fetch('/api/switch', {
1638
+ method: 'POST',
1639
+ headers: { 'Content-Type': 'application/json' }
1640
+ }).then(() => {
1641
+ // 页面会由服务端重新打开新的登录页面
1642
+ window.close();
1643
+ });
1644
+ }
1645
+
1646
+ function retryInitTcb(sessionId) {
1647
+ const retryBtn = event.target.closest('.error-retry-btn');
1648
+ if (retryBtn) {
1649
+ retryBtn.disabled = true;
1650
+ retryBtn.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg> 重试中...';
1651
+ }
1652
+
1653
+ fetch('/api/retry-init-tcb', {
1654
+ method: 'POST',
1655
+ headers: { 'Content-Type': 'application/json' },
1656
+ body: JSON.stringify({ sessionId })
1657
+ }).then(response => response.json())
1658
+ .then(result => {
1659
+ if (result.success) {
1660
+ // Refresh page to retry initialization
1661
+ setTimeout(() => {
1662
+ window.location.reload();
1663
+ }, 500);
1664
+ } else {
1665
+ alert('重试失败,请稍后再试');
1666
+ if (retryBtn) {
1667
+ retryBtn.disabled = false;
1668
+ retryBtn.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M23 4v6h-6M1 20v-6h6M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/></svg> 重试';
1669
+ }
1670
+ }
1671
+ })
1672
+ .catch(err => {
1673
+ console.error('Retry failed:', err);
1674
+ alert('重试失败,请稍后再试');
1675
+ if (retryBtn) {
1676
+ retryBtn.disabled = false;
1677
+ retryBtn.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M23 4v6h-6M1 20v-6h6M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/></svg> 重试';
1678
+ }
1679
+ });
1680
+ }
1681
+ </script>
1682
+ </body>
1683
+ </html>`;
1684
+ }
1685
+ getClarificationHTML(message, options) {
1686
+ const optionsArray = options || null;
1687
+ return `
1688
+ <!DOCTYPE html>
1689
+ <html lang="zh-CN">
1690
+ <head>
1691
+ <meta charset="UTF-8">
1692
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1693
+ <title>CloudBase AI Toolkit - 需求澄清</title>
1694
+ <style>
1695
+ @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&display=swap');
1696
+
1697
+ * { margin: 0; padding: 0; box-sizing: border-box; }
1698
+ :root {
1699
+ --primary-color: #1a1a1a;
1700
+ --primary-hover: #000000;
1701
+ --accent-color: #67E9E9;
1702
+ --accent-hover: #2BCCCC;
1703
+ --text-primary: #ffffff;
1704
+ --text-secondary: #a0a0a0;
1705
+ --border-color: rgba(255, 255, 255, 0.15);
1706
+ --bg-secondary: rgba(255, 255, 255, 0.08);
1707
+ --bg-glass: rgba(26, 26, 26, 0.95);
1708
+ --shadow: 0 25px 50px rgba(0, 0, 0, 0.3), 0 10px 20px rgba(0, 0, 0, 0.2);
1709
+ --font-mono: 'JetBrains Mono', 'SF Mono', 'Monaco', monospace;
1710
+ --header-bg: linear-gradient(135deg, #0a0a0a 0%, #1a1a1a 50%, #0d1117 100%);
1711
+ }
1712
+
1713
+ body {
1714
+ font-family: var(--font-mono);
1715
+ background: linear-gradient(135deg, #0a0a0a 0%, #1a1a1a 100%);
1716
+ min-height: 100vh;
1717
+ display: flex;
1718
+ align-items: center;
1719
+ justify-content: center;
1720
+ padding: 20px;
1721
+ position: relative;
1722
+ overflow-x: hidden;
1723
+ overflow-y: auto;
1724
+ }
1725
+
1726
+ /* Custom scrollbar styles */
1727
+ ::-webkit-scrollbar {
1728
+ width: 8px;
1729
+ }
1730
+
1731
+ ::-webkit-scrollbar-track {
1732
+ background: rgba(255, 255, 255, 0.05);
1733
+ border-radius: 4px;
1734
+ }
1735
+
1736
+ ::-webkit-scrollbar-thumb {
1737
+ background: var(--accent-color);
1738
+ border-radius: 4px;
1739
+ }
1740
+
1741
+ ::-webkit-scrollbar-thumb:hover {
1742
+ background: var(--accent-hover);
1743
+ }
1744
+
1745
+ body::before {
1746
+ content: '';
1747
+ position: fixed;
1748
+ top: 0; left: 0; right: 0; bottom: 0;
1749
+ background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="grid" width="20" height="20" patternUnits="userSpaceOnUse"><path d="M 20 0 L 0 0 0 20" fill="none" stroke="rgba(255,255,255,0.02)" stroke-width="1"/></pattern></defs><rect width="100" height="100" fill="url(%23grid)"/></svg>') repeat;
1750
+ pointer-events: none;
1751
+ z-index: -1;
1752
+ }
1753
+
1754
+ body::after {
1755
+ content: '';
1756
+ position: fixed;
1757
+ top: 50%; left: 50%;
1758
+ width: 500px; height: 500px;
1759
+ background: radial-gradient(circle, rgba(103, 233, 233, 0.05) 0%, transparent 70%);
1760
+ transform: translate(-50%, -50%);
1761
+ pointer-events: none;
1762
+ z-index: -1;
1763
+ animation: pulse 8s ease-in-out infinite;
1764
+ }
1765
+
1766
+ @keyframes pulse {
1767
+ 0%, 100% { opacity: 0.3; transform: translate(-50%, -50%) scale(1); }
1768
+ 50% { opacity: 0.6; transform: translate(-50%, -50%) scale(1.1); }
1769
+ }
1770
+
1771
+ .modal {
1772
+ background: var(--bg-glass);
1773
+ backdrop-filter: blur(20px);
1774
+ border-radius: 20px;
1775
+ box-shadow: var(--shadow);
1776
+ border: 2px solid var(--border-color);
1777
+ width: 100%;
1778
+ max-width: 600px;
1779
+ overflow: hidden;
1780
+ animation: modalIn 0.6s cubic-bezier(0.175, 0.885, 0.32, 1.275);
1781
+ position: relative;
1782
+ }
1783
+
1784
+ .modal::before {
1785
+ content: '';
1786
+ position: absolute;
1787
+ top: 0; left: 0; right: 0; bottom: 0;
1788
+ background: linear-gradient(45deg, transparent 30%, rgba(255,255,255,0.02) 50%, transparent 70%);
1789
+ animation: shimmer 3s infinite;
1790
+ pointer-events: none;
1791
+ }
1792
+
1793
+ @keyframes shimmer {
1794
+ 0% { transform: translateX(-100%); }
1795
+ 100% { transform: translateX(100%); }
1796
+ }
1797
+
1798
+ @keyframes modalIn {
1799
+ from {
1800
+ opacity: 0;
1801
+ transform: scale(0.9) translateY(-20px);
1802
+ }
1803
+ to {
1804
+ opacity: 1;
1805
+ transform: scale(1) translateY(0);
1806
+ }
1807
+ }
1808
+
1809
+ .header {
1810
+ background: var(--header-bg);
1811
+ color: var(--text-primary);
1812
+ padding: 24px 28px;
1813
+ display: flex;
1814
+ align-items: center;
1815
+ justify-content: space-between;
1816
+ position: relative;
1817
+ overflow: hidden;
1818
+ }
1819
+
1820
+ .header::before {
1821
+ content: '';
1822
+ position: absolute;
1823
+ top: 0; left: 0; right: 0; bottom: 0;
1824
+ background: linear-gradient(45deg, transparent 30%, rgba(255,255,255,0.03) 50%, transparent 70%);
1825
+ animation: headerShimmer 4s infinite;
1826
+ pointer-events: none;
1827
+ }
1828
+
1829
+ @keyframes headerShimmer {
1830
+ 0% { transform: translateX(-100%); }
1831
+ 100% { transform: translateX(100%); }
1832
+ }
1833
+
1834
+ .header-left {
1835
+ display: flex;
1836
+ align-items: center;
1837
+ gap: 16px;
1838
+ z-index: 1;
1839
+ }
1840
+
1841
+ .logo {
1842
+ width: 32px;
1843
+ height: 32px;
1844
+ filter: drop-shadow(0 4px 8px rgba(0,0,0,0.2));
1845
+ animation: logoFloat 3s ease-in-out infinite;
1846
+ }
1847
+
1848
+ @keyframes logoFloat {
1849
+ 0%, 100% { transform: translateY(0px); }
1850
+ 50% { transform: translateY(-3px); }
1851
+ }
1852
+
1853
+ .title {
1854
+ font-size: 20px;
1855
+ font-weight: 700;
1856
+ text-shadow: 0 2px 4px rgba(0,0,0,0.1);
1857
+ }
1858
+
1859
+ .github-link {
1860
+ color: var(--text-primary);
1861
+ text-decoration: none;
1862
+ display: flex;
1863
+ align-items: center;
1864
+ gap: 8px;
1865
+ font-size: 14px;
1866
+ background: rgba(255,255,255,0.08);
1867
+ border: 1px solid rgba(255, 255, 255, 0.12);
1868
+ backdrop-filter: blur(10px);
1869
+ padding: 8px 16px;
1870
+ border-radius: 8px;
1871
+ font-weight: 500;
1872
+ z-index: 1;
1873
+ transition: all 0.3s ease;
1874
+ }
1875
+
1876
+ .github-link:hover {
1877
+ background: rgba(255,255,255,0.15);
1878
+ transform: translateY(-1px);
1879
+ box-shadow: 0 4px 12px rgba(0,0,0,0.3);
1880
+ }
1881
+
1882
+ .content {
1883
+ padding: 32px 24px;
1884
+ position: relative;
1885
+ }
1886
+
1887
+ .content-title {
1888
+ font-size: 24px;
1889
+ font-weight: 700;
1890
+ color: var(--text-primary);
1891
+ margin-bottom: 8px;
1892
+ animation: fadeInUp 0.8s ease-out 0.2s both;
1893
+ }
1894
+
1895
+ @keyframes fadeInUp {
1896
+ from {
1897
+ opacity: 0;
1898
+ transform: translateY(20px);
1899
+ }
1900
+ to {
1901
+ opacity: 1;
1902
+ transform: translateY(0);
1903
+ }
1904
+ }
1905
+
1906
+ .message {
1907
+ background: rgba(103, 233, 233, 0.1);
1908
+ border: 1px solid var(--accent-color);
1909
+ border-left: 4px solid var(--accent-color);
1910
+ padding: 20px;
1911
+ border-radius: 12px;
1912
+ margin-bottom: 24px;
1913
+ white-space: pre-wrap;
1914
+ font-size: 15px;
1915
+ line-height: 1.6;
1916
+ color: var(--text-primary);
1917
+ animation: fadeInUp 0.8s ease-out 0.4s both;
1918
+ position: relative;
1919
+ overflow: hidden;
1920
+ }
1921
+
1922
+ .message::before {
1923
+ content: '';
1924
+ position: absolute;
1925
+ top: 0; left: 0;
1926
+ width: 100%; height: 2px;
1927
+ background: linear-gradient(90deg, var(--accent-color), transparent);
1928
+ animation: progress 2s ease-out;
1929
+ }
1930
+
1931
+ @keyframes progress {
1932
+ from { width: 0%; }
1933
+ to { width: 100%; }
1934
+ }
1935
+
1936
+ .options {
1937
+ margin-bottom: 24px;
1938
+ animation: fadeInUp 0.8s ease-out 0.6s both;
1939
+ }
1940
+
1941
+ .option-item {
1942
+ padding: 16px 20px;
1943
+ border: 1px solid var(--border-color);
1944
+ border-radius: 12px;
1945
+ margin-bottom: 12px;
1946
+ cursor: pointer;
1947
+ transition: all 0.3s ease;
1948
+ display: flex;
1949
+ align-items: center;
1950
+ gap: 14px;
1951
+ background: rgba(255, 255, 255, 0.03);
1952
+ position: relative;
1953
+ overflow: hidden;
1954
+ color: var(--text-primary);
1955
+ }
1956
+
1957
+ .option-item::before {
1958
+ content: '';
1959
+ position: absolute;
1960
+ left: 0; top: 0; bottom: 0;
1961
+ width: 0;
1962
+ background: var(--accent-color);
1963
+ transition: width 0.3s ease;
1964
+ }
1965
+
1966
+ .option-item:hover {
1967
+ background: var(--bg-secondary);
1968
+ border-color: var(--accent-color);
1969
+ transform: translateX(5px);
1970
+ }
1971
+
1972
+ .option-item:hover::before {
1973
+ width: 4px;
1974
+ }
1975
+
1976
+ .option-item.selected {
1977
+ background: rgba(103, 233, 233, 0.1);
1978
+ border-color: var(--accent-color);
1979
+ transform: translateX(5px);
1980
+ }
1981
+
1982
+ .option-item.selected::before {
1983
+ width: 4px;
1984
+ }
1985
+
1986
+ .option-icon {
1987
+ width: 20px;
1988
+ height: 20px;
1989
+ color: var(--accent-color);
1990
+ flex-shrink: 0;
1991
+ animation: iconGlow 2s ease-in-out infinite;
1992
+ }
1993
+
1994
+ @keyframes iconGlow {
1995
+ 0%, 100% { filter: drop-shadow(0 0 2px rgba(103, 233, 233, 0.3)); }
1996
+ 50% { filter: drop-shadow(0 0 8px rgba(103, 233, 233, 0.6)); }
1997
+ }
1998
+
1999
+ .custom-input {
2000
+ margin-bottom: 24px;
2001
+ animation: fadeInUp 0.8s ease-out 0.8s both;
2002
+ }
2003
+
2004
+ .custom-input textarea {
2005
+ width: 100%;
2006
+ min-height: 120px;
2007
+ padding: 16px;
2008
+ border: 1px solid var(--border-color);
2009
+ border-radius: 12px;
2010
+ font-size: 15px;
2011
+ font-family: var(--font-mono);
2012
+ resize: vertical;
2013
+ transition: all 0.3s ease;
2014
+ line-height: 1.5;
2015
+ background: rgba(255, 255, 255, 0.03);
2016
+ color: var(--text-primary);
2017
+ }
2018
+
2019
+ .custom-input textarea::placeholder {
2020
+ color: var(--text-secondary);
2021
+ }
2022
+
2023
+ .custom-input textarea:focus {
2024
+ outline: none;
2025
+ border-color: var(--accent-color);
2026
+ box-shadow: 0 0 0 3px rgba(103, 233, 233, 0.1);
2027
+ background: rgba(255, 255, 255, 0.05);
2028
+ }
2029
+
2030
+ .actions {
2031
+ display: flex;
2032
+ gap: 12px;
2033
+ justify-content: flex-end;
2034
+ animation: fadeInUp 0.8s ease-out 1s both;
2035
+ }
2036
+
2037
+ .btn {
2038
+ padding: 12px 20px;
2039
+ border: none;
2040
+ border-radius: 8px;
2041
+ font-size: 14px;
2042
+ font-weight: 600;
2043
+ cursor: pointer;
2044
+ transition: all 0.3s ease;
2045
+ display: flex;
2046
+ align-items: center;
2047
+ gap: 8px;
2048
+ font-family: var(--font-mono);
2049
+ position: relative;
2050
+ overflow: hidden;
2051
+ }
2052
+
2053
+ .btn::before {
2054
+ content: '';
2055
+ position: absolute;
2056
+ top: 50%; left: 50%;
2057
+ width: 0; height: 0;
2058
+ background: rgba(255,255,255,0.2);
2059
+ border-radius: 50%;
2060
+ transition: all 0.3s ease;
2061
+ transform: translate(-50%, -50%);
2062
+ }
2063
+
2064
+ .btn:hover::before {
2065
+ width: 100px; height: 100px;
2066
+ }
2067
+
2068
+ .btn-primary {
2069
+ background: var(--primary-color);
2070
+ color: var(--text-primary);
2071
+ border: 1px solid var(--border-color);
2072
+ }
2073
+
2074
+ .btn-primary:hover:not(:disabled) {
2075
+ background: var(--primary-hover);
2076
+ transform: translateY(-2px);
2077
+ box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3);
2078
+ }
2079
+
2080
+ .btn-secondary {
2081
+ background: var(--bg-secondary);
2082
+ color: var(--text-secondary);
2083
+ border: 1px solid var(--border-color);
2084
+ }
2085
+
2086
+ .btn-secondary:hover {
2087
+ background: rgba(255, 255, 255, 0.15);
2088
+ color: var(--text-primary);
2089
+ }
2090
+
2091
+ .btn:disabled {
2092
+ opacity: 0.5;
2093
+ cursor: not-allowed;
2094
+ }
2095
+
2096
+ .loading {
2097
+ display: none;
2098
+ align-items: center;
2099
+ justify-content: center;
2100
+ gap: 8px;
2101
+ margin-top: 16px;
2102
+ color: var(--text-secondary);
2103
+ font-size: 14px;
2104
+ }
2105
+
2106
+ .spinner {
2107
+ width: 16px;
2108
+ height: 16px;
2109
+ border: 2px solid var(--border-color);
2110
+ border-top: 2px solid var(--accent-color);
2111
+ border-radius: 50%;
2112
+ animation: spin 1s linear infinite;
2113
+ }
2114
+
2115
+ @keyframes spin {
2116
+ 0% { transform: rotate(0deg); }
2117
+ 100% { transform: rotate(360deg); }
2118
+ }
2119
+
2120
+ @keyframes spin {
2121
+ 0% { transform: rotate(0deg); }
2122
+ 100% { transform: rotate(360deg); }
2123
+ }
2124
+
2125
+ .success-state {
2126
+ text-align: center;
2127
+ padding: 40px 20px;
2128
+ animation: fadeInUp 0.8s ease-out both;
2129
+ }
2130
+
2131
+ .success-icon {
2132
+ margin-bottom: 20px;
2133
+ color: var(--accent-color);
2134
+ animation: successPulse 2s ease-in-out infinite;
2135
+ }
2136
+
2137
+ @keyframes successPulse {
2138
+ 0%, 100% {
2139
+ transform: scale(1);
2140
+ filter: drop-shadow(0 0 8px rgba(103, 233, 233, 0.3));
2141
+ }
2142
+ 50% {
2143
+ transform: scale(1.1);
2144
+ filter: drop-shadow(0 0 16px rgba(103, 233, 233, 0.6));
2145
+ }
2146
+ }
2147
+
2148
+ .success-title {
2149
+ font-size: 24px;
2150
+ font-weight: 700;
2151
+ color: var(--text-primary);
2152
+ margin-bottom: 12px;
2153
+ }
2154
+
2155
+ .success-message {
2156
+ color: var(--text-secondary);
2157
+ font-size: 16px;
2158
+ line-height: 1.5;
2159
+ }
2160
+
2161
+ .selected-env-info {
2162
+ margin-top: 20px;
2163
+ padding: 16px;
2164
+ background: rgba(103, 233, 233, 0.1);
2165
+ border: 1px solid var(--accent-color);
2166
+ border-radius: 12px;
2167
+ display: flex;
2168
+ align-items: center;
2169
+ gap: 12px;
2170
+ }
2171
+
2172
+ .env-label {
2173
+ color: var(--text-secondary);
2174
+ font-size: 14px;
2175
+ font-weight: 500;
2176
+ }
2177
+
2178
+ .env-value {
2179
+ color: var(--accent-color);
2180
+ font-size: 16px;
2181
+ font-weight: 600;
2182
+ font-family: var(--font-mono);
2183
+ }
2184
+ </style>
2185
+ </head>
2186
+ <body>
2187
+ <div class="modal">
2188
+ <div class="header">
2189
+ <div class="header-left">
2190
+ <img class="logo" src="https://7463-tcb-advanced-a656fc-1257967285.tcb.qcloud.la/mcp/cloudbase-logo.svg" alt="CloudBase Logo" />
2191
+ <span class="title">CloudBase AI Toolkit</span>
2192
+ </div>
2193
+ <a href="https://github.com/TencentCloudBase/CloudBase-AI-ToolKit" target="_blank" class="github-link">
2194
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
2195
+ <path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
2196
+ </svg>
2197
+ GitHub
2198
+ </a>
2199
+ </div>
2200
+
2201
+ <div class="content">
2202
+ <h1 class="content-title">AI 需要您确认</h1>
2203
+ <div class="message">${message}</div>
2204
+
2205
+ ${optionsArray
2206
+ ? `
2207
+ <div class="options" id="options">
2208
+ ${optionsArray
2209
+ .map((option, index) => `
2210
+ <div class="option-item" onclick="selectOption('${option}')" style="animation-delay: ${index * 0.1}s;">
2211
+ <svg class="option-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
2212
+ <path d="M21 11.5a8.38 8.38 0 01-.9 3.8 8.5 8.5 0 01-7.6 4.7 8.38 8.38 0 01-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 01-.9-3.8 8.5 8.5 0 014.7-7.6 8.38 8.38 0 013.8-.9h.5a8.48 8.48 0 018 8v.5z"/>
2213
+ </svg>
2214
+ <span>${option}</span>
2215
+ </div>
2216
+ `)
2217
+ .join("")}
2218
+ </div>
2219
+ `
2220
+ : ""}
2221
+
2222
+ <div class="custom-input">
2223
+ <textarea id="customInput" placeholder="请输入您的具体需求或建议..." onkeyup="updateSubmitButton()"></textarea>
2224
+ </div>
2225
+
2226
+ <div class="actions">
2227
+ <button class="btn btn-secondary" onclick="cancel()">
2228
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
2229
+ <path d="M18 6L6 18M6 6l12 12"/>
2230
+ </svg>
2231
+ 取消
2232
+ </button>
2233
+ <button class="btn btn-primary" id="submitBtn" onclick="submit()">
2234
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
2235
+ <path d="M20 6L9 17l-5-5"/>
2236
+ </svg>
2237
+ 确认执行
2238
+ </button>
2239
+ </div>
2240
+
2241
+ <div class="loading" id="loading">
2242
+ <div class="spinner"></div>
2243
+ <span>正在提交...</span>
2244
+ </div>
2245
+ </div>
2246
+ </div>
2247
+
2248
+ <script>
2249
+ let selectedOption = null;
2250
+
2251
+ function selectOption(option) {
2252
+ selectedOption = option;
2253
+
2254
+ document.querySelectorAll('.option-item').forEach(item => {
2255
+ item.classList.remove('selected');
2256
+ });
2257
+ event.currentTarget.classList.add('selected');
2258
+
2259
+ updateSubmitButton();
2260
+ }
2261
+
2262
+ function updateSubmitButton() {
2263
+ const customInput = document.getElementById('customInput').value.trim();
2264
+ const submitBtn = document.getElementById('submitBtn');
2265
+
2266
+ if (selectedOption || customInput) {
2267
+ submitBtn.disabled = false;
2268
+ submitBtn.style.opacity = '1';
2269
+ } else {
2270
+ submitBtn.disabled = true;
2271
+ submitBtn.style.opacity = '0.5';
2272
+ }
2273
+ }
2274
+
2275
+ function submit() {
2276
+ const customInput = document.getElementById('customInput').value.trim();
2277
+ const data = selectedOption || customInput;
2278
+
2279
+ if (!data) return;
2280
+
2281
+ document.getElementById('loading').style.display = 'flex';
2282
+ document.getElementById('submitBtn').disabled = true;
2283
+
2284
+ fetch('/api/submit', {
2285
+ method: 'POST',
2286
+ headers: { 'Content-Type': 'application/json' },
2287
+ body: JSON.stringify({
2288
+ type: 'clarification',
2289
+ data: data
2290
+ })
2291
+ }).then(response => response.json())
2292
+ .then(result => {
2293
+ if (result.success) {
2294
+ window.close();
2295
+ }
2296
+ }).catch(err => {
2297
+ console.error('Error:', err);
2298
+ document.getElementById('loading').style.display = 'none';
2299
+ document.getElementById('submitBtn').disabled = false;
2300
+ });
2301
+ }
2302
+
2303
+ function cancel() {
2304
+ fetch('/api/cancel', {
2305
+ method: 'POST',
2306
+ headers: { 'Content-Type': 'application/json' }
2307
+ }).then(() => {
2308
+ window.close();
2309
+ });
2310
+ }
2311
+
2312
+ // Initialize
2313
+ updateSubmitButton();
2314
+ </script>
2315
+ </body>
2316
+ </html>`;
2317
+ }
2318
+ getConfirmationHTML(message, risks, options) {
2319
+ const availableOptions = options || ["确认执行", "取消操作"];
2320
+ return `
2321
+ <!DOCTYPE html>
2322
+ <html lang="zh-CN">
2323
+ <head>
2324
+ <meta charset="UTF-8">
2325
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
2326
+ <title>CloudBase AI Toolkit - 操作确认</title>
2327
+ <style>
2328
+ @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&display=swap');
2329
+
2330
+ * { margin: 0; padding: 0; box-sizing: border-box; }
2331
+ :root {
2332
+ --primary-color: #1a1a1a;
2333
+ --primary-hover: #000000;
2334
+ --accent-color: #67E9E9;
2335
+ --accent-hover: #2BCCCC;
2336
+ --text-primary: #ffffff;
2337
+ --text-secondary: #a0a0a0;
2338
+ --border-color: rgba(255, 255, 255, 0.15);
2339
+ --bg-secondary: rgba(255, 255, 255, 0.08);
2340
+ --bg-glass: rgba(26, 26, 26, 0.95);
2341
+ --warning-color: #ff6b6b;
2342
+ --warning-bg: rgba(255, 107, 107, 0.1);
2343
+ --warning-border: rgba(255, 107, 107, 0.3);
2344
+ --shadow: 0 25px 50px rgba(0, 0, 0, 0.3), 0 10px 20px rgba(0, 0, 0, 0.2);
2345
+ --font-mono: 'JetBrains Mono', 'SF Mono', 'Monaco', monospace;
2346
+ --header-bg: linear-gradient(135deg, #0a0a0a 0%, #1a1a1a 50%, #0d1117 100%);
2347
+ }
2348
+
2349
+ body {
2350
+ font-family: var(--font-mono);
2351
+ background: linear-gradient(135deg, #0a0a0a 0%, #1a1a1a 100%);
2352
+ min-height: 100vh;
2353
+ display: flex;
2354
+ align-items: center;
2355
+ justify-content: center;
2356
+ padding: 20px;
2357
+ position: relative;
2358
+ overflow-x: hidden;
2359
+ overflow-y: auto;
2360
+ }
2361
+
2362
+ /* Custom scrollbar styles */
2363
+ ::-webkit-scrollbar {
2364
+ width: 8px;
2365
+ }
2366
+
2367
+ ::-webkit-scrollbar-track {
2368
+ background: rgba(255, 255, 255, 0.05);
2369
+ border-radius: 4px;
2370
+ }
2371
+
2372
+ ::-webkit-scrollbar-thumb {
2373
+ background: var(--accent-color);
2374
+ border-radius: 4px;
2375
+ }
2376
+
2377
+ ::-webkit-scrollbar-thumb:hover {
2378
+ background: var(--accent-hover);
2379
+ }
2380
+
2381
+ body::before {
2382
+ content: '';
2383
+ position: fixed;
2384
+ top: 0; left: 0; right: 0; bottom: 0;
2385
+ background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="grid" width="20" height="20" patternUnits="userSpaceOnUse"><path d="M 20 0 L 0 0 0 20" fill="none" stroke="rgba(255,255,255,0.02)" stroke-width="1"/></pattern></defs><rect width="100" height="100" fill="url(%23grid)"/></svg>') repeat;
2386
+ pointer-events: none;
2387
+ z-index: -1;
2388
+ }
2389
+
2390
+ body::after {
2391
+ content: '';
2392
+ position: fixed;
2393
+ top: 50%; left: 50%;
2394
+ width: 500px; height: 500px;
2395
+ background: radial-gradient(circle, rgba(255, 107, 107, 0.03) 0%, transparent 70%);
2396
+ transform: translate(-50%, -50%);
2397
+ pointer-events: none;
2398
+ z-index: -1;
2399
+ animation: pulse 8s ease-in-out infinite;
2400
+ }
2401
+
2402
+ @keyframes pulse {
2403
+ 0%, 100% { opacity: 0.3; transform: translate(-50%, -50%) scale(1); }
2404
+ 50% { opacity: 0.6; transform: translate(-50%, -50%) scale(1.1); }
2405
+ }
2406
+
2407
+ .modal {
2408
+ background: var(--bg-glass);
2409
+ backdrop-filter: blur(20px);
2410
+ border-radius: 20px;
2411
+ box-shadow: var(--shadow);
2412
+ border: 2px solid var(--border-color);
2413
+ width: 100%;
2414
+ max-width: 600px;
2415
+ overflow: hidden;
2416
+ animation: modalIn 0.6s cubic-bezier(0.175, 0.885, 0.32, 1.275);
2417
+ position: relative;
2418
+ }
2419
+
2420
+ .modal::before {
2421
+ content: '';
2422
+ position: absolute;
2423
+ top: 0; left: 0; right: 0; bottom: 0;
2424
+ background: linear-gradient(45deg, transparent 30%, rgba(255,255,255,0.02) 50%, transparent 70%);
2425
+ animation: shimmer 3s infinite;
2426
+ pointer-events: none;
2427
+ }
2428
+
2429
+ @keyframes shimmer {
2430
+ 0% { transform: translateX(-100%); }
2431
+ 100% { transform: translateX(100%); }
2432
+ }
2433
+
2434
+ @keyframes modalIn {
2435
+ from {
2436
+ opacity: 0;
2437
+ transform: scale(0.9) translateY(-20px);
2438
+ }
2439
+ to {
2440
+ opacity: 1;
2441
+ transform: scale(1) translateY(0);
2442
+ }
2443
+ }
2444
+
2445
+ .header {
2446
+ background: var(--header-bg);
2447
+ color: var(--text-primary);
2448
+ padding: 24px 28px;
2449
+ display: flex;
2450
+ align-items: center;
2451
+ justify-content: space-between;
2452
+ position: relative;
2453
+ overflow: hidden;
2454
+ }
2455
+
2456
+ .header::before {
2457
+ content: '';
2458
+ position: absolute;
2459
+ top: 0; left: 0; right: 0; bottom: 0;
2460
+ background: linear-gradient(45deg, transparent 30%, rgba(255,255,255,0.03) 50%, transparent 70%);
2461
+ animation: headerShimmer 4s infinite;
2462
+ pointer-events: none;
2463
+ }
2464
+
2465
+ @keyframes headerShimmer {
2466
+ 0% { transform: translateX(-100%); }
2467
+ 100% { transform: translateX(100%); }
2468
+ }
2469
+
2470
+ .header-left {
2471
+ display: flex;
2472
+ align-items: center;
2473
+ gap: 16px;
2474
+ z-index: 1;
2475
+ }
2476
+
2477
+ .logo {
2478
+ width: 32px;
2479
+ height: 32px;
2480
+ filter: drop-shadow(0 4px 8px rgba(0,0,0,0.2));
2481
+ animation: logoFloat 3s ease-in-out infinite;
2482
+ }
2483
+
2484
+ @keyframes logoFloat {
2485
+ 0%, 100% { transform: translateY(0px); }
2486
+ 50% { transform: translateY(-3px); }
2487
+ }
2488
+
2489
+ .title {
2490
+ font-size: 20px;
2491
+ font-weight: 700;
2492
+ text-shadow: 0 2px 4px rgba(0,0,0,0.1);
2493
+ }
2494
+
2495
+ .github-link {
2496
+ color: var(--text-primary);
2497
+ text-decoration: none;
2498
+ display: flex;
2499
+ align-items: center;
2500
+ gap: 8px;
2501
+ font-size: 14px;
2502
+ background: rgba(255,255,255,0.08);
2503
+ border: 1px solid rgba(255, 255, 255, 0.12);
2504
+ backdrop-filter: blur(10px);
2505
+ padding: 8px 16px;
2506
+ border-radius: 8px;
2507
+ font-weight: 500;
2508
+ z-index: 1;
2509
+ transition: all 0.3s ease;
2510
+ }
2511
+
2512
+ .github-link:hover {
2513
+ background: rgba(255,255,255,0.15);
2514
+ transform: translateY(-1px);
2515
+ box-shadow: 0 4px 12px rgba(0,0,0,0.3);
2516
+ }
2517
+
2518
+ .content {
2519
+ padding: 32px 24px;
2520
+ position: relative;
2521
+ }
2522
+
2523
+ @keyframes fadeInUp {
2524
+ from {
2525
+ opacity: 0;
2526
+ transform: translateY(20px);
2527
+ }
2528
+ to {
2529
+ opacity: 1;
2530
+ transform: translateY(0);
2531
+ }
2532
+ }
2533
+
2534
+ .content-title {
2535
+ font-size: 24px;
2536
+ margin-bottom: 8px;
2537
+ color: var(--text-primary);
2538
+ display: flex;
2539
+ align-items: center;
2540
+ gap: 12px;
2541
+ position: relative;
2542
+ animation: fadeInUp 0.8s ease-out 0.2s both;
2543
+ }
2544
+
2545
+ .message {
2546
+ background: rgba(103, 233, 233, 0.1);
2547
+ border: 1px solid var(--accent-color);
2548
+ border-left: 4px solid var(--accent-color);
2549
+ padding: 20px;
2550
+ border-radius: 12px;
2551
+ margin-bottom: 24px;
2552
+ font-size: 15px;
2553
+ line-height: 1.6;
2554
+ color: var(--text-primary);
2555
+ animation: fadeInUp 0.8s ease-out 0.4s both;
2556
+ position: relative;
2557
+ overflow: scroll;
2558
+ white-space: pre-wrap;
2559
+ max-height: 300px;
2560
+ }
2561
+
2562
+ .message::before {
2563
+ content: '';
2564
+ position: absolute;
2565
+ top: 0; left: 0;
2566
+ width: 100%; height: 2px;
2567
+ background: linear-gradient(90deg, var(--accent-color), transparent);
2568
+ animation: progress 2s ease-out;
2569
+ }
2570
+
2571
+ @keyframes progress {
2572
+ from { width: 0%; }
2573
+ to { width: 100%; }
2574
+ }
2575
+
2576
+ .risks {
2577
+ background: var(--warning-bg);
2578
+ border: 1px solid var(--warning-border);
2579
+ border-radius: 12px;
2580
+ padding: 20px;
2581
+ margin-bottom: 24px;
2582
+ animation: fadeInUp 0.8s ease-out 0.6s both;
2583
+ }
2584
+
2585
+ .risks-title {
2586
+ color: var(--warning-color);
2587
+ font-weight: 600;
2588
+ margin-bottom: 12px;
2589
+ display: flex;
2590
+ align-items: center;
2591
+ gap: 8px;
2592
+ animation: warningGlow 2s ease-in-out infinite;
2593
+ }
2594
+
2595
+ @keyframes warningGlow {
2596
+ 0%, 100% { filter: drop-shadow(0 0 2px rgba(255, 107, 107, 0.3)); }
2597
+ 50% { filter: drop-shadow(0 0 8px rgba(255, 107, 107, 0.6)); }
2598
+ }
2599
+
2600
+ .risk-item {
2601
+ color: var(--text-primary);
2602
+ margin-bottom: 8px;
2603
+ padding-left: 24px;
2604
+ position: relative;
2605
+ }
2606
+
2607
+ .risk-item:before {
2608
+ content: "⚠️";
2609
+ position: absolute;
2610
+ left: 0;
2611
+ color: var(--warning-color);
2612
+ }
2613
+
2614
+ .options {
2615
+ margin-bottom: 24px;
2616
+ animation: fadeInUp 0.8s ease-out 0.8s both;
2617
+ }
2618
+
2619
+ .option-item {
2620
+ padding: 16px 20px;
2621
+ border: 1px solid var(--border-color);
2622
+ border-radius: 12px;
2623
+ margin-bottom: 12px;
2624
+ cursor: pointer;
2625
+ transition: all 0.3s ease;
2626
+ display: flex;
2627
+ align-items: center;
2628
+ gap: 14px;
2629
+ background: rgba(255, 255, 255, 0.03);
2630
+ position: relative;
2631
+ overflow: hidden;
2632
+ color: var(--text-primary);
2633
+ }
2634
+
2635
+ .option-item::before {
2636
+ content: '';
2637
+ position: absolute;
2638
+ left: 0; top: 0; bottom: 0;
2639
+ width: 0;
2640
+ background: var(--accent-color);
2641
+ transition: width 0.3s ease;
2642
+ }
2643
+
2644
+ .option-item.confirm::before {
2645
+ background: var(--accent-color);
2646
+ }
2647
+
2648
+ .option-item.cancel::before {
2649
+ background: var(--warning-color);
2650
+ }
2651
+
2652
+ .option-item:hover {
2653
+ background: var(--bg-secondary);
2654
+ transform: translateX(5px);
2655
+ }
2656
+
2657
+ .option-item:hover::before {
2658
+ width: 4px;
2659
+ }
2660
+
2661
+ .option-item.selected {
2662
+ background: rgba(103, 233, 233, 0.1);
2663
+ border-color: var(--accent-color);
2664
+ transform: translateX(5px);
2665
+ }
2666
+
2667
+ .option-item.selected.cancel {
2668
+ background: rgba(255, 107, 107, 0.1);
2669
+ border-color: var(--warning-color);
2670
+ }
2671
+
2672
+ .option-item.selected::before {
2673
+ width: 4px;
2674
+ }
2675
+
2676
+ .option-icon {
2677
+ width: 20px;
2678
+ height: 20px;
2679
+ color: var(--accent-color);
2680
+ flex-shrink: 0;
2681
+ }
2682
+
2683
+ .option-item.cancel .option-icon {
2684
+ color: var(--warning-color);
2685
+ }
2686
+
2687
+ .loading {
2688
+ display: none;
2689
+ align-items: center;
2690
+ justify-content: center;
2691
+ gap: 8px;
2692
+ margin-top: 16px;
2693
+ color: var(--text-secondary);
2694
+ font-size: 14px;
2695
+ animation: fadeInUp 0.8s ease-out 1s both;
2696
+ }
2697
+
2698
+ .spinner {
2699
+ width: 16px;
2700
+ height: 16px;
2701
+ border: 2px solid var(--border-color);
2702
+ border-top: 2px solid var(--accent-color);
2703
+ border-radius: 50%;
2704
+ animation: spin 1s linear infinite;
2705
+ }
2706
+
2707
+ @keyframes spin {
2708
+ 0% { transform: rotate(0deg); }
2709
+ 100% { transform: rotate(360deg); }
2710
+ }
2711
+ </style>
2712
+ </head>
2713
+ <body>
2714
+ <div class="modal">
2715
+ <div class="header">
2716
+ <div class="header-left">
2717
+ <img class="logo" src="https://7463-tcb-advanced-a656fc-1257967285.tcb.qcloud.la/mcp/cloudbase-logo.svg" alt="CloudBase Logo" />
2718
+ <span class="title">CloudBase AI Toolkit</span>
2719
+ </div>
2720
+ <a href="https://github.com/TencentCloudBase/CloudBase-AI-ToolKit" target="_blank" class="github-link">
2721
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
2722
+ <path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
2723
+ </svg>
2724
+ GitHub
2725
+ </a>
2726
+ </div>
2727
+
2728
+ <div class="content">
2729
+ <h1 class="content-title">
2730
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
2731
+ <path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/>
2732
+ <line x1="12" y1="9" x2="12" y2="13"/>
2733
+ <line x1="12" y1="17" x2="12.01" y2="17"/>
2734
+ </svg>
2735
+ 操作确认
2736
+ </h1>
2737
+ <div class="message">${message}</div>
2738
+
2739
+ ${risks && risks.length > 0
2740
+ ? `
2741
+ <div class="risks">
2742
+ <div class="risks-title">
2743
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
2744
+ <path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/>
2745
+ <line x1="12" y1="9" x2="12" y2="13"/>
2746
+ <line x1="12" y1="17" x2="12.01" y2="17"/>
2747
+ </svg>
2748
+ 风险提示
2749
+ </div>
2750
+ ${risks.map((risk) => `<div class="risk-item">${risk}</div>`).join("")}
2751
+ </div>
2752
+ `
2753
+ : ""}
2754
+
2755
+ <div class="options">
2756
+ ${availableOptions
2757
+ .map((option, index) => {
2758
+ const isCancel = option.includes("取消") ||
2759
+ option.toLowerCase().includes("cancel");
2760
+ const className = isCancel ? "cancel" : "confirm";
2761
+ const iconPath = isCancel
2762
+ ? '<path d="M18 6L6 18M6 6l12 12"/>'
2763
+ : '<path d="M20 6L9 17l-5-5"/>';
2764
+ return `
2765
+ <div class="option-item ${className}" onclick="selectOption('${option}')" style="animation-delay: ${index * 0.1}s;">
2766
+ <svg class="option-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
2767
+ ${iconPath}
2768
+ </svg>
2769
+ <span>${option}</span>
2770
+ </div>
2771
+ `;
2772
+ })
2773
+ .join("")}
2774
+ </div>
2775
+
2776
+ <div class="loading" id="loading">
2777
+ <div class="spinner"></div>
2778
+ <span>正在处理...</span>
2779
+ </div>
2780
+ </div>
2781
+ </div>
2782
+
2783
+ <script>
2784
+ let selectedOption = null;
2785
+
2786
+ function selectOption(option) {
2787
+ selectedOption = option;
2788
+
2789
+ document.querySelectorAll('.option-item').forEach(item => {
2790
+ item.classList.remove('selected');
2791
+ });
2792
+ event.currentTarget.classList.add('selected');
2793
+
2794
+ // Auto submit after selection
2795
+ setTimeout(() => {
2796
+ submit();
2797
+ }, 500);
2798
+ }
2799
+
2800
+ function submit() {
2801
+ if (!selectedOption) return;
2802
+
2803
+ document.getElementById('loading').style.display = 'flex';
2804
+
2805
+ const isConfirmed = !selectedOption.includes('取消') && !selectedOption.toLowerCase().includes('cancel');
2806
+
2807
+ fetch('/api/submit', {
2808
+ method: 'POST',
2809
+ headers: { 'Content-Type': 'application/json' },
2810
+ body: JSON.stringify({
2811
+ type: 'confirmation',
2812
+ data: {
2813
+ confirmed: isConfirmed,
2814
+ option: selectedOption
2815
+ }
2816
+ })
2817
+ }).then(response => response.json())
2818
+ .then(result => {
2819
+ if (result.success) {
2820
+ window.close();
2821
+ }
2822
+ }).catch(err => {
2823
+ console.error('Error:', err);
2824
+ document.getElementById('loading').style.display = 'none';
2825
+ });
2826
+ }
2827
+
2828
+ function cancel() {
2829
+ fetch('/api/cancel', {
2830
+ method: 'POST',
2831
+ headers: { 'Content-Type': 'application/json' }
2832
+ }).then(() => {
2833
+ window.close();
2834
+ });
2835
+ }
2836
+ </script>
2837
+ </body>
2838
+ </html>`;
2839
+ }
2840
+ // 公共方法获取运行状态
2841
+ get running() {
2842
+ return this.isRunning;
2843
+ }
2844
+ // 公共方法获取端口
2845
+ get currentPort() {
2846
+ return this.port;
2847
+ }
2848
+ }
2849
+ exports.InteractiveServer = InteractiveServer;
2850
+ // 单例实例
2851
+ let interactiveServerInstance = null;
2852
+ function getInteractiveServer(mcpServer) {
2853
+ if (!interactiveServerInstance) {
2854
+ interactiveServerInstance = new InteractiveServer(mcpServer);
2855
+ }
2856
+ else if (mcpServer) {
2857
+ // Always update mcpServer if provided, to ensure it's current
2858
+ interactiveServerInstance.mcpServer = mcpServer;
2859
+ (0, logger_js_1.debug)(`[getInteractiveServer] Updated mcpServer, has server: ${!!mcpServer?.server}, has ide: ${!!mcpServer?.ide}`);
2860
+ }
2861
+ return interactiveServerInstance;
2862
+ }
2863
+ async function resetInteractiveServer() {
2864
+ if (interactiveServerInstance) {
2865
+ try {
2866
+ await interactiveServerInstance.stop();
2867
+ }
2868
+ catch (err) {
2869
+ (0, logger_js_1.error)("Error stopping existing server instance:", err instanceof Error ? err : new Error(String(err)));
2870
+ }
2871
+ interactiveServerInstance = null;
2872
+ }
2873
+ }
2874
+ async function getInteractiveServerSafe(mcpServer) {
2875
+ // 如果当前实例存在但不在运行状态,先清理
2876
+ if (interactiveServerInstance && !interactiveServerInstance.running) {
2877
+ try {
2878
+ await interactiveServerInstance.stop();
2879
+ }
2880
+ catch (err) {
2881
+ (0, logger_js_1.debug)("Error stopping non-running server:", err instanceof Error ? err : new Error(String(err)));
2882
+ }
2883
+ interactiveServerInstance = null;
2884
+ }
2885
+ return getInteractiveServer(mcpServer);
2886
+ }
2887
+
2888
+
2889
+ /***/ }),
2890
+
2891
+ /***/ 4112:
2892
+ /***/ ((__unused_webpack_module, exports) => {
2893
+
2894
+
2895
+ /**
2896
+ * HTML template structure
2897
+ */
2898
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
2899
+ exports.buildHTMLPage = buildHTMLPage;
2900
+ function buildHTMLPage(css, body, scripts) {
2901
+ return `
2902
+ <!DOCTYPE html>
2903
+ <html lang="zh-CN">
2904
+ <head>
2905
+ <meta charset="UTF-8">
2906
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
2907
+ <title>CloudBase MCP - 环境配置</title>
2908
+ ${css}
2909
+ </head>
2910
+ <body>
2911
+ ${body}
2912
+ ${scripts}
2913
+ </body>
2914
+ </html>
2915
+ `.trim();
2916
+ }
2917
+
2918
+
2919
+ /***/ }),
2920
+
2921
+ /***/ 6975:
2922
+ /***/ ((__unused_webpack_module, exports) => {
2923
+
2924
+
2925
+ /**
2926
+ * CSS styles for env setup page
2927
+ */
2928
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
2929
+ exports.CSS_STYLES = void 0;
2930
+ exports.CSS_STYLES = `
2931
+ <style>
2932
+ @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&display=swap');
2933
+
2934
+ * { margin: 0; padding: 0; box-sizing: border-box; }
2935
+
2936
+ :root {
2937
+ --primary-color: #1a1a1a;
2938
+ --primary-hover: #000000;
2939
+ --accent-color: #67E9E9;
2940
+ --accent-hover: #2BCCCC;
2941
+ --text-primary: #ffffff;
2942
+ --text-secondary: #a0a0a0;
2943
+ --border-color: rgba(255, 255, 255, 0.15);
2944
+ --bg-secondary: rgba(255, 255, 255, 0.08);
2945
+ --bg-glass: rgba(26, 26, 26, 0.95);
2946
+ --shadow: 0 25px 50px rgba(0, 0, 0, 0.3), 0 10px 20px rgba(0, 0, 0, 0.2);
2947
+ --font-mono: 'JetBrains Mono', 'SF Mono', 'Monaco', monospace;
2948
+ --header-bg: linear-gradient(135deg, #0a0a0a 0%, #1a1a1a 50%, #0d1117 100%);
2949
+ }
2950
+
2951
+ body {
2952
+ font-family: var(--font-mono);
2953
+ background: linear-gradient(135deg, #0a0a0a 0%, #1a1a1a 100%);
2954
+ min-height: 100vh;
2955
+ display: flex;
2956
+ align-items: center;
2957
+ justify-content: center;
2958
+ padding: 20px;
2959
+ position: relative;
2960
+ overflow-x: hidden;
2961
+ overflow-y: auto;
2962
+ }
2963
+
2964
+ /* Custom scrollbar styles */
2965
+ ::-webkit-scrollbar {
2966
+ width: 8px;
2967
+ }
2968
+
2969
+ ::-webkit-scrollbar-track {
2970
+ background: rgba(255, 255, 255, 0.05);
2971
+ border-radius: 4px;
2972
+ }
2973
+
2974
+ ::-webkit-scrollbar-thumb {
2975
+ background: var(--accent-color);
2976
+ border-radius: 4px;
2977
+ }
2978
+
2979
+ ::-webkit-scrollbar-thumb:hover {
2980
+ background: var(--accent-hover);
2981
+ }
2982
+
2983
+ body::before {
2984
+ content: '';
2985
+ position: fixed;
2986
+ top: 0; left: 0; right: 0; bottom: 0;
2987
+ background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="grid" width="20" height="20" patternUnits="userSpaceOnUse"><path d="M 20 0 L 0 0 0 20" fill="none" stroke="rgba(255,255,255,0.02)" stroke-width="1"/></pattern></defs><rect width="100" height="100" fill="url(%23grid)"/></svg>') repeat;
2988
+ pointer-events: none;
2989
+ z-index: -1;
2990
+ }
2991
+
2992
+ body::after {
2993
+ content: '';
2994
+ position: fixed;
2995
+ top: 50%; left: 50%;
2996
+ width: 500px; height: 500px;
2997
+ background: radial-gradient(circle, rgba(103, 233, 233, 0.05) 0%, transparent 70%);
2998
+ transform: translate(-50%, -50%);
2999
+ pointer-events: none;
3000
+ z-index: -1;
3001
+ animation: pulse 8s ease-in-out infinite;
3002
+ }
3003
+
3004
+ @keyframes pulse {
3005
+ 0%, 100% { opacity: 0.3; transform: translate(-50%, -50%) scale(1); }
3006
+ 50% { opacity: 0.6; transform: translate(-50%, -50%) scale(1.1); }
3007
+ }
3008
+
3009
+ .modal {
3010
+ background: var(--bg-glass);
3011
+ backdrop-filter: blur(20px);
3012
+ border-radius: 20px;
3013
+ box-shadow: var(--shadow);
3014
+ border: 1px solid var(--border-color);
3015
+ width: 100%;
3016
+ max-width: 640px;
3017
+ overflow: hidden;
3018
+ animation: modalIn 0.6s cubic-bezier(0.175, 0.885, 0.32, 1.275);
3019
+ position: relative;
3020
+ }
3021
+
3022
+ .modal::before {
3023
+ content: '';
3024
+ position: absolute;
3025
+ top: 0; left: 0; right: 0; bottom: 0;
3026
+ background: linear-gradient(45deg, transparent 30%, rgba(255,255,255,0.02) 50%, transparent 70%);
3027
+ animation: shimmer 3s infinite;
3028
+ pointer-events: none;
3029
+ }
3030
+
3031
+ @keyframes shimmer {
3032
+ 0% { transform: translateX(-100%); }
3033
+ 100% { transform: translateX(100%); }
3034
+ }
3035
+
3036
+ @keyframes modalIn {
3037
+ from {
3038
+ opacity: 0;
3039
+ transform: scale(0.9) translateY(-20px);
3040
+ }
3041
+ to {
3042
+ opacity: 1;
3043
+ transform: scale(1) translateY(0);
3044
+ }
3045
+ }
3046
+
3047
+ .header {
3048
+ background: var(--header-bg);
3049
+ color: var(--text-primary);
3050
+ padding: 20px 32px;
3051
+ display: flex;
3052
+ align-items: center;
3053
+ justify-content: space-between;
3054
+ position: relative;
3055
+ overflow: hidden;
3056
+ }
3057
+
3058
+ .header::before {
3059
+ content: '';
3060
+ position: absolute;
3061
+ top: 0; left: 0; right: 0; bottom: 0;
3062
+ background: linear-gradient(45deg, transparent 30%, rgba(255,255,255,0.03) 50%, transparent 70%);
3063
+ animation: headerShimmer 4s infinite;
3064
+ pointer-events: none;
3065
+ }
3066
+
3067
+ @keyframes headerShimmer {
3068
+ 0% { transform: translateX(-100%); }
3069
+ 100% { transform: translateX(100%); }
3070
+ }
3071
+
3072
+ .header-left {
3073
+ display: flex;
3074
+ align-items: center;
3075
+ gap: 16px;
3076
+ z-index: 1;
3077
+ }
3078
+
3079
+ .logo {
3080
+ width: 32px;
3081
+ height: 32px;
3082
+ filter: drop-shadow(0 4px 8px rgba(0,0,0,0.2));
3083
+ animation: logoFloat 3s ease-in-out infinite;
3084
+ }
3085
+
3086
+ @keyframes logoFloat {
3087
+ 0%, 100% { transform: translateY(0px); }
3088
+ 50% { transform: translateY(-3px); }
3089
+ }
3090
+
3091
+ .title {
3092
+ font-size: 18px;
3093
+ font-weight: 600;
3094
+ text-shadow: 0 2px 4px rgba(0,0,0,0.1);
3095
+ }
3096
+
3097
+ .header-right {
3098
+ display: flex;
3099
+ align-items: center;
3100
+ gap: 16px;
3101
+ z-index: 1;
3102
+ }
3103
+
3104
+ .account-section {
3105
+ display: flex;
3106
+ align-items: center;
3107
+ gap: 8px;
3108
+ padding: 6px 12px;
3109
+ background: rgba(255, 255, 255, 0.04);
3110
+ border: 1px solid var(--border-color);
3111
+ border-radius: 8px;
3112
+ }
3113
+
3114
+ .account-uin {
3115
+ font-size: 12px;
3116
+ color: var(--text-secondary);
3117
+ font-family: var(--font-mono);
3118
+ }
3119
+
3120
+ .btn-switch {
3121
+ display: flex;
3122
+ align-items: center;
3123
+ justify-content: center;
3124
+ padding: 4px;
3125
+ background: transparent;
3126
+ border: none;
3127
+ color: var(--text-secondary);
3128
+ cursor: pointer;
3129
+ transition: all 0.3s ease;
3130
+ border-radius: 4px;
3131
+ }
3132
+
3133
+ .btn-switch:hover {
3134
+ background: rgba(255, 255, 255, 0.1);
3135
+ color: var(--accent-color);
3136
+ }
3137
+
3138
+ .btn-icon-link {
3139
+ display: flex;
3140
+ align-items: center;
3141
+ justify-content: center;
3142
+ padding: 7px;
3143
+ background: transparent;
3144
+ border: 1px solid var(--border-color);
3145
+ border-radius: 8px;
3146
+ color: var(--text-primary);
3147
+ cursor: pointer;
3148
+ transition: all 0.3s ease;
3149
+ text-decoration: none;
3150
+ }
3151
+
3152
+ .btn-icon-link:hover {
3153
+ background: rgba(255, 255, 255, 0.08);
3154
+ border-color: var(--accent-color);
3155
+ }
3156
+
3157
+ .content {
3158
+ padding: 32px 32px 28px;
3159
+ }
3160
+
3161
+ .content-title {
3162
+ font-size: 20px;
3163
+ font-weight: 600;
3164
+ color: var(--text-primary);
3165
+ margin-bottom: 28px;
3166
+ text-align: center;
3167
+ letter-spacing: -0.01em;
3168
+ }
3169
+
3170
+ /* Search section styles */
3171
+ .search-section {
3172
+ display: flex;
3173
+ gap: 10px;
3174
+ margin-bottom: 24px;
3175
+ }
3176
+
3177
+ .search-box {
3178
+ position: relative;
3179
+ flex: 1;
3180
+ }
3181
+
3182
+ .search-icon {
3183
+ position: absolute;
3184
+ left: 14px;
3185
+ top: 50%;
3186
+ transform: translateY(-50%);
3187
+ color: var(--text-secondary);
3188
+ pointer-events: none;
3189
+ z-index: 1;
3190
+ }
3191
+
3192
+ .search-input {
3193
+ width: 100%;
3194
+ padding: 11px 38px 11px 38px;
3195
+ background: var(--bg-secondary);
3196
+ border: 1px solid var(--border-color);
3197
+ border-radius: 10px;
3198
+ color: var(--text-primary);
3199
+ font-size: 13px;
3200
+ font-family: var(--font-mono);
3201
+ transition: all 0.3s ease;
3202
+ }
3203
+
3204
+ .search-input:focus {
3205
+ outline: none;
3206
+ border-color: var(--accent-color);
3207
+ background: rgba(255, 255, 255, 0.1);
3208
+ box-shadow: 0 0 0 3px rgba(103, 233, 233, 0.08);
3209
+ }
3210
+
3211
+ .search-input::placeholder {
3212
+ color: var(--text-secondary);
3213
+ opacity: 0.6;
3214
+ }
3215
+
3216
+ .search-clear {
3217
+ position: absolute;
3218
+ right: 12px;
3219
+ top: 50%;
3220
+ transform: translateY(-50%);
3221
+ background: transparent;
3222
+ border: none;
3223
+ color: var(--text-secondary);
3224
+ cursor: pointer;
3225
+ padding: 4px;
3226
+ display: flex;
3227
+ align-items: center;
3228
+ justify-content: center;
3229
+ border-radius: 4px;
3230
+ transition: all 0.3s ease;
3231
+ z-index: 1;
3232
+ }
3233
+
3234
+ .search-clear:hover {
3235
+ color: var(--text-primary);
3236
+ background: rgba(255, 255, 255, 0.1);
3237
+ }
3238
+
3239
+ .search-actions {
3240
+ display: flex;
3241
+ gap: 8px;
3242
+ }
3243
+
3244
+ .btn-action {
3245
+ display: flex;
3246
+ align-items: center;
3247
+ justify-content: center;
3248
+ gap: 6px;
3249
+ padding: 11px 14px;
3250
+ background: var(--bg-secondary);
3251
+ border: 1px solid var(--border-color);
3252
+ border-radius: 10px;
3253
+ color: var(--text-secondary);
3254
+ cursor: pointer;
3255
+ transition: all 0.3s ease;
3256
+ }
3257
+
3258
+ .btn-action:hover {
3259
+ background: rgba(255, 255, 255, 0.1);
3260
+ border-color: rgba(103, 233, 233, 0.5);
3261
+ color: var(--accent-color);
3262
+ }
3263
+
3264
+ .btn-action:active {
3265
+ transform: scale(0.95);
3266
+ }
3267
+
3268
+ .btn-text {
3269
+ font-size: 13px;
3270
+ font-weight: 500;
3271
+ }
3272
+
3273
+ .env-list {
3274
+ max-height: 340px;
3275
+ overflow-y: auto;
3276
+ padding-right: 6px;
3277
+ margin-bottom: 24px;
3278
+ }
3279
+
3280
+ .env-list::-webkit-scrollbar {
3281
+ width: 5px;
3282
+ }
3283
+
3284
+ .env-list::-webkit-scrollbar-track {
3285
+ background: transparent;
3286
+ }
3287
+
3288
+ .env-list::-webkit-scrollbar-thumb {
3289
+ background: var(--accent-color);
3290
+ border-radius: 3px;
3291
+ }
3292
+
3293
+ /* Env item styles */
3294
+ .env-item {
3295
+ display: flex;
3296
+ align-items: center;
3297
+ gap: 14px;
3298
+ padding: 14px 16px;
3299
+ background: var(--bg-secondary);
3300
+ border: 1px solid var(--border-color);
3301
+ border-radius: 10px;
3302
+ margin-bottom: 10px;
3303
+ cursor: pointer;
3304
+ transition: all 0.3s ease;
3305
+ position: relative;
3306
+ }
3307
+
3308
+ .env-item:hover {
3309
+ background: rgba(255, 255, 255, 0.1);
3310
+ border-color: rgba(103, 233, 233, 0.5);
3311
+ transform: translateX(2px);
3312
+ }
3313
+
3314
+ .env-item.selected {
3315
+ background: rgba(103, 233, 233, 0.12);
3316
+ border-color: var(--accent-color);
3317
+ box-shadow: 0 0 0 1px rgba(103, 233, 233, 0.2);
3318
+ }
3319
+
3320
+ .env-item.selected::after {
3321
+ content: '';
3322
+ position: absolute;
3323
+ right: 16px;
3324
+ top: 50%;
3325
+ transform: translateY(-50%);
3326
+ width: 6px;
3327
+ height: 6px;
3328
+ background: var(--accent-color);
3329
+ border-radius: 50%;
3330
+ box-shadow: 0 0 8px var(--accent-color);
3331
+ }
3332
+
3333
+ .env-info {
3334
+ flex: 1;
3335
+ min-width: 0;
3336
+ }
3337
+
3338
+ .env-name {
3339
+ font-size: 15px;
3340
+ font-weight: 500;
3341
+ color: var(--text-primary);
3342
+ margin-bottom: 5px;
3343
+ overflow: hidden;
3344
+ text-overflow: ellipsis;
3345
+ white-space: nowrap;
3346
+ letter-spacing: -0.01em;
3347
+ }
3348
+
3349
+ .env-name.unnamed {
3350
+ opacity: 0.5;
3351
+ }
3352
+
3353
+ .env-id {
3354
+ font-size: 11px;
3355
+ color: var(--text-secondary);
3356
+ font-family: var(--font-mono);
3357
+ word-break: break-all;
3358
+ opacity: 0.8;
3359
+ }
3360
+
3361
+ /* Empty state */
3362
+ .empty-state {
3363
+ padding: 60px 20px;
3364
+ text-align: center;
3365
+ }
3366
+
3367
+ .empty-message {
3368
+ font-size: 14px;
3369
+ color: var(--text-secondary);
3370
+ opacity: 0.7;
3371
+ line-height: 1.6;
3372
+ }
3373
+
3374
+ .create-env-link {
3375
+ color: var(--accent-color);
3376
+ text-decoration: none;
3377
+ border-bottom: 1px solid transparent;
3378
+ transition: all 0.3s ease;
3379
+ }
3380
+
3381
+ .create-env-link:hover {
3382
+ border-bottom-color: var(--accent-color);
3383
+ opacity: 0.8;
3384
+ }
3385
+
3386
+ /* Action buttons */
3387
+ .actions {
3388
+ display: flex;
3389
+ gap: 10px;
3390
+ margin-top: 28px;
3391
+ }
3392
+
3393
+ .btn {
3394
+ flex: 1;
3395
+ padding: 12px 20px;
3396
+ border: none;
3397
+ border-radius: 10px;
3398
+ font-size: 13px;
3399
+ font-weight: 500;
3400
+ cursor: pointer;
3401
+ transition: all 0.3s ease;
3402
+ display: flex;
3403
+ align-items: center;
3404
+ justify-content: center;
3405
+ gap: 6px;
3406
+ font-family: var(--font-mono);
3407
+ letter-spacing: -0.01em;
3408
+ }
3409
+
3410
+ .btn-primary {
3411
+ background: var(--accent-color);
3412
+ color: var(--primary-color);
3413
+ border: 1px solid var(--accent-color);
3414
+ }
3415
+
3416
+ .btn-primary:hover:not(:disabled) {
3417
+ background: var(--accent-hover);
3418
+ box-shadow: 0 4px 12px rgba(103, 233, 233, 0.3);
3419
+ }
3420
+
3421
+ .btn-primary:disabled {
3422
+ background: var(--bg-secondary);
3423
+ color: var(--text-secondary);
3424
+ cursor: not-allowed;
3425
+ opacity: 0.5;
3426
+ border-color: var(--border-color);
3427
+ }
3428
+
3429
+ .btn-secondary {
3430
+ background: transparent;
3431
+ color: var(--text-primary);
3432
+ border: 1px solid var(--border-color);
3433
+ }
3434
+
3435
+ .btn-secondary:hover {
3436
+ background: var(--bg-secondary);
3437
+ border-color: rgba(255, 255, 255, 0.3);
3438
+ }
3439
+
3440
+ /* Help links */
3441
+ .help-links {
3442
+ display: flex;
3443
+ gap: 20px;
3444
+ justify-content: center;
3445
+ margin-top: 24px;
3446
+ padding-top: 24px;
3447
+ border-top: 1px solid rgba(255, 255, 255, 0.06);
3448
+ }
3449
+
3450
+ .help-link {
3451
+ display: flex;
3452
+ align-items: center;
3453
+ gap: 6px;
3454
+ color: var(--text-secondary);
3455
+ text-decoration: none;
3456
+ font-size: 12px;
3457
+ padding: 6px 10px;
3458
+ border-radius: 6px;
3459
+ transition: all 0.3s ease;
3460
+ opacity: 0.8;
3461
+ }
3462
+
3463
+ .help-link:hover {
3464
+ background: rgba(103, 233, 233, 0.08);
3465
+ color: var(--accent-color);
3466
+ opacity: 1;
3467
+ }
3468
+
3469
+ /* Loading state */
3470
+ .loading {
3471
+ display: none;
3472
+ flex-direction: column;
3473
+ align-items: center;
3474
+ justify-content: center;
3475
+ padding: 40px;
3476
+ gap: 20px;
3477
+ }
3478
+
3479
+ .spinner {
3480
+ width: 40px;
3481
+ height: 40px;
3482
+ border: 3px solid var(--border-color);
3483
+ border-top-color: var(--accent-color);
3484
+ border-radius: 50%;
3485
+ animation: spin 0.8s linear infinite;
3486
+ }
3487
+
3488
+ @keyframes spin {
3489
+ to { transform: rotate(360deg); }
3490
+ }
3491
+
3492
+ .loading span {
3493
+ color: var(--text-secondary);
3494
+ font-size: 14px;
3495
+ }
3496
+
3497
+ /* Success state */
3498
+ .success-state {
3499
+ text-align: center;
3500
+ padding: 40px 20px;
3501
+ animation: fadeInUp 0.8s ease-out both;
3502
+ }
3503
+
3504
+ .success-icon {
3505
+ margin-bottom: 20px;
3506
+ color: var(--accent-color);
3507
+ animation: successPulse 2s ease-in-out infinite;
3508
+ }
3509
+
3510
+ @keyframes successPulse {
3511
+ 0%, 100% {
3512
+ transform: scale(1);
3513
+ filter: drop-shadow(0 0 8px rgba(103, 233, 233, 0.3));
3514
+ }
3515
+ 50% {
3516
+ transform: scale(1.1);
3517
+ filter: drop-shadow(0 0 16px rgba(103, 233, 233, 0.6));
3518
+ }
3519
+ }
3520
+
3521
+ .success-title {
3522
+ font-size: 24px;
3523
+ font-weight: 700;
3524
+ color: var(--text-primary);
3525
+ margin-bottom: 12px;
3526
+ }
3527
+
3528
+ .success-message {
3529
+ color: var(--text-secondary);
3530
+ font-size: 16px;
3531
+ line-height: 1.5;
3532
+ }
3533
+
3534
+ .selected-env-info {
3535
+ margin-top: 20px;
3536
+ padding: 16px;
3537
+ background: rgba(103, 233, 233, 0.1);
3538
+ border: 1px solid var(--accent-color);
3539
+ border-radius: 12px;
3540
+ display: flex;
3541
+ align-items: center;
3542
+ gap: 12px;
3543
+ }
3544
+
3545
+ .env-label {
3546
+ color: var(--text-secondary);
3547
+ font-size: 14px;
3548
+ font-weight: 500;
3549
+ }
3550
+
3551
+ .env-value {
3552
+ color: var(--accent-color);
3553
+ font-size: 16px;
3554
+ font-weight: 600;
3555
+ font-family: var(--font-mono);
3556
+ }
3557
+
3558
+ /* Info banner (for non-critical notifications) */
3559
+ .info-banner {
3560
+ margin-bottom: 20px;
3561
+ padding: 16px;
3562
+ background: rgba(103, 233, 233, 0.08);
3563
+ border: 1px solid rgba(103, 233, 233, 0.2);
3564
+ border-radius: 12px;
3565
+ }
3566
+
3567
+ .info-item {
3568
+ margin-bottom: 16px;
3569
+ }
3570
+
3571
+ .info-item:last-child {
3572
+ margin-bottom: 0;
3573
+ }
3574
+
3575
+ .info-header {
3576
+ display: flex;
3577
+ align-items: center;
3578
+ gap: 8px;
3579
+ margin-bottom: 8px;
3580
+ }
3581
+
3582
+ .info-header svg {
3583
+ color: var(--accent-color);
3584
+ }
3585
+
3586
+ .info-title {
3587
+ color: var(--text-primary);
3588
+ font-weight: 600;
3589
+ font-size: 14px;
3590
+ }
3591
+
3592
+ .info-message {
3593
+ color: var(--text-secondary);
3594
+ font-size: 13px;
3595
+ line-height: 1.5;
3596
+ margin-bottom: 12px;
3597
+ }
3598
+
3599
+ .info-details {
3600
+ display: flex;
3601
+ gap: 16px;
3602
+ flex-wrap: wrap;
3603
+ margin-bottom: 12px;
3604
+ font-size: 12px;
3605
+ color: var(--text-secondary);
3606
+ opacity: 0.7;
3607
+ }
3608
+
3609
+ .detail-label {
3610
+ font-weight: 500;
3611
+ }
3612
+
3613
+ .detail-value {
3614
+ font-family: var(--font-mono);
3615
+ opacity: 0.8;
3616
+ }
3617
+
3618
+ .error-action {
3619
+ display: flex;
3620
+ gap: 8px;
3621
+ flex-wrap: wrap;
3622
+ }
3623
+
3624
+ .error-link {
3625
+ display: inline-flex;
3626
+ align-items: center;
3627
+ gap: 6px;
3628
+ padding: 6px 12px;
3629
+ background: rgba(103, 233, 233, 0.15);
3630
+ border: 1px solid rgba(103, 233, 233, 0.3);
3631
+ border-radius: 6px;
3632
+ color: var(--accent-color);
3633
+ text-decoration: none;
3634
+ font-size: 13px;
3635
+ cursor: pointer;
3636
+ transition: all 0.3s ease;
3637
+ }
3638
+
3639
+ .error-link:hover {
3640
+ background: rgba(103, 233, 233, 0.25);
3641
+ border-color: var(--accent-color);
3642
+ }
3643
+
3644
+ .error-retry-btn {
3645
+ background: rgba(103, 233, 233, 0.2);
3646
+ border-color: var(--accent-color);
3647
+ color: var(--accent-color);
3648
+ }
3649
+
3650
+ .error-retry-btn:hover {
3651
+ background: rgba(103, 233, 233, 0.3);
3652
+ }
3653
+ </style>
3654
+ `;
3655
+
3656
+
3657
+ /***/ }),
3658
+
3659
+ /***/ 7053:
3660
+ /***/ ((__unused_webpack_module, exports) => {
3661
+
3662
+
3663
+ /**
3664
+ * JavaScript logic for env setup page
3665
+ */
3666
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
3667
+ exports.getJavaScripts = getJavaScripts;
3668
+ function getJavaScripts(wsPort) {
3669
+ return `
3670
+ <script>
3671
+ let selectedEnvId = null;
3672
+
3673
+ // Select environment
3674
+ function selectEnv(envId, element) {
3675
+ console.log('[env-setup] Selecting environment:', envId);
3676
+ selectedEnvId = envId;
3677
+
3678
+ // Remove selected class from all items
3679
+ document.querySelectorAll('.env-item').forEach(item => {
3680
+ item.classList.remove('selected');
3681
+ });
3682
+
3683
+ // Add selected class to current item
3684
+ element.classList.add('selected');
3685
+
3686
+ // Enable confirm button
3687
+ document.getElementById('confirmBtn').disabled = false;
3688
+ }
3689
+
3690
+ // Filter environments
3691
+ function filterEnvs(searchTerm) {
3692
+ const items = document.querySelectorAll('.env-item');
3693
+ const searchClear = document.getElementById('searchClear');
3694
+ let visibleCount = 0;
3695
+
3696
+ items.forEach(item => {
3697
+ const name = item.querySelector('.env-name').textContent.toLowerCase();
3698
+ const id = item.querySelector('.env-id').textContent.toLowerCase();
3699
+ const match = name.includes(searchTerm.toLowerCase()) ||
3700
+ id.includes(searchTerm.toLowerCase());
3701
+
3702
+ item.style.display = match ? 'flex' : 'none';
3703
+ if (match) visibleCount++;
3704
+ });
3705
+
3706
+ // Show/hide clear button
3707
+ if (searchClear) {
3708
+ searchClear.style.display = searchTerm ? 'flex' : 'none';
3709
+ }
3710
+
3711
+ // Show/hide no results message
3712
+ const noResults = document.getElementById('noResults');
3713
+ if (noResults) {
3714
+ const hasItems = items.length > 0;
3715
+ noResults.style.display = visibleCount === 0 && hasItems ? 'block' : 'none';
3716
+ }
3717
+ }
3718
+
3719
+ // Clear search
3720
+ function clearSearch() {
3721
+ const searchInput = document.getElementById('searchInput');
3722
+ if (searchInput) {
3723
+ searchInput.value = '';
3724
+ filterEnvs('');
3725
+ searchInput.focus();
3726
+ }
3727
+ }
3728
+
3729
+ // WebSocket: same host as page so it works in remote VS Code / Cloud IDE (no localhost)
3730
+ const wsScheme = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
3731
+ const wsUrl = wsScheme + '//' + window.location.host;
3732
+ const ws = new WebSocket(wsUrl);
3733
+
3734
+ ws.onopen = () => {
3735
+ console.log('[env-setup] WebSocket connected');
3736
+ // Send session ID to server to associate this WebSocket with the session
3737
+ const sessionId = window.location.pathname.split('/').pop();
3738
+ ws.send(JSON.stringify({
3739
+ type: 'registerSession',
3740
+ sessionId: sessionId
3741
+ }));
3742
+ };
3743
+
3744
+ ws.onmessage = (event) => {
3745
+ const data = JSON.parse(event.data);
3746
+ console.log('[env-setup] Received message:', data);
3747
+
3748
+ if (data.type === 'success' && data.envId) {
3749
+ showSuccess(data.envId);
3750
+ } else if (data.type === 'envListRefreshed') {
3751
+ if (data.success) {
3752
+ console.log('[env-setup] Environment list refreshed, reloading page');
3753
+ // Reload the page to show updated environment list
3754
+ window.location.reload();
3755
+ } else {
3756
+ console.error('[env-setup] Failed to refresh environment list:', data.error);
3757
+ alert('刷新环境列表失败: ' + (data.error || '未知错误'));
3758
+ }
3759
+ }
3760
+ };
3761
+
3762
+ ws.onerror = (error) => {
3763
+ console.error('[env-setup] WebSocket error:', error);
3764
+ };
3765
+
3766
+ ws.onclose = () => {
3767
+ console.log('[env-setup] WebSocket closed');
3768
+ };
3769
+
3770
+ // Show success state
3771
+ function showSuccess(envId) {
3772
+ document.querySelector('.content').style.display = 'none';
3773
+ const successState = document.getElementById('successState');
3774
+ const selectedEnvDisplay = document.getElementById('selectedEnvDisplay');
3775
+
3776
+ if (successState && selectedEnvDisplay) {
3777
+ selectedEnvDisplay.textContent = envId;
3778
+ successState.style.display = 'block';
3779
+ }
3780
+ }
3781
+
3782
+ // Confirm selection
3783
+ function confirm() {
3784
+ if (!selectedEnvId) {
3785
+ console.warn('[env-setup] No environment selected');
3786
+ return;
3787
+ }
3788
+
3789
+ console.log('[env-setup] Confirming selection:', selectedEnvId);
3790
+
3791
+ if (ws.readyState === WebSocket.OPEN) {
3792
+ ws.send(JSON.stringify({
3793
+ type: 'envId',
3794
+ data: selectedEnvId,
3795
+ cancelled: false
3796
+ }));
3797
+
3798
+ // Close window immediately after sending confirmation
3799
+ setTimeout(() => {
3800
+ window.close();
3801
+ }, 100);
3802
+ } else {
3803
+ console.error('[env-setup] WebSocket not connected');
3804
+ alert('连接已断开,请刷新页面重试');
3805
+ }
3806
+ }
3807
+
3808
+ // Cancel
3809
+ function cancel() {
3810
+ console.log('[env-setup] Cancelling');
3811
+ if (ws.readyState === WebSocket.OPEN) {
3812
+ ws.send(JSON.stringify({
3813
+ type: 'envId',
3814
+ data: null,
3815
+ cancelled: true
3816
+ }));
3817
+
3818
+ // Close window immediately after sending cancellation
3819
+ setTimeout(() => {
3820
+ window.close();
3821
+ }, 100);
3822
+ }
3823
+ }
3824
+
3825
+ // Switch account
3826
+ function switchAccount() {
3827
+ console.log('[env-setup] Switching account');
3828
+ if (ws.readyState === WebSocket.OPEN) {
3829
+ ws.send(JSON.stringify({
3830
+ type: 'envId',
3831
+ data: null,
3832
+ switch: true
3833
+ }));
3834
+
3835
+ // Close window immediately after sending switch request
3836
+ setTimeout(() => {
3837
+ window.close();
3838
+ }, 100);
3839
+ }
3840
+ }
3841
+
3842
+ // Universal URL opener - works with all IDEs including CodeBuddy
3843
+ async function openUrl(event, url) {
3844
+ if (event) {
3845
+ event.preventDefault();
3846
+ }
3847
+
3848
+ console.log('[env-setup] Opening URL:', url);
3849
+
3850
+ try {
3851
+ const response = await fetch('/api/open-url', {
3852
+ method: 'POST',
3853
+ headers: {
3854
+ 'Content-Type': 'application/json',
3855
+ },
3856
+ body: JSON.stringify({ url }),
3857
+ });
3858
+
3859
+ const result = await response.json();
3860
+
3861
+ if (!result.success) {
3862
+ console.error('[env-setup] Failed to open URL:', result.error);
3863
+ // Fallback to direct open if API fails
3864
+ window.open(url, '_blank');
3865
+ }
3866
+ } catch (error) {
3867
+ console.error('[env-setup] Error opening URL:', error);
3868
+ // Fallback to direct open if API fails
3869
+ window.open(url, '_blank');
3870
+ }
3871
+ }
3872
+
3873
+ // Create new environment
3874
+ function createNewEnv() {
3875
+ console.log('[env-setup] Creating new environment');
3876
+ openUrl(null, 'https://buy.cloud.tencent.com/lowcode?buyType=tcb&channel=mcp');
3877
+ }
3878
+
3879
+ // Refresh environment list
3880
+ function refreshEnvList() {
3881
+ console.log('[env-setup] Refreshing environment list');
3882
+ if (ws.readyState === WebSocket.OPEN) {
3883
+ ws.send(JSON.stringify({
3884
+ type: 'refreshEnvList'
3885
+ }));
3886
+
3887
+ // Add rotation animation
3888
+ const btn = event.target.closest('.btn-refresh');
3889
+ if (btn) {
3890
+ btn.style.transform = 'rotate(360deg)';
3891
+ setTimeout(() => {
3892
+ btn.style.transform = 'rotate(0deg)';
3893
+ }, 600);
3894
+ }
3895
+ }
3896
+ }
3897
+
3898
+ // Retry init TCB
3899
+ function retryInitTcb(sessionId) {
3900
+ console.log('[env-setup] Retrying TCB initialization:', sessionId);
3901
+ if (ws.readyState === WebSocket.OPEN) {
3902
+ ws.send(JSON.stringify({
3903
+ type: 'retryInitTcb',
3904
+ sessionId: sessionId
3905
+ }));
3906
+ }
3907
+ }
3908
+
3909
+ // Initialize on page load
3910
+ window.addEventListener('load', () => {
3911
+ // Auto-select first environment if exists
3912
+ const firstEnvItem = document.querySelector('.env-item');
3913
+ if (firstEnvItem && !selectedEnvId) {
3914
+ const envId = firstEnvItem.getAttribute('onclick')?.match(/selectEnv\\('([^']+)'/)?.[1];
3915
+ if (envId) {
3916
+ selectEnv(envId, firstEnvItem);
3917
+ console.log('[env-setup] Auto-selected first environment:', envId);
3918
+ }
3919
+ }
3920
+ });
3921
+ </script>
3922
+ `.trim();
3923
+ }
3924
+
3925
+
3926
+ /***/ }),
3927
+
3928
+ /***/ 7625:
3929
+ /***/ ((__unused_webpack_module, exports) => {
3930
+
3931
+
3932
+ /**
3933
+ * Reusable UI components for env setup page
3934
+ */
3935
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
3936
+ exports.renderHeader = renderHeader;
3937
+ exports.renderSearchBox = renderSearchBox;
3938
+ exports.renderEnvItem = renderEnvItem;
3939
+ exports.renderEmptyState = renderEmptyState;
3940
+ exports.renderNoResultsState = renderNoResultsState;
3941
+ exports.renderHelpLinks = renderHelpLinks;
3942
+ exports.renderActionButtons = renderActionButtons;
3943
+ exports.renderLoadingState = renderLoadingState;
3944
+ exports.renderSuccessState = renderSuccessState;
3945
+ exports.renderErrorBanner = renderErrorBanner;
3946
+ function renderHeader(accountInfo, ide) {
3947
+ const hasAccount = !!accountInfo?.uin;
3948
+ const isCodeBuddy = ide === "CodeBuddy";
3949
+ return `
3950
+ <div class="header">
3951
+ <div class="header-left">
3952
+ <img class="logo" src="https://7463-tcb-advanced-a656fc-1257967285.tcb.qcloud.la/mcp/cloudbase-logo.svg" alt="CloudBase Logo" />
3953
+ <span class="title">CloudBase</span>
3954
+ </div>
3955
+ ${(hasAccount && accountInfo.region !== 'ap-singapore') ? /** 国际站不支持切 */ `
3956
+ <div class="header-right">
3957
+ <div class="account-section">
3958
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
3959
+ <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/>
3960
+ <circle cx="12" cy="7" r="4"/>
3961
+ </svg>
3962
+ <span class="account-uin">${accountInfo.uin}</span>
3963
+ ${accountInfo.region ? `<span class="account-region">${accountInfo.region}</span>` : ''}
3964
+ ${!isCodeBuddy ? `
3965
+ <button class="btn-switch" onclick="switchAccount()" title="切换账号">
3966
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
3967
+ <path d="M17 2l4 4-4 4M7 22l-4-4 4-4M21 6H10M3 18h11"/>
3968
+ </svg>
3969
+ </button>
3970
+ ` : ''}
3971
+ </div>
3972
+ </div>
3973
+ ` : ''}
3974
+ </div>
3975
+ `.trim();
3976
+ }
3977
+ function renderSearchBox() {
3978
+ return `
3979
+ <div class="search-section">
3980
+ <div class="search-box">
3981
+ <svg class="search-icon" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
3982
+ <circle cx="11" cy="11" r="8"/>
3983
+ <path d="m21 21-4.35-4.35"/>
3984
+ </svg>
3985
+ <input
3986
+ type="text"
3987
+ id="searchInput"
3988
+ class="search-input"
3989
+ placeholder="搜索环境名称或 ID..."
3990
+ oninput="filterEnvs(this.value)"
3991
+ />
3992
+ <button class="search-clear" onclick="clearSearch()" id="searchClear" style="display: none;">
3993
+ <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
3994
+ <path d="M18 6L6 18M6 6l12 12"/>
3995
+ </svg>
3996
+ </button>
3997
+ </div>
3998
+ <div class="search-actions">
3999
+ <button class="btn-action" onclick="createNewEnv()" title="新建环境">
4000
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
4001
+ <path d="M12 5v14M5 12h14"/>
4002
+ </svg>
4003
+ <span class="btn-text">新建环境</span>
4004
+ </button>
4005
+ <button class="btn-action" onclick="refreshEnvList()" title="刷新环境列表">
4006
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
4007
+ <path d="M21.5 2v6h-6M2.5 22v-6h6M2 11.5a10 10 0 0 1 18.8-4.3M22 12.5a10 10 0 0 1-18.8 4.2"/>
4008
+ </svg>
4009
+ </button>
4010
+ </div>
4011
+ </div>
4012
+ `.trim();
4013
+ }
4014
+ function renderEnvItem(env, index) {
4015
+ const alias = env.Alias || '(未命名)';
4016
+ const envId = env.EnvId;
4017
+ const hasAlias = !!env.Alias;
4018
+ return `
4019
+ <div class="env-item" onclick="selectEnv('${envId}', this)">
4020
+ <div class="env-info">
4021
+ <div class="env-name ${!hasAlias ? 'unnamed' : ''}">${alias}</div>
4022
+ <div class="env-id">${envId}</div>
4023
+ </div>
4024
+ </div>
4025
+ `.trim();
4026
+ }
4027
+ function renderEmptyState(hasInitError) {
4028
+ return `
4029
+ <div class="empty-state">
4030
+ <p class="empty-message">
4031
+ 暂无环境,请先<a href="https://buy.cloud.tencent.com/lowcode?buyType=tcb&channel=mcp" onclick="openUrl(event, 'https://buy.cloud.tencent.com/lowcode?buyType=tcb&channel=mcp'); return false;" class="create-env-link">创建环境</a>,然后点击刷新按钮重试
4032
+ </p>
4033
+ </div>
4034
+ `.trim();
4035
+ }
4036
+ function renderNoResultsState() {
4037
+ return `
4038
+ <div class="empty-state" id="noResults" style="display: none;">
4039
+ <svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="opacity: 0.3;">
4040
+ <circle cx="11" cy="11" r="8"/>
4041
+ <path d="m21 21-4.35-4.35"/>
4042
+ </svg>
4043
+ <p class="empty-message">未找到匹配的环境</p>
4044
+ </div>
4045
+ `.trim();
4046
+ }
4047
+ function renderHelpLinks() {
4048
+ return `
4049
+ <div class="help-links">
4050
+ <a href="https://docs.cloudbase.net/ai/cloudbase-ai-toolkit/" onclick="openUrl(event, 'https://docs.cloudbase.net/ai/cloudbase-ai-toolkit/'); return false;" class="help-link">
4051
+ <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
4052
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
4053
+ <polyline points="14 2 14 8 20 8"/>
4054
+ <line x1="16" y1="13" x2="8" y2="13"/>
4055
+ <line x1="16" y1="17" x2="8" y2="17"/>
4056
+ </svg>
4057
+ 帮助文档
4058
+ </a>
4059
+ <a href="https://docs.cloudbase.net/ai/cloudbase-ai-toolkit/tutorials" onclick="openUrl(event, 'https://docs.cloudbase.net/ai/cloudbase-ai-toolkit/tutorials'); return false;" class="help-link">
4060
+ <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
4061
+ <polygon points="23 7 16 12 23 17 23 7"/>
4062
+ <rect x="1" y="5" width="15" height="14" rx="2" ry="2"/>
4063
+ </svg>
4064
+ 视频教程
4065
+ </a>
4066
+ <a href="https://github.com/TencentCloudBase/CloudBase-AI-ToolKit" onclick="openUrl(event, 'https://github.com/TencentCloudBase/CloudBase-AI-ToolKit'); return false;" class="help-link">
4067
+ <svg width="13" height="13" viewBox="0 0 24 24" fill="currentColor">
4068
+ <path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
4069
+ </svg>
4070
+ GitHub
4071
+ </a>
4072
+ </div>
4073
+ `.trim();
4074
+ }
4075
+ function renderActionButtons(hasEnvs, hasInitError) {
4076
+ return `
4077
+ <div class="actions">
4078
+ <button class="btn btn-secondary" onclick="cancel()">
4079
+ <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
4080
+ <path d="M18 6L6 18M6 6l12 12"/>
4081
+ </svg>
4082
+ 取消
4083
+ </button>
4084
+ <button class="btn btn-primary" id="confirmBtn" onclick="confirm()" disabled>
4085
+ <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
4086
+ <path d="M20 6L9 17l-5-5"/>
4087
+ </svg>
4088
+ 确认选择
4089
+ </button>
4090
+ </div>
4091
+ `.trim();
4092
+ }
4093
+ function renderLoadingState() {
4094
+ return `
4095
+ <div class="loading" id="loading" style="display: none;">
4096
+ <div class="spinner"></div>
4097
+ <span>正在配置环境...</span>
4098
+ </div>
4099
+ `.trim();
4100
+ }
4101
+ function renderSuccessState() {
4102
+ return `
4103
+ <div class="success-state" id="successState" style="display: none;">
4104
+ <div class="success-icon">
4105
+ <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
4106
+ <path d="M20 6L9 17l-5-5"/>
4107
+ </svg>
4108
+ </div>
4109
+ <h2 class="success-title">环境配置成功!</h2>
4110
+ <p class="success-message">已成功选择 CloudBase 环境</p>
4111
+ <div class="selected-env-info">
4112
+ <span class="env-label">环境 ID:</span>
4113
+ <span class="env-value" id="selectedEnvDisplay"></span>
4114
+ </div>
4115
+ </div>
4116
+ `.trim();
4117
+ }
4118
+ function renderErrorBanner(errorContext, sessionId) {
4119
+ const initTcbError = errorContext?.initTcbError;
4120
+ const createEnvError = errorContext?.createEnvError;
4121
+ // Only show initTcbError, hide createEnvError
4122
+ if (!initTcbError) {
4123
+ return '';
4124
+ }
4125
+ return `
4126
+ <div class="info-banner" id="errorBanner">
4127
+ ${initTcbError ? `
4128
+ <div class="info-item">
4129
+ <div class="info-header">
4130
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
4131
+ <circle cx="12" cy="12" r="10"/>
4132
+ <line x1="12" y1="8" x2="12" y2="12"/>
4133
+ <line x1="12" y1="16" x2="12.01" y2="16"/>
4134
+ </svg>
4135
+ <span class="info-title">${escapeHtml(initTcbError.message)}</span>
4136
+ </div>
4137
+ <div class="info-message">${escapeHtml(initTcbError.actionText || '')}</div>
4138
+ ${initTcbError.requestId ? `
4139
+ <div class="info-details">
4140
+ <span class="detail-label">错误码:</span> <span class="detail-value">${escapeHtml(initTcbError.code)}</span>
4141
+ <span class="detail-label">请求 ID:</span> <span class="detail-value">${escapeHtml(initTcbError.requestId)}</span>
4142
+ </div>
4143
+ ` : ''}
4144
+ ${initTcbError.needRealNameAuth ? `
4145
+ <div class="error-action">
4146
+ <a href="${initTcbError.helpUrl || 'https://buy.cloud.tencent.com/lowcode?buyType=tcb&channel=mcp'}" onclick="openUrl(event, '${initTcbError.helpUrl || 'https://buy.cloud.tencent.com/lowcode?buyType=tcb&channel=mcp'}'); return false;" class="error-link">
4147
+ 前往实名认证
4148
+ </a>
4149
+ </div>
4150
+ ` : ''}
4151
+ ${initTcbError.needCamAuth ? `
4152
+ <div class="error-action">
4153
+ <a href="${initTcbError.helpUrl || 'https://buy.cloud.tencent.com/lowcode?buyType=tcb&channel=mcp'}" onclick="openUrl(event, '${initTcbError.helpUrl || 'https://buy.cloud.tencent.com/lowcode?buyType=tcb&channel=mcp'}'); return false;" class="error-link">
4154
+ 前往开通 CloudBase 服务
4155
+ </a>
4156
+ ${sessionId ? `
4157
+ <button class="error-link error-retry-btn" onclick="retryInitTcb('${sessionId}')">
4158
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
4159
+ <path d="M23 4v6h-6M1 20v-6h6M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/>
4160
+ </svg>
4161
+ 重试
4162
+ </button>
4163
+ ` : ''}
4164
+ </div>
4165
+ ` : ''}
4166
+ ${!initTcbError.needRealNameAuth && !initTcbError.needCamAuth && initTcbError.helpUrl ? `
4167
+ <div class="error-action">
4168
+ <a href="${initTcbError.helpUrl}" onclick="openUrl(event, '${initTcbError.helpUrl}'); return false;" class="error-link">
4169
+ 前往开通 CloudBase 服务
4170
+ </a>
4171
+ ${sessionId ? `
4172
+ <button class="error-link error-retry-btn" onclick="retryInitTcb('${sessionId}')">
4173
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
4174
+ <path d="M23 4v6h-6M1 20v-6h6M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/>
4175
+ </svg>
4176
+ 重试
4177
+ </button>
4178
+ ` : ''}
4179
+ </div>
4180
+ ` : ''}
4181
+ </div>
4182
+ ` : ''}
4183
+ </div>
4184
+ `.trim();
4185
+ }
4186
+ // Helper function to escape HTML
4187
+ function escapeHtml(text) {
4188
+ const map = {
4189
+ '&': '&amp;',
4190
+ '<': '&lt;',
4191
+ '>': '&gt;',
4192
+ '"': '&quot;',
4193
+ "'": '&#039;'
4194
+ };
4195
+ return text.replace(/[&<>"']/g, (m) => map[m]);
4196
+ }
4197
+
4198
+
4199
+ /***/ }),
4200
+
4201
+ /***/ 9529:
4202
+ /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
4203
+
4204
+
4205
+ /**
4206
+ * Main renderer for env setup page
4207
+ */
4208
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
4209
+ exports.renderEnvSetupPage = renderEnvSetupPage;
4210
+ const components_js_1 = __webpack_require__(7625);
4211
+ const html_js_1 = __webpack_require__(4112);
4212
+ const scripts_js_1 = __webpack_require__(7053);
4213
+ const styles_js_1 = __webpack_require__(6975);
4214
+ /**
4215
+ * Render the complete env setup page
4216
+ */
4217
+ function renderEnvSetupPage(options) {
4218
+ const { envs = [], accountInfo, errorContext, sessionId, wsPort, ide } = options;
4219
+ const hasEnvs = envs.length > 0;
4220
+ const hasInitError = !!errorContext?.initTcbError;
4221
+ const hasErrors = !!(errorContext?.initTcbError || errorContext?.createEnvError);
4222
+ // Build env list HTML
4223
+ let envListHTML = '';
4224
+ if (hasEnvs) {
4225
+ envListHTML = `
4226
+ ${(0, components_js_1.renderSearchBox)()}
4227
+ <div class="env-list" id="envList">
4228
+ ${envs.map((env, index) => (0, components_js_1.renderEnvItem)(env, index)).join('\n')}
4229
+ </div>
4230
+ ${(0, components_js_1.renderNoResultsState)()}
4231
+ `;
4232
+ }
4233
+ else {
4234
+ // 暂无环境时也显示搜索框、新建和刷新按钮
4235
+ envListHTML = `
4236
+ ${(0, components_js_1.renderSearchBox)()}
4237
+ <div class="env-list" id="envList">
4238
+ ${(0, components_js_1.renderEmptyState)(hasInitError)}
4239
+ </div>
4240
+ ${(0, components_js_1.renderNoResultsState)()}
4241
+ `;
4242
+ }
4243
+ // Build body HTML
4244
+ const bodyHTML = `
4245
+ <div class="modal">
4246
+ ${(0, components_js_1.renderHeader)(accountInfo, ide)}
4247
+ <div class="content">
4248
+ <h1 class="content-title">选择环境</h1>
4249
+
4250
+ ${hasErrors ? (0, components_js_1.renderErrorBanner)(errorContext, sessionId) : ''}
4251
+
4252
+ ${envListHTML}
4253
+
4254
+ ${(0, components_js_1.renderActionButtons)(hasEnvs, hasInitError)}
4255
+
4256
+ ${(0, components_js_1.renderHelpLinks)()}
4257
+
4258
+ ${(0, components_js_1.renderLoadingState)()}
4259
+
4260
+ ${(0, components_js_1.renderSuccessState)()}
4261
+ </div>
4262
+ </div>
4263
+ `;
4264
+ // Get JavaScript with WebSocket port
4265
+ const scripts = (0, scripts_js_1.getJavaScripts)(wsPort);
4266
+ // Assemble final HTML
4267
+ return (0, html_js_1.buildHTMLPage)(styles_js_1.CSS_STYLES, bodyHTML, scripts);
4268
+ }
4269
+
4270
+
4271
+ /***/ })
4272
+
4273
+ };