cursor-feedback 1.2.0 → 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 +7 -0
- package/README.md +40 -7
- package/README_CN.md +40 -7
- package/dist/extension.js +480 -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 +29 -2
- 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 -759
- 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/webview/index.html
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="{{LANG}}">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<meta http-equiv="Content-Security-Policy" content="{{CSP}}">
|
|
7
|
-
<title>Cursor Feedback</title>
|
|
8
|
-
<script src="{{MARKED_JS_URI}}"></script>
|
|
9
|
-
<link rel="stylesheet" href="{{STYLES_CSS_URI}}">
|
|
10
|
-
</head>
|
|
11
|
-
<body>
|
|
12
|
-
<div class="container">
|
|
13
|
-
<!-- 服务器状态 -->
|
|
14
|
-
<div id="serverStatus" class="server-status">
|
|
15
|
-
<span class="dot"></span>
|
|
16
|
-
<span id="serverStatusText">{{i18n.checkingConnection}}</span>
|
|
17
|
-
<button id="langSwitchBtn" class="lang-switch-btn" title="Switch Language / 切换语言">🌐</button>
|
|
18
|
-
<span id="debugIcon" class="debug-icon">🔍</span>
|
|
19
|
-
<div id="debugTooltip" class="debug-tooltip"></div>
|
|
20
|
-
</div>
|
|
21
|
-
|
|
22
|
-
<!-- 等待状态 -->
|
|
23
|
-
<div id="waitingStatus" class="status waiting">
|
|
24
|
-
<div class="status-icon">⏳</div>
|
|
25
|
-
<p>{{i18n.waitingForAI}}</p>
|
|
26
|
-
<p style="font-size: 11px; margin-top: 10px; opacity: 0.8;">{{i18n.waitingHint}}</p>
|
|
27
|
-
</div>
|
|
28
|
-
|
|
29
|
-
<!-- 反馈表单 -->
|
|
30
|
-
<div id="feedbackForm" class="hidden">
|
|
31
|
-
<!-- AI 摘要 -->
|
|
32
|
-
<div class="section">
|
|
33
|
-
<div class="section-title">📋 {{i18n.aiSummary}}</div>
|
|
34
|
-
<div id="summaryContent" class="summary-content"></div>
|
|
35
|
-
<div id="projectInfo" class="project-info"></div>
|
|
36
|
-
</div>
|
|
37
|
-
|
|
38
|
-
<!-- 反馈输入 -->
|
|
39
|
-
<div class="section">
|
|
40
|
-
<div class="section-title">💬 {{i18n.yourFeedback}}</div>
|
|
41
|
-
<textarea id="feedbackInput" class="feedback-input" placeholder="{{i18n.feedbackPlaceholder}}"></textarea>
|
|
42
|
-
|
|
43
|
-
<!-- 附件区域 -->
|
|
44
|
-
<div class="attachments-area">
|
|
45
|
-
<div class="attachment-buttons">
|
|
46
|
-
<button id="uploadBtn" class="attachment-btn" data-tooltip="{{i18n.uploadImage}}">🖼️</button>
|
|
47
|
-
<button id="selectPathBtn" class="attachment-btn" data-tooltip="{{i18n.selectFilesOrFolders}}">📁</button>
|
|
48
|
-
</div>
|
|
49
|
-
<input type="file" id="imageInput" accept="image/*" multiple class="hidden">
|
|
50
|
-
<div id="imagePreview" class="image-preview"></div>
|
|
51
|
-
<div id="fileList" class="file-list"></div>
|
|
52
|
-
</div>
|
|
53
|
-
|
|
54
|
-
<div id="timeoutInfo" class="timeout-info"></div>
|
|
55
|
-
</div>
|
|
56
|
-
|
|
57
|
-
<!-- 提交按钮组 -->
|
|
58
|
-
<div class="submit-group">
|
|
59
|
-
<button id="submitBtn" class="submit-btn">{{i18n.ctrlEnterSubmitMode}}</button>
|
|
60
|
-
<button id="toggleKeyModeBtn" class="toggle-key-mode-btn" title="{{i18n.toggleKeyMode}}">⌨️</button>
|
|
61
|
-
</div>
|
|
62
|
-
</div>
|
|
63
|
-
</div>
|
|
64
|
-
|
|
65
|
-
<!-- 注入 i18n 数据 -->
|
|
66
|
-
<script>window.i18n = {{I18N_JSON}};</script>
|
|
67
|
-
<script src="{{SCRIPT_JS_URI}}"></script>
|
|
68
|
-
</body>
|
|
69
|
-
</html>
|
package/src/webview/script.js
DELETED
|
@@ -1,318 +0,0 @@
|
|
|
1
|
-
// WebView 脚本
|
|
2
|
-
(function() {
|
|
3
|
-
const vscode = acquireVsCodeApi();
|
|
4
|
-
const i18n = window.i18n || {};
|
|
5
|
-
|
|
6
|
-
// 恢复之前保存的文本
|
|
7
|
-
const previousState = vscode.getState();
|
|
8
|
-
|
|
9
|
-
// Markdown 渲染
|
|
10
|
-
function renderMarkdown(text) {
|
|
11
|
-
if (!text) return '';
|
|
12
|
-
try {
|
|
13
|
-
if (typeof marked !== 'undefined') {
|
|
14
|
-
marked.setOptions({ breaks: true, gfm: true, headerIds: false });
|
|
15
|
-
return marked.parse(text);
|
|
16
|
-
}
|
|
17
|
-
} catch (e) {
|
|
18
|
-
console.error('Markdown rendering error:', e);
|
|
19
|
-
}
|
|
20
|
-
return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\n/g, '<br>');
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// DOM 元素
|
|
24
|
-
const serverStatus = document.getElementById('serverStatus');
|
|
25
|
-
const serverStatusText = document.getElementById('serverStatusText');
|
|
26
|
-
const debugTooltip = document.getElementById('debugTooltip');
|
|
27
|
-
const langSwitchBtn = document.getElementById('langSwitchBtn');
|
|
28
|
-
const waitingStatus = document.getElementById('waitingStatus');
|
|
29
|
-
const feedbackForm = document.getElementById('feedbackForm');
|
|
30
|
-
const summaryContent = document.getElementById('summaryContent');
|
|
31
|
-
const projectInfo = document.getElementById('projectInfo');
|
|
32
|
-
const feedbackInput = document.getElementById('feedbackInput');
|
|
33
|
-
const submitBtn = document.getElementById('submitBtn');
|
|
34
|
-
const uploadBtn = document.getElementById('uploadBtn');
|
|
35
|
-
const selectPathBtn = document.getElementById('selectPathBtn');
|
|
36
|
-
const imageInput = document.getElementById('imageInput');
|
|
37
|
-
const imagePreview = document.getElementById('imagePreview');
|
|
38
|
-
const fileList = document.getElementById('fileList');
|
|
39
|
-
const timeoutInfo = document.getElementById('timeoutInfo');
|
|
40
|
-
const toggleKeyModeBtn = document.getElementById('toggleKeyModeBtn');
|
|
41
|
-
|
|
42
|
-
// 语言切换按钮
|
|
43
|
-
langSwitchBtn.addEventListener('click', () => {
|
|
44
|
-
vscode.postMessage({ type: 'switchLanguage' });
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
let uploadedImages = [];
|
|
48
|
-
let attachedFiles = [];
|
|
49
|
-
let currentRequestId = '';
|
|
50
|
-
let currentProjectDir = '';
|
|
51
|
-
let requestTimestamp = 0;
|
|
52
|
-
let requestTimeout = 300;
|
|
53
|
-
let countdownInterval = null;
|
|
54
|
-
|
|
55
|
-
// 快捷键模式:false = Ctrl+Enter 提交(默认),true = Enter 提交
|
|
56
|
-
let enterToSubmit = localStorage.getItem('cursorFeedback_enterToSubmit') === 'true';
|
|
57
|
-
|
|
58
|
-
// 更新快捷键模式 UI
|
|
59
|
-
function updateKeyModeUI() {
|
|
60
|
-
if (enterToSubmit) {
|
|
61
|
-
submitBtn.textContent = i18n.enterSubmitMode || 'Enter to submit · Shift+Enter for newline';
|
|
62
|
-
toggleKeyModeBtn.classList.add('enter-mode');
|
|
63
|
-
toggleKeyModeBtn.title = i18n.switchToCtrlEnter || 'Click to switch to Ctrl+Enter submit';
|
|
64
|
-
} else {
|
|
65
|
-
submitBtn.textContent = i18n.ctrlEnterSubmitMode || 'Ctrl+Enter to submit · Enter for newline';
|
|
66
|
-
toggleKeyModeBtn.classList.remove('enter-mode');
|
|
67
|
-
toggleKeyModeBtn.title = i18n.switchToEnter || 'Click to switch to Enter submit';
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// 初始化快捷键模式 UI
|
|
72
|
-
updateKeyModeUI();
|
|
73
|
-
|
|
74
|
-
// 切换快捷键模式
|
|
75
|
-
toggleKeyModeBtn.addEventListener('click', () => {
|
|
76
|
-
enterToSubmit = !enterToSubmit;
|
|
77
|
-
localStorage.setItem('cursorFeedback_enterToSubmit', enterToSubmit.toString());
|
|
78
|
-
updateKeyModeUI();
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
// 输入法组合状态(用于中文等输入法兼容)
|
|
82
|
-
let isComposing = false;
|
|
83
|
-
feedbackInput.addEventListener('compositionstart', () => {
|
|
84
|
-
isComposing = true;
|
|
85
|
-
});
|
|
86
|
-
feedbackInput.addEventListener('compositionend', () => {
|
|
87
|
-
isComposing = false;
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
// 恢复输入框文本
|
|
91
|
-
if (previousState?.text) {
|
|
92
|
-
feedbackInput.value = previousState.text;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// 输入时保存文本
|
|
96
|
-
feedbackInput.addEventListener('input', () => {
|
|
97
|
-
vscode.setState({ text: feedbackInput.value });
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
// 图片上传
|
|
101
|
-
uploadBtn.addEventListener('click', () => imageInput.click());
|
|
102
|
-
selectPathBtn.addEventListener('click', () => vscode.postMessage({ type: 'selectPath' }));
|
|
103
|
-
|
|
104
|
-
// 添加已选文件到列表
|
|
105
|
-
function addAttachedFile(path) {
|
|
106
|
-
if (attachedFiles.includes(path)) return;
|
|
107
|
-
attachedFiles.push(path);
|
|
108
|
-
|
|
109
|
-
const item = document.createElement('div');
|
|
110
|
-
item.className = 'file-item';
|
|
111
|
-
|
|
112
|
-
const icon = document.createElement('span');
|
|
113
|
-
icon.className = 'file-icon';
|
|
114
|
-
icon.textContent = (path.endsWith('/') || !path.split('/').pop().includes('.')) ? '📁' : '📄';
|
|
115
|
-
|
|
116
|
-
const pathSpan = document.createElement('span');
|
|
117
|
-
pathSpan.className = 'file-path';
|
|
118
|
-
pathSpan.textContent = path;
|
|
119
|
-
pathSpan.title = path;
|
|
120
|
-
|
|
121
|
-
const removeBtn = document.createElement('button');
|
|
122
|
-
removeBtn.className = 'file-remove';
|
|
123
|
-
removeBtn.textContent = '×';
|
|
124
|
-
removeBtn.onclick = () => {
|
|
125
|
-
const idx = attachedFiles.indexOf(path);
|
|
126
|
-
if (idx > -1) attachedFiles.splice(idx, 1);
|
|
127
|
-
item.remove();
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
item.appendChild(icon);
|
|
131
|
-
item.appendChild(pathSpan);
|
|
132
|
-
item.appendChild(removeBtn);
|
|
133
|
-
fileList.appendChild(item);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
imageInput.addEventListener('change', (e) => {
|
|
137
|
-
for (const file of e.target.files) addImageFile(file);
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
// 添加图片到预览
|
|
141
|
-
function addImageFile(file) {
|
|
142
|
-
const reader = new FileReader();
|
|
143
|
-
reader.onload = (e) => {
|
|
144
|
-
const base64 = e.target.result;
|
|
145
|
-
const imgData = {
|
|
146
|
-
name: file.name || ('pasted-image-' + Date.now() + '.png'),
|
|
147
|
-
data: base64.split(',')[1],
|
|
148
|
-
size: file.size
|
|
149
|
-
};
|
|
150
|
-
uploadedImages.push(imgData);
|
|
151
|
-
|
|
152
|
-
const container = document.createElement('div');
|
|
153
|
-
container.className = 'image-preview-item';
|
|
154
|
-
|
|
155
|
-
const img = document.createElement('img');
|
|
156
|
-
img.src = base64;
|
|
157
|
-
|
|
158
|
-
const removeBtn = document.createElement('button');
|
|
159
|
-
removeBtn.className = 'image-remove';
|
|
160
|
-
removeBtn.textContent = '×';
|
|
161
|
-
removeBtn.onclick = () => {
|
|
162
|
-
const index = uploadedImages.indexOf(imgData);
|
|
163
|
-
if (index > -1) uploadedImages.splice(index, 1);
|
|
164
|
-
container.remove();
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
container.appendChild(img);
|
|
168
|
-
container.appendChild(removeBtn);
|
|
169
|
-
imagePreview.appendChild(container);
|
|
170
|
-
};
|
|
171
|
-
reader.readAsDataURL(file);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// 粘贴图片支持
|
|
175
|
-
document.addEventListener('paste', (e) => {
|
|
176
|
-
const items = e.clipboardData?.items;
|
|
177
|
-
if (!items) return;
|
|
178
|
-
for (const item of items) {
|
|
179
|
-
if (item.type.startsWith('image/')) {
|
|
180
|
-
e.preventDefault();
|
|
181
|
-
const file = item.getAsFile();
|
|
182
|
-
if (file) addImageFile(file);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
// 更新倒计时
|
|
188
|
-
function updateCountdown() {
|
|
189
|
-
if (!requestTimestamp || !requestTimeout) return;
|
|
190
|
-
const elapsed = Math.floor((Date.now() - requestTimestamp) / 1000);
|
|
191
|
-
const remaining = Math.max(0, requestTimeout - elapsed);
|
|
192
|
-
const minutes = Math.floor(remaining / 60);
|
|
193
|
-
const seconds = remaining % 60;
|
|
194
|
-
const remainingLabel = i18n.remainingTime || 'Remaining time';
|
|
195
|
-
timeoutInfo.textContent = remainingLabel + ': ' + minutes + ':' + seconds.toString().padStart(2, '0');
|
|
196
|
-
if (remaining <= 0) {
|
|
197
|
-
clearInterval(countdownInterval);
|
|
198
|
-
timeoutInfo.textContent = i18n.timeout || 'Timeout';
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// 提交反馈
|
|
203
|
-
function submitFeedback() {
|
|
204
|
-
if (!currentRequestId) return;
|
|
205
|
-
|
|
206
|
-
vscode.postMessage({
|
|
207
|
-
type: 'submitFeedback',
|
|
208
|
-
payload: {
|
|
209
|
-
requestId: currentRequestId,
|
|
210
|
-
interactive_feedback: feedbackInput.value.trim(),
|
|
211
|
-
images: uploadedImages,
|
|
212
|
-
attachedFiles: attachedFiles,
|
|
213
|
-
project_directory: currentProjectDir
|
|
214
|
-
}
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
// 重置表单
|
|
218
|
-
feedbackInput.value = '';
|
|
219
|
-
uploadedImages = [];
|
|
220
|
-
attachedFiles = [];
|
|
221
|
-
imagePreview.innerHTML = '';
|
|
222
|
-
fileList.innerHTML = '';
|
|
223
|
-
currentRequestId = '';
|
|
224
|
-
vscode.setState({}); // 清除保存的文本
|
|
225
|
-
|
|
226
|
-
if (countdownInterval) {
|
|
227
|
-
clearInterval(countdownInterval);
|
|
228
|
-
countdownInterval = null;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
submitBtn.addEventListener('click', submitFeedback);
|
|
233
|
-
feedbackInput.addEventListener('keydown', (e) => {
|
|
234
|
-
// 如果正在使用输入法(如中文输入),不触发提交
|
|
235
|
-
if (isComposing || e.isComposing) return;
|
|
236
|
-
|
|
237
|
-
if (e.key === 'Enter') {
|
|
238
|
-
if (enterToSubmit) {
|
|
239
|
-
// Enter 提交模式:Enter 提交,Shift+Enter 换行
|
|
240
|
-
if (!e.shiftKey) {
|
|
241
|
-
e.preventDefault();
|
|
242
|
-
submitFeedback();
|
|
243
|
-
}
|
|
244
|
-
} else {
|
|
245
|
-
// Ctrl+Enter 提交模式:Ctrl+Enter 提交,Enter 换行
|
|
246
|
-
if (e.ctrlKey || e.metaKey) {
|
|
247
|
-
e.preventDefault();
|
|
248
|
-
submitFeedback();
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
// 接收消息
|
|
255
|
-
window.addEventListener('message', event => {
|
|
256
|
-
const message = event.data;
|
|
257
|
-
|
|
258
|
-
switch (message.type) {
|
|
259
|
-
case 'showFeedbackRequest':
|
|
260
|
-
waitingStatus.classList.add('hidden');
|
|
261
|
-
feedbackForm.classList.remove('hidden');
|
|
262
|
-
currentRequestId = message.payload.requestId;
|
|
263
|
-
currentProjectDir = message.payload.projectDir;
|
|
264
|
-
requestTimestamp = message.payload.timestamp;
|
|
265
|
-
requestTimeout = message.payload.timeout;
|
|
266
|
-
summaryContent.innerHTML = renderMarkdown(message.payload.summary);
|
|
267
|
-
summaryContent.scrollTop = 0;
|
|
268
|
-
projectInfo.textContent = '📁 ' + message.payload.projectDir;
|
|
269
|
-
feedbackInput.focus();
|
|
270
|
-
if (countdownInterval) clearInterval(countdownInterval);
|
|
271
|
-
updateCountdown();
|
|
272
|
-
countdownInterval = setInterval(updateCountdown, 1000);
|
|
273
|
-
break;
|
|
274
|
-
|
|
275
|
-
case 'showWaiting':
|
|
276
|
-
feedbackForm.classList.add('hidden');
|
|
277
|
-
waitingStatus.classList.remove('hidden');
|
|
278
|
-
if (countdownInterval) {
|
|
279
|
-
clearInterval(countdownInterval);
|
|
280
|
-
countdownInterval = null;
|
|
281
|
-
}
|
|
282
|
-
break;
|
|
283
|
-
|
|
284
|
-
case 'serverStatus':
|
|
285
|
-
if (message.payload.connected) {
|
|
286
|
-
serverStatus.classList.add('connected');
|
|
287
|
-
serverStatusText.textContent = i18n.mcpServerConnected || 'MCP Server connected';
|
|
288
|
-
} else {
|
|
289
|
-
serverStatus.classList.remove('connected');
|
|
290
|
-
serverStatusText.textContent = i18n.mcpServerDisconnected || 'MCP Server disconnected';
|
|
291
|
-
}
|
|
292
|
-
break;
|
|
293
|
-
|
|
294
|
-
case 'updateDebugInfo':
|
|
295
|
-
const d = message.payload;
|
|
296
|
-
const debugLabel = i18n.debugInfo || 'Debug Info';
|
|
297
|
-
const scanPortLabel = i18n.scanPort || 'Scan port';
|
|
298
|
-
const workspaceLabel = i18n.workspace || 'Workspace';
|
|
299
|
-
const currentPortLabel = i18n.currentPort || 'Current port';
|
|
300
|
-
const connectedLabel = i18n.connected || 'Connected';
|
|
301
|
-
const noneLabel = i18n.none || 'None';
|
|
302
|
-
const statusLabel = i18n.status || 'Status';
|
|
303
|
-
debugTooltip.textContent = `🔍 ${debugLabel}\n━━━━━━━━━━━━\n${scanPortLabel}: ${d.portRange}\n${workspaceLabel}: ${d.workspacePath}\n${currentPortLabel}: ${d.activePort || '-'}\n${connectedLabel}: ${d.connectedPorts.length > 0 ? d.connectedPorts.join(', ') : noneLabel}\n${statusLabel}: ${d.lastStatus}`;
|
|
304
|
-
break;
|
|
305
|
-
|
|
306
|
-
case 'filesSelected':
|
|
307
|
-
if (message.payload.paths) {
|
|
308
|
-
for (const path of message.payload.paths) addAttachedFile(path);
|
|
309
|
-
}
|
|
310
|
-
break;
|
|
311
|
-
}
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
// 定期检查服务器状态
|
|
315
|
-
setInterval(() => vscode.postMessage({ type: 'checkServer' }), 5000);
|
|
316
|
-
vscode.postMessage({ type: 'ready' });
|
|
317
|
-
vscode.postMessage({ type: 'checkServer' });
|
|
318
|
-
})();
|