cursor-feedback 1.1.1 → 2.0.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/CHANGELOG.md +14 -0
- package/README.md +48 -4
- package/README_CN.md +48 -4
- package/dist/extension.js +546 -53
- package/dist/feishu.js +667 -0
- package/dist/i18n/en.json +49 -7
- package/dist/i18n/index.js +46 -4
- package/dist/i18n/zh-CN.json +49 -7
- package/dist/mcp-server.js +514 -20
- package/dist/webview/index.html +193 -43
- package/dist/webview/script.js +795 -145
- package/dist/webview/styles.css +921 -298
- package/package.json +38 -1
- package/.github/workflows/release-please.yml +0 -19
- package/.husky/pre-commit +0 -2
- package/.husky/pre-push +0 -1
- package/.versionrc.json +0 -15
- package/.vscode/launch.json +0 -28
- package/.vscode/tasks.json +0 -22
- package/.vscodeignore +0 -11
- package/demo.gif +0 -0
- package/dist/extension.js.map +0 -1
- package/dist/i18n/index.js.map +0 -1
- package/dist/mcp-server.js.map +0 -1
- package/icon.png +0 -0
- package/mcp-install-dark.png +0 -0
- package/resources/icon.svg +0 -4
- package/resources/vendor/marked.min.js +0 -69
- package/scripts/check-changelog.js +0 -42
- package/src/extension.ts +0 -692
- package/src/i18n/en.json +0 -34
- package/src/i18n/index.ts +0 -131
- package/src/i18n/zh-CN.json +0 -34
- package/src/mcp-server.ts +0 -768
- package/src/webview/index.html +0 -69
- package/src/webview/script.js +0 -318
- package/src/webview/styles.css +0 -422
- package/tsconfig.json +0 -17
package/src/extension.ts
DELETED
|
@@ -1,692 +0,0 @@
|
|
|
1
|
-
import * as vscode from 'vscode';
|
|
2
|
-
import * as http from 'http';
|
|
3
|
-
import * as fs from 'fs';
|
|
4
|
-
import * as path from 'path';
|
|
5
|
-
import { loadMessages, getLanguage, I18nMessages } from './i18n';
|
|
6
|
-
|
|
7
|
-
let feedbackViewProvider: FeedbackViewProvider | null = null;
|
|
8
|
-
let pollingInterval: NodeJS.Timeout | null = null;
|
|
9
|
-
|
|
10
|
-
export function activate(context: vscode.ExtensionContext) {
|
|
11
|
-
console.log('Cursor Feedback extension is now active!');
|
|
12
|
-
|
|
13
|
-
// 注册侧边栏 WebView(端口从 61927 开始自动扫描)
|
|
14
|
-
feedbackViewProvider = new FeedbackViewProvider(context.extensionUri, 61927);
|
|
15
|
-
context.subscriptions.push(
|
|
16
|
-
vscode.window.registerWebviewViewProvider(
|
|
17
|
-
'cursorFeedback.feedbackView',
|
|
18
|
-
feedbackViewProvider
|
|
19
|
-
)
|
|
20
|
-
);
|
|
21
|
-
|
|
22
|
-
// 注册命令:显示反馈面板
|
|
23
|
-
context.subscriptions.push(
|
|
24
|
-
vscode.commands.registerCommand('cursorFeedback.showPanel', () => {
|
|
25
|
-
vscode.commands.executeCommand('cursorFeedback.feedbackView.focus');
|
|
26
|
-
})
|
|
27
|
-
);
|
|
28
|
-
|
|
29
|
-
// 注册命令:启动轮询
|
|
30
|
-
context.subscriptions.push(
|
|
31
|
-
vscode.commands.registerCommand('cursorFeedback.startPolling', () => {
|
|
32
|
-
if (feedbackViewProvider) {
|
|
33
|
-
feedbackViewProvider.startPolling();
|
|
34
|
-
vscode.window.showInformationMessage(feedbackViewProvider.getMessage('startListening'));
|
|
35
|
-
}
|
|
36
|
-
})
|
|
37
|
-
);
|
|
38
|
-
|
|
39
|
-
// 注册命令:停止轮询
|
|
40
|
-
context.subscriptions.push(
|
|
41
|
-
vscode.commands.registerCommand('cursorFeedback.stopPolling', () => {
|
|
42
|
-
if (feedbackViewProvider) {
|
|
43
|
-
feedbackViewProvider.stopPolling();
|
|
44
|
-
vscode.window.showInformationMessage(feedbackViewProvider.getMessage('stopListening'));
|
|
45
|
-
}
|
|
46
|
-
})
|
|
47
|
-
);
|
|
48
|
-
|
|
49
|
-
// 自动开始轮询
|
|
50
|
-
setTimeout(() => {
|
|
51
|
-
feedbackViewProvider?.startPolling();
|
|
52
|
-
}, 1000);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export function deactivate() {
|
|
56
|
-
if (feedbackViewProvider) {
|
|
57
|
-
feedbackViewProvider.stopPolling();
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* 反馈请求接口
|
|
63
|
-
*/
|
|
64
|
-
interface FeedbackRequest {
|
|
65
|
-
id: string;
|
|
66
|
-
summary: string;
|
|
67
|
-
projectDir: string;
|
|
68
|
-
timeout: number;
|
|
69
|
-
timestamp: number;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* 获取当前工作区路径列表
|
|
74
|
-
*/
|
|
75
|
-
function getWorkspacePaths(): string[] {
|
|
76
|
-
const folders = vscode.workspace.workspaceFolders;
|
|
77
|
-
if (!folders) {
|
|
78
|
-
return [];
|
|
79
|
-
}
|
|
80
|
-
return folders.map(f => f.uri.fsPath);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* 检查路径是否匹配当前工作区(精确匹配)
|
|
85
|
-
* - 有工作区的窗口:只接收匹配工作区路径的消息
|
|
86
|
-
* - 没有工作区的窗口:只接收没有指定项目路径的消息
|
|
87
|
-
*/
|
|
88
|
-
function isPathInWorkspace(targetPath: string): boolean {
|
|
89
|
-
const workspacePaths = getWorkspacePaths();
|
|
90
|
-
|
|
91
|
-
// 规范化路径(去除末尾斜杠,统一分隔符,小写)
|
|
92
|
-
const normalize = (p: string) => p.replace(/\\/g, '/').replace(/\/+$/, '').toLowerCase();
|
|
93
|
-
const normalizedTarget = normalize(targetPath);
|
|
94
|
-
|
|
95
|
-
// 检查 targetPath 是否为空或默认值
|
|
96
|
-
const isEmptyPath = !targetPath || targetPath === '.' || normalizedTarget === '' || normalizedTarget === '.';
|
|
97
|
-
|
|
98
|
-
if (workspacePaths.length === 0) {
|
|
99
|
-
// 没有打开工作区时,只接收没有指定项目路径的消息
|
|
100
|
-
return isEmptyPath;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// 有工作区时,不接收空路径的消息
|
|
104
|
-
if (isEmptyPath) {
|
|
105
|
-
return false;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
for (const wsPath of workspacePaths) {
|
|
109
|
-
const normalizedWs = normalize(wsPath);
|
|
110
|
-
// 精确匹配:只匹配完全相同的路径
|
|
111
|
-
if (normalizedTarget === normalizedWs) {
|
|
112
|
-
return true;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return false;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* 侧边栏 WebView Provider
|
|
121
|
-
*/
|
|
122
|
-
class FeedbackViewProvider implements vscode.WebviewViewProvider {
|
|
123
|
-
public static readonly viewType = 'cursorFeedback.feedbackView';
|
|
124
|
-
private _view?: vscode.WebviewView;
|
|
125
|
-
private _pollingInterval: NodeJS.Timeout | null = null;
|
|
126
|
-
private _currentRequest: FeedbackRequest | null = null;
|
|
127
|
-
private _basePort: number;
|
|
128
|
-
private _activePort: number | null = null;
|
|
129
|
-
private _portScanRange = 20; // 扫描端口范围
|
|
130
|
-
private _seenRequestIds: Set<string> = new Set(); // 已处理过的请求 ID
|
|
131
|
-
private _i18n: I18nMessages;
|
|
132
|
-
private _debugInfo: {
|
|
133
|
-
portRange: string;
|
|
134
|
-
workspacePath: string;
|
|
135
|
-
connectedPorts: number[]; // 所有窗口使用的端口
|
|
136
|
-
activePort: number | null; // 当前项目监听的端口
|
|
137
|
-
lastStatus: string;
|
|
138
|
-
} = {
|
|
139
|
-
portRange: '',
|
|
140
|
-
workspacePath: '',
|
|
141
|
-
connectedPorts: [],
|
|
142
|
-
activePort: null,
|
|
143
|
-
lastStatus: ''
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
constructor(
|
|
147
|
-
private readonly _extensionUri: vscode.Uri,
|
|
148
|
-
port: number
|
|
149
|
-
) {
|
|
150
|
-
this._basePort = port;
|
|
151
|
-
this._debugInfo.portRange = `${port}-${port + this._portScanRange - 1}`;
|
|
152
|
-
this._i18n = loadMessages(this._extensionUri.fsPath);
|
|
153
|
-
this._debugInfo.lastStatus = this._i18n.checkingConnection;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* 获取翻译消息
|
|
158
|
-
*/
|
|
159
|
-
public getMessage(key: keyof I18nMessages): string {
|
|
160
|
-
return this._i18n[key] || key;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
public resolveWebviewView(
|
|
164
|
-
webviewView: vscode.WebviewView,
|
|
165
|
-
_context: vscode.WebviewViewResolveContext,
|
|
166
|
-
_token: vscode.CancellationToken
|
|
167
|
-
) {
|
|
168
|
-
this._view = webviewView;
|
|
169
|
-
|
|
170
|
-
webviewView.webview.options = {
|
|
171
|
-
enableScripts: true,
|
|
172
|
-
localResourceRoots: [this._extensionUri]
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
webviewView.webview.html = this._getHtmlForWebview(webviewView.webview);
|
|
176
|
-
|
|
177
|
-
// 处理来自 WebView 的消息
|
|
178
|
-
webviewView.webview.onDidReceiveMessage(async data => {
|
|
179
|
-
switch (data.type) {
|
|
180
|
-
case 'submitFeedback':
|
|
181
|
-
await this._handleFeedbackSubmit(data.payload);
|
|
182
|
-
break;
|
|
183
|
-
case 'ready':
|
|
184
|
-
console.log('Feedback WebView is ready');
|
|
185
|
-
// WebView 准备就绪后,检查是否有待处理的请求
|
|
186
|
-
if (this._currentRequest) {
|
|
187
|
-
this._showFeedbackRequest(this._currentRequest);
|
|
188
|
-
}
|
|
189
|
-
break;
|
|
190
|
-
case 'checkServer':
|
|
191
|
-
await this._checkServerHealth();
|
|
192
|
-
break;
|
|
193
|
-
case 'selectPath':
|
|
194
|
-
await this._handleSelectPath();
|
|
195
|
-
break;
|
|
196
|
-
case 'switchLanguage':
|
|
197
|
-
await this._handleSwitchLanguage();
|
|
198
|
-
break;
|
|
199
|
-
}
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
// 当 view 变为可见时,检查当前请求
|
|
203
|
-
webviewView.onDidChangeVisibility(() => {
|
|
204
|
-
if (webviewView.visible && this._currentRequest) {
|
|
205
|
-
this._showFeedbackRequest(this._currentRequest);
|
|
206
|
-
}
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* 开始轮询 MCP Server
|
|
212
|
-
*/
|
|
213
|
-
public startPolling() {
|
|
214
|
-
if (this._pollingInterval) {
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
console.log(`Starting polling MCP server from port ${this._basePort}`);
|
|
219
|
-
|
|
220
|
-
this._pollingInterval = setInterval(async () => {
|
|
221
|
-
await this._pollForFeedbackRequest();
|
|
222
|
-
}, 1000); // 每秒检查一次
|
|
223
|
-
|
|
224
|
-
// 立即执行一次
|
|
225
|
-
this._pollForFeedbackRequest();
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* 停止轮询
|
|
230
|
-
*/
|
|
231
|
-
public stopPolling() {
|
|
232
|
-
if (this._pollingInterval) {
|
|
233
|
-
clearInterval(this._pollingInterval);
|
|
234
|
-
this._pollingInterval = null;
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* 轮询检查是否有新的反馈请求
|
|
240
|
-
* 优化:如果已有活跃端口,先尝试该端口;失败则扫描所有端口
|
|
241
|
-
*/
|
|
242
|
-
private async _pollForFeedbackRequest() {
|
|
243
|
-
try {
|
|
244
|
-
// 更新工作区路径
|
|
245
|
-
const workspacePaths = getWorkspacePaths();
|
|
246
|
-
this._debugInfo.workspacePath = workspacePaths.length > 0 ? workspacePaths[0] : '(无工作区)';
|
|
247
|
-
const currentWorkspace = workspacePaths[0] || '';
|
|
248
|
-
const normalize = (p: string) => p.replace(/\\/g, '/').replace(/\/+$/, '').toLowerCase();
|
|
249
|
-
const normalizedCurrentWorkspace = normalize(currentWorkspace);
|
|
250
|
-
|
|
251
|
-
// 如果有活跃端口,先尝试只轮询该端口
|
|
252
|
-
if (this._activePort) {
|
|
253
|
-
const result = await this._checkPortForRequest(this._activePort);
|
|
254
|
-
|
|
255
|
-
// 检查是否仍然是我们的 Server
|
|
256
|
-
if (result.connected) {
|
|
257
|
-
const serverOwner = result.ownerWorkspace ? normalize(result.ownerWorkspace) : '';
|
|
258
|
-
const isMyServer = !serverOwner || serverOwner === normalizedCurrentWorkspace;
|
|
259
|
-
|
|
260
|
-
if (isMyServer) {
|
|
261
|
-
// 端口仍然有效,保持使用
|
|
262
|
-
this._debugInfo.connectedPorts = [this._activePort];
|
|
263
|
-
this._debugInfo.activePort = this._activePort;
|
|
264
|
-
|
|
265
|
-
if (result.request && !this._seenRequestIds.has(result.request.id)) {
|
|
266
|
-
this._debugInfo.lastStatus = `监听端口 ${this._activePort}`;
|
|
267
|
-
this._handleNewRequest(result.request, this._activePort);
|
|
268
|
-
this._updateDebugInfo();
|
|
269
|
-
return;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// 端口有效但无新请求,继续保持连接
|
|
273
|
-
this._debugInfo.lastStatus = `监听端口 ${this._activePort}`;
|
|
274
|
-
this._updateDebugInfo();
|
|
275
|
-
return;
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// 活跃端口失效(连接失败或工作区不匹配),重置并扫描所有端口
|
|
280
|
-
this._activePort = null;
|
|
281
|
-
this._debugInfo.activePort = null;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// 扫描所有端口
|
|
285
|
-
const ports = [];
|
|
286
|
-
for (let i = 0; i < this._portScanRange; i++) {
|
|
287
|
-
ports.push(this._basePort + i);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// 并行检查所有端口
|
|
291
|
-
const results = await Promise.all(ports.map(port => this._checkPortForRequest(port)));
|
|
292
|
-
|
|
293
|
-
// 更新已连接的端口列表
|
|
294
|
-
this._debugInfo.connectedPorts = results.filter(r => r.connected).map(r => r.port);
|
|
295
|
-
|
|
296
|
-
// 找出属于当前工作区的请求
|
|
297
|
-
const myRequests = results.filter(r => {
|
|
298
|
-
if (!r.request || this._seenRequestIds.has(r.request.id)) {
|
|
299
|
-
return false;
|
|
300
|
-
}
|
|
301
|
-
const serverOwner = r.ownerWorkspace ? normalize(r.ownerWorkspace) : '';
|
|
302
|
-
return !serverOwner || serverOwner === normalizedCurrentWorkspace;
|
|
303
|
-
}).sort((a, b) => b.request!.timestamp - a.request!.timestamp);
|
|
304
|
-
|
|
305
|
-
// 处理最新的请求
|
|
306
|
-
if (myRequests.length > 0) {
|
|
307
|
-
const newest = myRequests[0];
|
|
308
|
-
this._activePort = newest.port;
|
|
309
|
-
this._debugInfo.activePort = newest.port;
|
|
310
|
-
this._debugInfo.lastStatus = `找到请求 (端口 ${newest.port})`;
|
|
311
|
-
this._handleNewRequest(newest.request!, newest.port);
|
|
312
|
-
this._updateDebugInfo();
|
|
313
|
-
return;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
// 没有新请求,检查是否有当前请求
|
|
317
|
-
if (this._currentRequest && this._activePort) {
|
|
318
|
-
this._debugInfo.activePort = this._activePort;
|
|
319
|
-
this._debugInfo.lastStatus = `监听端口 ${this._activePort}`;
|
|
320
|
-
this._updateDebugInfo();
|
|
321
|
-
return;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
// 没有任何请求
|
|
325
|
-
this._debugInfo.activePort = null;
|
|
326
|
-
|
|
327
|
-
if (this._debugInfo.connectedPorts.length === 0) {
|
|
328
|
-
this._debugInfo.lastStatus = '未找到 MCP Server';
|
|
329
|
-
} else {
|
|
330
|
-
this._debugInfo.lastStatus = `已连接 ${this._debugInfo.connectedPorts.length} 个端口,等待请求`;
|
|
331
|
-
}
|
|
332
|
-
this._updateDebugInfo();
|
|
333
|
-
} catch (error) {
|
|
334
|
-
this._debugInfo.lastStatus = `轮询错误: ${error}`;
|
|
335
|
-
this._updateDebugInfo();
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
/**
|
|
340
|
-
* 检查指定端口是否有反馈请求
|
|
341
|
-
*/
|
|
342
|
-
private async _checkPortForRequest(port: number): Promise<{
|
|
343
|
-
connected: boolean;
|
|
344
|
-
request: FeedbackRequest | null;
|
|
345
|
-
port: number;
|
|
346
|
-
mismatch?: boolean; // 是否有请求但路径不匹配
|
|
347
|
-
ownerWorkspace?: string | null; // Server 的所属工作区
|
|
348
|
-
startTime?: number; // Server 的启动时间
|
|
349
|
-
}> {
|
|
350
|
-
try {
|
|
351
|
-
// 带上工作区路径用于匹配
|
|
352
|
-
const workspacePaths = getWorkspacePaths();
|
|
353
|
-
const workspacePath = workspacePaths.length > 0 ? workspacePaths[0] : '';
|
|
354
|
-
const url = `http://127.0.0.1:${port}/api/feedback/current?workspace=${encodeURIComponent(workspacePath)}`;
|
|
355
|
-
const response = await this._httpGet(url);
|
|
356
|
-
const parsed = JSON.parse(response);
|
|
357
|
-
|
|
358
|
-
// 兼容新旧两种响应格式
|
|
359
|
-
// 新格式: { request, ownerWorkspace, startTime }
|
|
360
|
-
// 旧格式: FeedbackRequest | null
|
|
361
|
-
let request: FeedbackRequest | null;
|
|
362
|
-
let ownerWorkspace: string | null = null;
|
|
363
|
-
let startTime: number = 0;
|
|
364
|
-
|
|
365
|
-
if (parsed && typeof parsed === 'object' && 'startTime' in parsed) {
|
|
366
|
-
// 新格式
|
|
367
|
-
request = parsed.request;
|
|
368
|
-
ownerWorkspace = parsed.ownerWorkspace;
|
|
369
|
-
startTime = parsed.startTime;
|
|
370
|
-
} else {
|
|
371
|
-
// 旧格式(兼容 npm 上的旧版本)
|
|
372
|
-
request = parsed as FeedbackRequest | null;
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
// 检查请求是否属于当前工作区
|
|
376
|
-
if (request) {
|
|
377
|
-
const isMatch = isPathInWorkspace(request.projectDir);
|
|
378
|
-
|
|
379
|
-
if (!isMatch) {
|
|
380
|
-
// 请求不属于当前工作区,返回特殊标记
|
|
381
|
-
return { connected: true, request: null, port, mismatch: true, ownerWorkspace, startTime };
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
return { connected: true, request, port, ownerWorkspace, startTime };
|
|
386
|
-
} catch {
|
|
387
|
-
return { connected: false, request: null, port };
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
/**
|
|
392
|
-
* 处理新的反馈请求
|
|
393
|
-
*/
|
|
394
|
-
private _handleNewRequest(request: FeedbackRequest, port: number) {
|
|
395
|
-
// 如果已经处理过这个请求,跳过
|
|
396
|
-
if (this._seenRequestIds.has(request.id)) {
|
|
397
|
-
return;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
// 判断是否为"新鲜"请求:创建后 10 秒内被发现
|
|
401
|
-
const requestAge = Date.now() - request.timestamp;
|
|
402
|
-
const isFreshRequest = requestAge < 10000; // 10秒内
|
|
403
|
-
|
|
404
|
-
console.log(`Feedback request on port ${port}:`, request.id,
|
|
405
|
-
`age: ${requestAge}ms, isFresh: ${isFreshRequest}`);
|
|
406
|
-
|
|
407
|
-
// 标记为已见过
|
|
408
|
-
this._seenRequestIds.add(request.id);
|
|
409
|
-
|
|
410
|
-
// 清理旧的请求 ID(保留最近 100 个)
|
|
411
|
-
if (this._seenRequestIds.size > 100) {
|
|
412
|
-
const ids = Array.from(this._seenRequestIds);
|
|
413
|
-
this._seenRequestIds = new Set(ids.slice(-50));
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
if (!this._currentRequest || request.id !== this._currentRequest.id) {
|
|
417
|
-
this._currentRequest = request;
|
|
418
|
-
this._activePort = port;
|
|
419
|
-
|
|
420
|
-
// 显示请求内容
|
|
421
|
-
this._showFeedbackRequest(request);
|
|
422
|
-
|
|
423
|
-
// 只对新鲜请求自动聚焦和通知
|
|
424
|
-
if (isFreshRequest) {
|
|
425
|
-
vscode.commands.executeCommand('cursorFeedback.feedbackView.focus');
|
|
426
|
-
vscode.window.showInformationMessage(this._i18n.aiWaitingFeedback);
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
/**
|
|
432
|
-
* 检查服务器健康状态
|
|
433
|
-
*/
|
|
434
|
-
private async _checkServerHealth() {
|
|
435
|
-
// 扫描端口查找可用的服务器
|
|
436
|
-
for (let i = 0; i < this._portScanRange; i++) {
|
|
437
|
-
const port = this._basePort + i;
|
|
438
|
-
try {
|
|
439
|
-
const response = await this._httpGet(`http://127.0.0.1:${port}/api/health`);
|
|
440
|
-
const health = JSON.parse(response);
|
|
441
|
-
this._view?.webview.postMessage({
|
|
442
|
-
type: 'serverStatus',
|
|
443
|
-
payload: { connected: true, port, ...health }
|
|
444
|
-
});
|
|
445
|
-
return;
|
|
446
|
-
} catch {
|
|
447
|
-
// 继续尝试下一个端口
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
this._view?.webview.postMessage({
|
|
452
|
-
type: 'serverStatus',
|
|
453
|
-
payload: { connected: false }
|
|
454
|
-
});
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
/**
|
|
458
|
-
* 显示反馈请求
|
|
459
|
-
*/
|
|
460
|
-
private _showFeedbackRequest(request: FeedbackRequest) {
|
|
461
|
-
if (this._view) {
|
|
462
|
-
this._view.show?.(true);
|
|
463
|
-
this._view.webview.postMessage({
|
|
464
|
-
type: 'showFeedbackRequest',
|
|
465
|
-
payload: {
|
|
466
|
-
requestId: request.id,
|
|
467
|
-
summary: request.summary,
|
|
468
|
-
projectDir: request.projectDir,
|
|
469
|
-
timeout: request.timeout,
|
|
470
|
-
timestamp: request.timestamp
|
|
471
|
-
}
|
|
472
|
-
});
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
/**
|
|
477
|
-
* 显示等待状态
|
|
478
|
-
*/
|
|
479
|
-
private _showWaitingState() {
|
|
480
|
-
if (this._view) {
|
|
481
|
-
this._view.webview.postMessage({
|
|
482
|
-
type: 'showWaiting'
|
|
483
|
-
});
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
/**
|
|
488
|
-
* 更新调试信息到 WebView
|
|
489
|
-
*/
|
|
490
|
-
private _updateDebugInfo() {
|
|
491
|
-
if (this._view) {
|
|
492
|
-
this._view.webview.postMessage({
|
|
493
|
-
type: 'updateDebugInfo',
|
|
494
|
-
payload: this._debugInfo
|
|
495
|
-
});
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
/**
|
|
500
|
-
* 处理反馈提交
|
|
501
|
-
*/
|
|
502
|
-
private async _handleFeedbackSubmit(payload: {
|
|
503
|
-
requestId: string;
|
|
504
|
-
interactive_feedback: string;
|
|
505
|
-
images: Array<{ name: string; data: string; size: number }>;
|
|
506
|
-
attachedFiles: string[];
|
|
507
|
-
project_directory: string;
|
|
508
|
-
}) {
|
|
509
|
-
// 使用活跃端口提交反馈
|
|
510
|
-
const port = this._activePort || this._basePort;
|
|
511
|
-
|
|
512
|
-
try {
|
|
513
|
-
const response = await this._httpPost(
|
|
514
|
-
`http://127.0.0.1:${port}/api/feedback/submit`,
|
|
515
|
-
JSON.stringify({
|
|
516
|
-
requestId: payload.requestId,
|
|
517
|
-
feedback: {
|
|
518
|
-
interactive_feedback: payload.interactive_feedback,
|
|
519
|
-
images: payload.images,
|
|
520
|
-
attachedFiles: payload.attachedFiles || [],
|
|
521
|
-
project_directory: payload.project_directory
|
|
522
|
-
}
|
|
523
|
-
})
|
|
524
|
-
);
|
|
525
|
-
|
|
526
|
-
const result = JSON.parse(response);
|
|
527
|
-
if (result.success) {
|
|
528
|
-
vscode.window.showInformationMessage(this._i18n.feedbackSubmitted);
|
|
529
|
-
this._currentRequest = null;
|
|
530
|
-
this._showWaitingState();
|
|
531
|
-
} else {
|
|
532
|
-
vscode.window.showErrorMessage(this._i18n.submitFailed + ': ' + result.error);
|
|
533
|
-
}
|
|
534
|
-
} catch (error) {
|
|
535
|
-
vscode.window.showErrorMessage(this._i18n.submitFailed + ': ' + this._i18n.cannotConnectMCP);
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
/**
|
|
540
|
-
* 处理选择文件/文件夹
|
|
541
|
-
*/
|
|
542
|
-
private async _handleSelectPath() {
|
|
543
|
-
const result = await vscode.window.showOpenDialog({
|
|
544
|
-
canSelectMany: true,
|
|
545
|
-
canSelectFiles: true,
|
|
546
|
-
canSelectFolders: true,
|
|
547
|
-
openLabel: this._i18n.select
|
|
548
|
-
});
|
|
549
|
-
|
|
550
|
-
if (result && result.length > 0) {
|
|
551
|
-
const paths = result.map(uri => uri.fsPath);
|
|
552
|
-
this._view?.webview.postMessage({
|
|
553
|
-
type: 'filesSelected',
|
|
554
|
-
payload: { paths }
|
|
555
|
-
});
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
/**
|
|
560
|
-
* 处理语言切换
|
|
561
|
-
*/
|
|
562
|
-
private async _handleSwitchLanguage() {
|
|
563
|
-
const config = vscode.workspace.getConfiguration('cursorFeedback');
|
|
564
|
-
const currentConfigLang = config.get<string>('language') || 'auto';
|
|
565
|
-
|
|
566
|
-
const languages = [
|
|
567
|
-
{ label: '🌐 Auto (System)', value: 'auto', description: 'Detect from system language' },
|
|
568
|
-
{ label: '简体中文', value: 'zh-CN', description: '' },
|
|
569
|
-
{ label: 'English', value: 'en', description: '' }
|
|
570
|
-
];
|
|
571
|
-
|
|
572
|
-
const selected = await vscode.window.showQuickPick(
|
|
573
|
-
languages.map(l => ({
|
|
574
|
-
label: l.label + (l.value === currentConfigLang ? ' ✓' : ''),
|
|
575
|
-
description: l.description,
|
|
576
|
-
value: l.value
|
|
577
|
-
})),
|
|
578
|
-
{
|
|
579
|
-
placeHolder: 'Select Language / 选择语言'
|
|
580
|
-
}
|
|
581
|
-
);
|
|
582
|
-
|
|
583
|
-
if (selected && selected.value !== currentConfigLang) {
|
|
584
|
-
// 更新设置
|
|
585
|
-
await config.update('language', selected.value, vscode.ConfigurationTarget.Global);
|
|
586
|
-
|
|
587
|
-
// 重新加载 i18n(如果是 auto,需要重新检测)
|
|
588
|
-
this._i18n = loadMessages(this._extensionUri.fsPath);
|
|
589
|
-
|
|
590
|
-
// 重新渲染 WebView
|
|
591
|
-
if (this._view) {
|
|
592
|
-
this._view.webview.html = this._getHtmlForWebview(this._view.webview);
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
const effectiveLang = getLanguage();
|
|
596
|
-
vscode.window.showInformationMessage(
|
|
597
|
-
effectiveLang === 'en'
|
|
598
|
-
? 'Language changed to English'
|
|
599
|
-
: '语言已切换为简体中文'
|
|
600
|
-
);
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
/**
|
|
605
|
-
* HTTP GET 请求
|
|
606
|
-
*/
|
|
607
|
-
private _httpGet(url: string): Promise<string> {
|
|
608
|
-
return new Promise((resolve, reject) => {
|
|
609
|
-
const req = http.get(url, { timeout: 3000 }, (res) => {
|
|
610
|
-
let data = '';
|
|
611
|
-
res.on('data', chunk => data += chunk);
|
|
612
|
-
res.on('end', () => resolve(data));
|
|
613
|
-
});
|
|
614
|
-
req.on('error', reject);
|
|
615
|
-
req.on('timeout', () => {
|
|
616
|
-
req.destroy();
|
|
617
|
-
reject(new Error('Request timeout'));
|
|
618
|
-
});
|
|
619
|
-
});
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
/**
|
|
623
|
-
* HTTP POST 请求
|
|
624
|
-
*/
|
|
625
|
-
private _httpPost(url: string, body: string): Promise<string> {
|
|
626
|
-
return new Promise((resolve, reject) => {
|
|
627
|
-
const urlObj = new URL(url);
|
|
628
|
-
const options = {
|
|
629
|
-
hostname: urlObj.hostname,
|
|
630
|
-
port: urlObj.port,
|
|
631
|
-
path: urlObj.pathname,
|
|
632
|
-
method: 'POST',
|
|
633
|
-
headers: {
|
|
634
|
-
'Content-Type': 'application/json',
|
|
635
|
-
'Content-Length': Buffer.byteLength(body)
|
|
636
|
-
},
|
|
637
|
-
timeout: 5000
|
|
638
|
-
};
|
|
639
|
-
|
|
640
|
-
const req = http.request(options, (res) => {
|
|
641
|
-
let data = '';
|
|
642
|
-
res.on('data', chunk => data += chunk);
|
|
643
|
-
res.on('end', () => resolve(data));
|
|
644
|
-
});
|
|
645
|
-
req.on('error', reject);
|
|
646
|
-
req.on('timeout', () => {
|
|
647
|
-
req.destroy();
|
|
648
|
-
reject(new Error('Request timeout'));
|
|
649
|
-
});
|
|
650
|
-
req.write(body);
|
|
651
|
-
req.end();
|
|
652
|
-
});
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
private _getHtmlForWebview(webview: vscode.Webview): string {
|
|
656
|
-
// 获取资源文件的 URI
|
|
657
|
-
const markedJsUri = webview.asWebviewUri(
|
|
658
|
-
vscode.Uri.joinPath(this._extensionUri, 'resources', 'vendor', 'marked.min.js')
|
|
659
|
-
);
|
|
660
|
-
const stylesCssUri = webview.asWebviewUri(
|
|
661
|
-
vscode.Uri.joinPath(this._extensionUri, 'dist', 'webview', 'styles.css')
|
|
662
|
-
);
|
|
663
|
-
const scriptJsUri = webview.asWebviewUri(
|
|
664
|
-
vscode.Uri.joinPath(this._extensionUri, 'dist', 'webview', 'script.js')
|
|
665
|
-
);
|
|
666
|
-
|
|
667
|
-
// 读取 HTML 模板
|
|
668
|
-
const htmlTemplatePath = path.join(this._extensionUri.fsPath, 'dist', 'webview', 'index.html');
|
|
669
|
-
let htmlTemplate = fs.readFileSync(htmlTemplatePath, 'utf-8');
|
|
670
|
-
|
|
671
|
-
// CSP 策略
|
|
672
|
-
const csp = `default-src 'none'; style-src ${webview.cspSource}; script-src 'unsafe-inline' ${webview.cspSource}; img-src data:;`;
|
|
673
|
-
|
|
674
|
-
// 获取语言设置
|
|
675
|
-
const language = getLanguage();
|
|
676
|
-
const langCode = language === 'zh-TW' ? 'zh-TW' : (language === 'en' ? 'en' : 'zh-CN');
|
|
677
|
-
|
|
678
|
-
// 替换占位符
|
|
679
|
-
htmlTemplate = htmlTemplate
|
|
680
|
-
.replace(/\{\{CSP\}\}/g, csp)
|
|
681
|
-
.replace(/\{\{LANG\}\}/g, langCode)
|
|
682
|
-
.replace(/\{\{MARKED_JS_URI\}\}/g, markedJsUri.toString())
|
|
683
|
-
.replace(/\{\{STYLES_CSS_URI\}\}/g, stylesCssUri.toString())
|
|
684
|
-
.replace(/\{\{SCRIPT_JS_URI\}\}/g, scriptJsUri.toString())
|
|
685
|
-
.replace(/\{\{I18N_JSON\}\}/g, JSON.stringify(this._i18n))
|
|
686
|
-
.replace(/\{\{i18n\.(\w+)\}\}/g, (_, key) => {
|
|
687
|
-
return (this._i18n as any)[key] || key;
|
|
688
|
-
});
|
|
689
|
-
|
|
690
|
-
return htmlTemplate;
|
|
691
|
-
}
|
|
692
|
-
}
|