diff --git a/docs/index.html b/docs/index.html index 621b704f..d368a4ad 100644 --- a/docs/index.html +++ b/docs/index.html @@ -2303,6 +2303,7 @@ async function send(){ // 3. 调用API —— 优先用用户自己的API Key(直接走用户网络),其次尝试后端 sending=true;document.getElementById('chatSnd').disabled=true;showTyp(); var replied=false; + var backendError=''; // 用户配了API Key → 直接用用户的网络通道调用,不经过后端 if(apiCfg.connected&&apiCfg.key){ @@ -2327,28 +2328,48 @@ async function send(){ document.getElementById('mM').textContent=apiCfg.model; replied=true; } - }catch(e){} + }catch(e){backendError='用户API: '+(e.name==='TimeoutError'?'请求超时':e.message)} } - // 用户没配Key或调用失败 → 尝试后端 + // 用户没配Key或调用失败 → 尝试后端(含自动重试) if(!replied){ - try{ - var res=await fetch('/api/chat',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({message:msg,userId:usr.id}),signal:AbortSignal.timeout(60000)}); - var data=await res.json();hideTyp(); - if(data.message){ - replied=true; - chatMsg(data.message,'b',{m:data.model||'铸渊',t:data.tier||''}); - if(data.tier!=='free')mCalls++; - document.getElementById('mC').textContent=mCalls; - document.getElementById('chatTg').textContent=data.model||'ready'; - if(data.model)document.getElementById('mM').textContent=data.model; + var retryCount=0;var maxRetries=2; + while(retryCount<=maxRetries&&!replied){ + try{ + var res=await fetch('/api/chat',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({message:msg,userId:usr.id}),signal:AbortSignal.timeout(retryCount===0?60000:30000)}); + if(!res.ok){ + backendError='服务器返回 '+res.status; + retryCount++;continue; + } + var data=await res.json();hideTyp(); + if(data.message){ + replied=true; + chatMsg(data.message,'b',{m:data.model||'铸渊',t:data.tier||''}); + if(data.tier!=='free')mCalls++; + document.getElementById('mC').textContent=mCalls; + document.getElementById('chatTg').textContent=data.model||'ready'; + if(data.model)document.getElementById('mM').textContent=data.model; + }else if(data.error){ + backendError=data.message||'服务端错误'; + retryCount++;continue; + } + break; + }catch(e){ + backendError=e.name==='TimeoutError'?'后端响应超时':'网络连接失败'; + retryCount++; + if(retryCount<=maxRetries){await new Promise(function(r){setTimeout(r,1000*retryCount)})} } - }catch(e){} + } } - // 所有外部通道都失败 → 用本地智能回复(用户浏览器就是计算资源) + // 所有外部通道都失败 → 用本地智能回复 + 显示连接状态 if(!replied){ - hideTyp();chatMsg(offline(msg),'b',{m:'铸渊',t:'本地智能'}); + hideTyp(); + var offlineMsg=offline(msg); + if(backendError){offlineMsg+='\n\n⚠️ 后端状态: '+backendError+'\n点击右下角💬重试,或配置API密钥直连大模型。'} + chatMsg(offlineMsg,'b',{m:'铸渊',t:'本地智能'}); + document.getElementById('chatTg').textContent='离线'; + document.getElementById('chatTg').style.color='var(--err,#f87171)'; } sending=false;document.getElementById('chatSnd').disabled=false; @@ -2761,7 +2782,11 @@ checkConn();setInterval(checkConn,30000); chatOverlay.classList.toggle('open',chatOpen); fab.classList.toggle('on',chatOpen); fab.textContent=chatOpen?'✕':'💬'; - if(chatOpen)setTimeout(function(){chatIn.focus()},200); + if(chatOpen){ + setTimeout(function(){chatIn.focus()},200); + // 打开聊天时预检后端连接状态 + checkChatBackend(); + } }; fab.onclick=function(e){ if(hasMoved){hasMoved=false;return} @@ -2773,6 +2798,59 @@ checkConn();setInterval(checkConn,30000); }; })(); +/* ════════════════════════════════════════ + CHAT BACKEND HEALTH CHECK (打开聊天时预检) + ════════════════════════════════════════ */ +var chatBackendOk=null; +async function checkChatBackend(){ + var tg=document.getElementById('chatTg'); + try{ + var r=await fetch('/api/health',{signal:AbortSignal.timeout(5000)}); + if(r.ok){ + chatBackendOk=true; + if(tg){tg.textContent='ready';tg.style.color=''} + }else{ + chatBackendOk=false; + if(tg){tg.textContent='后端异常('+r.status+')';tg.style.color='var(--err,#f87171)'} + } + }catch(e){ + chatBackendOk=false; + if(tg){tg.textContent='后端未连接';tg.style.color='var(--err,#f87171)'} + // 显示连接提示 + if(chatMs&&!document.getElementById('chatConnTip')){ + var tip=document.createElement('div'); + tip.id='chatConnTip'; + tip.className='cm s'; + tip.innerHTML='⚠️ 后端服务暂时未连接。本地智能回复可用,配置API密钥可解锁AI对话。
'; + chatMs.appendChild(tip); + chatMs.scrollTop=chatMs.scrollHeight; + } + } +} +async function retryChatBackend(){ + var tip=document.getElementById('chatConnTip'); + if(tip)tip.remove(); + var tg=document.getElementById('chatTg'); + if(tg){tg.textContent='连接中...';tg.style.color='var(--warn,#fbbf24)'} + try{ + var r=await fetch('/api/chat/reconnect',{method:'POST',signal:AbortSignal.timeout(15000)}); + var data=await r.json(); + if(data.connected){ + chatBackendOk=true; + if(tg){tg.textContent='ready';tg.style.color=''} + chatMsg(data.message||'🌊 铸渊已重新连接!','b',{m:'铸渊',t:'reconnect'}); + }else{ + chatBackendOk=false; + if(tg){tg.textContent='离线';tg.style.color='var(--err,#f87171)'} + chatMsg('⚠️ 后端大模型暂时不可用。本地智能回复仍然可用。','b',{m:'铸渊',t:'本地'}); + } + }catch(e){ + chatBackendOk=false; + if(tg){tg.textContent='离线';tg.style.color='var(--err,#f87171)'} + chatMsg('⚠️ 无法连接后端服务。请稍后重试或配置API密钥。','b',{m:'铸渊',t:'本地'}); + } +} + /* ════════════════════════════════════════ SYSTEM STATUS (左面板实时数据) ════════════════════════════════════════ */ diff --git a/server/app/server.js b/server/app/server.js index 94b0d92a..53353d5a 100644 --- a/server/app/server.js +++ b/server/app/server.js @@ -291,24 +291,42 @@ app.post('/api/chat', async (req, res) => { // 降级到通用聊天引擎 if (chatEngine) { - const result = await chatEngine.chat(sessionId, message); - return res.json({ - success: true, - ...result, - sessionId - }); + try { + const result = await chatEngine.chat(sessionId, message); + return res.json({ + success: true, + ...result, + sessionId + }); + } catch (ceErr) { + console.error(`[聊天引擎] 通用引擎异常: ${ceErr.message}`); + // 继续到离线回复 + } } - // 所有引擎都不可用 + // 所有引擎都不可用 — 仍然返回成功,给用户有意义的反馈 + const engineStatus = []; + if (!domesticGateway) engineStatus.push('国内模型网关未加载'); + else { + try { + const gwStats = domesticGateway.getGatewayStats(); + if (gwStats.availableModels === 0) engineStatus.push('无可用模型API密钥'); + else engineStatus.push(`已配置${gwStats.availableModels}个模型`); + } catch (e) { engineStatus.push('网关状态未知'); } + } + if (!chatEngine) engineStatus.push('通用引擎未加载'); + res.json({ success: true, - message: '💫 铸渊正在唤醒中...聊天引擎尚未加载。请稍后再试。', + message: '💫 铸渊正在唤醒中...聊天引擎尚未就绪。请稍后再试,或在聊天面板中配置你的API密钥直连大模型。', model: 'offline', tier: 'free', + engineStatus: engineStatus.join(' · '), sessionId }); } catch (err) { - res.status(500).json({ error: true, message: err.message }); + console.error(`[聊天API] 未捕获异常: ${err.message}`); + res.status(500).json({ error: true, message: '聊天服务暂时异常,请稍后重试' }); } }); diff --git a/server/sites/cn-landing/index.html b/server/sites/cn-landing/index.html index c8ee8428..5e2bf8b7 100644 --- a/server/sites/cn-landing/index.html +++ b/server/sites/cn-landing/index.html @@ -3,8 +3,18 @@ -光湖实验室 · GuanghuLab - +光湖实验室 · GuanghuLab — 语言驱动的智能创作平台 + + + + + + + + + + +