cursor-feedback 2.2.0 → 2.3.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/dist/extension.js +228 -18
- package/dist/feishu.js +113 -0
- package/dist/i18n/en.json +23 -3
- package/dist/i18n/index.js +23 -1
- package/dist/i18n/zh-CN.json +23 -3
- package/dist/mcp-server.js +153 -15
- package/dist/webview/index.html +40 -12
- package/dist/webview/script.js +409 -28
- package/dist/webview/styles.css +215 -19
- package/package.json +4 -1
package/dist/mcp-server.js
CHANGED
|
@@ -72,6 +72,16 @@ function debugLog(message) {
|
|
|
72
72
|
const timestamp = new Date().toISOString();
|
|
73
73
|
console.error(`[${timestamp}] ${message}`);
|
|
74
74
|
}
|
|
75
|
+
// 包版本真相源:package.json(dist/mcp-server.js 的上一级目录)。读不到时兜底 0.0.0。
|
|
76
|
+
const PKG_VERSION = (() => {
|
|
77
|
+
try {
|
|
78
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf-8'));
|
|
79
|
+
return typeof pkg.version === 'string' ? pkg.version : '0.0.0';
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return '0.0.0';
|
|
83
|
+
}
|
|
84
|
+
})();
|
|
75
85
|
/**
|
|
76
86
|
* MCP Feedback Server
|
|
77
87
|
*/
|
|
@@ -95,11 +105,14 @@ class McpFeedbackServer {
|
|
|
95
105
|
/** 面板在超时空窗内提交的反馈:暂存到下一轮 pending 注册时立即兑现 */
|
|
96
106
|
this.panelStash = null;
|
|
97
107
|
/**
|
|
98
|
-
* 忙时消息队列:AI
|
|
108
|
+
* 忙时消息队列:AI 正在干活(该项目空间没有等待中的反馈请求)时用户发来的消息
|
|
99
109
|
* 不再丢弃,而是按项目空间排队;等 AI 下一轮调 interactive_feedback 时合并送达,
|
|
100
|
-
*
|
|
110
|
+
* 并附「任务期间追加」提示头。飞书与面板消息共用同一个队列(按到达顺序),
|
|
111
|
+
* 飞书侧开关见 FeishuBridge.queueWhenBusy(面板 / FEISHU_QUEUE);面板排队恒可用。
|
|
101
112
|
*/
|
|
102
113
|
this.queuedInbound = [];
|
|
114
|
+
/** 队列项 id 自增序号(配合时间戳保证进程内唯一) */
|
|
115
|
+
this.queueSeq = 0;
|
|
103
116
|
/** 暂存过期提示定时器:到点仍未被认领则「回复」那条消息告知没送到,避免静默丢弃 */
|
|
104
117
|
this.stashExpiryTimer = null;
|
|
105
118
|
// 最近一次被飞书回复 resolve 的请求:供插件端精确区分「飞书回复」与「超时」,
|
|
@@ -139,7 +152,7 @@ class McpFeedbackServer {
|
|
|
139
152
|
this.basePort = port;
|
|
140
153
|
this.server = new index_js_1.Server({
|
|
141
154
|
name: 'cursor-feedback-server',
|
|
142
|
-
version:
|
|
155
|
+
version: PKG_VERSION,
|
|
143
156
|
}, {
|
|
144
157
|
capabilities: {
|
|
145
158
|
tools: {},
|
|
@@ -933,13 +946,43 @@ class McpFeedbackServer {
|
|
|
933
946
|
}
|
|
934
947
|
return false;
|
|
935
948
|
}
|
|
936
|
-
/**
|
|
949
|
+
/** 队列里是否有属于某项目空间的排队消息(不看渠道开关:面板消息不受飞书队列开关约束) */
|
|
937
950
|
hasQueuedFor(projectDir) {
|
|
938
|
-
if (!this.feishu.isQueueWhenBusy())
|
|
939
|
-
return false;
|
|
940
951
|
const owner = this.normalizePath(projectDir);
|
|
941
952
|
return this.queuedInbound.some((q) => this.pathsRelated(this.normalizePath(q.forProjectDir), owner));
|
|
942
953
|
}
|
|
954
|
+
/** 某项目空间的排队消息快照(随 /api/feedback/current 下发给面板展示队列列表) */
|
|
955
|
+
queuedSnapshotFor(normalizedWs) {
|
|
956
|
+
if (!normalizedWs)
|
|
957
|
+
return [];
|
|
958
|
+
return this.queuedInbound
|
|
959
|
+
.filter((q) => this.pathsRelated(this.normalizePath(q.forProjectDir), normalizedWs))
|
|
960
|
+
.map((q) => ({
|
|
961
|
+
id: q.id,
|
|
962
|
+
at: q.at,
|
|
963
|
+
source: q.source,
|
|
964
|
+
text: q.text,
|
|
965
|
+
images: q.images.length,
|
|
966
|
+
files: q.files.length,
|
|
967
|
+
}));
|
|
968
|
+
}
|
|
969
|
+
/**
|
|
970
|
+
* 撤回一条排队消息(面板队列列表的小叉触发)。
|
|
971
|
+
* 飞书来源的消息同步回执「已撤回」,两边状态一致;面板消息撤掉后列表随轮询消失即回执。
|
|
972
|
+
* 返回 false = 没找到(可能已被消费或过期),面板下一秒轮询自然对齐,无需特殊处理。
|
|
973
|
+
*/
|
|
974
|
+
removeQueuedById(id) {
|
|
975
|
+
const idx = this.queuedInbound.findIndex((q) => q.id === id);
|
|
976
|
+
if (idx < 0)
|
|
977
|
+
return false;
|
|
978
|
+
const [item] = this.queuedInbound.splice(idx, 1);
|
|
979
|
+
clearTimeout(item.expiryTimer);
|
|
980
|
+
debugLog(`Queued message recalled (id=${id}, source=${item.source}, queueSize=${this.queuedInbound.length})`);
|
|
981
|
+
if (item.source === 'feishu') {
|
|
982
|
+
this.feishu.replyToMessage(item.messageId || undefined, item.chatId, '🗑️ 这条排队消息已在插件面板被撤回,不会送达 AI。');
|
|
983
|
+
}
|
|
984
|
+
return true;
|
|
985
|
+
}
|
|
943
986
|
/**
|
|
944
987
|
* 用户回复了一张「已结束」的卡片(不在超时续接窗口内)→ 忙时排队:
|
|
945
988
|
* - 该项目已有新一轮在等 → 引导回复最新卡片(避免旧回复窜入错误轮次),视为已处理;
|
|
@@ -962,23 +1005,29 @@ class McpFeedbackServer {
|
|
|
962
1005
|
/**
|
|
963
1006
|
* 消息入队 + 回执用户「已排队」。每条消息带 60 分钟过期兜底:
|
|
964
1007
|
* 到点仍未被 AI 读取(对话可能已结束)→ 引用回复告知未送达,绝不静默丢弃。
|
|
1008
|
+
* 飞书与面板消息共用同一个队列(严格按到达顺序),但回执链路分渠道:
|
|
1009
|
+
* 飞书走引用回复/表情,面板消息没有推送通道,靠轮询下发的队列列表反映在/不在。
|
|
965
1010
|
*/
|
|
966
|
-
enqueueInbound(text, chatId, images, files, messageId, forProjectDir) {
|
|
1011
|
+
enqueueInbound(text, chatId, images, files, messageId, forProjectDir, source = 'feishu') {
|
|
967
1012
|
// 上限保护:挤出最老的一条并告知未送达
|
|
968
1013
|
while (this.queuedInbound.length >= McpFeedbackServer.QUEUE_MAX) {
|
|
969
1014
|
const dropped = this.queuedInbound.shift();
|
|
970
1015
|
if (!dropped)
|
|
971
1016
|
break;
|
|
972
1017
|
clearTimeout(dropped.expiryTimer);
|
|
973
|
-
|
|
1018
|
+
if (dropped.source === 'feishu') {
|
|
1019
|
+
this.feishu.replyToMessage(dropped.messageId || undefined, dropped.chatId, '⚠️ 排队消息过多,这条消息已被挤出队列、未送达 AI,请稍后重发。');
|
|
1020
|
+
}
|
|
974
1021
|
}
|
|
975
1022
|
const item = {
|
|
1023
|
+
id: `q${Date.now()}_${++this.queueSeq}`,
|
|
976
1024
|
text,
|
|
977
1025
|
chatId,
|
|
978
1026
|
images,
|
|
979
1027
|
files,
|
|
980
1028
|
at: Date.now(),
|
|
981
1029
|
messageId,
|
|
1030
|
+
source,
|
|
982
1031
|
forProjectDir,
|
|
983
1032
|
expiryTimer: undefined,
|
|
984
1033
|
};
|
|
@@ -987,12 +1036,16 @@ class McpFeedbackServer {
|
|
|
987
1036
|
if (idx < 0)
|
|
988
1037
|
return; // 已被消费
|
|
989
1038
|
this.queuedInbound.splice(idx, 1);
|
|
990
|
-
|
|
1039
|
+
if (item.source === 'feishu') {
|
|
1040
|
+
this.feishu.replyToMessage(item.messageId || undefined, item.chatId, '⚠️ 这条排队消息等了 60 分钟仍未被 AI 读取(对话可能已结束),未能送达。需要的话请在 AI 下次询问时重发。');
|
|
1041
|
+
}
|
|
991
1042
|
}, McpFeedbackServer.QUEUE_TTL_MS);
|
|
992
1043
|
item.expiryTimer.unref?.();
|
|
993
1044
|
this.queuedInbound.push(item);
|
|
994
|
-
debugLog(`Inbound queued for busy AI (project=${forProjectDir}, queueSize=${this.queuedInbound.length})`);
|
|
995
|
-
|
|
1045
|
+
debugLog(`Inbound queued for busy AI (source=${source}, project=${forProjectDir}, queueSize=${this.queuedInbound.length})`);
|
|
1046
|
+
if (source === 'feishu') {
|
|
1047
|
+
this.feishu.replyToMessage(messageId || undefined, chatId, `🤖 AI 正在工作中,这条消息已排队,将在「${this.projectName(forProjectDir)}」当前任务完成后自动读取。`);
|
|
1048
|
+
}
|
|
996
1049
|
}
|
|
997
1050
|
/**
|
|
998
1051
|
* 队列兑现:新一轮 pending 注册时,把属于该项目空间的所有排队消息合并成一次反馈送达,
|
|
@@ -1022,7 +1075,8 @@ class McpFeedbackServer {
|
|
|
1022
1075
|
});
|
|
1023
1076
|
const text = '[QUEUED_MESSAGES] 以下是用户在你执行上一轮任务期间「追加」发送的消息(当时你正忙,消息已排队暂存)。' +
|
|
1024
1077
|
'注意:这些内容不是对你最新一轮工作摘要的回复,请结合任务上下文阅读并处理;' +
|
|
1025
|
-
'
|
|
1078
|
+
'如与你摘要中的询问冲突,以用户追加内容为准。本次的 summary 用户并没有收到,如有必要可在下次带上。' +
|
|
1079
|
+
'处理完后照常调用 interactive_feedback 继续对话。\n\n' +
|
|
1026
1080
|
`=== 追加消息(${matched.length} 条)===\n` +
|
|
1027
1081
|
lines.join('\n\n');
|
|
1028
1082
|
debugLog(`Consuming ${matched.length} queued message(s) for request: ${requestId}`);
|
|
@@ -1037,9 +1091,12 @@ class McpFeedbackServer {
|
|
|
1037
1091
|
this.feishu.clearPending(requestId);
|
|
1038
1092
|
// 标记为「飞书渠道 resolve」:插件面板据此重置,避免对着已消失的请求提交
|
|
1039
1093
|
this.lastFeishuResolved = { id: requestId, at: Date.now() };
|
|
1040
|
-
//
|
|
1094
|
+
// 送达回执:给每条飞书排队消息补 ✅ 表情(面板消息没有回执通道,
|
|
1095
|
+
// 其送达状态由轮询下发的队列列表体现——消费后列表随即清空)
|
|
1041
1096
|
for (const m of matched) {
|
|
1042
|
-
|
|
1097
|
+
if (m.source === 'feishu') {
|
|
1098
|
+
this.feishu.reactDone(m.messageId || undefined, m.chatId);
|
|
1099
|
+
}
|
|
1043
1100
|
}
|
|
1044
1101
|
}
|
|
1045
1102
|
/** 「我的窗口」是否仍活着(无插件 host 无从判定,视为活跃) */
|
|
@@ -1293,9 +1350,66 @@ class McpFeedbackServer {
|
|
|
1293
1350
|
feishu: this.feishu.getStatus(),
|
|
1294
1351
|
feishuResolvedId: (this.lastFeishuResolved && Date.now() - this.lastFeishuResolved.at < 30000) ? this.lastFeishuResolved.id : null,
|
|
1295
1352
|
pause: this.getPauseStateFor(chosen?.id),
|
|
1353
|
+
// 该窗口的忙时排队消息快照(飞书 + 面板同队列),供面板展示队列列表
|
|
1354
|
+
queued: this.queuedSnapshotFor(pollWs),
|
|
1296
1355
|
}));
|
|
1297
1356
|
return;
|
|
1298
1357
|
}
|
|
1358
|
+
// 面板忙时排队:AI 正忙(该项目无等待中的请求)时,把面板消息排进忙时队列,
|
|
1359
|
+
// 与飞书消息共用同一队列(按到达顺序),下一轮 interactive_feedback 合并送达
|
|
1360
|
+
if (req.method === 'POST' && req.url === '/api/feedback/enqueue') {
|
|
1361
|
+
let body = '';
|
|
1362
|
+
req.on('data', (chunk) => { body += chunk.toString(); });
|
|
1363
|
+
req.on('end', () => {
|
|
1364
|
+
try {
|
|
1365
|
+
const { text, images, attachedFiles, projectDir } = JSON.parse(body);
|
|
1366
|
+
if (!projectDir) {
|
|
1367
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
1368
|
+
res.end(JSON.stringify({ queued: false, reason: 'projectDir required' }));
|
|
1369
|
+
return;
|
|
1370
|
+
}
|
|
1371
|
+
// 忙时排队是全局开关(面板 UI 已隐藏排队入口,这里兜底防竞态)
|
|
1372
|
+
if (!this.feishu.isQueueWhenBusy()) {
|
|
1373
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1374
|
+
res.end(JSON.stringify({ queued: false, reason: 'disabled' }));
|
|
1375
|
+
return;
|
|
1376
|
+
}
|
|
1377
|
+
// 该项目有等待中的请求 → 不该排队,面板应直接提交(正常轮询下一秒就会显示该请求)
|
|
1378
|
+
if (this.hasPendingForProject(projectDir)) {
|
|
1379
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1380
|
+
res.end(JSON.stringify({ queued: false, reason: 'pending' }));
|
|
1381
|
+
return;
|
|
1382
|
+
}
|
|
1383
|
+
this.enqueueInbound(text || '', '', images || [], attachedFiles || [], '', projectDir, 'panel');
|
|
1384
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1385
|
+
res.end(JSON.stringify({ queued: true }));
|
|
1386
|
+
}
|
|
1387
|
+
catch {
|
|
1388
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
1389
|
+
res.end(JSON.stringify({ queued: false, reason: 'invalid body' }));
|
|
1390
|
+
}
|
|
1391
|
+
});
|
|
1392
|
+
return;
|
|
1393
|
+
}
|
|
1394
|
+
// 撤回排队消息:面板队列列表的删除按钮触发,按队列项 id 定位。
|
|
1395
|
+
// 飞书来源的消息由 removeQueuedById 同步回执,保证两边状态一致
|
|
1396
|
+
if (req.method === 'POST' && req.url === '/api/feedback/queue/remove') {
|
|
1397
|
+
let body = '';
|
|
1398
|
+
req.on('data', (chunk) => { body += chunk.toString(); });
|
|
1399
|
+
req.on('end', () => {
|
|
1400
|
+
try {
|
|
1401
|
+
const { id } = JSON.parse(body);
|
|
1402
|
+
const removed = !!id && this.removeQueuedById(id);
|
|
1403
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1404
|
+
res.end(JSON.stringify({ removed }));
|
|
1405
|
+
}
|
|
1406
|
+
catch {
|
|
1407
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
1408
|
+
res.end(JSON.stringify({ removed: false }));
|
|
1409
|
+
}
|
|
1410
|
+
});
|
|
1411
|
+
return;
|
|
1412
|
+
}
|
|
1299
1413
|
// 提交反馈
|
|
1300
1414
|
if (req.method === 'POST' && req.url === '/api/feedback/submit') {
|
|
1301
1415
|
let body = '';
|
|
@@ -1342,6 +1456,30 @@ class McpFeedbackServer {
|
|
|
1342
1456
|
return;
|
|
1343
1457
|
}
|
|
1344
1458
|
// 配置飞书(来自插件 UI;凭证未变只补 chatId,变了则重建长连接)
|
|
1459
|
+
// 扫码一键创建飞书应用:启动 Device Grant 流程并返回待扫码链接(等二维码就绪才响应)
|
|
1460
|
+
if (req.method === 'POST' && req.url === '/api/feishu/register/start') {
|
|
1461
|
+
this.feishu.startRegister().then((state) => {
|
|
1462
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1463
|
+
res.end(JSON.stringify(state));
|
|
1464
|
+
}).catch(() => {
|
|
1465
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
1466
|
+
res.end(JSON.stringify({ status: 'error', error: 'internal' }));
|
|
1467
|
+
});
|
|
1468
|
+
return;
|
|
1469
|
+
}
|
|
1470
|
+
// 扫码创建流程状态(插件轮询直到 success / error)
|
|
1471
|
+
if (req.method === 'GET' && req.url === '/api/feishu/register/status') {
|
|
1472
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1473
|
+
res.end(JSON.stringify(this.feishu.getRegisterState()));
|
|
1474
|
+
return;
|
|
1475
|
+
}
|
|
1476
|
+
// 取消进行中的扫码创建流程(用户关闭设置弹窗)
|
|
1477
|
+
if (req.method === 'POST' && req.url === '/api/feishu/register/cancel') {
|
|
1478
|
+
this.feishu.cancelRegister();
|
|
1479
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1480
|
+
res.end(JSON.stringify({ ok: true }));
|
|
1481
|
+
return;
|
|
1482
|
+
}
|
|
1345
1483
|
if (req.method === 'POST' && req.url === '/api/feishu/config') {
|
|
1346
1484
|
let body = '';
|
|
1347
1485
|
req.on('data', (chunk) => { body += chunk.toString(); });
|
|
@@ -1541,7 +1679,7 @@ class McpFeedbackServer {
|
|
|
1541
1679
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1542
1680
|
res.end(JSON.stringify({
|
|
1543
1681
|
status: 'ok',
|
|
1544
|
-
version:
|
|
1682
|
+
version: PKG_VERSION,
|
|
1545
1683
|
hasCurrentRequest: this.currentRequest !== null,
|
|
1546
1684
|
pid: process.pid,
|
|
1547
1685
|
}));
|
package/dist/webview/index.html
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><path d="M2 12h20"/><path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20"/></svg>
|
|
21
21
|
</button>
|
|
22
22
|
<button id="autoRetryBtn" class="iconbtn" type="button" aria-label="Timeout keep-waiting / 超时续期" data-tip="Timeout keep-waiting / 超时续期">
|
|
23
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="
|
|
23
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 22h14"/><path d="M5 2h14"/><path d="M17 22v-4.172a2 2 0 0 0-.586-1.414L12 12l-4.414 4.414A2 2 0 0 0 7 17.828V22"/><path d="M7 2v4.172a2 2 0 0 0 .586 1.414L12 12l4.414-4.414A2 2 0 0 0 17 6.172V2"/></svg>
|
|
24
24
|
</button>
|
|
25
25
|
<button id="themeAccentBtn" class="iconbtn" type="button" aria-label="Accent color / 主题色" data-tip="Accent color / 主题色">
|
|
26
26
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="13.5" cy="6.5" r=".5" fill="currentColor"/><circle cx="17.5" cy="10.5" r=".5" fill="currentColor"/><circle cx="8.5" cy="7.5" r=".5" fill="currentColor"/><circle cx="6.5" cy="12.5" r=".5" fill="currentColor"/><path d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10c.926 0 1.648-.746 1.648-1.688 0-.437-.18-.835-.437-1.125-.29-.289-.438-.652-.438-1.125a1.64 1.64 0 0 1 1.668-1.668h1.996c3.051 0 5.555-2.503 5.555-5.555C21.965 6.012 17.461 2 12 2z"/></svg>
|
|
@@ -37,13 +37,20 @@
|
|
|
37
37
|
</div>
|
|
38
38
|
</header>
|
|
39
39
|
|
|
40
|
-
<!--
|
|
40
|
+
<!-- 等待态(排队模式下排队消息列表内嵌在此卡片中,整体高度由分隔条控制) -->
|
|
41
41
|
<section id="waitingStatus" class="empty">
|
|
42
42
|
<div class="empty__icon" aria-hidden="true">
|
|
43
43
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
|
|
44
44
|
</div>
|
|
45
45
|
<p class="empty__title">{{i18n.waitingForAI}}</p>
|
|
46
46
|
<p class="empty__hint">{{i18n.waitingHint}}</p>
|
|
47
|
+
<div id="queueCard" class="waiting-queue hidden">
|
|
48
|
+
<div class="waiting-queue__head">
|
|
49
|
+
<span class="waiting-queue__title">{{i18n.queuedTitle}}</span>
|
|
50
|
+
<span id="queueCount" class="queue-count"></span>
|
|
51
|
+
</div>
|
|
52
|
+
<div id="queueList" class="queue-list"></div>
|
|
53
|
+
</div>
|
|
47
54
|
</section>
|
|
48
55
|
|
|
49
56
|
<!-- 反馈表单 -->
|
|
@@ -55,6 +62,16 @@
|
|
|
55
62
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .962 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.962 0z"/><path d="M20 3v4"/><path d="M22 5h-4"/></svg>
|
|
56
63
|
</span>
|
|
57
64
|
<h2 class="card__title">{{i18n.aiSummary}}</h2>
|
|
65
|
+
<!-- 历史摘要导航:存过 2 条以上时显示,可回看之前轮次的 AI 摘要 -->
|
|
66
|
+
<div id="summaryNav" class="summary-nav" hidden>
|
|
67
|
+
<button id="summaryPrevBtn" class="iconbtn summary-nav__btn" type="button" aria-label="{{i18n.summaryPrevTip}}" data-tip="{{i18n.summaryPrevTip}}">
|
|
68
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m15 18-6-6 6-6"/></svg>
|
|
69
|
+
</button>
|
|
70
|
+
<span id="summaryNavPos" class="summary-nav__pos"></span>
|
|
71
|
+
<button id="summaryNextBtn" class="iconbtn summary-nav__btn" type="button" aria-label="{{i18n.summaryNextTip}}" data-tip="{{i18n.summaryNextTip}}">
|
|
72
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m9 18 6-6-6-6"/></svg>
|
|
73
|
+
</button>
|
|
74
|
+
</div>
|
|
58
75
|
</div>
|
|
59
76
|
<div id="summaryContent" class="summary markdown-body"></div>
|
|
60
77
|
<div id="projectInfo" class="project-chip"></div>
|
|
@@ -183,7 +200,17 @@
|
|
|
183
200
|
</div>
|
|
184
201
|
</div>
|
|
185
202
|
|
|
186
|
-
<!--
|
|
203
|
+
<!-- 区块②:忙时消息排队(全局开关:飞书与面板消息共用忙时队列) -->
|
|
204
|
+
<div class="feishu-section">
|
|
205
|
+
<label class="feishu-switch">
|
|
206
|
+
<span class="feishu-switch__label feishu-switch__label--strong">{{i18n.queueWhenBusyLabel}}</span>
|
|
207
|
+
<input id="queueWhenBusyToggle" type="checkbox" class="feishu-switch__input">
|
|
208
|
+
<span class="feishu-switch__track"><span class="feishu-switch__thumb"></span></span>
|
|
209
|
+
</label>
|
|
210
|
+
<p class="feishu-modal__desc">{{i18n.queueWhenBusyDesc}}</p>
|
|
211
|
+
</div>
|
|
212
|
+
|
|
213
|
+
<!-- 区块③:飞书通知(推送开关 + 凭证 + 绑定状态) -->
|
|
187
214
|
<div class="feishu-section">
|
|
188
215
|
<label class="feishu-switch">
|
|
189
216
|
<span class="feishu-switch__label feishu-switch__label--strong">{{i18n.feishuSettings}}</span>
|
|
@@ -199,14 +226,6 @@
|
|
|
199
226
|
</label>
|
|
200
227
|
<p class="feishu-modal__desc feishu-modal__desc--sub">{{i18n.feishuAckDesc}}</p>
|
|
201
228
|
</div>
|
|
202
|
-
<div id="feishuQueueSub" class="feishu-subitem">
|
|
203
|
-
<label class="feishu-switch">
|
|
204
|
-
<span class="feishu-switch__label">{{i18n.feishuQueueLabel}}</span>
|
|
205
|
-
<input id="feishuQueueToggle" type="checkbox" class="feishu-switch__input">
|
|
206
|
-
<span class="feishu-switch__track"><span class="feishu-switch__thumb"></span></span>
|
|
207
|
-
</label>
|
|
208
|
-
<p class="feishu-modal__desc feishu-modal__desc--sub">{{i18n.feishuQueueDesc}}</p>
|
|
209
|
-
</div>
|
|
210
229
|
<label class="feishu-field">
|
|
211
230
|
<span class="feishu-field__label">{{i18n.feishuAppId}}</span>
|
|
212
231
|
<input id="feishuAppId" class="feishu-input" type="text" placeholder="{{i18n.feishuAppIdPlaceholder}}" autocomplete="off" spellcheck="false">
|
|
@@ -226,7 +245,16 @@
|
|
|
226
245
|
<span id="feishuStatusText" class="feishu-status__text">{{i18n.feishuStatusUnconfigured}}</span>
|
|
227
246
|
</div>
|
|
228
247
|
<div class="feishu-modal__actions">
|
|
229
|
-
<button id="
|
|
248
|
+
<button id="feishuRegisterBtn" class="feishu-link feishu-link--primary" type="button">{{i18n.feishuRegisterBtn}}</button>
|
|
249
|
+
</div>
|
|
250
|
+
<!-- 扫码一键创建应用:点击上方按钮后展开,展示验证二维码与流程状态 -->
|
|
251
|
+
<div id="feishuRegisterPanel" class="feishu-register hidden">
|
|
252
|
+
<img id="feishuRegisterQr" class="feishu-register__qr hidden" alt="QR code">
|
|
253
|
+
<p id="feishuRegisterHint" class="feishu-register__hint"></p>
|
|
254
|
+
<div class="feishu-register__actions">
|
|
255
|
+
<button id="feishuRegisterOpenBtn" class="feishu-link hidden" type="button">{{i18n.registerOpenLink}}</button>
|
|
256
|
+
<button id="feishuRegisterRetryBtn" class="feishu-link hidden" type="button">{{i18n.registerRetry}}</button>
|
|
257
|
+
</div>
|
|
230
258
|
</div>
|
|
231
259
|
<p class="feishu-modal__hint">{{i18n.feishuHint}}</p>
|
|
232
260
|
</div>
|