cursor-feedback 2.0.5 → 2.1.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/CHANGELOG.md +19 -0
- package/dist/extension.js +152 -19
- package/dist/i18n/en.json +19 -1
- package/dist/i18n/index.js +19 -1
- package/dist/i18n/zh-CN.json +19 -1
- package/dist/mcp-server.js +297 -44
- package/dist/webview/index.html +22 -0
- package/dist/webview/script.js +351 -10
- package/dist/webview/styles.css +163 -0
- package/package.json +1 -1
package/dist/webview/script.js
CHANGED
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
remove: '<path d="M18 6 6 18"/><path d="m6 6 12 12"/>',
|
|
11
11
|
file: '<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/>',
|
|
12
12
|
folder: '<path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"/>',
|
|
13
|
-
code: '<polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/>'
|
|
13
|
+
code: '<polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/>',
|
|
14
|
+
pencil: '<path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/>',
|
|
15
|
+
check: '<path d="M20 6 9 17l-5-5"/>'
|
|
14
16
|
};
|
|
15
17
|
function svg(paths, cls) {
|
|
16
18
|
const el = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
@@ -72,6 +74,8 @@
|
|
|
72
74
|
const timeoutWrap = document.getElementById('timeoutWrap');
|
|
73
75
|
const timeoutBar = document.getElementById('timeoutBar');
|
|
74
76
|
const timeoutInfo = document.getElementById('timeoutInfo');
|
|
77
|
+
const pauseBtn = document.getElementById('pauseBtn');
|
|
78
|
+
const quickReplies = document.getElementById('quickReplies');
|
|
75
79
|
const toggleKeyModeBtn = document.getElementById('toggleKeyModeBtn');
|
|
76
80
|
const dropOverlay = document.getElementById('dropOverlay');
|
|
77
81
|
const summaryCard = document.getElementById('summaryCard');
|
|
@@ -96,6 +100,11 @@
|
|
|
96
100
|
const feishuAckToggle = document.getElementById('feishuAckToggle');
|
|
97
101
|
const osNotifySub = document.getElementById('osNotifySub');
|
|
98
102
|
const feishuAckSub = document.getElementById('feishuAckSub');
|
|
103
|
+
const notifyTestBtn = document.getElementById('notifyTestBtn');
|
|
104
|
+
const notifyTestHint = document.getElementById('notifyTestHint');
|
|
105
|
+
const historyBtn = document.getElementById('historyBtn');
|
|
106
|
+
const historyPopup = document.getElementById('historyPopup');
|
|
107
|
+
const toastEl = document.getElementById('toast');
|
|
99
108
|
|
|
100
109
|
let uploadedImages = [];
|
|
101
110
|
let attachedFiles = [];
|
|
@@ -106,6 +115,11 @@
|
|
|
106
115
|
let requestTimeout = 300;
|
|
107
116
|
let countdownInterval = null;
|
|
108
117
|
let submitFallbackTimer = null;
|
|
118
|
+
// 倒计时暂停态:真相源在 MCP server(HTTP 指令暂停/恢复真实计时器),
|
|
119
|
+
// 这里只维护显示用的锚点,poll 每秒回传的 pauseState 会持续校准,不怕 webview 重建。
|
|
120
|
+
let cdPaused = false;
|
|
121
|
+
let cdRemainingMs = 0;
|
|
122
|
+
let cdAnchor = 0;
|
|
109
123
|
|
|
110
124
|
// ---------- Language switch ----------
|
|
111
125
|
langSwitchBtn.addEventListener('click', () => {
|
|
@@ -226,6 +240,15 @@
|
|
|
226
240
|
vscode.postMessage({ type: 'toggleFeishuAck', payload: { enabled: feishuAckToggle.checked } });
|
|
227
241
|
});
|
|
228
242
|
|
|
243
|
+
// 发送测试通知:一键验证系统通知链路通不通(权限被拒时收不到,配合上方排查提示定位)
|
|
244
|
+
let notifyTestHintTimer = null;
|
|
245
|
+
notifyTestBtn.addEventListener('click', () => {
|
|
246
|
+
vscode.postMessage({ type: 'testNotification' });
|
|
247
|
+
notifyTestHint.textContent = i18n.notifyTestSentHint || 'Sent — check your system notifications.';
|
|
248
|
+
if (notifyTestHintTimer) clearTimeout(notifyTestHintTimer);
|
|
249
|
+
notifyTestHintTimer = setTimeout(() => { notifyTestHint.textContent = ''; }, 8000);
|
|
250
|
+
});
|
|
251
|
+
|
|
229
252
|
// 子开关受主开关约束:主关时子置灰禁用
|
|
230
253
|
function syncSubSwitchDisabled() {
|
|
231
254
|
const osOn = systemNotifyToggle.checked;
|
|
@@ -710,29 +733,312 @@
|
|
|
710
733
|
if (!document.hidden) restoreSummaryHeight();
|
|
711
734
|
});
|
|
712
735
|
|
|
736
|
+
// ---------- Quick replies (一键发送的快捷短语,可编辑,存 localStorage) ----------
|
|
737
|
+
const QUICK_REPLY_KEY = 'cursorFeedback_quickReplies';
|
|
738
|
+
let quickReplyEditing = false;
|
|
739
|
+
|
|
740
|
+
function defaultQuickReplies() {
|
|
741
|
+
return [
|
|
742
|
+
i18n.quickReplyDefault1 || 'Keep waiting for my feedback',
|
|
743
|
+
i18n.quickReplyDefault2 || 'End the task'
|
|
744
|
+
];
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
function loadQuickReplies() {
|
|
748
|
+
try {
|
|
749
|
+
const raw = localStorage.getItem(QUICK_REPLY_KEY);
|
|
750
|
+
if (raw) {
|
|
751
|
+
const arr = JSON.parse(raw);
|
|
752
|
+
if (Array.isArray(arr)) {
|
|
753
|
+
return arr.filter((s) => typeof s === 'string' && s.trim()).slice(0, 20);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
} catch (e) { /* ignore */ }
|
|
757
|
+
return defaultQuickReplies();
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
function saveQuickReplies(list) {
|
|
761
|
+
try { localStorage.setItem(QUICK_REPLY_KEY, JSON.stringify(list)); } catch (e) { /* ignore */ }
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// 点击快捷短语=立即提交。已有草稿时把短语追加到末尾一起发,不悄悄丢弃用户已输入的内容
|
|
765
|
+
function sendQuickReply(phrase) {
|
|
766
|
+
if (!currentRequestId || submitBtn.disabled) return;
|
|
767
|
+
const cur = feedbackInput.value.trim();
|
|
768
|
+
feedbackInput.value = cur ? cur + '\n' + phrase : phrase;
|
|
769
|
+
saveDraft();
|
|
770
|
+
updateCharCount();
|
|
771
|
+
submitFeedback();
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// 编辑模式下把「添加输入框」里未回车的内容也收进列表(防止用户直接点完成而丢词)
|
|
775
|
+
function commitQuickReplyDraft() {
|
|
776
|
+
const inp = quickReplies.querySelector('.quick-add-input');
|
|
777
|
+
const v = inp && inp.value.trim();
|
|
778
|
+
if (!v) return;
|
|
779
|
+
const next = loadQuickReplies();
|
|
780
|
+
if (!next.includes(v)) {
|
|
781
|
+
next.push(v);
|
|
782
|
+
saveQuickReplies(next);
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
function renderQuickReplies() {
|
|
787
|
+
if (!quickReplies) return;
|
|
788
|
+
const list = loadQuickReplies();
|
|
789
|
+
quickReplies.innerHTML = '';
|
|
790
|
+
|
|
791
|
+
list.forEach((phrase, idx) => {
|
|
792
|
+
// 编辑态用 span:chip 内含删除按钮,button 嵌 button 不合法
|
|
793
|
+
const chip = document.createElement(quickReplyEditing ? 'span' : 'button');
|
|
794
|
+
if (!quickReplyEditing) chip.type = 'button';
|
|
795
|
+
chip.className = 'quick-chip' + (quickReplyEditing ? ' is-editing' : '');
|
|
796
|
+
chip.title = quickReplyEditing ? phrase : ((i18n.quickReplySendTip || 'Click to send') + ' · ' + phrase);
|
|
797
|
+
|
|
798
|
+
const label = document.createElement('span');
|
|
799
|
+
label.className = 'quick-chip__label';
|
|
800
|
+
label.textContent = phrase;
|
|
801
|
+
chip.appendChild(label);
|
|
802
|
+
|
|
803
|
+
if (quickReplyEditing) {
|
|
804
|
+
chip.appendChild(makeRemoveBtn(() => {
|
|
805
|
+
const next = loadQuickReplies();
|
|
806
|
+
next.splice(idx, 1);
|
|
807
|
+
saveQuickReplies(next);
|
|
808
|
+
renderQuickReplies();
|
|
809
|
+
}));
|
|
810
|
+
} else {
|
|
811
|
+
chip.addEventListener('click', () => sendQuickReply(phrase));
|
|
812
|
+
}
|
|
813
|
+
quickReplies.appendChild(chip);
|
|
814
|
+
});
|
|
815
|
+
|
|
816
|
+
if (quickReplyEditing) {
|
|
817
|
+
const input = document.createElement('input');
|
|
818
|
+
input.type = 'text';
|
|
819
|
+
input.className = 'quick-add-input';
|
|
820
|
+
input.placeholder = i18n.quickReplyAddPlaceholder || 'Type a phrase and press Enter';
|
|
821
|
+
input.addEventListener('keydown', (e) => {
|
|
822
|
+
if (e.isComposing) return;
|
|
823
|
+
if (e.key === 'Enter') {
|
|
824
|
+
e.preventDefault();
|
|
825
|
+
const v = input.value.trim();
|
|
826
|
+
if (!v) return;
|
|
827
|
+
const next = loadQuickReplies();
|
|
828
|
+
if (!next.includes(v)) {
|
|
829
|
+
next.push(v);
|
|
830
|
+
saveQuickReplies(next);
|
|
831
|
+
}
|
|
832
|
+
renderQuickReplies();
|
|
833
|
+
const ni = quickReplies.querySelector('.quick-add-input');
|
|
834
|
+
if (ni) ni.focus();
|
|
835
|
+
} else if (e.key === 'Escape') {
|
|
836
|
+
quickReplyEditing = false;
|
|
837
|
+
renderQuickReplies();
|
|
838
|
+
}
|
|
839
|
+
});
|
|
840
|
+
quickReplies.appendChild(input);
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
const editBtn = document.createElement('button');
|
|
844
|
+
editBtn.type = 'button';
|
|
845
|
+
editBtn.className = 'iconbtn quick-edit-btn' + (quickReplyEditing ? ' is-on' : '');
|
|
846
|
+
const tip = quickReplyEditing
|
|
847
|
+
? (i18n.quickReplyDone || 'Done editing')
|
|
848
|
+
: (i18n.quickReplyEdit || 'Edit quick replies');
|
|
849
|
+
editBtn.setAttribute('aria-label', tip);
|
|
850
|
+
editBtn.setAttribute('data-tip', tip);
|
|
851
|
+
editBtn.appendChild(svg(quickReplyEditing ? ICONS.check : ICONS.pencil));
|
|
852
|
+
editBtn.addEventListener('click', () => {
|
|
853
|
+
if (quickReplyEditing) commitQuickReplyDraft();
|
|
854
|
+
quickReplyEditing = !quickReplyEditing;
|
|
855
|
+
renderQuickReplies();
|
|
856
|
+
if (quickReplyEditing) {
|
|
857
|
+
const ni = quickReplies.querySelector('.quick-add-input');
|
|
858
|
+
if (ni) ni.focus();
|
|
859
|
+
}
|
|
860
|
+
});
|
|
861
|
+
quickReplies.appendChild(editBtn);
|
|
862
|
+
}
|
|
863
|
+
renderQuickReplies();
|
|
864
|
+
|
|
865
|
+
// ---------- Toast (提交结果轻提示) ----------
|
|
866
|
+
let toastTimer = null;
|
|
867
|
+
function showToast(text) {
|
|
868
|
+
toastEl.textContent = text;
|
|
869
|
+
toastEl.classList.remove('hidden');
|
|
870
|
+
// 重触发进入动画
|
|
871
|
+
toastEl.classList.remove('is-in');
|
|
872
|
+
void toastEl.offsetWidth;
|
|
873
|
+
toastEl.classList.add('is-in');
|
|
874
|
+
if (toastTimer) clearTimeout(toastTimer);
|
|
875
|
+
toastTimer = setTimeout(() => {
|
|
876
|
+
toastEl.classList.remove('is-in');
|
|
877
|
+
toastTimer = setTimeout(() => toastEl.classList.add('hidden'), 300);
|
|
878
|
+
}, 3500);
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
// ---------- Feedback history (最近提交的反馈,点击回填) ----------
|
|
882
|
+
// 只存文本不存图片(图片 base64 太大会撑爆 localStorage);上限 20 条
|
|
883
|
+
const HISTORY_KEY = 'cursorFeedback_history';
|
|
884
|
+
const HISTORY_MAX = 20;
|
|
885
|
+
let historyOpen = false;
|
|
886
|
+
// 提交发出时暂存文本,等 extension 确认成功(feedbackSubmitted)后才写入历史,
|
|
887
|
+
// 避免提交失败的内容混进历史
|
|
888
|
+
let pendingHistoryText = '';
|
|
889
|
+
|
|
890
|
+
function loadHistory() {
|
|
891
|
+
try {
|
|
892
|
+
const arr = JSON.parse(localStorage.getItem(HISTORY_KEY) || '[]');
|
|
893
|
+
if (Array.isArray(arr)) {
|
|
894
|
+
return arr.filter((e) => e && typeof e.text === 'string' && e.text.trim());
|
|
895
|
+
}
|
|
896
|
+
} catch (e) { /* ignore */ }
|
|
897
|
+
return [];
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
function pushHistory(text) {
|
|
901
|
+
const t = (text || '').trim();
|
|
902
|
+
if (!t) return; // 纯图片/附件提交没有文本,不记
|
|
903
|
+
let list = loadHistory();
|
|
904
|
+
// 与最近一条相同(如快捷短语连点)就只刷新时间,不堆重复条目
|
|
905
|
+
list = list.filter((e) => e.text !== t);
|
|
906
|
+
list.unshift({ text: t, at: Date.now() });
|
|
907
|
+
if (list.length > HISTORY_MAX) list = list.slice(0, HISTORY_MAX);
|
|
908
|
+
try { localStorage.setItem(HISTORY_KEY, JSON.stringify(list)); } catch (e) { /* ignore */ }
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
function formatHistoryTime(at) {
|
|
912
|
+
const d = new Date(at);
|
|
913
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
914
|
+
const hm = pad(d.getHours()) + ':' + pad(d.getMinutes());
|
|
915
|
+
const now = new Date();
|
|
916
|
+
const sameDay = d.getFullYear() === now.getFullYear() &&
|
|
917
|
+
d.getMonth() === now.getMonth() && d.getDate() === now.getDate();
|
|
918
|
+
return sameDay ? hm : (pad(d.getMonth() + 1) + '-' + pad(d.getDate()) + ' ' + hm);
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
function positionHistoryPopup() {
|
|
922
|
+
const r = historyBtn.getBoundingClientRect();
|
|
923
|
+
historyPopup.style.left = '8px';
|
|
924
|
+
historyPopup.style.right = '8px';
|
|
925
|
+
historyPopup.style.bottom = (window.innerHeight - r.top + 6) + 'px';
|
|
926
|
+
historyPopup.style.maxHeight = Math.min(280, r.top - 12) + 'px';
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
function renderHistoryPopup() {
|
|
930
|
+
const list = loadHistory();
|
|
931
|
+
historyPopup.innerHTML = '';
|
|
932
|
+
if (list.length === 0) {
|
|
933
|
+
const empty = document.createElement('div');
|
|
934
|
+
empty.className = 'mention-empty';
|
|
935
|
+
empty.textContent = i18n.historyEmpty || 'No history yet';
|
|
936
|
+
historyPopup.appendChild(empty);
|
|
937
|
+
return;
|
|
938
|
+
}
|
|
939
|
+
list.forEach((entry) => {
|
|
940
|
+
const item = document.createElement('div');
|
|
941
|
+
item.className = 'history-item';
|
|
942
|
+
item.title = entry.text;
|
|
943
|
+
|
|
944
|
+
const text = document.createElement('span');
|
|
945
|
+
text.className = 'history-item__text';
|
|
946
|
+
text.textContent = entry.text.replace(/\s+/g, ' ');
|
|
947
|
+
item.appendChild(text);
|
|
948
|
+
|
|
949
|
+
const time = document.createElement('span');
|
|
950
|
+
time.className = 'history-item__time';
|
|
951
|
+
time.textContent = formatHistoryTime(entry.at);
|
|
952
|
+
item.appendChild(time);
|
|
953
|
+
|
|
954
|
+
item.appendChild(makeRemoveBtn((ev) => {
|
|
955
|
+
ev.stopPropagation();
|
|
956
|
+
const next = loadHistory().filter((e) => !(e.text === entry.text && e.at === entry.at));
|
|
957
|
+
try { localStorage.setItem(HISTORY_KEY, JSON.stringify(next)); } catch (e) { /* ignore */ }
|
|
958
|
+
renderHistoryPopup();
|
|
959
|
+
}, 'history-item__remove'));
|
|
960
|
+
|
|
961
|
+
// 点击回填:已有草稿则换行追加,不覆盖用户正在写的内容
|
|
962
|
+
item.addEventListener('click', () => {
|
|
963
|
+
const cur = feedbackInput.value;
|
|
964
|
+
feedbackInput.value = cur.trim() ? cur.replace(/\n?$/, '\n') + entry.text : entry.text;
|
|
965
|
+
saveDraft();
|
|
966
|
+
updateCharCount();
|
|
967
|
+
closeHistoryPopup();
|
|
968
|
+
feedbackInput.focus();
|
|
969
|
+
});
|
|
970
|
+
historyPopup.appendChild(item);
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
function openHistoryPopup() {
|
|
975
|
+
renderHistoryPopup();
|
|
976
|
+
positionHistoryPopup();
|
|
977
|
+
historyPopup.classList.remove('hidden');
|
|
978
|
+
historyOpen = true;
|
|
979
|
+
}
|
|
980
|
+
function closeHistoryPopup() {
|
|
981
|
+
historyPopup.classList.add('hidden');
|
|
982
|
+
historyOpen = false;
|
|
983
|
+
}
|
|
984
|
+
historyBtn.addEventListener('click', (e) => {
|
|
985
|
+
e.stopPropagation();
|
|
986
|
+
if (historyOpen) closeHistoryPopup();
|
|
987
|
+
else openHistoryPopup();
|
|
988
|
+
});
|
|
989
|
+
document.addEventListener('click', (e) => {
|
|
990
|
+
if (historyOpen && !historyPopup.contains(e.target)) closeHistoryPopup();
|
|
991
|
+
});
|
|
992
|
+
document.addEventListener('keydown', (e) => {
|
|
993
|
+
if (e.key === 'Escape' && historyOpen) closeHistoryPopup();
|
|
994
|
+
});
|
|
995
|
+
|
|
713
996
|
// ---------- Countdown + progress bar ----------
|
|
997
|
+
function updatePauseUI() {
|
|
998
|
+
timeoutWrap.classList.toggle('is-paused', cdPaused);
|
|
999
|
+
const tip = cdPaused
|
|
1000
|
+
? (i18n.resumeCountdown || 'Resume countdown')
|
|
1001
|
+
: (i18n.pauseCountdown || 'Pause countdown');
|
|
1002
|
+
pauseBtn.setAttribute('data-tip', tip);
|
|
1003
|
+
pauseBtn.setAttribute('aria-label', tip);
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
function currentRemainingMs() {
|
|
1007
|
+
return cdPaused ? cdRemainingMs : Math.max(0, cdRemainingMs - (Date.now() - cdAnchor));
|
|
1008
|
+
}
|
|
1009
|
+
|
|
714
1010
|
function updateCountdown() {
|
|
715
|
-
if (!
|
|
716
|
-
const
|
|
717
|
-
const
|
|
1011
|
+
if (!requestTimeout) return;
|
|
1012
|
+
const totalMs = requestTimeout * 1000;
|
|
1013
|
+
const remainingMs = currentRemainingMs();
|
|
1014
|
+
const remaining = Math.max(0, Math.round(remainingMs / 1000));
|
|
718
1015
|
const minutes = Math.floor(remaining / 60);
|
|
719
1016
|
const seconds = remaining % 60;
|
|
720
|
-
const ratio = Math.max(0, Math.min(1,
|
|
1017
|
+
const ratio = Math.max(0, Math.min(1, remainingMs / totalMs));
|
|
721
1018
|
|
|
722
1019
|
timeoutBar.style.width = (ratio * 100) + '%';
|
|
723
|
-
timeoutWrap.classList.toggle('is-warning', ratio <= 0.25 && ratio > 0.1);
|
|
724
|
-
timeoutWrap.classList.toggle('is-danger', ratio <= 0.1);
|
|
1020
|
+
timeoutWrap.classList.toggle('is-warning', !cdPaused && ratio <= 0.25 && ratio > 0.1);
|
|
1021
|
+
timeoutWrap.classList.toggle('is-danger', !cdPaused && ratio <= 0.1);
|
|
725
1022
|
|
|
726
|
-
const label = i18n.remainingTime || 'Remaining time';
|
|
1023
|
+
const label = cdPaused ? (i18n.pausedLabel || 'Paused') : (i18n.remainingTime || 'Remaining time');
|
|
727
1024
|
timeoutInfo.textContent = label + ' ' + minutes + ':' + seconds.toString().padStart(2, '0');
|
|
728
1025
|
|
|
729
|
-
if (
|
|
1026
|
+
if (!cdPaused && remainingMs <= 0) {
|
|
730
1027
|
clearInterval(countdownInterval);
|
|
731
1028
|
countdownInterval = null;
|
|
732
1029
|
timeoutInfo.textContent = i18n.timeout || 'Timeout';
|
|
733
1030
|
}
|
|
734
1031
|
}
|
|
735
1032
|
|
|
1033
|
+
// 暂停/恢复:发给插件 → HTTP 通知 MCP server 冻结/重排真实计时器;UI 待 server 确认后刷新
|
|
1034
|
+
pauseBtn.addEventListener('click', () => {
|
|
1035
|
+
if (!currentRequestId) return;
|
|
1036
|
+
vscode.postMessage({
|
|
1037
|
+
type: 'togglePause',
|
|
1038
|
+
payload: { requestId: currentRequestId, paused: !cdPaused }
|
|
1039
|
+
});
|
|
1040
|
+
});
|
|
1041
|
+
|
|
736
1042
|
// ---------- Submit ----------
|
|
737
1043
|
function setSubmitting(on) {
|
|
738
1044
|
if (on) {
|
|
@@ -778,11 +1084,13 @@
|
|
|
778
1084
|
if (!currentRequestId || submitBtn.disabled) return;
|
|
779
1085
|
|
|
780
1086
|
setSubmitting(true);
|
|
1087
|
+
// 先暂存本次文本,等 extension 确认成功后写入历史(提交失败不入历史)
|
|
1088
|
+
pendingHistoryText = buildFeedbackText();
|
|
781
1089
|
vscode.postMessage({
|
|
782
1090
|
type: 'submitFeedback',
|
|
783
1091
|
payload: {
|
|
784
1092
|
requestId: currentRequestId,
|
|
785
|
-
interactive_feedback:
|
|
1093
|
+
interactive_feedback: pendingHistoryText,
|
|
786
1094
|
images: uploadedImages.map(im => ({ name: im.name, data: im.dataUrl.split(',')[1], size: im.size })),
|
|
787
1095
|
attachedFiles: attachedFiles,
|
|
788
1096
|
project_directory: currentProjectDir
|
|
@@ -826,6 +1134,10 @@
|
|
|
826
1134
|
currentProjectDir = message.payload.projectDir;
|
|
827
1135
|
requestTimestamp = message.payload.timestamp;
|
|
828
1136
|
requestTimeout = message.payload.timeout;
|
|
1137
|
+
cdPaused = false;
|
|
1138
|
+
cdRemainingMs = Math.max(0, requestTimeout * 1000 - (Date.now() - requestTimestamp));
|
|
1139
|
+
cdAnchor = Date.now();
|
|
1140
|
+
updatePauseUI();
|
|
829
1141
|
summaryContent.innerHTML = renderMarkdown(message.payload.summary);
|
|
830
1142
|
highlightCodeBlocks(summaryContent);
|
|
831
1143
|
summaryContent.scrollTop = 0;
|
|
@@ -848,6 +1160,8 @@
|
|
|
848
1160
|
submitBar.classList.add('hidden');
|
|
849
1161
|
waitingStatus.classList.remove('hidden');
|
|
850
1162
|
timeoutWrap.hidden = true;
|
|
1163
|
+
cdPaused = false;
|
|
1164
|
+
updatePauseUI();
|
|
851
1165
|
break;
|
|
852
1166
|
|
|
853
1167
|
case 'serverStatus':
|
|
@@ -919,9 +1233,36 @@
|
|
|
919
1233
|
updateAutoRetryUI(!!message.payload.enabled);
|
|
920
1234
|
break;
|
|
921
1235
|
|
|
1236
|
+
case 'pauseState': {
|
|
1237
|
+
// server 是暂停态与剩余时间的真相源:按钮确认与每秒 poll 都走这里持续校准,
|
|
1238
|
+
// webview 重建后也能恢复正确的暂停显示
|
|
1239
|
+
const p = message.payload || {};
|
|
1240
|
+
if (!currentRequestId || p.requestId !== currentRequestId) break;
|
|
1241
|
+
cdPaused = !!p.paused;
|
|
1242
|
+
if (typeof p.remainingMs === 'number') {
|
|
1243
|
+
cdRemainingMs = Math.max(0, p.remainingMs);
|
|
1244
|
+
cdAnchor = Date.now();
|
|
1245
|
+
}
|
|
1246
|
+
updatePauseUI();
|
|
1247
|
+
if (!countdownInterval && (cdPaused || cdRemainingMs > 0)) {
|
|
1248
|
+
countdownInterval = setInterval(updateCountdown, 1000);
|
|
1249
|
+
}
|
|
1250
|
+
updateCountdown();
|
|
1251
|
+
break;
|
|
1252
|
+
}
|
|
1253
|
+
|
|
922
1254
|
case 'feishuState':
|
|
923
1255
|
updateFeishuUI(message.payload);
|
|
924
1256
|
break;
|
|
1257
|
+
|
|
1258
|
+
case 'feedbackSubmitted':
|
|
1259
|
+
// 提交已被 server 确认:写入历史 + 轻提示(queued = 撞上超时空窗、暂存待下一轮送达)
|
|
1260
|
+
pushHistory(pendingHistoryText);
|
|
1261
|
+
pendingHistoryText = '';
|
|
1262
|
+
showToast(message.payload && message.payload.queued
|
|
1263
|
+
? (i18n.toastQueued || 'Queued — will be delivered to AI next round')
|
|
1264
|
+
: (i18n.toastSubmitted || 'Feedback sent'));
|
|
1265
|
+
break;
|
|
925
1266
|
}
|
|
926
1267
|
});
|
|
927
1268
|
|
package/dist/webview/styles.css
CHANGED
|
@@ -664,6 +664,144 @@ body.vscode-light .hljs-regexp { color: #811f3f; }
|
|
|
664
664
|
}
|
|
665
665
|
.ref-chip .chip-remove:hover { opacity: 1; background: transparent; color: var(--vscode-errorForeground, #e51400); }
|
|
666
666
|
|
|
667
|
+
/* ---------- Quick replies (一键发送短语) ---------- */
|
|
668
|
+
.quick-replies {
|
|
669
|
+
flex: none;
|
|
670
|
+
display: flex;
|
|
671
|
+
flex-wrap: wrap;
|
|
672
|
+
align-items: center;
|
|
673
|
+
gap: 4px;
|
|
674
|
+
margin-bottom: 6px;
|
|
675
|
+
}
|
|
676
|
+
.quick-chip {
|
|
677
|
+
display: inline-flex;
|
|
678
|
+
align-items: center;
|
|
679
|
+
gap: 4px;
|
|
680
|
+
max-width: 100%;
|
|
681
|
+
padding: 3px 10px;
|
|
682
|
+
font-family: inherit;
|
|
683
|
+
font-size: 11px;
|
|
684
|
+
line-height: 1.5;
|
|
685
|
+
border-radius: 999px;
|
|
686
|
+
background: color-mix(in srgb, var(--accent) 10%, var(--surface-sunken));
|
|
687
|
+
border: 1px solid color-mix(in srgb, var(--accent) 30%, var(--line-soft));
|
|
688
|
+
color: var(--vscode-foreground);
|
|
689
|
+
cursor: pointer;
|
|
690
|
+
transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease),
|
|
691
|
+
transform var(--dur) var(--ease);
|
|
692
|
+
}
|
|
693
|
+
.quick-chip:hover {
|
|
694
|
+
background: color-mix(in srgb, var(--accent) 20%, var(--surface-sunken));
|
|
695
|
+
border-color: color-mix(in srgb, var(--accent) 55%, var(--line-soft));
|
|
696
|
+
transform: translateY(-1px);
|
|
697
|
+
}
|
|
698
|
+
.quick-chip:active { transform: translateY(0); }
|
|
699
|
+
.quick-chip.is-editing { cursor: default; }
|
|
700
|
+
.quick-chip.is-editing:hover { transform: none; }
|
|
701
|
+
.quick-chip__label {
|
|
702
|
+
overflow: hidden;
|
|
703
|
+
text-overflow: ellipsis;
|
|
704
|
+
white-space: nowrap;
|
|
705
|
+
max-width: 180px;
|
|
706
|
+
}
|
|
707
|
+
.quick-chip .chip-remove {
|
|
708
|
+
position: static;
|
|
709
|
+
width: 14px;
|
|
710
|
+
height: 14px;
|
|
711
|
+
opacity: 0.6;
|
|
712
|
+
background: transparent;
|
|
713
|
+
color: var(--muted);
|
|
714
|
+
}
|
|
715
|
+
.quick-chip .chip-remove:hover { opacity: 1; background: transparent; color: var(--vscode-errorForeground, #e51400); }
|
|
716
|
+
.quick-edit-btn { width: 22px; height: 22px; }
|
|
717
|
+
.quick-edit-btn svg { width: 12px; height: 12px; }
|
|
718
|
+
.quick-add-input {
|
|
719
|
+
width: 150px;
|
|
720
|
+
padding: 3px 10px;
|
|
721
|
+
font-family: inherit;
|
|
722
|
+
font-size: 11px;
|
|
723
|
+
line-height: 1.5;
|
|
724
|
+
color: var(--vscode-input-foreground, var(--vscode-foreground));
|
|
725
|
+
background: var(--vscode-input-background, var(--surface-sunken));
|
|
726
|
+
border: 1px dashed var(--line);
|
|
727
|
+
border-radius: 999px;
|
|
728
|
+
}
|
|
729
|
+
.quick-add-input:focus { outline: none; border-color: var(--accent); border-style: solid; }
|
|
730
|
+
.quick-add-input::placeholder { color: var(--vscode-input-placeholderForeground, var(--muted)); opacity: 0.8; }
|
|
731
|
+
|
|
732
|
+
/* ---------- Toast (提交结果轻提示) ---------- */
|
|
733
|
+
.toast {
|
|
734
|
+
position: fixed;
|
|
735
|
+
top: 40px;
|
|
736
|
+
left: 50%;
|
|
737
|
+
z-index: 60;
|
|
738
|
+
max-width: calc(100% - 32px);
|
|
739
|
+
padding: 6px 14px;
|
|
740
|
+
font-size: 12px;
|
|
741
|
+
line-height: 1.5;
|
|
742
|
+
color: var(--vscode-foreground);
|
|
743
|
+
background: var(--surface-raised, var(--vscode-editorWidget-background, #252526));
|
|
744
|
+
border: 1px solid color-mix(in srgb, var(--vscode-charts-green, #3fb950) 45%, var(--line-soft));
|
|
745
|
+
border-left: 3px solid var(--vscode-charts-green, #3fb950);
|
|
746
|
+
border-radius: 6px;
|
|
747
|
+
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25);
|
|
748
|
+
opacity: 0;
|
|
749
|
+
transform: translate(-50%, -6px);
|
|
750
|
+
transition: opacity 0.25s ease, transform 0.25s ease;
|
|
751
|
+
pointer-events: none;
|
|
752
|
+
}
|
|
753
|
+
.toast.is-in { opacity: 1; transform: translate(-50%, 0); }
|
|
754
|
+
.toast.hidden { display: none; }
|
|
755
|
+
|
|
756
|
+
/* ---------- Feedback history (反馈历史浮层) ---------- */
|
|
757
|
+
.history-popup {
|
|
758
|
+
position: fixed;
|
|
759
|
+
z-index: 50;
|
|
760
|
+
overflow-y: auto;
|
|
761
|
+
background: var(--surface-raised, var(--vscode-editorWidget-background, #252526));
|
|
762
|
+
border: 1px solid var(--line);
|
|
763
|
+
border-radius: 8px;
|
|
764
|
+
box-shadow: 0 6px 24px rgba(0, 0, 0, 0.3);
|
|
765
|
+
padding: 4px;
|
|
766
|
+
}
|
|
767
|
+
.history-popup.hidden { display: none; }
|
|
768
|
+
.history-item {
|
|
769
|
+
display: flex;
|
|
770
|
+
align-items: center;
|
|
771
|
+
gap: 8px;
|
|
772
|
+
padding: 6px 8px;
|
|
773
|
+
border-radius: 5px;
|
|
774
|
+
cursor: pointer;
|
|
775
|
+
font-size: 12px;
|
|
776
|
+
}
|
|
777
|
+
.history-item:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); }
|
|
778
|
+
.history-item__text {
|
|
779
|
+
flex: 1;
|
|
780
|
+
overflow: hidden;
|
|
781
|
+
text-overflow: ellipsis;
|
|
782
|
+
white-space: nowrap;
|
|
783
|
+
color: var(--vscode-foreground);
|
|
784
|
+
}
|
|
785
|
+
.history-item__time {
|
|
786
|
+
flex: none;
|
|
787
|
+
font-size: 10px;
|
|
788
|
+
color: var(--muted);
|
|
789
|
+
}
|
|
790
|
+
.history-item .chip-remove.history-item__remove {
|
|
791
|
+
position: static;
|
|
792
|
+
flex: none;
|
|
793
|
+
width: 14px;
|
|
794
|
+
height: 14px;
|
|
795
|
+
opacity: 0.5;
|
|
796
|
+
background: transparent;
|
|
797
|
+
color: var(--muted);
|
|
798
|
+
}
|
|
799
|
+
.history-item .chip-remove.history-item__remove:hover {
|
|
800
|
+
opacity: 1;
|
|
801
|
+
background: transparent;
|
|
802
|
+
color: var(--vscode-errorForeground, #e51400);
|
|
803
|
+
}
|
|
804
|
+
|
|
667
805
|
/* ---------- Countdown ---------- */
|
|
668
806
|
.countdown {
|
|
669
807
|
display: flex;
|
|
@@ -671,6 +809,18 @@ body.vscode-light .hljs-regexp { color: #811f3f; }
|
|
|
671
809
|
gap: var(--sp-2);
|
|
672
810
|
}
|
|
673
811
|
.countdown[hidden] { display: none; }
|
|
812
|
+
.countdown__pause {
|
|
813
|
+
width: 22px;
|
|
814
|
+
height: 22px;
|
|
815
|
+
flex: none;
|
|
816
|
+
}
|
|
817
|
+
.countdown__pause svg { width: 12px; height: 12px; }
|
|
818
|
+
.countdown__play-icon { display: none; }
|
|
819
|
+
.countdown.is-paused .countdown__pause-icon { display: none; }
|
|
820
|
+
.countdown.is-paused .countdown__play-icon { display: block; }
|
|
821
|
+
.countdown.is-paused .countdown__pause { color: var(--accent); opacity: 1; }
|
|
822
|
+
.countdown.is-paused .countdown__bar { background: var(--muted); }
|
|
823
|
+
.countdown.is-paused .countdown__text { color: var(--muted); }
|
|
674
824
|
.countdown__track {
|
|
675
825
|
flex: 1;
|
|
676
826
|
height: 4px;
|
|
@@ -990,6 +1140,19 @@ body.vscode-light .hljs-regexp { color: #811f3f; }
|
|
|
990
1140
|
}
|
|
991
1141
|
.feishu-subitem.is-disabled { opacity: 0.45; pointer-events: none; }
|
|
992
1142
|
.feishu-modal__desc--sub { font-size: 11px; opacity: 0.9; }
|
|
1143
|
+
/* 通知排查提示:linux 等平台文案为空时整行隐藏 */
|
|
1144
|
+
.feishu-troubleshoot { opacity: 0.7; }
|
|
1145
|
+
.feishu-troubleshoot:empty { display: none; }
|
|
1146
|
+
.feishu-test-row {
|
|
1147
|
+
display: flex;
|
|
1148
|
+
align-items: baseline;
|
|
1149
|
+
gap: 8px;
|
|
1150
|
+
flex-wrap: wrap;
|
|
1151
|
+
}
|
|
1152
|
+
.feishu-test-hint {
|
|
1153
|
+
font-size: 11px;
|
|
1154
|
+
color: var(--vscode-charts-green, #3fb950);
|
|
1155
|
+
}
|
|
993
1156
|
.feishu-input-wrap { position: relative; display: flex; align-items: center; }
|
|
994
1157
|
.feishu-input-wrap .feishu-input { width: 100%; padding-right: 34px; box-sizing: border-box; }
|
|
995
1158
|
.feishu-eye {
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "cursor-feedback",
|
|
3
3
|
"displayName": "Cursor Feedback",
|
|
4
4
|
"description": "One Cursor conversation, unlimited AI interactions - Save your monthly request quota! Interactive feedback loop for AI chat via MCP",
|
|
5
|
-
"version": "2.
|
|
5
|
+
"version": "2.1.1",
|
|
6
6
|
"icon": "icon.png",
|
|
7
7
|
"author": "jianger666",
|
|
8
8
|
"license": "MIT",
|