1- // 原生消息类
2-
31/**
42 * 在页面右下角显示一条临时消息提示(Toast)
53 *
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 */
1612function 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- / E r r o r | 异 常 | 失 败 | 无 法 | s o r r y | u n d e f i n e d | 分 析 失 败 / i. test ( clean )
363- ) {
364- return null ; // 表示无效注释
415+ if ( ! clean || clean . length === 0 || clean . length > 30 || / E r r o r | 异 常 | 失 败 | 无 法 | s o r r y | u n d e f i n e d | 分 析 失 败 / 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