cursor-feedback 0.1.34 → 1.0.1
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 +2 -1
- package/dist/extension.js +49 -49
- package/dist/extension.js.map +1 -1
- package/dist/mcp/McpServer.js +3 -3
- package/dist/mcp/McpServer.js.map +1 -1
- package/dist/mcp-server.js +24 -79
- package/dist/mcp-server.js.map +1 -1
- package/package.json +1 -1
- package/src/extension.ts +57 -53
- package/src/mcp-server.ts +26 -94
- package/src/mcp/McpServer.ts +0 -480
- package/src/webview/FeedbackPanel.ts +0 -134
package/src/mcp/McpServer.ts
DELETED
|
@@ -1,480 +0,0 @@
|
|
|
1
|
-
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
-
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
-
import {
|
|
4
|
-
CallToolRequestSchema,
|
|
5
|
-
ListToolsRequestSchema,
|
|
6
|
-
} from '@modelcontextprotocol/sdk/types.js';
|
|
7
|
-
import * as http from 'http';
|
|
8
|
-
import * as os from 'os';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* 反馈请求接口
|
|
12
|
-
*/
|
|
13
|
-
interface FeedbackRequest {
|
|
14
|
-
id: string;
|
|
15
|
-
summary: string;
|
|
16
|
-
projectDir: string;
|
|
17
|
-
timeout: number;
|
|
18
|
-
timestamp: number;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* 反馈响应接口
|
|
23
|
-
*/
|
|
24
|
-
interface FeedbackResponse {
|
|
25
|
-
interactive_feedback: string;
|
|
26
|
-
images: Array<{
|
|
27
|
-
name: string;
|
|
28
|
-
data: string;
|
|
29
|
-
size: number;
|
|
30
|
-
}>;
|
|
31
|
-
attachedFiles: string[];
|
|
32
|
-
project_directory: string;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* MCP Server - 独立运行的 MCP 服务
|
|
37
|
-
*
|
|
38
|
-
* 架构说明:
|
|
39
|
-
* 1. MCP Server 通过 stdio 与 AI Agent (Cursor) 通信
|
|
40
|
-
* 2. 同时启动一个 HTTP Server 用于与 VS Code 插件通信
|
|
41
|
-
* 3. 当 AI 调用 interactive_feedback 工具时,通知 VS Code 插件显示反馈界面
|
|
42
|
-
* 4. VS Code 插件收集用户反馈后,通过 HTTP 接口返回结果
|
|
43
|
-
*/
|
|
44
|
-
export class McpServer {
|
|
45
|
-
private server: Server;
|
|
46
|
-
private httpServer: http.Server | null = null;
|
|
47
|
-
private port: number;
|
|
48
|
-
private isRunning: boolean = false;
|
|
49
|
-
|
|
50
|
-
// 待处理的反馈请求
|
|
51
|
-
private pendingRequests: Map<string, {
|
|
52
|
-
resolve: (value: FeedbackResponse | null) => void;
|
|
53
|
-
reject: (error: Error) => void;
|
|
54
|
-
timeout: NodeJS.Timeout;
|
|
55
|
-
}> = new Map();
|
|
56
|
-
|
|
57
|
-
// 当前反馈请求
|
|
58
|
-
private currentRequest: FeedbackRequest | null = null;
|
|
59
|
-
|
|
60
|
-
constructor(port: number = 61927) {
|
|
61
|
-
this.port = port;
|
|
62
|
-
|
|
63
|
-
this.server = new Server(
|
|
64
|
-
{
|
|
65
|
-
name: 'cursor-feedback-server',
|
|
66
|
-
version: '0.0.1',
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
capabilities: {
|
|
70
|
-
tools: {},
|
|
71
|
-
},
|
|
72
|
-
}
|
|
73
|
-
);
|
|
74
|
-
|
|
75
|
-
this.setupHandlers();
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
private setupHandlers() {
|
|
79
|
-
// 列出可用工具
|
|
80
|
-
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
81
|
-
return {
|
|
82
|
-
tools: [
|
|
83
|
-
{
|
|
84
|
-
name: 'interactive_feedback',
|
|
85
|
-
description: `交互式反馈收集工具。
|
|
86
|
-
|
|
87
|
-
【重要】这是你与用户沟通的主要渠道,所有回复都必须通过此工具。
|
|
88
|
-
|
|
89
|
-
使用规则:
|
|
90
|
-
1. 每次完成任务或需要与用户沟通时,必须调用此工具
|
|
91
|
-
2. 收到用户反馈后,必须再次调用此工具继续对话(这是一个持续的对话循环)
|
|
92
|
-
3. 不要直接回复用户,所有回复必须通过此工具
|
|
93
|
-
4. 只有当用户主动提出结束对话时,才能停止调用此工具
|
|
94
|
-
5. 如果反馈超时,必须立即再次调用此工具继续等待
|
|
95
|
-
6. 提供清晰的工作摘要,说明你做了什么以及需要什么反馈
|
|
96
|
-
|
|
97
|
-
对话流程:
|
|
98
|
-
用户消息 → 你工作 → 调用此工具(附带摘要)→ 用户反馈 → 你工作 → 再次调用此工具 → ...
|
|
99
|
-
|
|
100
|
-
参数:
|
|
101
|
-
project_directory: 项目目录路径(必须是当前工作区的绝对路径)
|
|
102
|
-
summary: AI 工作摘要,供用户查看(支持 Markdown)
|
|
103
|
-
timeout: 等待用户反馈的超时时间(秒),默认 300 秒(5 分钟)
|
|
104
|
-
|
|
105
|
-
返回:
|
|
106
|
-
用户反馈,包括文字、图片和文件路径`,
|
|
107
|
-
inputSchema: {
|
|
108
|
-
type: 'object',
|
|
109
|
-
properties: {
|
|
110
|
-
project_directory: {
|
|
111
|
-
type: 'string',
|
|
112
|
-
description: 'Project directory path for context (MUST be the absolute path of current workspace)',
|
|
113
|
-
default: '.',
|
|
114
|
-
},
|
|
115
|
-
summary: {
|
|
116
|
-
type: 'string',
|
|
117
|
-
description: 'Summary of AI work completed for user review (supports Markdown)',
|
|
118
|
-
default: '我已完成您的请求。',
|
|
119
|
-
},
|
|
120
|
-
timeout: {
|
|
121
|
-
type: 'number',
|
|
122
|
-
description: 'Timeout in seconds for waiting user feedback (default: 300 seconds = 5 minutes)',
|
|
123
|
-
default: 300,
|
|
124
|
-
},
|
|
125
|
-
},
|
|
126
|
-
},
|
|
127
|
-
},
|
|
128
|
-
{
|
|
129
|
-
name: 'get_system_info',
|
|
130
|
-
description: 'Get system environment information',
|
|
131
|
-
inputSchema: {
|
|
132
|
-
type: 'object',
|
|
133
|
-
properties: {},
|
|
134
|
-
},
|
|
135
|
-
},
|
|
136
|
-
],
|
|
137
|
-
};
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
// 处理工具调用
|
|
141
|
-
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
142
|
-
const { name, arguments: args } = request.params;
|
|
143
|
-
|
|
144
|
-
switch (name) {
|
|
145
|
-
case 'interactive_feedback':
|
|
146
|
-
return this.handleInteractiveFeedback(args);
|
|
147
|
-
case 'get_system_info':
|
|
148
|
-
return this.handleGetSystemInfo();
|
|
149
|
-
default:
|
|
150
|
-
throw new Error(`Unknown tool: ${name}`);
|
|
151
|
-
}
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* 处理交互式反馈请求
|
|
157
|
-
*/
|
|
158
|
-
private async handleInteractiveFeedback(args: Record<string, unknown> | undefined): Promise<{
|
|
159
|
-
content: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;
|
|
160
|
-
}> {
|
|
161
|
-
const projectDir = (args?.project_directory as string) || '.';
|
|
162
|
-
const summary = (args?.summary as string) || '我已完成您的请求。';
|
|
163
|
-
// 超时时间优先级:环境变量 > 工具参数 > 默认值(300秒)
|
|
164
|
-
// 这样用户配置的环境变量永远生效,不会被 AI 覆盖
|
|
165
|
-
const envTimeout = process.env.MCP_FEEDBACK_TIMEOUT ? parseInt(process.env.MCP_FEEDBACK_TIMEOUT, 10) : null;
|
|
166
|
-
const timeout = envTimeout || (args?.timeout as number) || 300;
|
|
167
|
-
|
|
168
|
-
const requestId = this.generateRequestId();
|
|
169
|
-
|
|
170
|
-
// 创建反馈请求
|
|
171
|
-
this.currentRequest = {
|
|
172
|
-
id: requestId,
|
|
173
|
-
summary,
|
|
174
|
-
projectDir,
|
|
175
|
-
timeout,
|
|
176
|
-
timestamp: Date.now(),
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
console.error(`[MCP] Feedback request created: ${requestId}`);
|
|
180
|
-
console.error(`[MCP] Waiting for VS Code extension to collect feedback...`);
|
|
181
|
-
|
|
182
|
-
try {
|
|
183
|
-
// 等待用户反馈
|
|
184
|
-
const result = await this.waitForFeedback(requestId, timeout * 1000);
|
|
185
|
-
|
|
186
|
-
if (!result) {
|
|
187
|
-
return {
|
|
188
|
-
content: [
|
|
189
|
-
{
|
|
190
|
-
type: 'text',
|
|
191
|
-
text: 'User cancelled the feedback or timeout.',
|
|
192
|
-
},
|
|
193
|
-
],
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
const contentItems: Array<{ type: string; text?: string; data?: string; mimeType?: string }> = [];
|
|
198
|
-
|
|
199
|
-
// 构建反馈文本
|
|
200
|
-
let feedbackText = '';
|
|
201
|
-
|
|
202
|
-
// 添加文字反馈
|
|
203
|
-
if (result.interactive_feedback) {
|
|
204
|
-
feedbackText += `=== User Feedback ===\n${result.interactive_feedback}`;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// 添加附加文件路径
|
|
208
|
-
if (result.attachedFiles && result.attachedFiles.length > 0) {
|
|
209
|
-
feedbackText += `\n\n=== Attached Files ===\n`;
|
|
210
|
-
for (const filePath of result.attachedFiles) {
|
|
211
|
-
feedbackText += `${filePath}\n`;
|
|
212
|
-
}
|
|
213
|
-
feedbackText += `\nPlease read the above files to understand the context.`;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
if (feedbackText) {
|
|
217
|
-
contentItems.push({
|
|
218
|
-
type: 'text',
|
|
219
|
-
text: feedbackText,
|
|
220
|
-
});
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// 添加图片
|
|
224
|
-
if (result.images && result.images.length > 0) {
|
|
225
|
-
for (const img of result.images) {
|
|
226
|
-
contentItems.push({
|
|
227
|
-
type: 'image',
|
|
228
|
-
data: img.data,
|
|
229
|
-
mimeType: this.getMimeType(img.name),
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
if (contentItems.length === 0) {
|
|
235
|
-
contentItems.push({
|
|
236
|
-
type: 'text',
|
|
237
|
-
text: 'User did not provide any feedback.',
|
|
238
|
-
});
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
return { content: contentItems };
|
|
242
|
-
} catch (error) {
|
|
243
|
-
return {
|
|
244
|
-
content: [
|
|
245
|
-
{
|
|
246
|
-
type: 'text',
|
|
247
|
-
text: `Error collecting feedback: ${error}`,
|
|
248
|
-
},
|
|
249
|
-
],
|
|
250
|
-
};
|
|
251
|
-
} finally {
|
|
252
|
-
this.currentRequest = null;
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* 等待用户反馈
|
|
258
|
-
*/
|
|
259
|
-
private waitForFeedback(requestId: string, timeoutMs: number): Promise<FeedbackResponse | null> {
|
|
260
|
-
return new Promise((resolve, reject) => {
|
|
261
|
-
const timeout = setTimeout(() => {
|
|
262
|
-
this.pendingRequests.delete(requestId);
|
|
263
|
-
resolve(null);
|
|
264
|
-
}, timeoutMs);
|
|
265
|
-
|
|
266
|
-
this.pendingRequests.set(requestId, { resolve, reject, timeout });
|
|
267
|
-
});
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
/**
|
|
271
|
-
* 处理获取系统信息请求
|
|
272
|
-
*/
|
|
273
|
-
private handleGetSystemInfo(): {
|
|
274
|
-
content: Array<{ type: string; text: string }>;
|
|
275
|
-
} {
|
|
276
|
-
const systemInfo = {
|
|
277
|
-
platform: process.platform,
|
|
278
|
-
nodeVersion: process.version,
|
|
279
|
-
arch: process.arch,
|
|
280
|
-
hostname: os.hostname(),
|
|
281
|
-
interfaceType: 'VS Code Extension',
|
|
282
|
-
mcpServerPort: this.port,
|
|
283
|
-
};
|
|
284
|
-
|
|
285
|
-
return {
|
|
286
|
-
content: [
|
|
287
|
-
{
|
|
288
|
-
type: 'text',
|
|
289
|
-
text: JSON.stringify(systemInfo, null, 2),
|
|
290
|
-
},
|
|
291
|
-
],
|
|
292
|
-
};
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
/**
|
|
296
|
-
* 根据文件名获取 MIME 类型
|
|
297
|
-
*/
|
|
298
|
-
private getMimeType(filename: string): string {
|
|
299
|
-
const ext = filename.toLowerCase().split('.').pop();
|
|
300
|
-
switch (ext) {
|
|
301
|
-
case 'jpg':
|
|
302
|
-
case 'jpeg':
|
|
303
|
-
return 'image/jpeg';
|
|
304
|
-
case 'png':
|
|
305
|
-
return 'image/png';
|
|
306
|
-
case 'gif':
|
|
307
|
-
return 'image/gif';
|
|
308
|
-
case 'webp':
|
|
309
|
-
return 'image/webp';
|
|
310
|
-
default:
|
|
311
|
-
return 'image/png';
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
/**
|
|
316
|
-
* 生成唯一的请求 ID
|
|
317
|
-
*/
|
|
318
|
-
private generateRequestId(): string {
|
|
319
|
-
return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
/**
|
|
323
|
-
* 启动 HTTP 服务器,用于与 VS Code 插件通信
|
|
324
|
-
*/
|
|
325
|
-
private startHttpServer(): Promise<void> {
|
|
326
|
-
return new Promise((resolve, reject) => {
|
|
327
|
-
this.httpServer = http.createServer((req, res) => {
|
|
328
|
-
// 设置 CORS 头
|
|
329
|
-
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
330
|
-
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
331
|
-
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
332
|
-
|
|
333
|
-
if (req.method === 'OPTIONS') {
|
|
334
|
-
res.writeHead(200);
|
|
335
|
-
res.end();
|
|
336
|
-
return;
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
// 获取当前反馈请求
|
|
340
|
-
if (req.method === 'GET' && req.url === '/api/feedback/current') {
|
|
341
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
342
|
-
res.end(JSON.stringify(this.currentRequest || null));
|
|
343
|
-
return;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
// 提交反馈
|
|
347
|
-
if (req.method === 'POST' && req.url === '/api/feedback/submit') {
|
|
348
|
-
let body = '';
|
|
349
|
-
req.on('data', chunk => {
|
|
350
|
-
body += chunk.toString();
|
|
351
|
-
});
|
|
352
|
-
req.on('end', () => {
|
|
353
|
-
try {
|
|
354
|
-
const data = JSON.parse(body) as { requestId: string; feedback: FeedbackResponse };
|
|
355
|
-
const { requestId, feedback } = data;
|
|
356
|
-
|
|
357
|
-
const pending = this.pendingRequests.get(requestId);
|
|
358
|
-
if (pending) {
|
|
359
|
-
clearTimeout(pending.timeout);
|
|
360
|
-
pending.resolve(feedback);
|
|
361
|
-
this.pendingRequests.delete(requestId);
|
|
362
|
-
|
|
363
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
364
|
-
res.end(JSON.stringify({ success: true }));
|
|
365
|
-
} else {
|
|
366
|
-
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
367
|
-
res.end(JSON.stringify({ error: 'Request not found' }));
|
|
368
|
-
}
|
|
369
|
-
} catch (error) {
|
|
370
|
-
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
371
|
-
res.end(JSON.stringify({ error: 'Invalid request body' }));
|
|
372
|
-
}
|
|
373
|
-
});
|
|
374
|
-
return;
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
// 健康检查
|
|
378
|
-
if (req.method === 'GET' && req.url === '/api/health') {
|
|
379
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
380
|
-
res.end(JSON.stringify({ status: 'ok', version: '0.0.1' }));
|
|
381
|
-
return;
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
res.writeHead(404);
|
|
385
|
-
res.end('Not Found');
|
|
386
|
-
});
|
|
387
|
-
|
|
388
|
-
this.httpServer.on('error', (err: NodeJS.ErrnoException) => {
|
|
389
|
-
if (err.code === 'EADDRINUSE') {
|
|
390
|
-
console.error(`[MCP] Port ${this.port} is already in use`);
|
|
391
|
-
reject(err);
|
|
392
|
-
} else {
|
|
393
|
-
reject(err);
|
|
394
|
-
}
|
|
395
|
-
});
|
|
396
|
-
|
|
397
|
-
this.httpServer.listen(this.port, '127.0.0.1', () => {
|
|
398
|
-
console.error(`[MCP] HTTP Server listening on http://127.0.0.1:${this.port}`);
|
|
399
|
-
resolve();
|
|
400
|
-
});
|
|
401
|
-
});
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
/**
|
|
405
|
-
* 启动服务器
|
|
406
|
-
*/
|
|
407
|
-
async start(): Promise<void> {
|
|
408
|
-
if (this.isRunning) {
|
|
409
|
-
console.error('[MCP] Server is already running');
|
|
410
|
-
return;
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
try {
|
|
414
|
-
// 启动 HTTP 服务器
|
|
415
|
-
await this.startHttpServer();
|
|
416
|
-
|
|
417
|
-
// 启动 MCP stdio 传输
|
|
418
|
-
const transport = new StdioServerTransport();
|
|
419
|
-
await this.server.connect(transport);
|
|
420
|
-
|
|
421
|
-
this.isRunning = true;
|
|
422
|
-
console.error('[MCP] MCP Server started successfully');
|
|
423
|
-
} catch (error) {
|
|
424
|
-
console.error('[MCP] Failed to start server:', error);
|
|
425
|
-
throw error;
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
/**
|
|
430
|
-
* 停止服务器
|
|
431
|
-
*/
|
|
432
|
-
stop(): void {
|
|
433
|
-
if (!this.isRunning) {
|
|
434
|
-
return;
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
// 关闭 HTTP 服务器
|
|
438
|
-
if (this.httpServer) {
|
|
439
|
-
this.httpServer.close();
|
|
440
|
-
this.httpServer = null;
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
// 清理待处理的请求
|
|
444
|
-
for (const [, pending] of this.pendingRequests) {
|
|
445
|
-
clearTimeout(pending.timeout);
|
|
446
|
-
pending.resolve(null);
|
|
447
|
-
}
|
|
448
|
-
this.pendingRequests.clear();
|
|
449
|
-
|
|
450
|
-
// 关闭 MCP 服务器
|
|
451
|
-
this.server.close();
|
|
452
|
-
this.isRunning = false;
|
|
453
|
-
console.error('[MCP] Server stopped');
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
/**
|
|
458
|
-
* 独立运行入口
|
|
459
|
-
*/
|
|
460
|
-
async function main() {
|
|
461
|
-
const port = parseInt(process.env.MCP_PORT || '8766', 10);
|
|
462
|
-
const server = new McpServer(port);
|
|
463
|
-
|
|
464
|
-
process.on('SIGINT', () => {
|
|
465
|
-
server.stop();
|
|
466
|
-
process.exit(0);
|
|
467
|
-
});
|
|
468
|
-
|
|
469
|
-
process.on('SIGTERM', () => {
|
|
470
|
-
server.stop();
|
|
471
|
-
process.exit(0);
|
|
472
|
-
});
|
|
473
|
-
|
|
474
|
-
await server.start();
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
// 如果直接运行此文件
|
|
478
|
-
if (require.main === module) {
|
|
479
|
-
main().catch(console.error);
|
|
480
|
-
}
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
import * as vscode from 'vscode';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* 独立的反馈面板(用于在编辑器区域显示)
|
|
5
|
-
*/
|
|
6
|
-
export class FeedbackPanel {
|
|
7
|
-
public static currentPanel: FeedbackPanel | undefined;
|
|
8
|
-
public static readonly viewType = 'cursorFeedback';
|
|
9
|
-
|
|
10
|
-
private readonly _panel: vscode.WebviewPanel;
|
|
11
|
-
private readonly _extensionUri: vscode.Uri;
|
|
12
|
-
private _disposables: vscode.Disposable[] = [];
|
|
13
|
-
|
|
14
|
-
public static createOrShow(extensionUri: vscode.Uri) {
|
|
15
|
-
const column = vscode.window.activeTextEditor
|
|
16
|
-
? vscode.window.activeTextEditor.viewColumn
|
|
17
|
-
: undefined;
|
|
18
|
-
|
|
19
|
-
// 如果面板已存在,显示它
|
|
20
|
-
if (FeedbackPanel.currentPanel) {
|
|
21
|
-
FeedbackPanel.currentPanel._panel.reveal(column);
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// 创建新面板
|
|
26
|
-
const panel = vscode.window.createWebviewPanel(
|
|
27
|
-
FeedbackPanel.viewType,
|
|
28
|
-
'Cursor Feedback',
|
|
29
|
-
column || vscode.ViewColumn.One,
|
|
30
|
-
{
|
|
31
|
-
enableScripts: true,
|
|
32
|
-
localResourceRoots: [extensionUri],
|
|
33
|
-
retainContextWhenHidden: true,
|
|
34
|
-
}
|
|
35
|
-
);
|
|
36
|
-
|
|
37
|
-
FeedbackPanel.currentPanel = new FeedbackPanel(panel, extensionUri);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
private constructor(panel: vscode.WebviewPanel, extensionUri: vscode.Uri) {
|
|
41
|
-
this._panel = panel;
|
|
42
|
-
this._extensionUri = extensionUri;
|
|
43
|
-
|
|
44
|
-
// 设置 HTML 内容
|
|
45
|
-
this._update();
|
|
46
|
-
|
|
47
|
-
// 监听面板关闭
|
|
48
|
-
this._panel.onDidDispose(() => this.dispose(), null, this._disposables);
|
|
49
|
-
|
|
50
|
-
// 监听面板状态变化
|
|
51
|
-
this._panel.onDidChangeViewState(
|
|
52
|
-
() => {
|
|
53
|
-
if (this._panel.visible) {
|
|
54
|
-
this._update();
|
|
55
|
-
}
|
|
56
|
-
},
|
|
57
|
-
null,
|
|
58
|
-
this._disposables
|
|
59
|
-
);
|
|
60
|
-
|
|
61
|
-
// 处理来自 webview 的消息
|
|
62
|
-
this._panel.webview.onDidReceiveMessage(
|
|
63
|
-
(message) => {
|
|
64
|
-
switch (message.type) {
|
|
65
|
-
case 'submitFeedback':
|
|
66
|
-
vscode.window.showInformationMessage(
|
|
67
|
-
`Feedback received: ${message.payload.interactive_feedback}`
|
|
68
|
-
);
|
|
69
|
-
break;
|
|
70
|
-
}
|
|
71
|
-
},
|
|
72
|
-
null,
|
|
73
|
-
this._disposables
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
public dispose() {
|
|
78
|
-
FeedbackPanel.currentPanel = undefined;
|
|
79
|
-
|
|
80
|
-
this._panel.dispose();
|
|
81
|
-
|
|
82
|
-
while (this._disposables.length) {
|
|
83
|
-
const x = this._disposables.pop();
|
|
84
|
-
if (x) {
|
|
85
|
-
x.dispose();
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
private _update() {
|
|
91
|
-
const webview = this._panel.webview;
|
|
92
|
-
this._panel.title = 'Cursor Feedback';
|
|
93
|
-
this._panel.webview.html = this._getHtmlForWebview(webview);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
private _getHtmlForWebview(webview: vscode.Webview): string {
|
|
97
|
-
return `<!DOCTYPE html>
|
|
98
|
-
<html lang="zh-CN">
|
|
99
|
-
<head>
|
|
100
|
-
<meta charset="UTF-8">
|
|
101
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
102
|
-
<title>Cursor Feedback</title>
|
|
103
|
-
<style>
|
|
104
|
-
body {
|
|
105
|
-
font-family: var(--vscode-font-family);
|
|
106
|
-
padding: 20px;
|
|
107
|
-
color: var(--vscode-foreground);
|
|
108
|
-
background-color: var(--vscode-editor-background);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
h1 {
|
|
112
|
-
color: var(--vscode-foreground);
|
|
113
|
-
border-bottom: 1px solid var(--vscode-panel-border);
|
|
114
|
-
padding-bottom: 10px;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
.info {
|
|
118
|
-
background: var(--vscode-textBlockQuote-background);
|
|
119
|
-
padding: 15px;
|
|
120
|
-
border-radius: 4px;
|
|
121
|
-
margin: 20px 0;
|
|
122
|
-
}
|
|
123
|
-
</style>
|
|
124
|
-
</head>
|
|
125
|
-
<body>
|
|
126
|
-
<h1>Cursor Feedback Panel</h1>
|
|
127
|
-
<div class="info">
|
|
128
|
-
<p>此面板用于显示独立的反馈界面。</p>
|
|
129
|
-
<p>通常情况下,请使用侧边栏中的反馈面板进行交互。</p>
|
|
130
|
-
</div>
|
|
131
|
-
</body>
|
|
132
|
-
</html>`;
|
|
133
|
-
}
|
|
134
|
-
}
|