@cloudbase/cloudbase-mcp 1.5.0 → 1.7.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.
- package/README.md +133 -50
- package/dist/auth.js +4 -0
- package/dist/cloudbase-manager.js +81 -10
- package/dist/index.js +7 -1
- package/dist/interactive-server.js +1621 -0
- package/dist/tools/env.js +59 -13
- package/dist/tools/functions.js +20 -2
- package/dist/tools/interactive.js +198 -0
- package/dist/tools/rag.js +3 -2
- package/dist/tools/setup.js +186 -0
- package/dist/utils/logger.js +119 -0
- package/package.json +9 -1
|
@@ -0,0 +1,1621 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import http from 'http';
|
|
3
|
+
import { WebSocketServer } from 'ws';
|
|
4
|
+
import open from 'open';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
7
|
+
import { debug, info, warn, error, getLogs, getLoggerStatus, clearLogs } from './utils/logger.js';
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = path.dirname(__filename);
|
|
10
|
+
export class InteractiveServer {
|
|
11
|
+
app;
|
|
12
|
+
server;
|
|
13
|
+
wss;
|
|
14
|
+
port = 0;
|
|
15
|
+
isRunning = false;
|
|
16
|
+
currentResolver = null;
|
|
17
|
+
sessionData = new Map();
|
|
18
|
+
// 固定端口配置
|
|
19
|
+
DEFAULT_PORT = 3721;
|
|
20
|
+
FALLBACK_PORTS = [3722, 3723, 3724, 3725];
|
|
21
|
+
constructor() {
|
|
22
|
+
this.app = express();
|
|
23
|
+
this.server = http.createServer(this.app);
|
|
24
|
+
this.wss = new WebSocketServer({ server: this.server });
|
|
25
|
+
this.setupExpress();
|
|
26
|
+
this.setupWebSocket();
|
|
27
|
+
}
|
|
28
|
+
setupExpress() {
|
|
29
|
+
this.app.use(express.json());
|
|
30
|
+
this.app.use(express.static(path.join(__dirname, '../static')));
|
|
31
|
+
// 环境ID收集页面
|
|
32
|
+
this.app.get('/env-setup/:sessionId', (req, res) => {
|
|
33
|
+
const { sessionId } = req.params;
|
|
34
|
+
const sessionData = this.sessionData.get(sessionId);
|
|
35
|
+
if (!sessionData) {
|
|
36
|
+
res.status(404).send('会话不存在或已过期');
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
res.send(this.getEnvSetupHTML(sessionData.envs));
|
|
40
|
+
});
|
|
41
|
+
// 需求澄清页面
|
|
42
|
+
this.app.get('/clarification/:sessionId', (req, res) => {
|
|
43
|
+
const { sessionId } = req.params;
|
|
44
|
+
const sessionData = this.sessionData.get(sessionId);
|
|
45
|
+
if (!sessionData) {
|
|
46
|
+
res.status(404).send('会话不存在或已过期');
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
res.send(this.getClarificationHTML(sessionData.message, sessionData.options));
|
|
50
|
+
});
|
|
51
|
+
// 日志查看页面
|
|
52
|
+
this.app.get('/debug/logs', async (req, res) => {
|
|
53
|
+
try {
|
|
54
|
+
const logs = await getLogs(1000);
|
|
55
|
+
const status = getLoggerStatus();
|
|
56
|
+
res.send(this.getLogsHTML(logs, status));
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
res.status(500).send('获取日志失败');
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
// 日志API
|
|
63
|
+
this.app.get('/api/logs', async (req, res) => {
|
|
64
|
+
try {
|
|
65
|
+
const maxLines = parseInt(req.query.maxLines) || 1000;
|
|
66
|
+
const logs = await getLogs(maxLines);
|
|
67
|
+
const status = getLoggerStatus();
|
|
68
|
+
res.json({ logs, status, success: true });
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
res.status(500).json({ success: false, error: 'Failed to get logs' });
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
this.app.post('/api/logs/clear', async (req, res) => {
|
|
75
|
+
try {
|
|
76
|
+
await clearLogs();
|
|
77
|
+
res.json({ success: true });
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
res.status(500).json({ success: false, error: 'Failed to clear logs' });
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
// API接口
|
|
84
|
+
this.app.post('/api/submit', (req, res) => {
|
|
85
|
+
const { type, data } = req.body;
|
|
86
|
+
debug('Received submit request', { type, data });
|
|
87
|
+
if (this.currentResolver) {
|
|
88
|
+
info('Resolving with user data');
|
|
89
|
+
this.currentResolver({ type, data });
|
|
90
|
+
this.currentResolver = null;
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
warn('No resolver waiting for response');
|
|
94
|
+
}
|
|
95
|
+
res.json({ success: true });
|
|
96
|
+
});
|
|
97
|
+
this.app.post('/api/cancel', (req, res) => {
|
|
98
|
+
info('Received cancel request');
|
|
99
|
+
if (this.currentResolver) {
|
|
100
|
+
info('Resolving with cancelled status');
|
|
101
|
+
this.currentResolver({ type: 'clarification', data: null, cancelled: true });
|
|
102
|
+
this.currentResolver = null;
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
warn('No resolver waiting for cancellation');
|
|
106
|
+
}
|
|
107
|
+
res.json({ success: true });
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
setupWebSocket() {
|
|
111
|
+
this.wss.on('connection', (ws) => {
|
|
112
|
+
debug('WebSocket client connected');
|
|
113
|
+
ws.on('message', (message) => {
|
|
114
|
+
try {
|
|
115
|
+
const data = JSON.parse(message.toString());
|
|
116
|
+
debug('WebSocket message received', data);
|
|
117
|
+
if (this.currentResolver) {
|
|
118
|
+
this.currentResolver(data);
|
|
119
|
+
this.currentResolver = null;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
error('WebSocket message parsing error', err);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
ws.on('close', () => {
|
|
127
|
+
debug('WebSocket client disconnected');
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
async start() {
|
|
132
|
+
if (this.isRunning) {
|
|
133
|
+
debug(`Interactive server already running on port ${this.port}`);
|
|
134
|
+
return this.port;
|
|
135
|
+
}
|
|
136
|
+
return new Promise((resolve, reject) => {
|
|
137
|
+
info('Starting interactive server...');
|
|
138
|
+
const tryPorts = [this.DEFAULT_PORT, ...this.FALLBACK_PORTS];
|
|
139
|
+
let currentIndex = 0;
|
|
140
|
+
const tryNextPort = () => {
|
|
141
|
+
if (currentIndex >= tryPorts.length) {
|
|
142
|
+
const err = new Error('All ports are in use, failed to start server');
|
|
143
|
+
error('Server start failed', err);
|
|
144
|
+
reject(err);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
const portToTry = tryPorts[currentIndex];
|
|
148
|
+
currentIndex++;
|
|
149
|
+
debug(`Trying to start server on port ${portToTry}`);
|
|
150
|
+
// 清除之前的错误监听器
|
|
151
|
+
this.server.removeAllListeners('error');
|
|
152
|
+
this.server.on('error', (err) => {
|
|
153
|
+
if (err.code === 'EADDRINUSE') {
|
|
154
|
+
warn(`Port ${portToTry} is in use, trying next port...`);
|
|
155
|
+
tryNextPort();
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
error('Server error', err);
|
|
159
|
+
reject(err);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
this.server.listen(portToTry, '127.0.0.1', () => {
|
|
163
|
+
const address = this.server.address();
|
|
164
|
+
if (address && typeof address === 'object') {
|
|
165
|
+
this.port = address.port;
|
|
166
|
+
this.isRunning = true;
|
|
167
|
+
info(`Interactive server started successfully on http://localhost:${this.port}`);
|
|
168
|
+
resolve(this.port);
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
const err = new Error('Failed to get server address');
|
|
172
|
+
error('Server start error', err);
|
|
173
|
+
reject(err);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
};
|
|
177
|
+
tryNextPort();
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
async stop() {
|
|
181
|
+
if (!this.isRunning)
|
|
182
|
+
return;
|
|
183
|
+
return new Promise((resolve) => {
|
|
184
|
+
this.server.close(() => {
|
|
185
|
+
this.isRunning = false;
|
|
186
|
+
resolve();
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
async collectEnvId(availableEnvs) {
|
|
191
|
+
try {
|
|
192
|
+
info('Starting environment ID collection...');
|
|
193
|
+
debug(`Available environments: ${availableEnvs.length}`);
|
|
194
|
+
const port = await this.start();
|
|
195
|
+
const sessionId = Math.random().toString(36).substring(2, 15);
|
|
196
|
+
this.sessionData.set(sessionId, { envs: availableEnvs });
|
|
197
|
+
debug(`Created session: ${sessionId}`);
|
|
198
|
+
setTimeout(() => {
|
|
199
|
+
this.sessionData.delete(sessionId);
|
|
200
|
+
debug(`Session ${sessionId} expired`);
|
|
201
|
+
}, 5 * 60 * 1000);
|
|
202
|
+
const url = `http://localhost:${port}/env-setup/${sessionId}`;
|
|
203
|
+
info(`Opening browser: ${url}`);
|
|
204
|
+
try {
|
|
205
|
+
// 使用默认浏览器打开一个新窗口
|
|
206
|
+
await open(url, { wait: false });
|
|
207
|
+
info('Browser opened successfully');
|
|
208
|
+
}
|
|
209
|
+
catch (browserError) {
|
|
210
|
+
error('Failed to open browser', browserError);
|
|
211
|
+
warn(`Please manually open: ${url}`);
|
|
212
|
+
}
|
|
213
|
+
info('Waiting for user selection...');
|
|
214
|
+
return new Promise((resolve) => {
|
|
215
|
+
this.currentResolver = resolve;
|
|
216
|
+
setTimeout(() => {
|
|
217
|
+
if (this.currentResolver === resolve) {
|
|
218
|
+
warn('Request timeout, resolving with cancelled');
|
|
219
|
+
this.currentResolver = null;
|
|
220
|
+
resolve({ type: 'envId', data: null, cancelled: true });
|
|
221
|
+
}
|
|
222
|
+
}, 10 * 60 * 1000);
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
catch (err) {
|
|
226
|
+
error('Error in collectEnvId', err);
|
|
227
|
+
throw err;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
async clarifyRequest(message, options) {
|
|
231
|
+
const port = await this.start();
|
|
232
|
+
// 生成会话ID并存储数据
|
|
233
|
+
const sessionId = Math.random().toString(36).substring(2, 15);
|
|
234
|
+
this.sessionData.set(sessionId, { message, options });
|
|
235
|
+
// 设置会话过期时间(5分钟)
|
|
236
|
+
setTimeout(() => {
|
|
237
|
+
this.sessionData.delete(sessionId);
|
|
238
|
+
}, 5 * 60 * 1000);
|
|
239
|
+
const url = `http://localhost:${port}/clarification/${sessionId}`;
|
|
240
|
+
// 打开浏览器
|
|
241
|
+
await open(url);
|
|
242
|
+
return new Promise((resolve) => {
|
|
243
|
+
this.currentResolver = resolve;
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
getEnvSetupHTML(envs) {
|
|
247
|
+
return `
|
|
248
|
+
<!DOCTYPE html>
|
|
249
|
+
<html lang="zh-CN">
|
|
250
|
+
<head>
|
|
251
|
+
<meta charset="UTF-8">
|
|
252
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
253
|
+
<title>CloudBase AI Toolkit - 环境配置</title>
|
|
254
|
+
<style>
|
|
255
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
256
|
+
:root {
|
|
257
|
+
--primary-color: #4F46E5;
|
|
258
|
+
--primary-hover: #4338CA;
|
|
259
|
+
--text-primary: #1F2937;
|
|
260
|
+
--text-secondary: #6B7280;
|
|
261
|
+
--border-color: #E5E7EB;
|
|
262
|
+
--bg-secondary: #F9FAFB;
|
|
263
|
+
--shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
|
264
|
+
--font-mono: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', 'Source Code Pro', monospace;
|
|
265
|
+
}
|
|
266
|
+
body {
|
|
267
|
+
font-family: var(--font-mono);
|
|
268
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
269
|
+
min-height: 100vh;
|
|
270
|
+
display: flex;
|
|
271
|
+
align-items: center;
|
|
272
|
+
justify-content: center;
|
|
273
|
+
padding: 20px;
|
|
274
|
+
}
|
|
275
|
+
.modal {
|
|
276
|
+
background: white;
|
|
277
|
+
border-radius: 16px;
|
|
278
|
+
box-shadow: var(--shadow);
|
|
279
|
+
width: 100%;
|
|
280
|
+
max-width: 500px;
|
|
281
|
+
overflow: hidden;
|
|
282
|
+
animation: modalIn 0.3s ease-out;
|
|
283
|
+
}
|
|
284
|
+
@keyframes modalIn {
|
|
285
|
+
from {
|
|
286
|
+
opacity: 0;
|
|
287
|
+
transform: scale(0.95) translateY(-10px);
|
|
288
|
+
}
|
|
289
|
+
to {
|
|
290
|
+
opacity: 1;
|
|
291
|
+
transform: scale(1) translateY(0);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
.header {
|
|
295
|
+
background: var(--primary-color);
|
|
296
|
+
color: white;
|
|
297
|
+
padding: 20px 24px;
|
|
298
|
+
display: flex;
|
|
299
|
+
align-items: center;
|
|
300
|
+
justify-content: space-between;
|
|
301
|
+
}
|
|
302
|
+
.header-left {
|
|
303
|
+
display: flex;
|
|
304
|
+
align-items: center;
|
|
305
|
+
gap: 12px;
|
|
306
|
+
}
|
|
307
|
+
.logo {
|
|
308
|
+
width: 28px;
|
|
309
|
+
height: 28px;
|
|
310
|
+
animation: pulse 2s infinite;
|
|
311
|
+
}
|
|
312
|
+
@keyframes pulse {
|
|
313
|
+
0%, 100% { opacity: 1; }
|
|
314
|
+
50% { opacity: 0.8; }
|
|
315
|
+
}
|
|
316
|
+
.title {
|
|
317
|
+
font-size: 18px;
|
|
318
|
+
font-weight: 600;
|
|
319
|
+
letter-spacing: -0.025em;
|
|
320
|
+
}
|
|
321
|
+
.github-link {
|
|
322
|
+
color: white;
|
|
323
|
+
text-decoration: none;
|
|
324
|
+
display: flex;
|
|
325
|
+
align-items: center;
|
|
326
|
+
gap: 6px;
|
|
327
|
+
font-size: 14px;
|
|
328
|
+
opacity: 0.9;
|
|
329
|
+
transition: opacity 0.2s;
|
|
330
|
+
padding: 6px 10px;
|
|
331
|
+
border-radius: 6px;
|
|
332
|
+
background: rgba(255,255,255,0.1);
|
|
333
|
+
}
|
|
334
|
+
.github-link:hover {
|
|
335
|
+
opacity: 1;
|
|
336
|
+
background: rgba(255,255,255,0.2);
|
|
337
|
+
}
|
|
338
|
+
.content {
|
|
339
|
+
padding: 32px 24px;
|
|
340
|
+
}
|
|
341
|
+
.content-title {
|
|
342
|
+
font-size: 24px;
|
|
343
|
+
font-weight: 700;
|
|
344
|
+
color: var(--text-primary);
|
|
345
|
+
margin-bottom: 8px;
|
|
346
|
+
}
|
|
347
|
+
.content-subtitle {
|
|
348
|
+
color: var(--text-secondary);
|
|
349
|
+
margin-bottom: 24px;
|
|
350
|
+
line-height: 1.5;
|
|
351
|
+
}
|
|
352
|
+
.env-list {
|
|
353
|
+
border: 1px solid var(--border-color);
|
|
354
|
+
border-radius: 12px;
|
|
355
|
+
margin-bottom: 24px;
|
|
356
|
+
max-height: 300px;
|
|
357
|
+
overflow-y: auto;
|
|
358
|
+
}
|
|
359
|
+
.env-item {
|
|
360
|
+
padding: 16px 20px;
|
|
361
|
+
border-bottom: 1px solid var(--border-color);
|
|
362
|
+
cursor: pointer;
|
|
363
|
+
transition: all 0.2s;
|
|
364
|
+
display: flex;
|
|
365
|
+
align-items: center;
|
|
366
|
+
gap: 14px;
|
|
367
|
+
}
|
|
368
|
+
.env-item:last-child {
|
|
369
|
+
border-bottom: none;
|
|
370
|
+
}
|
|
371
|
+
.env-item:hover {
|
|
372
|
+
background: var(--bg-secondary);
|
|
373
|
+
}
|
|
374
|
+
.env-item.selected {
|
|
375
|
+
background: #EEF2FF;
|
|
376
|
+
border-left: 4px solid var(--primary-color);
|
|
377
|
+
}
|
|
378
|
+
.env-icon {
|
|
379
|
+
width: 20px;
|
|
380
|
+
height: 20px;
|
|
381
|
+
color: var(--primary-color);
|
|
382
|
+
flex-shrink: 0;
|
|
383
|
+
}
|
|
384
|
+
.env-info {
|
|
385
|
+
flex: 1;
|
|
386
|
+
min-width: 0;
|
|
387
|
+
}
|
|
388
|
+
.env-name {
|
|
389
|
+
font-weight: 600;
|
|
390
|
+
color: var(--text-primary);
|
|
391
|
+
margin-bottom: 4px;
|
|
392
|
+
}
|
|
393
|
+
.env-id {
|
|
394
|
+
font-size: 13px;
|
|
395
|
+
color: var(--text-secondary);
|
|
396
|
+
font-family: var(--font-mono);
|
|
397
|
+
background: var(--bg-secondary);
|
|
398
|
+
padding: 2px 8px;
|
|
399
|
+
border-radius: 4px;
|
|
400
|
+
display: inline-block;
|
|
401
|
+
}
|
|
402
|
+
.actions {
|
|
403
|
+
display: flex;
|
|
404
|
+
gap: 12px;
|
|
405
|
+
justify-content: flex-end;
|
|
406
|
+
}
|
|
407
|
+
.btn {
|
|
408
|
+
padding: 12px 20px;
|
|
409
|
+
border: none;
|
|
410
|
+
border-radius: 8px;
|
|
411
|
+
font-size: 14px;
|
|
412
|
+
font-weight: 600;
|
|
413
|
+
cursor: pointer;
|
|
414
|
+
transition: all 0.2s;
|
|
415
|
+
display: flex;
|
|
416
|
+
align-items: center;
|
|
417
|
+
gap: 8px;
|
|
418
|
+
font-family: var(--font-mono);
|
|
419
|
+
}
|
|
420
|
+
.btn-primary {
|
|
421
|
+
background: var(--primary-color);
|
|
422
|
+
color: white;
|
|
423
|
+
}
|
|
424
|
+
.btn-primary:hover:not(:disabled) {
|
|
425
|
+
background: var(--primary-hover);
|
|
426
|
+
transform: translateY(-1px);
|
|
427
|
+
}
|
|
428
|
+
.btn-secondary {
|
|
429
|
+
background: var(--bg-secondary);
|
|
430
|
+
color: var(--text-secondary);
|
|
431
|
+
border: 1px solid var(--border-color);
|
|
432
|
+
}
|
|
433
|
+
.btn-secondary:hover {
|
|
434
|
+
background: #F3F4F6;
|
|
435
|
+
}
|
|
436
|
+
.btn:disabled {
|
|
437
|
+
opacity: 0.5;
|
|
438
|
+
cursor: not-allowed;
|
|
439
|
+
}
|
|
440
|
+
.loading {
|
|
441
|
+
display: none;
|
|
442
|
+
align-items: center;
|
|
443
|
+
justify-content: center;
|
|
444
|
+
gap: 8px;
|
|
445
|
+
margin-top: 16px;
|
|
446
|
+
color: var(--text-secondary);
|
|
447
|
+
font-size: 14px;
|
|
448
|
+
}
|
|
449
|
+
.spinner {
|
|
450
|
+
width: 16px;
|
|
451
|
+
height: 16px;
|
|
452
|
+
border: 2px solid var(--border-color);
|
|
453
|
+
border-top: 2px solid var(--primary-color);
|
|
454
|
+
border-radius: 50%;
|
|
455
|
+
animation: spin 1s linear infinite;
|
|
456
|
+
}
|
|
457
|
+
@keyframes spin {
|
|
458
|
+
0% { transform: rotate(0deg); }
|
|
459
|
+
100% { transform: rotate(360deg); }
|
|
460
|
+
}
|
|
461
|
+
</style>
|
|
462
|
+
</head>
|
|
463
|
+
<body>
|
|
464
|
+
<div class="modal">
|
|
465
|
+
<div class="header">
|
|
466
|
+
<div class="header-left">
|
|
467
|
+
<svg class="logo" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
468
|
+
<path d="M12 2L2 7V17L12 22L22 17V7L12 2Z"/>
|
|
469
|
+
<path d="M12 22V12"/>
|
|
470
|
+
<path d="M22 7L12 12L2 7"/>
|
|
471
|
+
</svg>
|
|
472
|
+
<span class="title">CloudBase AI Toolkit</span>
|
|
473
|
+
</div>
|
|
474
|
+
<a href="https://github.com/TencentCloudBase/CloudBase-AI-ToolKit" target="_blank" class="github-link">
|
|
475
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
|
|
476
|
+
<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"/>
|
|
477
|
+
</svg>
|
|
478
|
+
GitHub
|
|
479
|
+
</a>
|
|
480
|
+
</div>
|
|
481
|
+
|
|
482
|
+
<div class="content">
|
|
483
|
+
<h1 class="content-title">选择云开发环境</h1>
|
|
484
|
+
<p class="content-subtitle">请选择要使用的云开发环境,系统将自动配置环境ID</p>
|
|
485
|
+
|
|
486
|
+
<div class="env-list" id="envList">
|
|
487
|
+
<!-- 环境列表将通过JavaScript动态加载 -->
|
|
488
|
+
</div>
|
|
489
|
+
|
|
490
|
+
<div class="actions">
|
|
491
|
+
<button class="btn btn-secondary" onclick="cancel()">
|
|
492
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
493
|
+
<path d="M18 6L6 18M6 6l12 12"/>
|
|
494
|
+
</svg>
|
|
495
|
+
取消
|
|
496
|
+
</button>
|
|
497
|
+
<button class="btn btn-primary" id="confirmBtn" onclick="confirm()" disabled>
|
|
498
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
499
|
+
<path d="M20 6L9 17l-5-5"/>
|
|
500
|
+
</svg>
|
|
501
|
+
确认选择
|
|
502
|
+
</button>
|
|
503
|
+
</div>
|
|
504
|
+
|
|
505
|
+
<div class="loading" id="loading">
|
|
506
|
+
<div class="spinner"></div>
|
|
507
|
+
<span>正在配置环境...</span>
|
|
508
|
+
</div>
|
|
509
|
+
</div>
|
|
510
|
+
</div>
|
|
511
|
+
|
|
512
|
+
<script>
|
|
513
|
+
let selectedEnvId = null;
|
|
514
|
+
const envs = ${JSON.stringify(envs || [])};
|
|
515
|
+
|
|
516
|
+
function renderEnvList() {
|
|
517
|
+
const envList = document.getElementById('envList');
|
|
518
|
+
if (envs.length === 0) {
|
|
519
|
+
envList.innerHTML = '<div style="padding: 24px; text-align: center; color: var(--text-secondary);">暂无可用环境</div>';
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
envList.innerHTML = envs.map((env, index) => \`
|
|
524
|
+
<div class="env-item" onclick="selectEnv('\${env.EnvId}')">
|
|
525
|
+
<svg class="env-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
526
|
+
<path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/>
|
|
527
|
+
</svg>
|
|
528
|
+
<div class="env-info">
|
|
529
|
+
<div class="env-name">\${env.Alias || env.EnvId}</div>
|
|
530
|
+
<div class="env-id">\${env.EnvId}</div>
|
|
531
|
+
</div>
|
|
532
|
+
</div>
|
|
533
|
+
\`).join('');
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
function selectEnv(envId) {
|
|
537
|
+
selectedEnvId = envId;
|
|
538
|
+
document.querySelectorAll('.env-item').forEach(item => {
|
|
539
|
+
item.classList.remove('selected');
|
|
540
|
+
});
|
|
541
|
+
event.currentTarget.classList.add('selected');
|
|
542
|
+
document.getElementById('confirmBtn').disabled = false;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
function confirm() {
|
|
546
|
+
if (!selectedEnvId) return;
|
|
547
|
+
document.getElementById('loading').style.display = 'flex';
|
|
548
|
+
fetch('/api/submit', {
|
|
549
|
+
method: 'POST',
|
|
550
|
+
headers: { 'Content-Type': 'application/json' },
|
|
551
|
+
body: JSON.stringify({
|
|
552
|
+
type: 'envId',
|
|
553
|
+
data: { envId: selectedEnvId }
|
|
554
|
+
})
|
|
555
|
+
}).then(() => window.close());
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
function cancel() {
|
|
559
|
+
fetch('/api/cancel', { method: 'POST' }).then(() => window.close());
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
renderEnvList();
|
|
563
|
+
if (envs.length === 1) {
|
|
564
|
+
selectEnv(envs[0].EnvId);
|
|
565
|
+
}
|
|
566
|
+
</script>
|
|
567
|
+
</body>
|
|
568
|
+
</html>`;
|
|
569
|
+
}
|
|
570
|
+
getLogsHTML(logs, status) {
|
|
571
|
+
return `
|
|
572
|
+
<!DOCTYPE html>
|
|
573
|
+
<html lang="zh-CN">
|
|
574
|
+
<head>
|
|
575
|
+
<meta charset="UTF-8">
|
|
576
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
577
|
+
<title>CloudBase MCP 调试日志</title>
|
|
578
|
+
<style>
|
|
579
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
580
|
+
body {
|
|
581
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
582
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
583
|
+
min-height: 100vh;
|
|
584
|
+
padding: 20px;
|
|
585
|
+
}
|
|
586
|
+
.container {
|
|
587
|
+
background: white;
|
|
588
|
+
border-radius: 20px;
|
|
589
|
+
padding: 30px;
|
|
590
|
+
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
|
|
591
|
+
max-width: 1200px;
|
|
592
|
+
margin: 0 auto;
|
|
593
|
+
}
|
|
594
|
+
.header {
|
|
595
|
+
text-align: center;
|
|
596
|
+
margin-bottom: 30px;
|
|
597
|
+
}
|
|
598
|
+
.icon {
|
|
599
|
+
width: 60px;
|
|
600
|
+
height: 60px;
|
|
601
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
602
|
+
border-radius: 50%;
|
|
603
|
+
margin: 0 auto 20px;
|
|
604
|
+
display: flex;
|
|
605
|
+
align-items: center;
|
|
606
|
+
justify-content: center;
|
|
607
|
+
font-size: 30px;
|
|
608
|
+
color: white;
|
|
609
|
+
}
|
|
610
|
+
h1 {
|
|
611
|
+
color: #2d3748;
|
|
612
|
+
margin-bottom: 10px;
|
|
613
|
+
font-size: 24px;
|
|
614
|
+
font-weight: 700;
|
|
615
|
+
}
|
|
616
|
+
.status {
|
|
617
|
+
background: #f7fafc;
|
|
618
|
+
border-radius: 12px;
|
|
619
|
+
padding: 20px;
|
|
620
|
+
margin-bottom: 20px;
|
|
621
|
+
display: grid;
|
|
622
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
623
|
+
gap: 15px;
|
|
624
|
+
}
|
|
625
|
+
.status-item {
|
|
626
|
+
display: flex;
|
|
627
|
+
justify-content: space-between;
|
|
628
|
+
align-items: center;
|
|
629
|
+
}
|
|
630
|
+
.status-label {
|
|
631
|
+
font-weight: 600;
|
|
632
|
+
color: #4a5568;
|
|
633
|
+
}
|
|
634
|
+
.status-value {
|
|
635
|
+
color: #2d3748;
|
|
636
|
+
font-family: 'Monaco', 'Menlo', monospace;
|
|
637
|
+
font-size: 14px;
|
|
638
|
+
}
|
|
639
|
+
.enabled {
|
|
640
|
+
color: #38a169;
|
|
641
|
+
}
|
|
642
|
+
.disabled {
|
|
643
|
+
color: #e53e3e;
|
|
644
|
+
}
|
|
645
|
+
.controls {
|
|
646
|
+
display: flex;
|
|
647
|
+
gap: 15px;
|
|
648
|
+
margin-bottom: 20px;
|
|
649
|
+
justify-content: space-between;
|
|
650
|
+
align-items: center;
|
|
651
|
+
flex-wrap: wrap;
|
|
652
|
+
}
|
|
653
|
+
.controls-left {
|
|
654
|
+
display: flex;
|
|
655
|
+
gap: 15px;
|
|
656
|
+
}
|
|
657
|
+
.btn {
|
|
658
|
+
padding: 10px 20px;
|
|
659
|
+
border: none;
|
|
660
|
+
border-radius: 8px;
|
|
661
|
+
font-size: 14px;
|
|
662
|
+
font-weight: 600;
|
|
663
|
+
cursor: pointer;
|
|
664
|
+
transition: all 0.3s ease;
|
|
665
|
+
}
|
|
666
|
+
.btn-primary {
|
|
667
|
+
background: #667eea;
|
|
668
|
+
color: white;
|
|
669
|
+
}
|
|
670
|
+
.btn-primary:hover {
|
|
671
|
+
background: #5a67d8;
|
|
672
|
+
}
|
|
673
|
+
.btn-danger {
|
|
674
|
+
background: #e53e3e;
|
|
675
|
+
color: white;
|
|
676
|
+
}
|
|
677
|
+
.btn-danger:hover {
|
|
678
|
+
background: #c53030;
|
|
679
|
+
}
|
|
680
|
+
.btn-secondary {
|
|
681
|
+
background: #e2e8f0;
|
|
682
|
+
color: #4a5568;
|
|
683
|
+
}
|
|
684
|
+
.btn-secondary:hover {
|
|
685
|
+
background: #cbd5e0;
|
|
686
|
+
}
|
|
687
|
+
.log-container {
|
|
688
|
+
background: #1a202c;
|
|
689
|
+
border-radius: 12px;
|
|
690
|
+
padding: 20px;
|
|
691
|
+
height: 500px;
|
|
692
|
+
overflow-y: auto;
|
|
693
|
+
font-family: 'Monaco', 'Menlo', 'Consolas', monospace;
|
|
694
|
+
font-size: 13px;
|
|
695
|
+
line-height: 1.4;
|
|
696
|
+
}
|
|
697
|
+
.log-container::-webkit-scrollbar {
|
|
698
|
+
width: 8px;
|
|
699
|
+
}
|
|
700
|
+
.log-container::-webkit-scrollbar-track {
|
|
701
|
+
background: #2d3748;
|
|
702
|
+
border-radius: 4px;
|
|
703
|
+
}
|
|
704
|
+
.log-container::-webkit-scrollbar-thumb {
|
|
705
|
+
background: #4a5568;
|
|
706
|
+
border-radius: 4px;
|
|
707
|
+
}
|
|
708
|
+
.log-container::-webkit-scrollbar-thumb:hover {
|
|
709
|
+
background: #718096;
|
|
710
|
+
}
|
|
711
|
+
.log-line {
|
|
712
|
+
color: #e2e8f0;
|
|
713
|
+
margin-bottom: 2px;
|
|
714
|
+
word-break: break-all;
|
|
715
|
+
}
|
|
716
|
+
.log-line.debug {
|
|
717
|
+
color: #a0aec0;
|
|
718
|
+
}
|
|
719
|
+
.log-line.info {
|
|
720
|
+
color: #63b3ed;
|
|
721
|
+
}
|
|
722
|
+
.log-line.warn {
|
|
723
|
+
color: #f6ad55;
|
|
724
|
+
}
|
|
725
|
+
.log-line.error {
|
|
726
|
+
color: #fc8181;
|
|
727
|
+
}
|
|
728
|
+
.timestamp {
|
|
729
|
+
color: #718096;
|
|
730
|
+
}
|
|
731
|
+
.level {
|
|
732
|
+
font-weight: bold;
|
|
733
|
+
margin: 0 8px;
|
|
734
|
+
}
|
|
735
|
+
.empty-state {
|
|
736
|
+
text-align: center;
|
|
737
|
+
color: #718096;
|
|
738
|
+
padding: 40px;
|
|
739
|
+
font-style: italic;
|
|
740
|
+
}
|
|
741
|
+
.footer {
|
|
742
|
+
margin-top: 30px;
|
|
743
|
+
padding-top: 20px;
|
|
744
|
+
border-top: 1px solid #e2e8f0;
|
|
745
|
+
text-align: center;
|
|
746
|
+
}
|
|
747
|
+
.footer .brand {
|
|
748
|
+
font-size: 16px;
|
|
749
|
+
font-weight: 700;
|
|
750
|
+
color: #2d3748;
|
|
751
|
+
margin-bottom: 10px;
|
|
752
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
753
|
+
-webkit-background-clip: text;
|
|
754
|
+
-webkit-text-fill-color: transparent;
|
|
755
|
+
background-clip: text;
|
|
756
|
+
}
|
|
757
|
+
.footer .links {
|
|
758
|
+
display: flex;
|
|
759
|
+
justify-content: center;
|
|
760
|
+
gap: 20px;
|
|
761
|
+
}
|
|
762
|
+
.footer .links a {
|
|
763
|
+
color: #718096;
|
|
764
|
+
text-decoration: none;
|
|
765
|
+
font-size: 14px;
|
|
766
|
+
transition: color 0.3s ease;
|
|
767
|
+
}
|
|
768
|
+
.footer .links a:hover {
|
|
769
|
+
color: #667eea;
|
|
770
|
+
}
|
|
771
|
+
</style>
|
|
772
|
+
</head>
|
|
773
|
+
<body>
|
|
774
|
+
<div class="container">
|
|
775
|
+
<div class="header">
|
|
776
|
+
<div class="icon">🔍</div>
|
|
777
|
+
<h1>CloudBase MCP 调试日志</h1>
|
|
778
|
+
<p style="color: #718096;">实时查看 MCP 服务器运行日志</p>
|
|
779
|
+
</div>
|
|
780
|
+
|
|
781
|
+
<div class="status">
|
|
782
|
+
<div class="status-item">
|
|
783
|
+
<span class="status-label">日志状态:</span>
|
|
784
|
+
<span class="status-value ${status.enabled ? 'enabled' : 'disabled'}">
|
|
785
|
+
${status.enabled ? '启用' : '禁用'}
|
|
786
|
+
</span>
|
|
787
|
+
</div>
|
|
788
|
+
<div class="status-item">
|
|
789
|
+
<span class="status-label">日志级别:</span>
|
|
790
|
+
<span class="status-value">${status.level}</span>
|
|
791
|
+
</div>
|
|
792
|
+
<div class="status-item">
|
|
793
|
+
<span class="status-label">日志文件:</span>
|
|
794
|
+
<span class="status-value">${status.logFile || '无'}</span>
|
|
795
|
+
</div>
|
|
796
|
+
<div class="status-item">
|
|
797
|
+
<span class="status-label">控制台输出:</span>
|
|
798
|
+
<span class="status-value ${status.useConsole ? 'enabled' : 'disabled'}">
|
|
799
|
+
${status.useConsole ? '启用' : '禁用'}
|
|
800
|
+
</span>
|
|
801
|
+
</div>
|
|
802
|
+
</div>
|
|
803
|
+
|
|
804
|
+
<div class="controls">
|
|
805
|
+
<div class="controls-left">
|
|
806
|
+
<button class="btn btn-primary" onclick="refreshLogs()">刷新日志</button>
|
|
807
|
+
<button class="btn btn-danger" onclick="clearLogs()">清空日志</button>
|
|
808
|
+
<button class="btn btn-secondary" onclick="window.close()">关闭</button>
|
|
809
|
+
</div>
|
|
810
|
+
<div>
|
|
811
|
+
<span style="color: #718096; font-size: 14px;">共 ${logs.length} 条日志</span>
|
|
812
|
+
</div>
|
|
813
|
+
</div>
|
|
814
|
+
|
|
815
|
+
<div class="log-container" id="logContainer">
|
|
816
|
+
${logs.length > 0 ? logs.map(line => {
|
|
817
|
+
const match = line.match(/\[(.*?)\] \[(.*?)\] (.*)/);
|
|
818
|
+
if (match) {
|
|
819
|
+
const [, timestamp, level, message] = match;
|
|
820
|
+
const levelClass = level.toLowerCase();
|
|
821
|
+
return `<div class="log-line ${levelClass}"><span class="timestamp">[${timestamp}]</span><span class="level ${levelClass}">[${level}]</span>${message}</div>`;
|
|
822
|
+
}
|
|
823
|
+
return `<div class="log-line">${line}</div>`;
|
|
824
|
+
}).join('') : '<div class="empty-state">暂无日志记录</div>'}
|
|
825
|
+
</div>
|
|
826
|
+
|
|
827
|
+
<div class="footer">
|
|
828
|
+
<p class="brand">CloudBase AI ToolKit</p>
|
|
829
|
+
<div class="links">
|
|
830
|
+
<a href="https://github.com/TencentCloudBase/CloudBase-AI-ToolKit" target="_blank">GitHub</a>
|
|
831
|
+
<a href="https://tcb.cloud.tencent.com/dev" target="_blank">控制台</a>
|
|
832
|
+
</div>
|
|
833
|
+
</div>
|
|
834
|
+
</div>
|
|
835
|
+
|
|
836
|
+
<script>
|
|
837
|
+
function refreshLogs() {
|
|
838
|
+
fetch('/api/logs')
|
|
839
|
+
.then(response => response.json())
|
|
840
|
+
.then(data => {
|
|
841
|
+
if (data.success) {
|
|
842
|
+
location.reload();
|
|
843
|
+
}
|
|
844
|
+
})
|
|
845
|
+
.catch(error => {
|
|
846
|
+
alert('刷新日志失败: ' + error.message);
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
function clearLogs() {
|
|
851
|
+
if (confirm('确定要清空所有日志吗?此操作不可恢复。')) {
|
|
852
|
+
fetch('/api/logs/clear', { method: 'POST' })
|
|
853
|
+
.then(response => response.json())
|
|
854
|
+
.then(data => {
|
|
855
|
+
if (data.success) {
|
|
856
|
+
location.reload();
|
|
857
|
+
} else {
|
|
858
|
+
alert('清空日志失败');
|
|
859
|
+
}
|
|
860
|
+
})
|
|
861
|
+
.catch(error => {
|
|
862
|
+
alert('清空日志失败: ' + error.message);
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
// 自动滚动到底部
|
|
868
|
+
const logContainer = document.getElementById('logContainer');
|
|
869
|
+
logContainer.scrollTop = logContainer.scrollHeight;
|
|
870
|
+
|
|
871
|
+
// 每5秒自动刷新
|
|
872
|
+
setInterval(() => {
|
|
873
|
+
const isAtBottom = logContainer.scrollHeight - logContainer.clientHeight <= logContainer.scrollTop + 1;
|
|
874
|
+
|
|
875
|
+
fetch('/api/logs')
|
|
876
|
+
.then(response => response.json())
|
|
877
|
+
.then(data => {
|
|
878
|
+
if (data.success && data.logs.length > 0) {
|
|
879
|
+
const newContent = data.logs.map(line => {
|
|
880
|
+
const match = line.match(/\\[(.*?)\\] \\[(.*?)\\] (.*)/);
|
|
881
|
+
if (match) {
|
|
882
|
+
const [, timestamp, level, message] = match;
|
|
883
|
+
const levelClass = level.toLowerCase();
|
|
884
|
+
return \`<div class="log-line \${levelClass}"><span class="timestamp">[\${timestamp}]</span><span class="level \${levelClass}">[\${level}]</span>\${message}</div>\`;
|
|
885
|
+
}
|
|
886
|
+
return \`<div class="log-line">\${line}</div>\`;
|
|
887
|
+
}).join('');
|
|
888
|
+
|
|
889
|
+
logContainer.innerHTML = newContent || '<div class="empty-state">暂无日志记录</div>';
|
|
890
|
+
|
|
891
|
+
if (isAtBottom) {
|
|
892
|
+
logContainer.scrollTop = logContainer.scrollHeight;
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
})
|
|
896
|
+
.catch(error => {
|
|
897
|
+
console.error('获取日志失败:', error);
|
|
898
|
+
});
|
|
899
|
+
}, 5000);
|
|
900
|
+
</script>
|
|
901
|
+
</body>
|
|
902
|
+
</html>`;
|
|
903
|
+
}
|
|
904
|
+
getClarificationHTML(message, options) {
|
|
905
|
+
const optionsArray = options || null;
|
|
906
|
+
return `
|
|
907
|
+
<!DOCTYPE html>
|
|
908
|
+
<html lang="zh-CN">
|
|
909
|
+
<head>
|
|
910
|
+
<meta charset="UTF-8">
|
|
911
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
912
|
+
<title>CloudBase AI Toolkit - 需求澄清</title>
|
|
913
|
+
<style>
|
|
914
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
915
|
+
:root {
|
|
916
|
+
--primary-color: #4F46E5;
|
|
917
|
+
--primary-hover: #4338CA;
|
|
918
|
+
--text-primary: #1F2937;
|
|
919
|
+
--text-secondary: #6B7280;
|
|
920
|
+
--border-color: #E5E7EB;
|
|
921
|
+
--bg-secondary: #F9FAFB;
|
|
922
|
+
--shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
|
923
|
+
--font-mono: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', 'Source Code Pro', monospace;
|
|
924
|
+
}
|
|
925
|
+
body {
|
|
926
|
+
font-family: var(--font-mono);
|
|
927
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
928
|
+
min-height: 100vh;
|
|
929
|
+
display: flex;
|
|
930
|
+
align-items: center;
|
|
931
|
+
justify-content: center;
|
|
932
|
+
padding: 20px;
|
|
933
|
+
}
|
|
934
|
+
.modal {
|
|
935
|
+
background: white;
|
|
936
|
+
border-radius: 16px;
|
|
937
|
+
box-shadow: var(--shadow);
|
|
938
|
+
width: 100%;
|
|
939
|
+
max-width: 600px;
|
|
940
|
+
overflow: hidden;
|
|
941
|
+
animation: modalIn 0.3s ease-out;
|
|
942
|
+
}
|
|
943
|
+
@keyframes modalIn {
|
|
944
|
+
from {
|
|
945
|
+
opacity: 0;
|
|
946
|
+
transform: scale(0.95) translateY(-10px);
|
|
947
|
+
}
|
|
948
|
+
to {
|
|
949
|
+
opacity: 1;
|
|
950
|
+
transform: scale(1) translateY(0);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
.header {
|
|
954
|
+
background: var(--primary-color);
|
|
955
|
+
color: white;
|
|
956
|
+
padding: 20px 24px;
|
|
957
|
+
display: flex;
|
|
958
|
+
align-items: center;
|
|
959
|
+
justify-content: space-between;
|
|
960
|
+
}
|
|
961
|
+
.header-left {
|
|
962
|
+
display: flex;
|
|
963
|
+
align-items: center;
|
|
964
|
+
gap: 12px;
|
|
965
|
+
}
|
|
966
|
+
.logo {
|
|
967
|
+
width: 28px;
|
|
968
|
+
height: 28px;
|
|
969
|
+
animation: pulse 2s infinite;
|
|
970
|
+
}
|
|
971
|
+
@keyframes pulse {
|
|
972
|
+
0%, 100% { opacity: 1; }
|
|
973
|
+
50% { opacity: 0.8; }
|
|
974
|
+
}
|
|
975
|
+
.title {
|
|
976
|
+
font-size: 18px;
|
|
977
|
+
font-weight: 600;
|
|
978
|
+
letter-spacing: -0.025em;
|
|
979
|
+
}
|
|
980
|
+
.github-link {
|
|
981
|
+
color: white;
|
|
982
|
+
text-decoration: none;
|
|
983
|
+
display: flex;
|
|
984
|
+
align-items: center;
|
|
985
|
+
gap: 6px;
|
|
986
|
+
font-size: 14px;
|
|
987
|
+
opacity: 0.9;
|
|
988
|
+
transition: opacity 0.2s;
|
|
989
|
+
padding: 6px 10px;
|
|
990
|
+
border-radius: 6px;
|
|
991
|
+
background: rgba(255,255,255,0.1);
|
|
992
|
+
}
|
|
993
|
+
.github-link:hover {
|
|
994
|
+
opacity: 1;
|
|
995
|
+
background: rgba(255,255,255,0.2);
|
|
996
|
+
}
|
|
997
|
+
.content {
|
|
998
|
+
padding: 32px 24px;
|
|
999
|
+
}
|
|
1000
|
+
.content-title {
|
|
1001
|
+
font-size: 24px;
|
|
1002
|
+
font-weight: 700;
|
|
1003
|
+
color: var(--text-primary);
|
|
1004
|
+
margin-bottom: 8px;
|
|
1005
|
+
}
|
|
1006
|
+
.message {
|
|
1007
|
+
background: #EEF2FF;
|
|
1008
|
+
border: 1px solid #C7D2FE;
|
|
1009
|
+
border-left: 4px solid var(--primary-color);
|
|
1010
|
+
padding: 20px;
|
|
1011
|
+
border-radius: 12px;
|
|
1012
|
+
margin-bottom: 24px;
|
|
1013
|
+
font-size: 15px;
|
|
1014
|
+
line-height: 1.6;
|
|
1015
|
+
color: var(--text-primary);
|
|
1016
|
+
}
|
|
1017
|
+
.options {
|
|
1018
|
+
margin-bottom: 24px;
|
|
1019
|
+
}
|
|
1020
|
+
.option-item {
|
|
1021
|
+
padding: 16px 20px;
|
|
1022
|
+
border: 1px solid var(--border-color);
|
|
1023
|
+
border-radius: 12px;
|
|
1024
|
+
margin-bottom: 12px;
|
|
1025
|
+
cursor: pointer;
|
|
1026
|
+
transition: all 0.2s;
|
|
1027
|
+
display: flex;
|
|
1028
|
+
align-items: center;
|
|
1029
|
+
gap: 14px;
|
|
1030
|
+
}
|
|
1031
|
+
.option-item:hover {
|
|
1032
|
+
background: var(--bg-secondary);
|
|
1033
|
+
border-color: var(--primary-color);
|
|
1034
|
+
}
|
|
1035
|
+
.option-item.selected {
|
|
1036
|
+
background: #EEF2FF;
|
|
1037
|
+
border-color: var(--primary-color);
|
|
1038
|
+
}
|
|
1039
|
+
.option-icon {
|
|
1040
|
+
width: 20px;
|
|
1041
|
+
height: 20px;
|
|
1042
|
+
color: var(--primary-color);
|
|
1043
|
+
flex-shrink: 0;
|
|
1044
|
+
}
|
|
1045
|
+
.custom-input {
|
|
1046
|
+
margin-bottom: 24px;
|
|
1047
|
+
}
|
|
1048
|
+
.custom-input textarea {
|
|
1049
|
+
width: 100%;
|
|
1050
|
+
min-height: 120px;
|
|
1051
|
+
padding: 16px;
|
|
1052
|
+
border: 1px solid var(--border-color);
|
|
1053
|
+
border-radius: 12px;
|
|
1054
|
+
font-size: 15px;
|
|
1055
|
+
font-family: var(--font-mono);
|
|
1056
|
+
resize: vertical;
|
|
1057
|
+
transition: all 0.2s;
|
|
1058
|
+
line-height: 1.5;
|
|
1059
|
+
}
|
|
1060
|
+
.custom-input textarea:focus {
|
|
1061
|
+
outline: none;
|
|
1062
|
+
border-color: var(--primary-color);
|
|
1063
|
+
box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1);
|
|
1064
|
+
}
|
|
1065
|
+
.actions {
|
|
1066
|
+
display: flex;
|
|
1067
|
+
gap: 12px;
|
|
1068
|
+
justify-content: flex-end;
|
|
1069
|
+
}
|
|
1070
|
+
.btn {
|
|
1071
|
+
padding: 12px 20px;
|
|
1072
|
+
border: none;
|
|
1073
|
+
border-radius: 8px;
|
|
1074
|
+
font-size: 14px;
|
|
1075
|
+
font-weight: 600;
|
|
1076
|
+
cursor: pointer;
|
|
1077
|
+
transition: all 0.2s;
|
|
1078
|
+
display: flex;
|
|
1079
|
+
align-items: center;
|
|
1080
|
+
gap: 8px;
|
|
1081
|
+
font-family: var(--font-mono);
|
|
1082
|
+
}
|
|
1083
|
+
.btn-primary {
|
|
1084
|
+
background: var(--primary-color);
|
|
1085
|
+
color: white;
|
|
1086
|
+
}
|
|
1087
|
+
.btn-primary:hover:not(:disabled) {
|
|
1088
|
+
background: var(--primary-hover);
|
|
1089
|
+
transform: translateY(-1px);
|
|
1090
|
+
}
|
|
1091
|
+
.btn-secondary {
|
|
1092
|
+
background: var(--bg-secondary);
|
|
1093
|
+
color: var(--text-secondary);
|
|
1094
|
+
}
|
|
1095
|
+
.btn-secondary:hover {
|
|
1096
|
+
background: #F3F4F6;
|
|
1097
|
+
}
|
|
1098
|
+
.btn:disabled {
|
|
1099
|
+
opacity: 0.5;
|
|
1100
|
+
cursor: not-allowed;
|
|
1101
|
+
}
|
|
1102
|
+
.loading {
|
|
1103
|
+
display: none;
|
|
1104
|
+
align-items: center;
|
|
1105
|
+
justify-content: center;
|
|
1106
|
+
gap: 8px;
|
|
1107
|
+
margin-top: 16px;
|
|
1108
|
+
color: var(--text-secondary);
|
|
1109
|
+
font-size: 14px;
|
|
1110
|
+
}
|
|
1111
|
+
.spinner {
|
|
1112
|
+
width: 16px;
|
|
1113
|
+
height: 16px;
|
|
1114
|
+
border: 2px solid var(--border-color);
|
|
1115
|
+
border-top: 2px solid var(--primary-color);
|
|
1116
|
+
border-radius: 50%;
|
|
1117
|
+
animation: spin 1s linear infinite;
|
|
1118
|
+
}
|
|
1119
|
+
@keyframes spin {
|
|
1120
|
+
0% { transform: rotate(0deg); }
|
|
1121
|
+
100% { transform: rotate(360deg); }
|
|
1122
|
+
}
|
|
1123
|
+
</style>
|
|
1124
|
+
</head>
|
|
1125
|
+
<body>
|
|
1126
|
+
<div class="modal">
|
|
1127
|
+
<div class="header">
|
|
1128
|
+
<div class="header-left">
|
|
1129
|
+
<svg class="logo" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
1130
|
+
<path d="M12 2L2 7V17L12 22L22 17V7L12 2Z"/>
|
|
1131
|
+
<path d="M12 22V12"/>
|
|
1132
|
+
<path d="M22 7L12 12L2 7"/>
|
|
1133
|
+
</svg>
|
|
1134
|
+
<span class="title">CloudBase AI Toolkit</span>
|
|
1135
|
+
</div>
|
|
1136
|
+
<a href="https://github.com/TencentCloudBase/CloudBase-AI-ToolKit" target="_blank" class="github-link">
|
|
1137
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
|
|
1138
|
+
<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"/>
|
|
1139
|
+
</svg>
|
|
1140
|
+
GitHub
|
|
1141
|
+
</a>
|
|
1142
|
+
</div>
|
|
1143
|
+
|
|
1144
|
+
<div class="content">
|
|
1145
|
+
<h1 class="content-title">需求澄清</h1>
|
|
1146
|
+
<div class="message">${message}</div>
|
|
1147
|
+
|
|
1148
|
+
${optionsArray ? `
|
|
1149
|
+
<div class="options" id="options">
|
|
1150
|
+
${optionsArray.map((option, index) => `
|
|
1151
|
+
<div class="option-item" onclick="selectOption('${option}')">
|
|
1152
|
+
<svg class="option-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
1153
|
+
<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"/>
|
|
1154
|
+
</svg>
|
|
1155
|
+
${option}
|
|
1156
|
+
</div>
|
|
1157
|
+
`).join('')}
|
|
1158
|
+
</div>
|
|
1159
|
+
` : ''}
|
|
1160
|
+
|
|
1161
|
+
<div class="custom-input">
|
|
1162
|
+
<textarea id="customInput" placeholder="请输入您的具体需求或建议..." onkeyup="updateSubmitButton()"></textarea>
|
|
1163
|
+
</div>
|
|
1164
|
+
|
|
1165
|
+
<div class="actions">
|
|
1166
|
+
<button class="btn btn-secondary" onclick="cancel()">
|
|
1167
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
1168
|
+
<path d="M18 6L6 18M6 6l12 12"/>
|
|
1169
|
+
</svg>
|
|
1170
|
+
取消
|
|
1171
|
+
</button>
|
|
1172
|
+
<button class="btn btn-primary" id="submitBtn" onclick="submit()">
|
|
1173
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
1174
|
+
<path d="M20 6L9 17l-5-5"/>
|
|
1175
|
+
</svg>
|
|
1176
|
+
提交反馈
|
|
1177
|
+
</button>
|
|
1178
|
+
</div>
|
|
1179
|
+
|
|
1180
|
+
<div class="loading" id="loading">
|
|
1181
|
+
<div class="spinner"></div>
|
|
1182
|
+
<span>正在提交...</span>
|
|
1183
|
+
</div>
|
|
1184
|
+
</div>
|
|
1185
|
+
</div>
|
|
1186
|
+
|
|
1187
|
+
<script>
|
|
1188
|
+
let selectedOption = null;
|
|
1189
|
+
|
|
1190
|
+
function selectOption(option) {
|
|
1191
|
+
selectedOption = option;
|
|
1192
|
+
|
|
1193
|
+
document.querySelectorAll('.option-item').forEach(item => {
|
|
1194
|
+
item.classList.remove('selected');
|
|
1195
|
+
});
|
|
1196
|
+
event.currentTarget.classList.add('selected');
|
|
1197
|
+
|
|
1198
|
+
document.getElementById('customInput').value = option;
|
|
1199
|
+
updateSubmitButton();
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
function updateSubmitButton() {
|
|
1203
|
+
const customInput = document.getElementById('customInput').value.trim();
|
|
1204
|
+
const submitBtn = document.getElementById('submitBtn');
|
|
1205
|
+
submitBtn.disabled = !customInput;
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
function submit() {
|
|
1209
|
+
const customInput = document.getElementById('customInput').value.trim();
|
|
1210
|
+
|
|
1211
|
+
if (!customInput) {
|
|
1212
|
+
alert('请输入反馈内容');
|
|
1213
|
+
return;
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
document.getElementById('loading').style.display = 'flex';
|
|
1217
|
+
|
|
1218
|
+
fetch('/api/submit', {
|
|
1219
|
+
method: 'POST',
|
|
1220
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1221
|
+
body: JSON.stringify({
|
|
1222
|
+
type: 'clarification',
|
|
1223
|
+
data: { response: customInput }
|
|
1224
|
+
})
|
|
1225
|
+
}).then(() => window.close());
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
function cancel() {
|
|
1229
|
+
fetch('/api/cancel', { method: 'POST' }).then(() => window.close());
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
updateSubmitButton();
|
|
1233
|
+
</script>
|
|
1234
|
+
</body>
|
|
1235
|
+
</html>`;
|
|
1236
|
+
}
|
|
1237
|
+
getConfirmationHTML(message, risks, options) {
|
|
1238
|
+
const confirmOptions = options || ["确认执行", "取消操作", "需要修改"];
|
|
1239
|
+
return `
|
|
1240
|
+
<!DOCTYPE html>
|
|
1241
|
+
<html lang="zh-CN">
|
|
1242
|
+
<head>
|
|
1243
|
+
<meta charset="UTF-8">
|
|
1244
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1245
|
+
<title>CloudBase AI Toolkit - 操作确认</title>
|
|
1246
|
+
<style>
|
|
1247
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
1248
|
+
:root {
|
|
1249
|
+
--primary-color: #4F46E5;
|
|
1250
|
+
--primary-hover: #4338CA;
|
|
1251
|
+
--text-primary: #1F2937;
|
|
1252
|
+
--text-secondary: #6B7280;
|
|
1253
|
+
--border-color: #E5E7EB;
|
|
1254
|
+
--bg-secondary: #F9FAFB;
|
|
1255
|
+
--warning-color: #DC2626;
|
|
1256
|
+
--warning-bg: #FEF2F2;
|
|
1257
|
+
--warning-border: #FECACA;
|
|
1258
|
+
--shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
|
1259
|
+
--font-mono: 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', 'Source Code Pro', monospace;
|
|
1260
|
+
}
|
|
1261
|
+
body {
|
|
1262
|
+
font-family: var(--font-mono);
|
|
1263
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
1264
|
+
min-height: 100vh;
|
|
1265
|
+
display: flex;
|
|
1266
|
+
align-items: center;
|
|
1267
|
+
justify-content: center;
|
|
1268
|
+
padding: 20px;
|
|
1269
|
+
}
|
|
1270
|
+
.modal {
|
|
1271
|
+
background: white;
|
|
1272
|
+
border-radius: 16px;
|
|
1273
|
+
box-shadow: var(--shadow);
|
|
1274
|
+
width: 100%;
|
|
1275
|
+
max-width: 600px;
|
|
1276
|
+
overflow: hidden;
|
|
1277
|
+
animation: modalIn 0.3s ease-out;
|
|
1278
|
+
}
|
|
1279
|
+
@keyframes modalIn {
|
|
1280
|
+
from {
|
|
1281
|
+
opacity: 0;
|
|
1282
|
+
transform: scale(0.95) translateY(-10px);
|
|
1283
|
+
}
|
|
1284
|
+
to {
|
|
1285
|
+
opacity: 1;
|
|
1286
|
+
transform: scale(1) translateY(0);
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
.header {
|
|
1290
|
+
background: var(--primary-color);
|
|
1291
|
+
color: white;
|
|
1292
|
+
padding: 20px 24px;
|
|
1293
|
+
display: flex;
|
|
1294
|
+
align-items: center;
|
|
1295
|
+
justify-content: space-between;
|
|
1296
|
+
}
|
|
1297
|
+
.header-left {
|
|
1298
|
+
display: flex;
|
|
1299
|
+
align-items: center;
|
|
1300
|
+
gap: 12px;
|
|
1301
|
+
}
|
|
1302
|
+
.logo {
|
|
1303
|
+
width: 28px;
|
|
1304
|
+
height: 28px;
|
|
1305
|
+
animation: pulse 2s infinite;
|
|
1306
|
+
}
|
|
1307
|
+
.title {
|
|
1308
|
+
font-size: 18px;
|
|
1309
|
+
font-weight: 600;
|
|
1310
|
+
letter-spacing: -0.025em;
|
|
1311
|
+
}
|
|
1312
|
+
.github-link {
|
|
1313
|
+
color: white;
|
|
1314
|
+
text-decoration: none;
|
|
1315
|
+
display: flex;
|
|
1316
|
+
align-items: center;
|
|
1317
|
+
gap: 6px;
|
|
1318
|
+
font-size: 14px;
|
|
1319
|
+
opacity: 0.9;
|
|
1320
|
+
transition: opacity 0.2s;
|
|
1321
|
+
padding: 6px 10px;
|
|
1322
|
+
border-radius: 6px;
|
|
1323
|
+
background: rgba(255,255,255,0.1);
|
|
1324
|
+
}
|
|
1325
|
+
.github-link:hover {
|
|
1326
|
+
opacity: 1;
|
|
1327
|
+
background: rgba(255,255,255,0.2);
|
|
1328
|
+
}
|
|
1329
|
+
.content {
|
|
1330
|
+
padding: 32px 24px;
|
|
1331
|
+
}
|
|
1332
|
+
@keyframes slideIn {
|
|
1333
|
+
from {
|
|
1334
|
+
opacity: 0;
|
|
1335
|
+
transform: translateY(-20px);
|
|
1336
|
+
}
|
|
1337
|
+
to {
|
|
1338
|
+
opacity: 1;
|
|
1339
|
+
transform: translateY(0);
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
@keyframes pulse {
|
|
1343
|
+
0% {
|
|
1344
|
+
transform: scale(1);
|
|
1345
|
+
}
|
|
1346
|
+
50% {
|
|
1347
|
+
transform: scale(1.05);
|
|
1348
|
+
}
|
|
1349
|
+
100% {
|
|
1350
|
+
transform: scale(1);
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
.title {
|
|
1354
|
+
font-size: 20px;
|
|
1355
|
+
margin-bottom: 16px;
|
|
1356
|
+
color: var(--text-primary);
|
|
1357
|
+
display: flex;
|
|
1358
|
+
align-items: center;
|
|
1359
|
+
gap: 12px;
|
|
1360
|
+
position: relative;
|
|
1361
|
+
}
|
|
1362
|
+
.title:after {
|
|
1363
|
+
content: '';
|
|
1364
|
+
position: absolute;
|
|
1365
|
+
bottom: -8px;
|
|
1366
|
+
left: 0;
|
|
1367
|
+
width: 40px;
|
|
1368
|
+
height: 3px;
|
|
1369
|
+
background: var(--primary-color);
|
|
1370
|
+
border-radius: 2px;
|
|
1371
|
+
}
|
|
1372
|
+
.message {
|
|
1373
|
+
background: var(--bg-secondary);
|
|
1374
|
+
border-left: 3px solid var(--primary-color);
|
|
1375
|
+
padding: 16px;
|
|
1376
|
+
border-radius: 6px;
|
|
1377
|
+
margin-bottom: 24px;
|
|
1378
|
+
font-size: 15px;
|
|
1379
|
+
line-height: 1.6;
|
|
1380
|
+
color: var(--text-primary);
|
|
1381
|
+
}
|
|
1382
|
+
.risks {
|
|
1383
|
+
background: var(--warning-bg);
|
|
1384
|
+
border: 1px solid var(--warning-color);
|
|
1385
|
+
border-radius: 6px;
|
|
1386
|
+
padding: 16px;
|
|
1387
|
+
margin-bottom: 24px;
|
|
1388
|
+
}
|
|
1389
|
+
.risks-title {
|
|
1390
|
+
color: var(--warning-color);
|
|
1391
|
+
font-weight: 500;
|
|
1392
|
+
margin-bottom: 12px;
|
|
1393
|
+
display: flex;
|
|
1394
|
+
align-items: center;
|
|
1395
|
+
gap: 8px;
|
|
1396
|
+
}
|
|
1397
|
+
.risk-item {
|
|
1398
|
+
color: var(--text-primary);
|
|
1399
|
+
margin-bottom: 8px;
|
|
1400
|
+
padding-left: 24px;
|
|
1401
|
+
position: relative;
|
|
1402
|
+
}
|
|
1403
|
+
.risk-item:before {
|
|
1404
|
+
content: "•";
|
|
1405
|
+
position: absolute;
|
|
1406
|
+
left: 8px;
|
|
1407
|
+
color: var(--warning-color);
|
|
1408
|
+
}
|
|
1409
|
+
.options {
|
|
1410
|
+
margin-bottom: 24px;
|
|
1411
|
+
}
|
|
1412
|
+
.option-item {
|
|
1413
|
+
padding: 16px;
|
|
1414
|
+
border: 1px solid var(--border-color);
|
|
1415
|
+
border-radius: 6px;
|
|
1416
|
+
margin-bottom: 12px;
|
|
1417
|
+
cursor: pointer;
|
|
1418
|
+
transition: all 0.2s;
|
|
1419
|
+
display: flex;
|
|
1420
|
+
align-items: center;
|
|
1421
|
+
gap: 12px;
|
|
1422
|
+
}
|
|
1423
|
+
.option-item:hover {
|
|
1424
|
+
background: var(--bg-secondary);
|
|
1425
|
+
}
|
|
1426
|
+
.option-item.selected {
|
|
1427
|
+
background: rgba(0, 82, 217, 0.1);
|
|
1428
|
+
border-color: var(--primary-color);
|
|
1429
|
+
}
|
|
1430
|
+
.button-group {
|
|
1431
|
+
display: flex;
|
|
1432
|
+
gap: 12px;
|
|
1433
|
+
justify-content: flex-end;
|
|
1434
|
+
}
|
|
1435
|
+
.btn {
|
|
1436
|
+
padding: 10px 20px;
|
|
1437
|
+
border: none;
|
|
1438
|
+
border-radius: 8px;
|
|
1439
|
+
font-size: 14px;
|
|
1440
|
+
font-weight: 500;
|
|
1441
|
+
cursor: pointer;
|
|
1442
|
+
transition: all 0.2s;
|
|
1443
|
+
display: flex;
|
|
1444
|
+
align-items: center;
|
|
1445
|
+
gap: 8px;
|
|
1446
|
+
font-family: var(--font-mono);
|
|
1447
|
+
text-transform: uppercase;
|
|
1448
|
+
letter-spacing: 0.5px;
|
|
1449
|
+
}
|
|
1450
|
+
.btn-primary {
|
|
1451
|
+
background: var(--primary-color);
|
|
1452
|
+
color: white;
|
|
1453
|
+
box-shadow: 0 4px 12px rgba(0,82,217,0.2);
|
|
1454
|
+
}
|
|
1455
|
+
.btn-primary:hover {
|
|
1456
|
+
background: var(--hover-color);
|
|
1457
|
+
transform: translateY(-1px);
|
|
1458
|
+
box-shadow: 0 6px 16px rgba(0,82,217,0.3);
|
|
1459
|
+
}
|
|
1460
|
+
.btn-secondary {
|
|
1461
|
+
background: var(--bg-secondary);
|
|
1462
|
+
color: var(--text-secondary);
|
|
1463
|
+
}
|
|
1464
|
+
.btn-secondary:hover {
|
|
1465
|
+
background: #E0E0E0;
|
|
1466
|
+
transform: translateY(-1px);
|
|
1467
|
+
}
|
|
1468
|
+
.btn-warning {
|
|
1469
|
+
background: var(--warning-color);
|
|
1470
|
+
color: white;
|
|
1471
|
+
}
|
|
1472
|
+
.btn-warning:hover {
|
|
1473
|
+
background: #FF7875;
|
|
1474
|
+
}
|
|
1475
|
+
.btn:disabled {
|
|
1476
|
+
opacity: 0.6;
|
|
1477
|
+
cursor: not-allowed;
|
|
1478
|
+
}
|
|
1479
|
+
.loading {
|
|
1480
|
+
display: none;
|
|
1481
|
+
align-items: center;
|
|
1482
|
+
gap: 8px;
|
|
1483
|
+
margin-top: 16px;
|
|
1484
|
+
color: var(--text-secondary);
|
|
1485
|
+
font-size: 14px;
|
|
1486
|
+
}
|
|
1487
|
+
.spinner {
|
|
1488
|
+
width: 16px;
|
|
1489
|
+
height: 16px;
|
|
1490
|
+
border: 2px solid var(--border-color);
|
|
1491
|
+
border-top: 2px solid var(--primary-color);
|
|
1492
|
+
border-radius: 50%;
|
|
1493
|
+
animation: spin 1s linear infinite;
|
|
1494
|
+
}
|
|
1495
|
+
@keyframes spin {
|
|
1496
|
+
0% { transform: rotate(0deg); }
|
|
1497
|
+
100% { transform: rotate(360deg); }
|
|
1498
|
+
}
|
|
1499
|
+
</style>
|
|
1500
|
+
</head>
|
|
1501
|
+
<body>
|
|
1502
|
+
<div class="modal">
|
|
1503
|
+
<div class="header">
|
|
1504
|
+
<div class="header-left">
|
|
1505
|
+
<svg class="logo" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
1506
|
+
<path d="M12 2L2 7V17L12 22L22 17V7L12 2Z"/>
|
|
1507
|
+
<path d="M12 22V12"/>
|
|
1508
|
+
<path d="M22 7L12 12L2 7"/>
|
|
1509
|
+
</svg>
|
|
1510
|
+
<span class="title">CloudBase AI Toolkit</span>
|
|
1511
|
+
</div>
|
|
1512
|
+
<a href="https://github.com/TencentCloudBase/CloudBase-AI-ToolKit" target="_blank" class="github-link">
|
|
1513
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
|
|
1514
|
+
<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"/>
|
|
1515
|
+
</svg>
|
|
1516
|
+
GitHub
|
|
1517
|
+
</a>
|
|
1518
|
+
</div>
|
|
1519
|
+
|
|
1520
|
+
<div class="content">
|
|
1521
|
+
<h1 class="content-title">
|
|
1522
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
1523
|
+
<path d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z"/>
|
|
1524
|
+
<path d="M12 16V12"/>
|
|
1525
|
+
<path d="M12 8H12.01"/>
|
|
1526
|
+
</svg>
|
|
1527
|
+
操作确认
|
|
1528
|
+
</h1>
|
|
1529
|
+
|
|
1530
|
+
<div class="message">${message}</div>
|
|
1531
|
+
|
|
1532
|
+
${risks && risks.length > 0 ? `
|
|
1533
|
+
<div class="risks">
|
|
1534
|
+
<div class="risks-title">
|
|
1535
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
1536
|
+
<path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/>
|
|
1537
|
+
<path d="M12 9v4"/>
|
|
1538
|
+
<path d="M12 17h.01"/>
|
|
1539
|
+
</svg>
|
|
1540
|
+
风险提示
|
|
1541
|
+
</div>
|
|
1542
|
+
${risks.map(risk => `<div class="risk-item">${risk}</div>`).join('')}
|
|
1543
|
+
</div>
|
|
1544
|
+
` : ''}
|
|
1545
|
+
|
|
1546
|
+
<div class="options">
|
|
1547
|
+
${confirmOptions.map((option, index) => {
|
|
1548
|
+
const className = option.includes('确认') ? 'confirm' : option.includes('取消') ? 'cancel' : '';
|
|
1549
|
+
const iconPath = option.includes('确认') ?
|
|
1550
|
+
'<path d="M20 6L9 17l-5-5"/>' :
|
|
1551
|
+
option.includes('取消') ?
|
|
1552
|
+
'<path d="M18 6L6 18M6 6l12 12"/>' :
|
|
1553
|
+
'<path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/>';
|
|
1554
|
+
return `
|
|
1555
|
+
<div class="option-item ${className}" onclick="selectOption('${option}')">
|
|
1556
|
+
<svg class="option-icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
1557
|
+
${iconPath}
|
|
1558
|
+
</svg>
|
|
1559
|
+
${option}
|
|
1560
|
+
</div>
|
|
1561
|
+
`;
|
|
1562
|
+
}).join('')}
|
|
1563
|
+
</div>
|
|
1564
|
+
|
|
1565
|
+
<div class="loading" id="loading">
|
|
1566
|
+
<div class="spinner"></div>
|
|
1567
|
+
<span>正在处理...</span>
|
|
1568
|
+
</div>
|
|
1569
|
+
</div>
|
|
1570
|
+
</div>
|
|
1571
|
+
|
|
1572
|
+
<script>
|
|
1573
|
+
let selectedOption = null;
|
|
1574
|
+
|
|
1575
|
+
function selectOption(option) {
|
|
1576
|
+
selectedOption = option;
|
|
1577
|
+
|
|
1578
|
+
document.querySelectorAll('.option-item').forEach(item => {
|
|
1579
|
+
item.classList.remove('selected');
|
|
1580
|
+
});
|
|
1581
|
+
event.currentTarget.classList.add('selected');
|
|
1582
|
+
|
|
1583
|
+
if (option.includes('确认')) {
|
|
1584
|
+
submit();
|
|
1585
|
+
} else if (option.includes('取消')) {
|
|
1586
|
+
cancel();
|
|
1587
|
+
} else {
|
|
1588
|
+
// 需要修改的情况
|
|
1589
|
+
cancel();
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
function submit() {
|
|
1594
|
+
document.getElementById('loading').style.display = 'flex';
|
|
1595
|
+
|
|
1596
|
+
fetch('/api/submit', {
|
|
1597
|
+
method: 'POST',
|
|
1598
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1599
|
+
body: JSON.stringify({
|
|
1600
|
+
type: 'confirmation',
|
|
1601
|
+
data: { response: selectedOption || '确认执行' }
|
|
1602
|
+
})
|
|
1603
|
+
}).then(() => window.close());
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
function cancel() {
|
|
1607
|
+
fetch('/api/cancel', { method: 'POST' }).then(() => window.close());
|
|
1608
|
+
}
|
|
1609
|
+
</script>
|
|
1610
|
+
</body>
|
|
1611
|
+
</html>`;
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
// 单例实例
|
|
1615
|
+
let interactiveServerInstance = null;
|
|
1616
|
+
export function getInteractiveServer() {
|
|
1617
|
+
if (!interactiveServerInstance) {
|
|
1618
|
+
interactiveServerInstance = new InteractiveServer();
|
|
1619
|
+
}
|
|
1620
|
+
return interactiveServerInstance;
|
|
1621
|
+
}
|