Skip to content

Commit 6dbe8b7

Browse files
committed
更友好的AI辅助提示
1 parent 27ede40 commit 6dbe8b7

File tree

2 files changed

+127
-78
lines changed

2 files changed

+127
-78
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
1. 新增主窗口支持最大化和最小化
66
2. 新增Ctrl+S触发保存
7+
3. 更友好的AI辅助提示
78

89
# 2.1.1
910

iframe/script/User_config/message.js

Lines changed: 126 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
// 原生消息类
2-
31
/**
42
* 在页面右下角显示一条临时消息提示(Toast)
53
*
@@ -8,9 +6,7 @@
86
* @param {number} [options.duration=3000] - 消息自动消失的毫秒数,默认 3000ms
97
* @param {string} [options.type='info'] - 消息类型,可选 'info' | 'success' | 'warning' | 'error'
108
* @param {boolean} [options.allowMultiple=false] - 是否允许多条消息同时存在
11-
*
129
* @returns {void}
13-
*
1410
* @throws {TypeError} 当 message 不是字符串时抛出错误
1511
*/
1612
function showToast(message, options = {}) {
@@ -36,13 +32,13 @@ function showToast(message, options = {}) {
3632
toastContainer = document.createElement('div');
3733
toastContainer.id = '__toast-container';
3834
toastContainer.style.cssText = `
39-
position: fixed;
40-
bottom: 20px;
41-
right: 20px;
42-
z-index: 10000;
43-
max-width: 80vw;
44-
pointer-events: none;
45-
`;
35+
position: fixed;
36+
bottom: 20px;
37+
right: 20px;
38+
z-index: 10000;
39+
max-width: 80vw;
40+
pointer-events: none;
41+
`;
4642
document.body.appendChild(toastContainer);
4743
}
4844
if (!config.allowMultiple) {
@@ -51,34 +47,34 @@ function showToast(message, options = {}) {
5147
const toastEl = document.createElement('div');
5248
toastEl.textContent = message;
5349
toastEl.style.cssText = `
54-
background: ${
55-
config.type === 'success' ? '#4caf50' : config.type === 'warning' ? '#ff9800' : config.type === 'error' ? '#f44336' : '#333'
56-
};
57-
color: white;
58-
padding: 12px 16px;
59-
border-radius: 6px;
60-
margin-top: 8px;
61-
font-size: 14px;
62-
line-height: 1.4;
63-
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
64-
opacity: 0;
65-
transform: translateY(20px);
66-
transition: opacity 0.3s ease, transform 0.3s ease;
67-
animation: __toast-fade-in 0.3s forwards;
68-
pointer-events: auto;
69-
word-break: break-word;
70-
`;
50+
background: ${
51+
config.type === 'success' ? '#4caf50' : config.type === 'warning' ? '#ff9800' : config.type === 'error' ? '#f44336' : '#333'
52+
};
53+
color: white;
54+
padding: 12px 16px;
55+
border-radius: 6px;
56+
margin-top: 8px;
57+
font-size: 14px;
58+
line-height: 1.4;
59+
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
60+
opacity: 0;
61+
transform: translateY(20px);
62+
transition: opacity 0.3s ease, transform 0.3s ease;
63+
animation: __toast-fade-in 0.3s forwards;
64+
pointer-events: auto;
65+
word-break: break-word;
66+
`;
7167
if (!document.getElementById('__toast-styles')) {
7268
const styleEl = document.createElement('style');
7369
styleEl.id = '__toast-styles';
7470
styleEl.textContent = `
75-
@keyframes __toast-fade-in {
76-
to { opacity: 1; transform: translateY(0); }
77-
}
78-
@keyframes __toast-fade-out {
79-
to { opacity: 0; transform: translateY(20px); }
80-
}
81-
`;
71+
@keyframes __toast-fade-in {
72+
to { opacity: 1; transform: translateY(0); }
73+
}
74+
@keyframes __toast-fade-out {
75+
to { opacity: 0; transform: translateY(20px); }
76+
}
77+
`;
8278
document.head.appendChild(styleEl);
8379
}
8480
toastContainer.appendChild(toastEl);
@@ -103,15 +99,26 @@ function showToast(message, options = {}) {
10399
}
104100
}
105101

106-
function activateECTAI() {
102+
/**
103+
* 激活 ECT AI 辅助功能
104+
*
105+
* 此函数负责:
106+
* 1. 检查 Ace 编辑器是否存在
107+
* 2. 尝试连接后端 AI 服务(/health 接口)
108+
* 3. 初始化运行按钮拦截与智能注释功能
109+
*
110+
* @returns {void}
111+
*/
112+
async function activateECTAI() {
107113
if (window.__eda_ai_injected__) {
108-
console.log('[AI Assist] 已激活,跳过重复注入');
114+
eda.sys_Message.showToastMessage('服务已连接,请勿重复唤起', 'info', 1);
109115
return;
110116
}
111117
window.__eda_ai_injected__ = true;
112118

113119
if (typeof ace === 'undefined') {
114120
console.error('[AI Assist] Ace 编辑器未加载');
121+
eda.sys_Message.showToastMessage('Ace 编辑器未加载', 'error');
115122
return;
116123
}
117124

@@ -120,21 +127,70 @@ function activateECTAI() {
120127
editor = ace.edit('editor');
121128
} catch (e) {
122129
console.error('[AI Assist] 无法获取编辑器:', e);
130+
eda.sys_Message.showToastMessage('无法获取编辑器', 'error');
123131
return;
124132
}
125133

126134
window._ai_editor = editor;
127135

128-
// ========== 原生 Toast(用于注释、加载等简单提示)==========
129-
const safeShowToast =
130-
typeof window.showToast === 'function' ?
131-
(msg) => {
132-
console.log('[TOAST]', msg);
133-
window.showToast(msg);
134-
} :
135-
(msg) => console.warn('[FALLBACK TOAST]', msg);
136+
// 后端健康检查配置
137+
const BACKEND_URL = 'http://localhost:5000';
138+
const MAX_RETRIES = 5;
139+
const INTERVAL_MS = 1;
140+
141+
let isConnected = false;
142+
let retryCount = 0;
136143

137-
// ========== 自定义 ECT 错误分析提示(暗色 + 进度条 + 精确重置倒计时)==========
144+
/**
145+
* 检查后端健康状态
146+
*
147+
* @returns {Promise<boolean>} 是否连接成功且配置完整
148+
*/
149+
const checkHealth = async () => {
150+
try {
151+
const res = await fetch(`${BACKEND_URL}/health`, { method: 'GET' });
152+
if (res.ok) {
153+
const data = await res.json();
154+
if (data.status === 'ok' && data.sk_set) {
155+
isConnected = true;
156+
eda.sys_Message.showToastMessage('连接成功!', 'success');
157+
return true;
158+
} else {
159+
console.warn('[AI Assist] 后端配置不完整:', data);
160+
return false;
161+
}
162+
}
163+
} catch (err) {
164+
console.warn('[AI Assist] 健康检查失败:', err.message);
165+
}
166+
return false;
167+
};
168+
169+
// 初始提示
170+
eda.sys_Message.showToastMessage('正在尝试连接AI服务器...');
171+
172+
while (retryCount < MAX_RETRIES && !isConnected) {
173+
if (retryCount > 0) {
174+
await new Promise((resolve) => setTimeout(resolve, INTERVAL_MS));
175+
eda.sys_Message.showToastMessage('正在尝试链接AI服务器...');
176+
}
177+
178+
isConnected = await checkHealth();
179+
retryCount++;
180+
}
181+
182+
if (!isConnected) {
183+
eda.sys_Message.showToastMessage('连接超时:无法连接到AI服务器', 'error');
184+
return;
185+
}
186+
187+
/**
188+
* 显示自定义 ECT 错误分析提示框
189+
*
190+
* @param {string} message - 要显示的错误解释文本
191+
* @param {boolean} [isPersistent=false] - 是否为持久提示(不自动消失)
192+
* @returns {Function} 清理函数,用于手动移除提示
193+
*/
138194
function showECTMessage(message, isPersistent = false) {
139195
const existing = document.getElementById('ect-toast');
140196
if (existing) existing.remove();
@@ -163,7 +219,6 @@ function activateECTAI() {
163219
gap: 12px;
164220
`;
165221

166-
// 进度条容器
167222
const progressContainer = document.createElement('div');
168223
progressContainer.style.cssText = `
169224
height: 4px;
@@ -186,7 +241,6 @@ function activateECTAI() {
186241
`;
187242
progressContainer.appendChild(progressBar);
188243

189-
// 内容与按钮区
190244
const contentWrapper = document.createElement('div');
191245
contentWrapper.style.display = 'flex';
192246
contentWrapper.style.justifyContent = 'space-between';
@@ -202,7 +256,6 @@ function activateECTAI() {
202256
buttonGroup.style.display = 'flex';
203257
buttonGroup.style.gap = '8px';
204258

205-
// 复制按钮
206259
const copyBtn = document.createElement('button');
207260
copyBtn.textContent = '复制';
208261
copyBtn.style.cssText = `
@@ -234,7 +287,6 @@ function activateECTAI() {
234287
}
235288
};
236289

237-
// 关闭按钮
238290
const closeBtn = document.createElement('button');
239291
closeBtn.textContent = '×';
240292
closeBtn.style.cssText = `
@@ -268,11 +320,10 @@ function activateECTAI() {
268320
container.appendChild(contentWrapper);
269321
document.body.appendChild(container);
270322

271-
// ====== 精确倒计时(悬停离开后重置为完整5秒)======
272323
let timeoutId = null;
273324
let startTime = null;
274325
let isHovered = false;
275-
const DURATION = 5000; // 5秒
326+
const DURATION = 5000;
276327

277328
function updateProgress() {
278329
if (isPersistent || isHovered) return;
@@ -296,7 +347,7 @@ function activateECTAI() {
296347
function resetTimer() {
297348
if (isPersistent) return;
298349
if (timeoutId) clearTimeout(timeoutId);
299-
startTimer(); // 重新开始完整5秒
350+
startTimer();
300351
}
301352

302353
container.addEventListener('mouseenter', () => {
@@ -306,21 +357,28 @@ function activateECTAI() {
306357

307358
container.addEventListener('mouseleave', () => {
308359
isHovered = false;
309-
resetTimer(); // ⭐ 关键:离开后重新计时5秒
360+
resetTimer();
310361
});
311362

312363
if (!isPersistent) {
313364
startTimer();
314365
}
315366

316-
// 返回清理函数(当前未使用,但保留接口)
317367
return () => {
318368
if (container.parentNode) container.remove();
319369
if (timeoutId) clearTimeout(timeoutId);
320370
};
321371
}
372+
373+
/**
374+
* 调用 AI 后端服务
375+
*
376+
* @param {string} prompt - 发送给 AI 的提示文本
377+
* @param {string} [mode='comment'] - 模式:'comment' 或 'error'
378+
* @returns {Promise<string|null>} AI 返回的文本,若无效则返回 null
379+
*/
322380
async function callAI(prompt, mode = 'comment') {
323-
const backendUrl = 'http://localhost:5000/chat';
381+
const backendUrl = `${BACKEND_URL}/chat`;
324382

325383
try {
326384
const controller = new AbortController();
@@ -352,16 +410,10 @@ function activateECTAI() {
352410

353411
let clean = result.trim();
354412

355-
// ✅ 仅对注释模式做严格过滤;错误分析模式不过滤
356413
if (mode === 'comment') {
357414
clean = clean.replace(/[.,!?;]+$/, '');
358-
if (
359-
!clean ||
360-
clean.length === 0 ||
361-
clean.length > 30 || // ← 限制注释长度
362-
/Error||||sorry|undefined|/i.test(clean)
363-
) {
364-
return null; // 表示无效注释
415+
if (!clean || clean.length === 0 || clean.length > 30 || /Error||||sorry|undefined|/i.test(clean)) {
416+
return null;
365417
}
366418
}
367419

@@ -372,27 +424,26 @@ function activateECTAI() {
372424
}
373425
}
374426

375-
// ========== 拦截“运行”按钮 ==========
427+
// 拦截“运行”按钮
376428
const runBtn = document.getElementById('run-btn');
377429
if (runBtn) {
378430
const newBtn = runBtn.cloneNode(true);
379431
runBtn.parentNode.replaceChild(newBtn, runBtn);
380432
newBtn.addEventListener('click', async () => {
381433
const code = editor.getValue().trim();
382434
if (!code) {
383-
safeShowToast('代码为空');
435+
eda.sys_Message.showToastMessage('代码为空');
384436
return;
385437
}
386438
try {
387439
(0, eval)(code);
388440
} catch (error) {
389-
console.error('执行出错:', error);
390-
safeShowToast('检测到错误,ECT 正在分析原因...');
441+
console.error('执行出错:', error);
442+
eda.sys_Message.showToastMessage('检测到错误,ECT 正在分析原因...');
391443
const explanation = await callAI(
392444
`[ERROR_ANALYSIS] 请用中文简明解释以下 JavaScript 错误:\n错误信息: ${error.message}\n代码:\n${code}`,
393445
'error',
394446
);
395-
// ✅ 关键修改:错误分析结果用 showECTMessage 显示(不是 showToast)
396447
if (explanation && !explanation.includes('ECT 分析失败')) {
397448
showECTMessage(explanation);
398449
} else {
@@ -402,7 +453,7 @@ function activateECTAI() {
402453
});
403454
}
404455

405-
// ========== 智能注释 ==========
456+
// 智能注释功能
406457
let debounceTimer = null;
407458
let lastTriggerAt = 0;
408459
const COOLDOWN = 1500;
@@ -420,16 +471,16 @@ function activateECTAI() {
420471
lastTriggerAt = Date.now();
421472
const targetRow = cursor.row - 1;
422473
if (targetRow < 0) {
423-
safeShowToast('上方无代码可注释');
474+
eda.sys_Message.showToastMessage('上方无代码可注释');
424475
return;
425476
}
426477
const codeLine = editor.session.getLine(targetRow).trim();
427478
if (!codeLine || codeLine.startsWith('//')) {
428-
safeShowToast('上方不是有效代码');
479+
eda.sys_Message.showToastMessage('上方不是有效代码');
429480
return;
430481
}
431482

432-
safeShowToast('ECT 正在生成注释...');
483+
eda.sys_Message.showToastMessage('ECT 正在生成注释...');
433484
const comment = await callAI(
434485
`[LINE_COMMENT] 请为以下 JavaScript 代码行生成一句简短中文注释(10字以内,不要标点):\n${codeLine}`,
435486
'comment',
@@ -439,12 +490,9 @@ function activateECTAI() {
439490
const pos = { row: cursor.row, column: editor.session.getLine(cursor.row).length };
440491
editor.session.insert(pos, ` ${comment}`);
441492
} else {
442-
safeShowToast('ECT 暂无法生成注释');
493+
eda.sys_Message.showToastMessage('ECT 暂无法生成注释');
443494
}
444495
}, 600);
445496
}
446497
});
447-
448-
console.log('[AI Assist] ECT AI 已激活 ✅');
449-
eda.sys_Message.showToastMessage('AI辅助已激活,请确保后端连接正常');
450-
}
498+
}

0 commit comments

Comments
 (0)