cursor-feedback 1.0.6 → 1.1.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/.github/workflows/release-please.yml +19 -0
- package/.husky/pre-commit +2 -0
- package/.husky/pre-push +1 -0
- package/.versionrc.json +15 -0
- package/CHANGELOG.md +117 -0
- package/README.md +121 -102
- package/README_CN.md +268 -0
- package/demo.gif +0 -0
- package/dist/extension.js +62 -9
- package/dist/extension.js.map +1 -1
- package/dist/i18n/en.json +34 -0
- package/dist/i18n/index.js +127 -0
- package/dist/i18n/index.js.map +1 -0
- package/dist/i18n/zh-CN.json +34 -0
- package/dist/webview/index.html +14 -11
- package/dist/webview/script.js +24 -9
- package/dist/webview/styles.css +17 -1
- package/icon.png +0 -0
- package/package.json +47 -5
- package/scripts/check-changelog.js +42 -0
- package/src/extension.ts +77 -9
- package/src/i18n/en.json +34 -0
- package/src/i18n/index.ts +131 -0
- package/src/i18n/zh-CN.json +34 -0
- package/src/webview/index.html +14 -11
- package/src/webview/script.js +24 -9
- package/src/webview/styles.css +17 -1
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"checkingConnection": "检查连接...",
|
|
3
|
+
"waitingForAI": "等待 AI 请求反馈...",
|
|
4
|
+
"waitingHint": "当 AI 需要您的反馈时,这里会显示输入界面",
|
|
5
|
+
"aiSummary": "AI 工作摘要",
|
|
6
|
+
"yourFeedback": "您的反馈",
|
|
7
|
+
"feedbackPlaceholder": "请输入您的反馈...",
|
|
8
|
+
"uploadImage": "上传图片",
|
|
9
|
+
"selectFilesOrFolders": "选择文件/文件夹",
|
|
10
|
+
"submitFeedback": "提交反馈",
|
|
11
|
+
"toggleKeyMode": "切换快捷键模式",
|
|
12
|
+
"remainingTime": "剩余时间",
|
|
13
|
+
"timeout": "已超时",
|
|
14
|
+
"enterSubmitMode": "Enter 提交 · Shift+Enter 换行",
|
|
15
|
+
"ctrlEnterSubmitMode": "Ctrl+Enter 提交 · Enter 换行",
|
|
16
|
+
"switchToCtrlEnter": "点击切换为 Ctrl+Enter 提交",
|
|
17
|
+
"switchToEnter": "点击切换为 Enter 提交",
|
|
18
|
+
"mcpServerConnected": "MCP Server 已连接",
|
|
19
|
+
"mcpServerDisconnected": "MCP Server 未连接",
|
|
20
|
+
"debugInfo": "调试信息",
|
|
21
|
+
"scanPort": "扫描端口",
|
|
22
|
+
"workspace": "工作区",
|
|
23
|
+
"currentPort": "当前端口",
|
|
24
|
+
"connected": "已连接",
|
|
25
|
+
"none": "无",
|
|
26
|
+
"status": "状态",
|
|
27
|
+
"startListening": "开始监听 MCP 反馈请求",
|
|
28
|
+
"stopListening": "已停止监听",
|
|
29
|
+
"aiWaitingFeedback": "AI 正在等待您的反馈",
|
|
30
|
+
"feedbackSubmitted": "反馈已提交",
|
|
31
|
+
"submitFailed": "提交失败",
|
|
32
|
+
"cannotConnectMCP": "无法连接到 MCP Server",
|
|
33
|
+
"select": "选择"
|
|
34
|
+
}
|
package/dist/webview/index.html
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
|
-
<html lang="
|
|
2
|
+
<html lang="{{LANG}}">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
<!-- 服务器状态 -->
|
|
14
14
|
<div id="serverStatus" class="server-status">
|
|
15
15
|
<span class="dot"></span>
|
|
16
|
-
<span id="serverStatusText"
|
|
16
|
+
<span id="serverStatusText">{{i18n.checkingConnection}}</span>
|
|
17
|
+
<button id="langSwitchBtn" class="lang-switch-btn" title="Switch Language / 切换语言">🌐</button>
|
|
17
18
|
<span id="debugIcon" class="debug-icon">🔍</span>
|
|
18
19
|
<div id="debugTooltip" class="debug-tooltip"></div>
|
|
19
20
|
</div>
|
|
@@ -21,29 +22,29 @@
|
|
|
21
22
|
<!-- 等待状态 -->
|
|
22
23
|
<div id="waitingStatus" class="status waiting">
|
|
23
24
|
<div class="status-icon">⏳</div>
|
|
24
|
-
<p
|
|
25
|
-
<p style="font-size: 11px; margin-top: 10px; opacity: 0.8;"
|
|
25
|
+
<p>{{i18n.waitingForAI}}</p>
|
|
26
|
+
<p style="font-size: 11px; margin-top: 10px; opacity: 0.8;">{{i18n.waitingHint}}</p>
|
|
26
27
|
</div>
|
|
27
28
|
|
|
28
29
|
<!-- 反馈表单 -->
|
|
29
30
|
<div id="feedbackForm" class="hidden">
|
|
30
31
|
<!-- AI 摘要 -->
|
|
31
32
|
<div class="section">
|
|
32
|
-
<div class="section-title">📋
|
|
33
|
+
<div class="section-title">📋 {{i18n.aiSummary}}</div>
|
|
33
34
|
<div id="summaryContent" class="summary-content"></div>
|
|
34
35
|
<div id="projectInfo" class="project-info"></div>
|
|
35
36
|
</div>
|
|
36
37
|
|
|
37
38
|
<!-- 反馈输入 -->
|
|
38
39
|
<div class="section">
|
|
39
|
-
<div class="section-title">💬
|
|
40
|
-
<textarea id="feedbackInput" class="feedback-input" placeholder="
|
|
40
|
+
<div class="section-title">💬 {{i18n.yourFeedback}}</div>
|
|
41
|
+
<textarea id="feedbackInput" class="feedback-input" placeholder="{{i18n.feedbackPlaceholder}}"></textarea>
|
|
41
42
|
|
|
42
43
|
<!-- 附件区域 -->
|
|
43
44
|
<div class="attachments-area">
|
|
44
45
|
<div class="attachment-buttons">
|
|
45
|
-
<button id="uploadBtn" class="attachment-btn" data-tooltip="
|
|
46
|
-
<button id="selectPathBtn" class="attachment-btn" data-tooltip="
|
|
46
|
+
<button id="uploadBtn" class="attachment-btn" data-tooltip="{{i18n.uploadImage}}">🖼️</button>
|
|
47
|
+
<button id="selectPathBtn" class="attachment-btn" data-tooltip="{{i18n.selectFilesOrFolders}}">📁</button>
|
|
47
48
|
</div>
|
|
48
49
|
<input type="file" id="imageInput" accept="image/*" multiple class="hidden">
|
|
49
50
|
<div id="imagePreview" class="image-preview"></div>
|
|
@@ -55,12 +56,14 @@
|
|
|
55
56
|
|
|
56
57
|
<!-- 提交按钮组 -->
|
|
57
58
|
<div class="submit-group">
|
|
58
|
-
<button id="submitBtn" class="submit-btn"
|
|
59
|
-
<button id="toggleKeyModeBtn" class="toggle-key-mode-btn" title="
|
|
59
|
+
<button id="submitBtn" class="submit-btn">{{i18n.ctrlEnterSubmitMode}}</button>
|
|
60
|
+
<button id="toggleKeyModeBtn" class="toggle-key-mode-btn" title="{{i18n.toggleKeyMode}}">⌨️</button>
|
|
60
61
|
</div>
|
|
61
62
|
</div>
|
|
62
63
|
</div>
|
|
63
64
|
|
|
65
|
+
<!-- 注入 i18n 数据 -->
|
|
66
|
+
<script>window.i18n = {{I18N_JSON}};</script>
|
|
64
67
|
<script src="{{SCRIPT_JS_URI}}"></script>
|
|
65
68
|
</body>
|
|
66
69
|
</html>
|
package/dist/webview/script.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// WebView 脚本
|
|
2
2
|
(function() {
|
|
3
3
|
const vscode = acquireVsCodeApi();
|
|
4
|
+
const i18n = window.i18n || {};
|
|
4
5
|
|
|
5
6
|
// 恢复之前保存的文本
|
|
6
7
|
const previousState = vscode.getState();
|
|
@@ -23,6 +24,7 @@
|
|
|
23
24
|
const serverStatus = document.getElementById('serverStatus');
|
|
24
25
|
const serverStatusText = document.getElementById('serverStatusText');
|
|
25
26
|
const debugTooltip = document.getElementById('debugTooltip');
|
|
27
|
+
const langSwitchBtn = document.getElementById('langSwitchBtn');
|
|
26
28
|
const waitingStatus = document.getElementById('waitingStatus');
|
|
27
29
|
const feedbackForm = document.getElementById('feedbackForm');
|
|
28
30
|
const summaryContent = document.getElementById('summaryContent');
|
|
@@ -37,6 +39,11 @@
|
|
|
37
39
|
const timeoutInfo = document.getElementById('timeoutInfo');
|
|
38
40
|
const toggleKeyModeBtn = document.getElementById('toggleKeyModeBtn');
|
|
39
41
|
|
|
42
|
+
// 语言切换按钮
|
|
43
|
+
langSwitchBtn.addEventListener('click', () => {
|
|
44
|
+
vscode.postMessage({ type: 'switchLanguage' });
|
|
45
|
+
});
|
|
46
|
+
|
|
40
47
|
let uploadedImages = [];
|
|
41
48
|
let attachedFiles = [];
|
|
42
49
|
let currentRequestId = '';
|
|
@@ -51,13 +58,13 @@
|
|
|
51
58
|
// 更新快捷键模式 UI
|
|
52
59
|
function updateKeyModeUI() {
|
|
53
60
|
if (enterToSubmit) {
|
|
54
|
-
submitBtn.textContent = 'Enter
|
|
61
|
+
submitBtn.textContent = i18n.enterSubmitMode || 'Enter to submit · Shift+Enter for newline';
|
|
55
62
|
toggleKeyModeBtn.classList.add('enter-mode');
|
|
56
|
-
toggleKeyModeBtn.title = '
|
|
63
|
+
toggleKeyModeBtn.title = i18n.switchToCtrlEnter || 'Click to switch to Ctrl+Enter submit';
|
|
57
64
|
} else {
|
|
58
|
-
submitBtn.textContent = 'Ctrl+Enter
|
|
65
|
+
submitBtn.textContent = i18n.ctrlEnterSubmitMode || 'Ctrl+Enter to submit · Enter for newline';
|
|
59
66
|
toggleKeyModeBtn.classList.remove('enter-mode');
|
|
60
|
-
toggleKeyModeBtn.title = '
|
|
67
|
+
toggleKeyModeBtn.title = i18n.switchToEnter || 'Click to switch to Enter submit';
|
|
61
68
|
}
|
|
62
69
|
}
|
|
63
70
|
|
|
@@ -184,10 +191,11 @@
|
|
|
184
191
|
const remaining = Math.max(0, requestTimeout - elapsed);
|
|
185
192
|
const minutes = Math.floor(remaining / 60);
|
|
186
193
|
const seconds = remaining % 60;
|
|
187
|
-
|
|
194
|
+
const remainingLabel = i18n.remainingTime || 'Remaining time';
|
|
195
|
+
timeoutInfo.textContent = remainingLabel + ': ' + minutes + ':' + seconds.toString().padStart(2, '0');
|
|
188
196
|
if (remaining <= 0) {
|
|
189
197
|
clearInterval(countdownInterval);
|
|
190
|
-
timeoutInfo.textContent = '
|
|
198
|
+
timeoutInfo.textContent = i18n.timeout || 'Timeout';
|
|
191
199
|
}
|
|
192
200
|
}
|
|
193
201
|
|
|
@@ -276,16 +284,23 @@
|
|
|
276
284
|
case 'serverStatus':
|
|
277
285
|
if (message.payload.connected) {
|
|
278
286
|
serverStatus.classList.add('connected');
|
|
279
|
-
serverStatusText.textContent = 'MCP Server
|
|
287
|
+
serverStatusText.textContent = i18n.mcpServerConnected || 'MCP Server connected';
|
|
280
288
|
} else {
|
|
281
289
|
serverStatus.classList.remove('connected');
|
|
282
|
-
serverStatusText.textContent = 'MCP Server
|
|
290
|
+
serverStatusText.textContent = i18n.mcpServerDisconnected || 'MCP Server disconnected';
|
|
283
291
|
}
|
|
284
292
|
break;
|
|
285
293
|
|
|
286
294
|
case 'updateDebugInfo':
|
|
287
295
|
const d = message.payload;
|
|
288
|
-
|
|
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}`;
|
|
289
304
|
break;
|
|
290
305
|
|
|
291
306
|
case 'filesSelected':
|
package/dist/webview/styles.css
CHANGED
|
@@ -224,8 +224,24 @@ body {
|
|
|
224
224
|
background: var(--vscode-notificationsInfoIcon-foreground);
|
|
225
225
|
}
|
|
226
226
|
|
|
227
|
-
.
|
|
227
|
+
.lang-switch-btn {
|
|
228
228
|
margin-left: auto;
|
|
229
|
+
padding: 2px 6px;
|
|
230
|
+
background: transparent;
|
|
231
|
+
border: 1px solid var(--vscode-input-border);
|
|
232
|
+
border-radius: 3px;
|
|
233
|
+
cursor: pointer;
|
|
234
|
+
font-size: 12px;
|
|
235
|
+
opacity: 0.7;
|
|
236
|
+
transition: opacity 0.15s, background 0.15s;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.lang-switch-btn:hover {
|
|
240
|
+
opacity: 1;
|
|
241
|
+
background: var(--vscode-button-secondaryBackground);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.debug-icon {
|
|
229
245
|
cursor: pointer;
|
|
230
246
|
opacity: 0.6;
|
|
231
247
|
font-size: 12px;
|
package/icon.png
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cursor-feedback",
|
|
3
3
|
"displayName": "Cursor Feedback",
|
|
4
|
-
"description": "
|
|
5
|
-
"version": "1.0
|
|
4
|
+
"description": "One Cursor conversation, unlimited AI interactions - Save your monthly request quota! Interactive feedback loop for AI chat via MCP",
|
|
5
|
+
"version": "1.1.0",
|
|
6
6
|
"icon": "icon.png",
|
|
7
7
|
"author": "jianger666",
|
|
8
8
|
"license": "MIT",
|
|
@@ -16,7 +16,20 @@
|
|
|
16
16
|
"vscode",
|
|
17
17
|
"ai",
|
|
18
18
|
"feedback",
|
|
19
|
-
"model-context-protocol"
|
|
19
|
+
"model-context-protocol",
|
|
20
|
+
"claude",
|
|
21
|
+
"copilot",
|
|
22
|
+
"ai-assistant",
|
|
23
|
+
"interactive",
|
|
24
|
+
"sidebar",
|
|
25
|
+
"agent",
|
|
26
|
+
"llm",
|
|
27
|
+
"chatgpt",
|
|
28
|
+
"anthropic",
|
|
29
|
+
"save-quota",
|
|
30
|
+
"unlimited-chat",
|
|
31
|
+
"conversation",
|
|
32
|
+
"human-in-the-loop"
|
|
20
33
|
],
|
|
21
34
|
"publisher": "jianger666",
|
|
22
35
|
"engines": {
|
|
@@ -24,6 +37,7 @@
|
|
|
24
37
|
"node": ">=18.0.0"
|
|
25
38
|
},
|
|
26
39
|
"categories": [
|
|
40
|
+
"Machine Learning",
|
|
27
41
|
"Other"
|
|
28
42
|
],
|
|
29
43
|
"activationEvents": [
|
|
@@ -34,6 +48,26 @@
|
|
|
34
48
|
"cursor-feedback-mcp": "./dist/mcp-server.js"
|
|
35
49
|
},
|
|
36
50
|
"contributes": {
|
|
51
|
+
"configuration": {
|
|
52
|
+
"title": "Cursor Feedback",
|
|
53
|
+
"properties": {
|
|
54
|
+
"cursorFeedback.language": {
|
|
55
|
+
"type": "string",
|
|
56
|
+
"default": "auto",
|
|
57
|
+
"enum": [
|
|
58
|
+
"auto",
|
|
59
|
+
"zh-CN",
|
|
60
|
+
"en"
|
|
61
|
+
],
|
|
62
|
+
"enumDescriptions": [
|
|
63
|
+
"Auto detect based on system language",
|
|
64
|
+
"简体中文",
|
|
65
|
+
"English"
|
|
66
|
+
],
|
|
67
|
+
"description": "Language for the extension UI"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
},
|
|
37
71
|
"commands": [
|
|
38
72
|
{
|
|
39
73
|
"command": "cursorFeedback.showPanel",
|
|
@@ -73,11 +107,16 @@
|
|
|
73
107
|
"scripts": {
|
|
74
108
|
"vscode:prepublish": "npm run compile",
|
|
75
109
|
"compile": "tsc -p ./ && npm run copy-webview",
|
|
76
|
-
"copy-webview": "mkdir -p dist/webview && cp src/webview/* dist/webview/",
|
|
110
|
+
"copy-webview": "mkdir -p dist/webview && cp src/webview/* dist/webview/ && mkdir -p dist/i18n && cp src/i18n/*.json dist/i18n/",
|
|
77
111
|
"watch": "tsc -watch -p ./",
|
|
78
112
|
"lint": "eslint src --ext ts",
|
|
79
113
|
"start:mcp": "node dist/mcp-server.js",
|
|
80
|
-
"prepublishOnly": "npm run compile"
|
|
114
|
+
"prepublishOnly": "npm run check-changelog && npm run compile",
|
|
115
|
+
"check-changelog": "node scripts/check-changelog.js",
|
|
116
|
+
"release": "standard-version",
|
|
117
|
+
"release:minor": "standard-version --release-as minor",
|
|
118
|
+
"release:major": "standard-version --release-as major",
|
|
119
|
+
"prepare": "husky"
|
|
81
120
|
},
|
|
82
121
|
"devDependencies": {
|
|
83
122
|
"@types/node": "^20.10.0",
|
|
@@ -86,6 +125,9 @@
|
|
|
86
125
|
"@typescript-eslint/eslint-plugin": "^6.13.0",
|
|
87
126
|
"@typescript-eslint/parser": "^6.13.0",
|
|
88
127
|
"eslint": "^8.54.0",
|
|
128
|
+
"husky": "^9.1.7",
|
|
129
|
+
"release-please": "^17.2.0",
|
|
130
|
+
"standard-version": "^9.5.0",
|
|
89
131
|
"typescript": "^5.3.0"
|
|
90
132
|
},
|
|
91
133
|
"dependencies": {
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 检查 changelog 是否需要更新
|
|
5
|
+
* 如果 package.json 中的版本号不在 changelog 中,提示运行 npm run release
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
|
|
11
|
+
const packageJsonPath = path.join(__dirname, '..', 'package.json');
|
|
12
|
+
const changelogPath = path.join(__dirname, '..', 'CHANGELOG.md');
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
// 读取 package.json 获取当前版本
|
|
16
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
17
|
+
const currentVersion = packageJson.version;
|
|
18
|
+
|
|
19
|
+
// 读取 CHANGELOG.md
|
|
20
|
+
const changelog = fs.readFileSync(changelogPath, 'utf8');
|
|
21
|
+
|
|
22
|
+
// 检查是否已经包含当前版本
|
|
23
|
+
// 支持两种格式:## [1.1.0] 或 ## 1.1.0
|
|
24
|
+
const escapedVersion = currentVersion.replace(/\./g, '\\.');
|
|
25
|
+
const versionPattern = new RegExp(`## (\\[${escapedVersion}\\]|${escapedVersion}\\s*\\([^)]+\\))`, 'i');
|
|
26
|
+
|
|
27
|
+
if (!versionPattern.test(changelog)) {
|
|
28
|
+
console.error('\n❌ 警告:CHANGELOG.md 中没有找到当前版本 ' + currentVersion);
|
|
29
|
+
console.error('📝 请运行以下命令更新 changelog:');
|
|
30
|
+
console.error(' npm run release # patch 版本');
|
|
31
|
+
console.error(' npm run release:minor # minor 版本');
|
|
32
|
+
console.error(' npm run release:major # major 版本');
|
|
33
|
+
console.error('');
|
|
34
|
+
process.exit(1);
|
|
35
|
+
} else {
|
|
36
|
+
console.log('✓ CHANGELOG.md 已包含版本 ' + currentVersion);
|
|
37
|
+
process.exit(0);
|
|
38
|
+
}
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.error('检查 changelog 时出错:', error.message);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
package/src/extension.ts
CHANGED
|
@@ -2,6 +2,7 @@ import * as vscode from 'vscode';
|
|
|
2
2
|
import * as http from 'http';
|
|
3
3
|
import * as fs from 'fs';
|
|
4
4
|
import * as path from 'path';
|
|
5
|
+
import { loadMessages, getLanguage, I18nMessages } from './i18n';
|
|
5
6
|
|
|
6
7
|
let feedbackViewProvider: FeedbackViewProvider | null = null;
|
|
7
8
|
let pollingInterval: NodeJS.Timeout | null = null;
|
|
@@ -30,7 +31,7 @@ export function activate(context: vscode.ExtensionContext) {
|
|
|
30
31
|
vscode.commands.registerCommand('cursorFeedback.startPolling', () => {
|
|
31
32
|
if (feedbackViewProvider) {
|
|
32
33
|
feedbackViewProvider.startPolling();
|
|
33
|
-
vscode.window.showInformationMessage('
|
|
34
|
+
vscode.window.showInformationMessage(feedbackViewProvider.getMessage('startListening'));
|
|
34
35
|
}
|
|
35
36
|
})
|
|
36
37
|
);
|
|
@@ -40,7 +41,7 @@ export function activate(context: vscode.ExtensionContext) {
|
|
|
40
41
|
vscode.commands.registerCommand('cursorFeedback.stopPolling', () => {
|
|
41
42
|
if (feedbackViewProvider) {
|
|
42
43
|
feedbackViewProvider.stopPolling();
|
|
43
|
-
vscode.window.showInformationMessage('
|
|
44
|
+
vscode.window.showInformationMessage(feedbackViewProvider.getMessage('stopListening'));
|
|
44
45
|
}
|
|
45
46
|
})
|
|
46
47
|
);
|
|
@@ -127,6 +128,7 @@ class FeedbackViewProvider implements vscode.WebviewViewProvider {
|
|
|
127
128
|
private _activePort: number | null = null;
|
|
128
129
|
private _portScanRange = 20; // 扫描端口范围
|
|
129
130
|
private _seenRequestIds: Set<string> = new Set(); // 已处理过的请求 ID
|
|
131
|
+
private _i18n: I18nMessages;
|
|
130
132
|
private _debugInfo: {
|
|
131
133
|
portRange: string;
|
|
132
134
|
workspacePath: string;
|
|
@@ -138,7 +140,7 @@ class FeedbackViewProvider implements vscode.WebviewViewProvider {
|
|
|
138
140
|
workspacePath: '',
|
|
139
141
|
connectedPorts: [],
|
|
140
142
|
activePort: null,
|
|
141
|
-
lastStatus: '
|
|
143
|
+
lastStatus: ''
|
|
142
144
|
};
|
|
143
145
|
|
|
144
146
|
constructor(
|
|
@@ -147,6 +149,15 @@ class FeedbackViewProvider implements vscode.WebviewViewProvider {
|
|
|
147
149
|
) {
|
|
148
150
|
this._basePort = port;
|
|
149
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;
|
|
150
161
|
}
|
|
151
162
|
|
|
152
163
|
public resolveWebviewView(
|
|
@@ -182,6 +193,9 @@ class FeedbackViewProvider implements vscode.WebviewViewProvider {
|
|
|
182
193
|
case 'selectPath':
|
|
183
194
|
await this._handleSelectPath();
|
|
184
195
|
break;
|
|
196
|
+
case 'switchLanguage':
|
|
197
|
+
await this._handleSwitchLanguage();
|
|
198
|
+
break;
|
|
185
199
|
}
|
|
186
200
|
});
|
|
187
201
|
|
|
@@ -409,7 +423,7 @@ class FeedbackViewProvider implements vscode.WebviewViewProvider {
|
|
|
409
423
|
// 只对新鲜请求自动聚焦和通知
|
|
410
424
|
if (isFreshRequest) {
|
|
411
425
|
vscode.commands.executeCommand('cursorFeedback.feedbackView.focus');
|
|
412
|
-
vscode.window.showInformationMessage(
|
|
426
|
+
vscode.window.showInformationMessage(this._i18n.aiWaitingFeedback);
|
|
413
427
|
}
|
|
414
428
|
}
|
|
415
429
|
}
|
|
@@ -511,14 +525,14 @@ class FeedbackViewProvider implements vscode.WebviewViewProvider {
|
|
|
511
525
|
|
|
512
526
|
const result = JSON.parse(response);
|
|
513
527
|
if (result.success) {
|
|
514
|
-
vscode.window.showInformationMessage(
|
|
528
|
+
vscode.window.showInformationMessage(this._i18n.feedbackSubmitted);
|
|
515
529
|
this._currentRequest = null;
|
|
516
530
|
this._showWaitingState();
|
|
517
531
|
} else {
|
|
518
|
-
vscode.window.showErrorMessage('
|
|
532
|
+
vscode.window.showErrorMessage(this._i18n.submitFailed + ': ' + result.error);
|
|
519
533
|
}
|
|
520
534
|
} catch (error) {
|
|
521
|
-
vscode.window.showErrorMessage('
|
|
535
|
+
vscode.window.showErrorMessage(this._i18n.submitFailed + ': ' + this._i18n.cannotConnectMCP);
|
|
522
536
|
}
|
|
523
537
|
}
|
|
524
538
|
|
|
@@ -530,7 +544,7 @@ class FeedbackViewProvider implements vscode.WebviewViewProvider {
|
|
|
530
544
|
canSelectMany: true,
|
|
531
545
|
canSelectFiles: true,
|
|
532
546
|
canSelectFolders: true,
|
|
533
|
-
openLabel:
|
|
547
|
+
openLabel: this._i18n.select
|
|
534
548
|
});
|
|
535
549
|
|
|
536
550
|
if (result && result.length > 0) {
|
|
@@ -542,6 +556,51 @@ class FeedbackViewProvider implements vscode.WebviewViewProvider {
|
|
|
542
556
|
}
|
|
543
557
|
}
|
|
544
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
|
+
|
|
545
604
|
/**
|
|
546
605
|
* HTTP GET 请求
|
|
547
606
|
*/
|
|
@@ -612,12 +671,21 @@ class FeedbackViewProvider implements vscode.WebviewViewProvider {
|
|
|
612
671
|
// CSP 策略
|
|
613
672
|
const csp = `default-src 'none'; style-src ${webview.cspSource}; script-src 'unsafe-inline' ${webview.cspSource}; img-src data:;`;
|
|
614
673
|
|
|
674
|
+
// 获取语言设置
|
|
675
|
+
const language = getLanguage();
|
|
676
|
+
const langCode = language === 'zh-TW' ? 'zh-TW' : (language === 'en' ? 'en' : 'zh-CN');
|
|
677
|
+
|
|
615
678
|
// 替换占位符
|
|
616
679
|
htmlTemplate = htmlTemplate
|
|
617
680
|
.replace(/\{\{CSP\}\}/g, csp)
|
|
681
|
+
.replace(/\{\{LANG\}\}/g, langCode)
|
|
618
682
|
.replace(/\{\{MARKED_JS_URI\}\}/g, markedJsUri.toString())
|
|
619
683
|
.replace(/\{\{STYLES_CSS_URI\}\}/g, stylesCssUri.toString())
|
|
620
|
-
.replace(/\{\{SCRIPT_JS_URI\}\}/g, scriptJsUri.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
|
+
});
|
|
621
689
|
|
|
622
690
|
return htmlTemplate;
|
|
623
691
|
}
|
package/src/i18n/en.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"checkingConnection": "Checking connection...",
|
|
3
|
+
"waitingForAI": "Waiting for AI feedback request...",
|
|
4
|
+
"waitingHint": "The input interface will appear here when AI needs your feedback",
|
|
5
|
+
"aiSummary": "AI Summary",
|
|
6
|
+
"yourFeedback": "Your Feedback",
|
|
7
|
+
"feedbackPlaceholder": "Enter your feedback...",
|
|
8
|
+
"uploadImage": "Upload image",
|
|
9
|
+
"selectFilesOrFolders": "Select files/folders",
|
|
10
|
+
"submitFeedback": "Submit Feedback",
|
|
11
|
+
"toggleKeyMode": "Toggle key mode",
|
|
12
|
+
"remainingTime": "Remaining time",
|
|
13
|
+
"timeout": "Timeout",
|
|
14
|
+
"enterSubmitMode": "Enter to submit · Shift+Enter for newline",
|
|
15
|
+
"ctrlEnterSubmitMode": "Ctrl+Enter to submit · Enter for newline",
|
|
16
|
+
"switchToCtrlEnter": "Click to switch to Ctrl+Enter submit",
|
|
17
|
+
"switchToEnter": "Click to switch to Enter submit",
|
|
18
|
+
"mcpServerConnected": "MCP Server connected",
|
|
19
|
+
"mcpServerDisconnected": "MCP Server disconnected",
|
|
20
|
+
"debugInfo": "Debug Info",
|
|
21
|
+
"scanPort": "Scan port",
|
|
22
|
+
"workspace": "Workspace",
|
|
23
|
+
"currentPort": "Current port",
|
|
24
|
+
"connected": "Connected",
|
|
25
|
+
"none": "None",
|
|
26
|
+
"status": "Status",
|
|
27
|
+
"startListening": "Started listening for MCP feedback requests",
|
|
28
|
+
"stopListening": "Stopped listening",
|
|
29
|
+
"aiWaitingFeedback": "AI is waiting for your feedback",
|
|
30
|
+
"feedbackSubmitted": "Feedback submitted",
|
|
31
|
+
"submitFailed": "Submit failed",
|
|
32
|
+
"cannotConnectMCP": "Cannot connect to MCP Server",
|
|
33
|
+
"select": "Select"
|
|
34
|
+
}
|