cursor-feedback 0.1.0 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.vscode/launch.json +28 -0
- package/.vscode/tasks.json +22 -0
- package/.vscodeignore +10 -0
- package/LICENSE +21 -0
- package/README.md +80 -26
- package/dist/extension.js +71 -92
- package/dist/extension.js.map +1 -1
- package/dist/mcp/McpServer.js +9 -8
- package/dist/mcp/McpServer.js.map +1 -1
- package/dist/mcp-server.js +110 -12
- package/dist/mcp-server.js.map +1 -1
- package/package.json +2 -17
- package/src/extension.ts +1071 -0
- package/src/mcp/McpServer.ts +455 -0
- package/src/mcp-server.ts +603 -0
- package/src/webview/FeedbackPanel.ts +134 -0
- package/tsconfig.json +17 -0
package/src/extension.ts
ADDED
|
@@ -0,0 +1,1071 @@
|
|
|
1
|
+
import * as vscode from 'vscode';
|
|
2
|
+
import * as http from 'http';
|
|
3
|
+
|
|
4
|
+
let feedbackViewProvider: FeedbackViewProvider | null = null;
|
|
5
|
+
let pollingInterval: NodeJS.Timeout | null = null;
|
|
6
|
+
|
|
7
|
+
export function activate(context: vscode.ExtensionContext) {
|
|
8
|
+
console.log('Cursor Feedback extension is now active!');
|
|
9
|
+
|
|
10
|
+
// 注册侧边栏 WebView(端口从 5678 开始自动扫描)
|
|
11
|
+
feedbackViewProvider = new FeedbackViewProvider(context.extensionUri, 5678);
|
|
12
|
+
context.subscriptions.push(
|
|
13
|
+
vscode.window.registerWebviewViewProvider(
|
|
14
|
+
'cursorFeedback.feedbackView',
|
|
15
|
+
feedbackViewProvider
|
|
16
|
+
)
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
// 注册命令:显示反馈面板
|
|
20
|
+
context.subscriptions.push(
|
|
21
|
+
vscode.commands.registerCommand('cursorFeedback.showPanel', () => {
|
|
22
|
+
vscode.commands.executeCommand('cursorFeedback.feedbackView.focus');
|
|
23
|
+
})
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
// 注册命令:启动轮询
|
|
27
|
+
context.subscriptions.push(
|
|
28
|
+
vscode.commands.registerCommand('cursorFeedback.startPolling', () => {
|
|
29
|
+
if (feedbackViewProvider) {
|
|
30
|
+
feedbackViewProvider.startPolling();
|
|
31
|
+
vscode.window.showInformationMessage('开始监听 MCP 反馈请求');
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
// 注册命令:停止轮询
|
|
37
|
+
context.subscriptions.push(
|
|
38
|
+
vscode.commands.registerCommand('cursorFeedback.stopPolling', () => {
|
|
39
|
+
if (feedbackViewProvider) {
|
|
40
|
+
feedbackViewProvider.stopPolling();
|
|
41
|
+
vscode.window.showInformationMessage('已停止监听');
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
// 自动开始轮询
|
|
47
|
+
setTimeout(() => {
|
|
48
|
+
feedbackViewProvider?.startPolling();
|
|
49
|
+
}, 1000);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function deactivate() {
|
|
53
|
+
if (feedbackViewProvider) {
|
|
54
|
+
feedbackViewProvider.stopPolling();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 反馈请求接口
|
|
60
|
+
*/
|
|
61
|
+
interface FeedbackRequest {
|
|
62
|
+
id: string;
|
|
63
|
+
summary: string;
|
|
64
|
+
projectDir: string;
|
|
65
|
+
timeout: number;
|
|
66
|
+
timestamp: number;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* 获取当前工作区路径列表
|
|
71
|
+
*/
|
|
72
|
+
function getWorkspacePaths(): string[] {
|
|
73
|
+
const folders = vscode.workspace.workspaceFolders;
|
|
74
|
+
if (!folders) {
|
|
75
|
+
return [];
|
|
76
|
+
}
|
|
77
|
+
return folders.map(f => f.uri.fsPath);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* 检查路径是否匹配当前工作区(精确匹配)
|
|
82
|
+
* - 有工作区的窗口:只接收匹配工作区路径的消息
|
|
83
|
+
* - 没有工作区的窗口:只接收没有指定项目路径的消息
|
|
84
|
+
*/
|
|
85
|
+
function isPathInWorkspace(targetPath: string): boolean {
|
|
86
|
+
const workspacePaths = getWorkspacePaths();
|
|
87
|
+
|
|
88
|
+
// 规范化路径(去除末尾斜杠,统一分隔符,小写)
|
|
89
|
+
const normalize = (p: string) => p.replace(/\\/g, '/').replace(/\/+$/, '').toLowerCase();
|
|
90
|
+
const normalizedTarget = normalize(targetPath);
|
|
91
|
+
|
|
92
|
+
// 检查 targetPath 是否为空或默认值
|
|
93
|
+
const isEmptyPath = !targetPath || targetPath === '.' || normalizedTarget === '' || normalizedTarget === '.';
|
|
94
|
+
|
|
95
|
+
if (workspacePaths.length === 0) {
|
|
96
|
+
// 没有打开工作区时,只接收没有指定项目路径的消息
|
|
97
|
+
return isEmptyPath;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// 有工作区时,不接收空路径的消息
|
|
101
|
+
if (isEmptyPath) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
for (const wsPath of workspacePaths) {
|
|
106
|
+
const normalizedWs = normalize(wsPath);
|
|
107
|
+
// 精确匹配:只匹配完全相同的路径
|
|
108
|
+
if (normalizedTarget === normalizedWs) {
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* 侧边栏 WebView Provider
|
|
118
|
+
*/
|
|
119
|
+
class FeedbackViewProvider implements vscode.WebviewViewProvider {
|
|
120
|
+
public static readonly viewType = 'cursorFeedback.feedbackView';
|
|
121
|
+
private _view?: vscode.WebviewView;
|
|
122
|
+
private _pollingInterval: NodeJS.Timeout | null = null;
|
|
123
|
+
private _currentRequest: FeedbackRequest | null = null;
|
|
124
|
+
private _basePort: number;
|
|
125
|
+
private _activePort: number | null = null;
|
|
126
|
+
private _portScanRange = 10; // 扫描端口范围
|
|
127
|
+
|
|
128
|
+
constructor(
|
|
129
|
+
private readonly _extensionUri: vscode.Uri,
|
|
130
|
+
port: number
|
|
131
|
+
) {
|
|
132
|
+
this._basePort = port;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
public resolveWebviewView(
|
|
136
|
+
webviewView: vscode.WebviewView,
|
|
137
|
+
_context: vscode.WebviewViewResolveContext,
|
|
138
|
+
_token: vscode.CancellationToken
|
|
139
|
+
) {
|
|
140
|
+
this._view = webviewView;
|
|
141
|
+
|
|
142
|
+
webviewView.webview.options = {
|
|
143
|
+
enableScripts: true,
|
|
144
|
+
localResourceRoots: [this._extensionUri]
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
webviewView.webview.html = this._getHtmlForWebview(webviewView.webview);
|
|
148
|
+
|
|
149
|
+
// 处理来自 WebView 的消息
|
|
150
|
+
webviewView.webview.onDidReceiveMessage(async data => {
|
|
151
|
+
switch (data.type) {
|
|
152
|
+
case 'submitFeedback':
|
|
153
|
+
await this._handleFeedbackSubmit(data.payload);
|
|
154
|
+
break;
|
|
155
|
+
case 'ready':
|
|
156
|
+
console.log('Feedback WebView is ready');
|
|
157
|
+
// WebView 准备就绪后,检查是否有待处理的请求
|
|
158
|
+
if (this._currentRequest) {
|
|
159
|
+
this._showFeedbackRequest(this._currentRequest);
|
|
160
|
+
}
|
|
161
|
+
break;
|
|
162
|
+
case 'checkServer':
|
|
163
|
+
await this._checkServerHealth();
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// 当 view 变为可见时,检查当前请求
|
|
169
|
+
webviewView.onDidChangeVisibility(() => {
|
|
170
|
+
if (webviewView.visible && this._currentRequest) {
|
|
171
|
+
this._showFeedbackRequest(this._currentRequest);
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* 开始轮询 MCP Server
|
|
178
|
+
*/
|
|
179
|
+
public startPolling() {
|
|
180
|
+
if (this._pollingInterval) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
console.log(`Starting polling MCP server from port ${this._basePort}`);
|
|
185
|
+
|
|
186
|
+
this._pollingInterval = setInterval(async () => {
|
|
187
|
+
await this._pollForFeedbackRequest();
|
|
188
|
+
}, 1000); // 每秒检查一次
|
|
189
|
+
|
|
190
|
+
// 立即执行一次
|
|
191
|
+
this._pollForFeedbackRequest();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* 停止轮询
|
|
196
|
+
*/
|
|
197
|
+
public stopPolling() {
|
|
198
|
+
if (this._pollingInterval) {
|
|
199
|
+
clearInterval(this._pollingInterval);
|
|
200
|
+
this._pollingInterval = null;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* 轮询检查是否有新的反馈请求
|
|
206
|
+
* 会扫描多个端口以找到有活跃请求的 MCP Server
|
|
207
|
+
*/
|
|
208
|
+
private async _pollForFeedbackRequest() {
|
|
209
|
+
try {
|
|
210
|
+
// 如果已知活跃端口,先检查该端口
|
|
211
|
+
if (this._activePort) {
|
|
212
|
+
const result = await this._checkPortForRequest(this._activePort);
|
|
213
|
+
if (result.request) {
|
|
214
|
+
this._handleNewRequest(result.request, result.port);
|
|
215
|
+
return;
|
|
216
|
+
} else if (result.connected && !result.request && this._currentRequest) {
|
|
217
|
+
// 请求已被处理或超时
|
|
218
|
+
this._currentRequest = null;
|
|
219
|
+
this._showWaitingState();
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// 扫描端口范围寻找有请求的服务器
|
|
225
|
+
for (let i = 0; i < this._portScanRange; i++) {
|
|
226
|
+
const port = this._basePort + i;
|
|
227
|
+
if (port === this._activePort) continue; // 已经检查过了
|
|
228
|
+
|
|
229
|
+
const result = await this._checkPortForRequest(port);
|
|
230
|
+
if (result.request) {
|
|
231
|
+
this._activePort = port;
|
|
232
|
+
this._handleNewRequest(result.request, port);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// 没有找到任何请求
|
|
238
|
+
if (this._currentRequest) {
|
|
239
|
+
this._currentRequest = null;
|
|
240
|
+
this._showWaitingState();
|
|
241
|
+
}
|
|
242
|
+
} catch (error) {
|
|
243
|
+
// 服务器可能未启动,静默处理
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* 检查指定端口是否有反馈请求
|
|
249
|
+
* 只返回属于当前工作区的请求
|
|
250
|
+
*/
|
|
251
|
+
private async _checkPortForRequest(port: number): Promise<{
|
|
252
|
+
connected: boolean;
|
|
253
|
+
request: FeedbackRequest | null;
|
|
254
|
+
port: number;
|
|
255
|
+
}> {
|
|
256
|
+
try {
|
|
257
|
+
const response = await this._httpGet(`http://127.0.0.1:${port}/api/feedback/current`);
|
|
258
|
+
const request = JSON.parse(response) as FeedbackRequest | null;
|
|
259
|
+
|
|
260
|
+
// 如果有请求,检查是否属于当前工作区
|
|
261
|
+
if (request && !isPathInWorkspace(request.projectDir)) {
|
|
262
|
+
// 请求不属于当前工作区,忽略
|
|
263
|
+
return { connected: true, request: null, port };
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return { connected: true, request, port };
|
|
267
|
+
} catch {
|
|
268
|
+
return { connected: false, request: null, port };
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* 处理新的反馈请求
|
|
274
|
+
*/
|
|
275
|
+
private _handleNewRequest(request: FeedbackRequest, port: number) {
|
|
276
|
+
if (!this._currentRequest || request.id !== this._currentRequest.id) {
|
|
277
|
+
console.log(`New feedback request received on port ${port}:`, request.id);
|
|
278
|
+
this._currentRequest = request;
|
|
279
|
+
this._activePort = port;
|
|
280
|
+
this._showFeedbackRequest(request);
|
|
281
|
+
|
|
282
|
+
// 显示通知
|
|
283
|
+
vscode.window.showInformationMessage(
|
|
284
|
+
'AI 正在等待您的反馈',
|
|
285
|
+
'查看'
|
|
286
|
+
).then(action => {
|
|
287
|
+
if (action === '查看') {
|
|
288
|
+
vscode.commands.executeCommand('cursorFeedback.feedbackView.focus');
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* 检查服务器健康状态
|
|
296
|
+
*/
|
|
297
|
+
private async _checkServerHealth() {
|
|
298
|
+
// 扫描端口查找可用的服务器
|
|
299
|
+
for (let i = 0; i < this._portScanRange; i++) {
|
|
300
|
+
const port = this._basePort + i;
|
|
301
|
+
try {
|
|
302
|
+
const response = await this._httpGet(`http://127.0.0.1:${port}/api/health`);
|
|
303
|
+
const health = JSON.parse(response);
|
|
304
|
+
this._view?.webview.postMessage({
|
|
305
|
+
type: 'serverStatus',
|
|
306
|
+
payload: { connected: true, port, ...health }
|
|
307
|
+
});
|
|
308
|
+
return;
|
|
309
|
+
} catch {
|
|
310
|
+
// 继续尝试下一个端口
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
this._view?.webview.postMessage({
|
|
315
|
+
type: 'serverStatus',
|
|
316
|
+
payload: { connected: false }
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* 显示反馈请求
|
|
322
|
+
*/
|
|
323
|
+
private _showFeedbackRequest(request: FeedbackRequest) {
|
|
324
|
+
if (this._view) {
|
|
325
|
+
this._view.show?.(true);
|
|
326
|
+
this._view.webview.postMessage({
|
|
327
|
+
type: 'showFeedbackRequest',
|
|
328
|
+
payload: {
|
|
329
|
+
requestId: request.id,
|
|
330
|
+
summary: request.summary,
|
|
331
|
+
projectDir: request.projectDir,
|
|
332
|
+
timeout: request.timeout,
|
|
333
|
+
timestamp: request.timestamp
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* 显示等待状态
|
|
341
|
+
*/
|
|
342
|
+
private _showWaitingState() {
|
|
343
|
+
if (this._view) {
|
|
344
|
+
this._view.webview.postMessage({
|
|
345
|
+
type: 'showWaiting'
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* 处理反馈提交
|
|
352
|
+
*/
|
|
353
|
+
private async _handleFeedbackSubmit(payload: {
|
|
354
|
+
requestId: string;
|
|
355
|
+
interactive_feedback: string;
|
|
356
|
+
images: Array<{ name: string; data: string; size: number }>;
|
|
357
|
+
project_directory: string;
|
|
358
|
+
}) {
|
|
359
|
+
// 使用活跃端口提交反馈
|
|
360
|
+
const port = this._activePort || this._basePort;
|
|
361
|
+
|
|
362
|
+
try {
|
|
363
|
+
const response = await this._httpPost(
|
|
364
|
+
`http://127.0.0.1:${port}/api/feedback/submit`,
|
|
365
|
+
JSON.stringify({
|
|
366
|
+
requestId: payload.requestId,
|
|
367
|
+
feedback: {
|
|
368
|
+
interactive_feedback: payload.interactive_feedback,
|
|
369
|
+
images: payload.images,
|
|
370
|
+
project_directory: payload.project_directory
|
|
371
|
+
}
|
|
372
|
+
})
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
const result = JSON.parse(response);
|
|
376
|
+
if (result.success) {
|
|
377
|
+
vscode.window.showInformationMessage('反馈已提交');
|
|
378
|
+
this._currentRequest = null;
|
|
379
|
+
this._showWaitingState();
|
|
380
|
+
} else {
|
|
381
|
+
vscode.window.showErrorMessage('提交失败:' + result.error);
|
|
382
|
+
}
|
|
383
|
+
} catch (error) {
|
|
384
|
+
vscode.window.showErrorMessage('提交失败:无法连接到 MCP Server');
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* HTTP GET 请求
|
|
390
|
+
*/
|
|
391
|
+
private _httpGet(url: string): Promise<string> {
|
|
392
|
+
return new Promise((resolve, reject) => {
|
|
393
|
+
const req = http.get(url, { timeout: 3000 }, (res) => {
|
|
394
|
+
let data = '';
|
|
395
|
+
res.on('data', chunk => data += chunk);
|
|
396
|
+
res.on('end', () => resolve(data));
|
|
397
|
+
});
|
|
398
|
+
req.on('error', reject);
|
|
399
|
+
req.on('timeout', () => {
|
|
400
|
+
req.destroy();
|
|
401
|
+
reject(new Error('Request timeout'));
|
|
402
|
+
});
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* HTTP POST 请求
|
|
408
|
+
*/
|
|
409
|
+
private _httpPost(url: string, body: string): Promise<string> {
|
|
410
|
+
return new Promise((resolve, reject) => {
|
|
411
|
+
const urlObj = new URL(url);
|
|
412
|
+
const options = {
|
|
413
|
+
hostname: urlObj.hostname,
|
|
414
|
+
port: urlObj.port,
|
|
415
|
+
path: urlObj.pathname,
|
|
416
|
+
method: 'POST',
|
|
417
|
+
headers: {
|
|
418
|
+
'Content-Type': 'application/json',
|
|
419
|
+
'Content-Length': Buffer.byteLength(body)
|
|
420
|
+
},
|
|
421
|
+
timeout: 5000
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
const req = http.request(options, (res) => {
|
|
425
|
+
let data = '';
|
|
426
|
+
res.on('data', chunk => data += chunk);
|
|
427
|
+
res.on('end', () => resolve(data));
|
|
428
|
+
});
|
|
429
|
+
req.on('error', reject);
|
|
430
|
+
req.on('timeout', () => {
|
|
431
|
+
req.destroy();
|
|
432
|
+
reject(new Error('Request timeout'));
|
|
433
|
+
});
|
|
434
|
+
req.write(body);
|
|
435
|
+
req.end();
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
private _getHtmlForWebview(webview: vscode.Webview): string {
|
|
440
|
+
const config = vscode.workspace.getConfiguration('cursorFeedback');
|
|
441
|
+
const language = config.get<string>('language', 'zh-CN');
|
|
442
|
+
|
|
443
|
+
const i18n = this._getI18n(language);
|
|
444
|
+
|
|
445
|
+
// 获取本地 marked.js 文件的 URI
|
|
446
|
+
const markedJsUri = webview.asWebviewUri(
|
|
447
|
+
vscode.Uri.joinPath(this._extensionUri, 'resources', 'vendor', 'marked.min.js')
|
|
448
|
+
);
|
|
449
|
+
|
|
450
|
+
return `<!DOCTYPE html>
|
|
451
|
+
<html lang="${language}">
|
|
452
|
+
<head>
|
|
453
|
+
<meta charset="UTF-8">
|
|
454
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
455
|
+
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; script-src 'unsafe-inline' ${webview.cspSource}; img-src data:;">
|
|
456
|
+
<title>Cursor Feedback</title>
|
|
457
|
+
<!-- 使用本地 marked.js 进行 Markdown 渲染 -->
|
|
458
|
+
<script src="${markedJsUri}"></script>
|
|
459
|
+
<style>
|
|
460
|
+
* {
|
|
461
|
+
box-sizing: border-box;
|
|
462
|
+
margin: 0;
|
|
463
|
+
padding: 0;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
body {
|
|
467
|
+
font-family: var(--vscode-font-family);
|
|
468
|
+
font-size: var(--vscode-font-size);
|
|
469
|
+
color: var(--vscode-foreground);
|
|
470
|
+
background-color: var(--vscode-sideBar-background);
|
|
471
|
+
padding: 12px;
|
|
472
|
+
min-height: 100vh;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
.container {
|
|
476
|
+
display: flex;
|
|
477
|
+
flex-direction: column;
|
|
478
|
+
gap: 12px;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
.section {
|
|
482
|
+
background: var(--vscode-input-background);
|
|
483
|
+
border: 1px solid var(--vscode-input-border);
|
|
484
|
+
border-radius: 6px;
|
|
485
|
+
padding: 12px;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
.section-title {
|
|
489
|
+
font-weight: 600;
|
|
490
|
+
margin-bottom: 8px;
|
|
491
|
+
color: var(--vscode-foreground);
|
|
492
|
+
display: flex;
|
|
493
|
+
align-items: center;
|
|
494
|
+
gap: 6px;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
.summary-content {
|
|
498
|
+
word-break: break-word;
|
|
499
|
+
max-height: 300px;
|
|
500
|
+
overflow-y: auto;
|
|
501
|
+
font-size: 13px;
|
|
502
|
+
line-height: 1.6;
|
|
503
|
+
background: var(--vscode-textBlockQuote-background);
|
|
504
|
+
padding: 12px;
|
|
505
|
+
border-radius: 4px;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/* Markdown 样式 */
|
|
509
|
+
.summary-content h1, .summary-content h2, .summary-content h3 {
|
|
510
|
+
margin-top: 12px;
|
|
511
|
+
margin-bottom: 8px;
|
|
512
|
+
font-weight: 600;
|
|
513
|
+
color: var(--vscode-foreground);
|
|
514
|
+
}
|
|
515
|
+
.summary-content h1 { font-size: 1.4em; border-bottom: 1px solid var(--vscode-panel-border); padding-bottom: 4px; }
|
|
516
|
+
.summary-content h2 { font-size: 1.2em; }
|
|
517
|
+
.summary-content h3 { font-size: 1.1em; }
|
|
518
|
+
.summary-content h1:first-child, .summary-content h2:first-child, .summary-content h3:first-child { margin-top: 0; }
|
|
519
|
+
|
|
520
|
+
.summary-content p { margin: 8px 0; }
|
|
521
|
+
.summary-content ul, .summary-content ol { margin: 8px 0; padding-left: 20px; }
|
|
522
|
+
.summary-content li { margin: 4px 0; }
|
|
523
|
+
|
|
524
|
+
.summary-content code {
|
|
525
|
+
background: var(--vscode-textCodeBlock-background);
|
|
526
|
+
padding: 2px 6px;
|
|
527
|
+
border-radius: 3px;
|
|
528
|
+
font-family: var(--vscode-editor-font-family), monospace;
|
|
529
|
+
font-size: 0.9em;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
.summary-content pre {
|
|
533
|
+
background: var(--vscode-textCodeBlock-background);
|
|
534
|
+
padding: 10px;
|
|
535
|
+
border-radius: 4px;
|
|
536
|
+
overflow-x: auto;
|
|
537
|
+
margin: 8px 0;
|
|
538
|
+
}
|
|
539
|
+
.summary-content pre code {
|
|
540
|
+
background: none;
|
|
541
|
+
padding: 0;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
.summary-content blockquote {
|
|
545
|
+
border-left: 3px solid var(--vscode-textLink-foreground);
|
|
546
|
+
margin: 8px 0;
|
|
547
|
+
padding: 4px 12px;
|
|
548
|
+
color: var(--vscode-descriptionForeground);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
.summary-content table {
|
|
552
|
+
border-collapse: collapse;
|
|
553
|
+
margin: 8px 0;
|
|
554
|
+
width: 100%;
|
|
555
|
+
}
|
|
556
|
+
.summary-content th, .summary-content td {
|
|
557
|
+
border: 1px solid var(--vscode-panel-border);
|
|
558
|
+
padding: 6px 10px;
|
|
559
|
+
text-align: left;
|
|
560
|
+
}
|
|
561
|
+
.summary-content th {
|
|
562
|
+
background: var(--vscode-textCodeBlock-background);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
.summary-content strong { font-weight: 600; }
|
|
566
|
+
.summary-content em { font-style: italic; }
|
|
567
|
+
.summary-content a { color: var(--vscode-textLink-foreground); }
|
|
568
|
+
.summary-content hr { border: none; border-top: 1px solid var(--vscode-panel-border); margin: 12px 0; }
|
|
569
|
+
|
|
570
|
+
.feedback-input {
|
|
571
|
+
width: 100%;
|
|
572
|
+
min-height: 120px;
|
|
573
|
+
resize: vertical;
|
|
574
|
+
background: var(--vscode-input-background);
|
|
575
|
+
color: var(--vscode-input-foreground);
|
|
576
|
+
border: 1px solid var(--vscode-input-border);
|
|
577
|
+
border-radius: 4px;
|
|
578
|
+
padding: 10px;
|
|
579
|
+
font-family: inherit;
|
|
580
|
+
font-size: 13px;
|
|
581
|
+
line-height: 1.5;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
.feedback-input:focus {
|
|
585
|
+
outline: none;
|
|
586
|
+
border-color: var(--vscode-focusBorder);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
.submit-btn {
|
|
591
|
+
width: 100%;
|
|
592
|
+
padding: 12px;
|
|
593
|
+
background: var(--vscode-button-background);
|
|
594
|
+
color: var(--vscode-button-foreground);
|
|
595
|
+
border: none;
|
|
596
|
+
border-radius: 6px;
|
|
597
|
+
cursor: pointer;
|
|
598
|
+
font-weight: 600;
|
|
599
|
+
font-size: 14px;
|
|
600
|
+
transition: background 0.15s;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
.submit-btn:hover {
|
|
604
|
+
background: var(--vscode-button-hoverBackground);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
.submit-btn:disabled {
|
|
608
|
+
opacity: 0.5;
|
|
609
|
+
cursor: not-allowed;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
.status {
|
|
613
|
+
text-align: center;
|
|
614
|
+
padding: 30px 20px;
|
|
615
|
+
color: var(--vscode-descriptionForeground);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
.status-icon {
|
|
619
|
+
font-size: 32px;
|
|
620
|
+
margin-bottom: 12px;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
.status.waiting .status-icon {
|
|
624
|
+
animation: pulse 2s ease-in-out infinite;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
@keyframes pulse {
|
|
628
|
+
0%, 100% { opacity: 1; }
|
|
629
|
+
50% { opacity: 0.5; }
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
.server-status {
|
|
633
|
+
display: flex;
|
|
634
|
+
align-items: center;
|
|
635
|
+
gap: 6px;
|
|
636
|
+
font-size: 11px;
|
|
637
|
+
padding: 6px 10px;
|
|
638
|
+
background: var(--vscode-textBlockQuote-background);
|
|
639
|
+
border-radius: 4px;
|
|
640
|
+
margin-bottom: 12px;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
.server-status .dot {
|
|
644
|
+
width: 8px;
|
|
645
|
+
height: 8px;
|
|
646
|
+
border-radius: 50%;
|
|
647
|
+
background: var(--vscode-errorForeground);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
.server-status.connected .dot {
|
|
651
|
+
background: var(--vscode-notificationsInfoIcon-foreground);
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
.image-upload {
|
|
655
|
+
margin-top: 10px;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
.image-upload-btn {
|
|
659
|
+
display: inline-flex;
|
|
660
|
+
align-items: center;
|
|
661
|
+
gap: 6px;
|
|
662
|
+
padding: 8px 14px;
|
|
663
|
+
background: var(--vscode-button-secondaryBackground);
|
|
664
|
+
color: var(--vscode-button-secondaryForeground);
|
|
665
|
+
border: none;
|
|
666
|
+
border-radius: 4px;
|
|
667
|
+
cursor: pointer;
|
|
668
|
+
font-size: 12px;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
.image-upload-btn:hover {
|
|
672
|
+
background: var(--vscode-button-secondaryHoverBackground);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
.image-preview {
|
|
676
|
+
display: flex;
|
|
677
|
+
flex-wrap: wrap;
|
|
678
|
+
gap: 8px;
|
|
679
|
+
margin-top: 10px;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
.image-preview-item {
|
|
683
|
+
position: relative;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
.image-preview img {
|
|
687
|
+
max-width: 80px;
|
|
688
|
+
max-height: 80px;
|
|
689
|
+
border-radius: 4px;
|
|
690
|
+
object-fit: cover;
|
|
691
|
+
border: 1px solid var(--vscode-input-border);
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
.image-remove {
|
|
695
|
+
position: absolute;
|
|
696
|
+
top: -6px;
|
|
697
|
+
right: -6px;
|
|
698
|
+
width: 18px;
|
|
699
|
+
height: 18px;
|
|
700
|
+
border-radius: 50%;
|
|
701
|
+
background: var(--vscode-errorForeground);
|
|
702
|
+
color: white;
|
|
703
|
+
border: none;
|
|
704
|
+
cursor: pointer;
|
|
705
|
+
font-size: 12px;
|
|
706
|
+
display: flex;
|
|
707
|
+
align-items: center;
|
|
708
|
+
justify-content: center;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
.hidden {
|
|
712
|
+
display: none !important;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
.project-info {
|
|
716
|
+
font-size: 11px;
|
|
717
|
+
color: var(--vscode-descriptionForeground);
|
|
718
|
+
margin-top: 6px;
|
|
719
|
+
padding: 6px 8px;
|
|
720
|
+
background: var(--vscode-textBlockQuote-background);
|
|
721
|
+
border-radius: 4px;
|
|
722
|
+
word-break: break-all;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
.timeout-info {
|
|
726
|
+
font-size: 11px;
|
|
727
|
+
color: var(--vscode-descriptionForeground);
|
|
728
|
+
text-align: right;
|
|
729
|
+
margin-top: 6px;
|
|
730
|
+
}
|
|
731
|
+
</style>
|
|
732
|
+
</head>
|
|
733
|
+
<body>
|
|
734
|
+
<div class="container">
|
|
735
|
+
<!-- 服务器状态 -->
|
|
736
|
+
<div id="serverStatus" class="server-status">
|
|
737
|
+
<span class="dot"></span>
|
|
738
|
+
<span id="serverStatusText">${i18n.checking}</span>
|
|
739
|
+
</div>
|
|
740
|
+
|
|
741
|
+
<!-- 等待状态 -->
|
|
742
|
+
<div id="waitingStatus" class="status waiting">
|
|
743
|
+
<div class="status-icon">⏳</div>
|
|
744
|
+
<p>${i18n.waiting}</p>
|
|
745
|
+
<p style="font-size: 11px; margin-top: 10px; opacity: 0.8;">${i18n.waitingHint}</p>
|
|
746
|
+
</div>
|
|
747
|
+
|
|
748
|
+
<!-- 反馈表单 -->
|
|
749
|
+
<div id="feedbackForm" class="hidden">
|
|
750
|
+
<!-- AI 摘要 -->
|
|
751
|
+
<div class="section">
|
|
752
|
+
<div class="section-title">📋 ${i18n.summary}</div>
|
|
753
|
+
<div id="summaryContent" class="summary-content"></div>
|
|
754
|
+
<div id="projectInfo" class="project-info"></div>
|
|
755
|
+
</div>
|
|
756
|
+
|
|
757
|
+
<!-- 反馈输入 -->
|
|
758
|
+
<div class="section">
|
|
759
|
+
<div class="section-title">💬 ${i18n.yourFeedback}</div>
|
|
760
|
+
<textarea
|
|
761
|
+
id="feedbackInput"
|
|
762
|
+
class="feedback-input"
|
|
763
|
+
placeholder="${i18n.placeholder}"
|
|
764
|
+
></textarea>
|
|
765
|
+
|
|
766
|
+
|
|
767
|
+
<!-- 图片上传 -->
|
|
768
|
+
<div class="image-upload">
|
|
769
|
+
<button id="uploadBtn" class="image-upload-btn">
|
|
770
|
+
📎 ${i18n.uploadImage}
|
|
771
|
+
</button>
|
|
772
|
+
<input type="file" id="imageInput" accept="image/*" multiple style="display:none">
|
|
773
|
+
<div id="imagePreview" class="image-preview"></div>
|
|
774
|
+
</div>
|
|
775
|
+
|
|
776
|
+
<div id="timeoutInfo" class="timeout-info"></div>
|
|
777
|
+
</div>
|
|
778
|
+
|
|
779
|
+
<!-- 提交按钮 -->
|
|
780
|
+
<button id="submitBtn" class="submit-btn">${i18n.submit} (Ctrl+Enter)</button>
|
|
781
|
+
</div>
|
|
782
|
+
</div>
|
|
783
|
+
|
|
784
|
+
<script>
|
|
785
|
+
const vscode = acquireVsCodeApi();
|
|
786
|
+
|
|
787
|
+
// 使用 marked.js 渲染 Markdown
|
|
788
|
+
function renderMarkdown(text) {
|
|
789
|
+
if (!text) return '';
|
|
790
|
+
|
|
791
|
+
try {
|
|
792
|
+
// 配置 marked
|
|
793
|
+
if (typeof marked !== 'undefined') {
|
|
794
|
+
marked.setOptions({
|
|
795
|
+
breaks: true, // 支持 GitHub 风格的换行
|
|
796
|
+
gfm: true, // 启用 GitHub 风格 Markdown
|
|
797
|
+
headerIds: false, // 禁用标题 ID(安全考虑)
|
|
798
|
+
});
|
|
799
|
+
|
|
800
|
+
// 使用 marked 解析
|
|
801
|
+
return marked.parse(text);
|
|
802
|
+
}
|
|
803
|
+
} catch (e) {
|
|
804
|
+
console.error('Markdown rendering error:', e);
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
// 降级:简单转义并保留换行
|
|
808
|
+
return text
|
|
809
|
+
.replace(/&/g, '&')
|
|
810
|
+
.replace(/</g, '<')
|
|
811
|
+
.replace(/>/g, '>')
|
|
812
|
+
.replace(/\\n/g, '<br>');
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// DOM 元素
|
|
816
|
+
const serverStatus = document.getElementById('serverStatus');
|
|
817
|
+
const serverStatusText = document.getElementById('serverStatusText');
|
|
818
|
+
const waitingStatus = document.getElementById('waitingStatus');
|
|
819
|
+
const feedbackForm = document.getElementById('feedbackForm');
|
|
820
|
+
const summaryContent = document.getElementById('summaryContent');
|
|
821
|
+
const projectInfo = document.getElementById('projectInfo');
|
|
822
|
+
const feedbackInput = document.getElementById('feedbackInput');
|
|
823
|
+
const submitBtn = document.getElementById('submitBtn');
|
|
824
|
+
const uploadBtn = document.getElementById('uploadBtn');
|
|
825
|
+
const imageInput = document.getElementById('imageInput');
|
|
826
|
+
const imagePreview = document.getElementById('imagePreview');
|
|
827
|
+
const timeoutInfo = document.getElementById('timeoutInfo');
|
|
828
|
+
|
|
829
|
+
let uploadedImages = [];
|
|
830
|
+
let currentRequestId = '';
|
|
831
|
+
let currentProjectDir = '';
|
|
832
|
+
let requestTimestamp = 0;
|
|
833
|
+
let requestTimeout = 300;
|
|
834
|
+
let countdownInterval = null;
|
|
835
|
+
|
|
836
|
+
// 国际化文本
|
|
837
|
+
const i18n = ${JSON.stringify(i18n)};
|
|
838
|
+
|
|
839
|
+
|
|
840
|
+
// 图片上传
|
|
841
|
+
uploadBtn.addEventListener('click', () => imageInput.click());
|
|
842
|
+
|
|
843
|
+
imageInput.addEventListener('change', (e) => {
|
|
844
|
+
const files = e.target.files;
|
|
845
|
+
for (const file of files) {
|
|
846
|
+
addImageFile(file);
|
|
847
|
+
}
|
|
848
|
+
});
|
|
849
|
+
|
|
850
|
+
// 添加图片文件到预览和上传列表
|
|
851
|
+
function addImageFile(file) {
|
|
852
|
+
const reader = new FileReader();
|
|
853
|
+
reader.onload = (e) => {
|
|
854
|
+
const base64 = e.target.result;
|
|
855
|
+
const imgData = {
|
|
856
|
+
name: file.name || ('pasted-image-' + Date.now() + '.png'),
|
|
857
|
+
data: base64.split(',')[1],
|
|
858
|
+
size: file.size
|
|
859
|
+
};
|
|
860
|
+
uploadedImages.push(imgData);
|
|
861
|
+
|
|
862
|
+
// 显示预览
|
|
863
|
+
const container = document.createElement('div');
|
|
864
|
+
container.className = 'image-preview-item';
|
|
865
|
+
|
|
866
|
+
const img = document.createElement('img');
|
|
867
|
+
img.src = base64;
|
|
868
|
+
|
|
869
|
+
const removeBtn = document.createElement('button');
|
|
870
|
+
removeBtn.className = 'image-remove';
|
|
871
|
+
removeBtn.textContent = '×';
|
|
872
|
+
removeBtn.onclick = () => {
|
|
873
|
+
const index = uploadedImages.indexOf(imgData);
|
|
874
|
+
if (index > -1) {
|
|
875
|
+
uploadedImages.splice(index, 1);
|
|
876
|
+
}
|
|
877
|
+
container.remove();
|
|
878
|
+
};
|
|
879
|
+
|
|
880
|
+
container.appendChild(img);
|
|
881
|
+
container.appendChild(removeBtn);
|
|
882
|
+
imagePreview.appendChild(container);
|
|
883
|
+
};
|
|
884
|
+
reader.readAsDataURL(file);
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
// 粘贴图片支持 (Ctrl+V / Cmd+V)
|
|
888
|
+
document.addEventListener('paste', (e) => {
|
|
889
|
+
const items = e.clipboardData?.items;
|
|
890
|
+
if (!items) return;
|
|
891
|
+
|
|
892
|
+
for (const item of items) {
|
|
893
|
+
if (item.type.startsWith('image/')) {
|
|
894
|
+
e.preventDefault();
|
|
895
|
+
const file = item.getAsFile();
|
|
896
|
+
if (file) {
|
|
897
|
+
addImageFile(file);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
});
|
|
902
|
+
|
|
903
|
+
// 更新倒计时
|
|
904
|
+
function updateCountdown() {
|
|
905
|
+
if (!requestTimestamp || !requestTimeout) return;
|
|
906
|
+
|
|
907
|
+
const elapsed = Math.floor((Date.now() - requestTimestamp) / 1000);
|
|
908
|
+
const remaining = Math.max(0, requestTimeout - elapsed);
|
|
909
|
+
const minutes = Math.floor(remaining / 60);
|
|
910
|
+
const seconds = remaining % 60;
|
|
911
|
+
|
|
912
|
+
timeoutInfo.textContent = i18n.timeout + ': ' + minutes + ':' + seconds.toString().padStart(2, '0');
|
|
913
|
+
|
|
914
|
+
if (remaining <= 0) {
|
|
915
|
+
clearInterval(countdownInterval);
|
|
916
|
+
timeoutInfo.textContent = i18n.expired;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
// 提交反馈
|
|
921
|
+
function submitFeedback() {
|
|
922
|
+
const feedback = feedbackInput.value.trim();
|
|
923
|
+
|
|
924
|
+
if (!currentRequestId) {
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
vscode.postMessage({
|
|
929
|
+
type: 'submitFeedback',
|
|
930
|
+
payload: {
|
|
931
|
+
requestId: currentRequestId,
|
|
932
|
+
interactive_feedback: feedback,
|
|
933
|
+
images: uploadedImages,
|
|
934
|
+
project_directory: currentProjectDir
|
|
935
|
+
}
|
|
936
|
+
});
|
|
937
|
+
|
|
938
|
+
// 重置表单
|
|
939
|
+
feedbackInput.value = '';
|
|
940
|
+
uploadedImages = [];
|
|
941
|
+
imagePreview.innerHTML = '';
|
|
942
|
+
currentRequestId = '';
|
|
943
|
+
|
|
944
|
+
if (countdownInterval) {
|
|
945
|
+
clearInterval(countdownInterval);
|
|
946
|
+
countdownInterval = null;
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
submitBtn.addEventListener('click', submitFeedback);
|
|
951
|
+
|
|
952
|
+
// 快捷键 Ctrl+Enter 提交
|
|
953
|
+
feedbackInput.addEventListener('keydown', (e) => {
|
|
954
|
+
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
|
|
955
|
+
submitFeedback();
|
|
956
|
+
}
|
|
957
|
+
});
|
|
958
|
+
|
|
959
|
+
// 接收来自插件的消息
|
|
960
|
+
window.addEventListener('message', event => {
|
|
961
|
+
const message = event.data;
|
|
962
|
+
|
|
963
|
+
switch (message.type) {
|
|
964
|
+
case 'showFeedbackRequest':
|
|
965
|
+
waitingStatus.classList.add('hidden');
|
|
966
|
+
feedbackForm.classList.remove('hidden');
|
|
967
|
+
|
|
968
|
+
currentRequestId = message.payload.requestId;
|
|
969
|
+
currentProjectDir = message.payload.projectDir;
|
|
970
|
+
requestTimestamp = message.payload.timestamp;
|
|
971
|
+
requestTimeout = message.payload.timeout;
|
|
972
|
+
|
|
973
|
+
summaryContent.innerHTML = renderMarkdown(message.payload.summary);
|
|
974
|
+
projectInfo.textContent = '📁 ' + message.payload.projectDir;
|
|
975
|
+
feedbackInput.focus();
|
|
976
|
+
|
|
977
|
+
// 启动倒计时
|
|
978
|
+
if (countdownInterval) {
|
|
979
|
+
clearInterval(countdownInterval);
|
|
980
|
+
}
|
|
981
|
+
updateCountdown();
|
|
982
|
+
countdownInterval = setInterval(updateCountdown, 1000);
|
|
983
|
+
break;
|
|
984
|
+
|
|
985
|
+
case 'showWaiting':
|
|
986
|
+
feedbackForm.classList.add('hidden');
|
|
987
|
+
waitingStatus.classList.remove('hidden');
|
|
988
|
+
|
|
989
|
+
if (countdownInterval) {
|
|
990
|
+
clearInterval(countdownInterval);
|
|
991
|
+
countdownInterval = null;
|
|
992
|
+
}
|
|
993
|
+
break;
|
|
994
|
+
|
|
995
|
+
case 'serverStatus':
|
|
996
|
+
if (message.payload.connected) {
|
|
997
|
+
serverStatus.classList.add('connected');
|
|
998
|
+
serverStatusText.textContent = i18n.connected;
|
|
999
|
+
} else {
|
|
1000
|
+
serverStatus.classList.remove('connected');
|
|
1001
|
+
serverStatusText.textContent = i18n.disconnected;
|
|
1002
|
+
}
|
|
1003
|
+
break;
|
|
1004
|
+
}
|
|
1005
|
+
});
|
|
1006
|
+
|
|
1007
|
+
// 定期检查服务器状态
|
|
1008
|
+
setInterval(() => {
|
|
1009
|
+
vscode.postMessage({ type: 'checkServer' });
|
|
1010
|
+
}, 5000);
|
|
1011
|
+
|
|
1012
|
+
// 通知插件 WebView 已准备就绪
|
|
1013
|
+
vscode.postMessage({ type: 'ready' });
|
|
1014
|
+
vscode.postMessage({ type: 'checkServer' });
|
|
1015
|
+
</script>
|
|
1016
|
+
</body>
|
|
1017
|
+
</html>`;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
/**
|
|
1021
|
+
* 获取国际化文本
|
|
1022
|
+
*/
|
|
1023
|
+
private _getI18n(lang: string): Record<string, string> {
|
|
1024
|
+
const translations: Record<string, Record<string, string>> = {
|
|
1025
|
+
'zh-CN': {
|
|
1026
|
+
waiting: '等待 AI 请求反馈...',
|
|
1027
|
+
waitingHint: '当 AI 需要您的反馈时,这里会显示输入界面',
|
|
1028
|
+
summary: 'AI 工作摘要',
|
|
1029
|
+
yourFeedback: '您的反馈',
|
|
1030
|
+
placeholder: '请输入您的反馈...',
|
|
1031
|
+
uploadImage: '上传图片',
|
|
1032
|
+
submit: '提交反馈',
|
|
1033
|
+
timeout: '剩余时间',
|
|
1034
|
+
expired: '已超时',
|
|
1035
|
+
checking: '检查连接...',
|
|
1036
|
+
connected: 'MCP Server 已连接',
|
|
1037
|
+
disconnected: 'MCP Server 未连接',
|
|
1038
|
+
},
|
|
1039
|
+
'zh-TW': {
|
|
1040
|
+
waiting: '等待 AI 請求回饋...',
|
|
1041
|
+
waitingHint: '當 AI 需要您的回饋時,這裡會顯示輸入介面',
|
|
1042
|
+
summary: 'AI 工作摘要',
|
|
1043
|
+
yourFeedback: '您的回饋',
|
|
1044
|
+
placeholder: '請輸入您的回饋...',
|
|
1045
|
+
uploadImage: '上傳圖片',
|
|
1046
|
+
submit: '提交回饋',
|
|
1047
|
+
timeout: '剩餘時間',
|
|
1048
|
+
expired: '已超時',
|
|
1049
|
+
checking: '檢查連接...',
|
|
1050
|
+
connected: 'MCP Server 已連接',
|
|
1051
|
+
disconnected: 'MCP Server 未連接',
|
|
1052
|
+
},
|
|
1053
|
+
'en': {
|
|
1054
|
+
waiting: 'Waiting for AI feedback request...',
|
|
1055
|
+
waitingHint: 'The feedback interface will appear when AI needs your input',
|
|
1056
|
+
summary: 'AI Work Summary',
|
|
1057
|
+
yourFeedback: 'Your Feedback',
|
|
1058
|
+
placeholder: 'Enter your feedback...',
|
|
1059
|
+
uploadImage: 'Upload Image',
|
|
1060
|
+
submit: 'Submit Feedback',
|
|
1061
|
+
timeout: 'Time remaining',
|
|
1062
|
+
expired: 'Expired',
|
|
1063
|
+
checking: 'Checking connection...',
|
|
1064
|
+
connected: 'MCP Server connected',
|
|
1065
|
+
disconnected: 'MCP Server disconnected',
|
|
1066
|
+
}
|
|
1067
|
+
};
|
|
1068
|
+
|
|
1069
|
+
return translations[lang] || translations['zh-CN'];
|
|
1070
|
+
}
|
|
1071
|
+
}
|