@cloudbase/cloudbase-mcp 2.35.1 → 2.36.0

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