Add AI chat backend via OpenRouter with knowledge-base fallback
POST /api/public/chat proxies to OpenRouter (model from OPENROUTER_MODEL, default deepseek/deepseek-v4-flash:nitro) with a system prompt built from live site config, sponsor queue, and strict guardrails (no income claims, seed-phrase warnings, Telegram fallback). Rate-limited per IP. Without OPENROUTER_API_KEY set, or on any API error, the widget transparently falls back to the built-in knowledge base, so the chat always works. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+16
-2
@@ -70,8 +70,22 @@
|
||||
document.body.appendChild(fab);document.body.appendChild(panel);
|
||||
const msgs=panel.querySelector('.chat-msgs'),chipsEl=panel.querySelector('.chat-chips'),form=panel.querySelector('.chat-input'),input=form.querySelector('input');
|
||||
|
||||
function addMsg(html,who){const d=document.createElement('div');d.className=`chat-msg ${who}`;if(who==='user'){d.textContent=html}else{d.innerHTML=html}msgs.appendChild(d);msgs.scrollTop=msgs.scrollHeight}
|
||||
function ask(q){addMsg(q,'user');setTimeout(()=>{addMsg(answer(q),'bot');},250)}
|
||||
function addMsg(html,who){const d=document.createElement('div');d.className=`chat-msg ${who}`;if(who==='user'){d.textContent=html}else{d.innerHTML=html}msgs.appendChild(d);msgs.scrollTop=msgs.scrollHeight;return d}
|
||||
function linkify(text){let h=esc(text);h=h.replace(/(https?:\/\/[^\s&<]+[^\s&<.,)])/g,u=>{const site=u.startsWith('https://rmcircle.saasy.top');const path=site?u.replace('https://rmcircle.saasy.top',''):u;return `<a href="${site?(path||'/'):u}"${site?'':' target="_blank" rel="noopener noreferrer"'}>${u}</a>`});return h.replace(/\n/g,'<br>')}
|
||||
const history=[];let aiDown=false;
|
||||
function stripHtml(h){const d=document.createElement('div');d.innerHTML=h;return d.textContent||''}
|
||||
async function ask(q){
|
||||
addMsg(q,'user');history.push({role:'user',content:q});
|
||||
if(aiDown){const a=answer(q);addMsg(a,'bot');history.push({role:'assistant',content:stripHtml(a)});return}
|
||||
const typing=addMsg('<span class="chat-typing">…</span>','bot');
|
||||
try{
|
||||
const r=await fetch('/api/public/chat',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({messages:history.slice(-8)})});
|
||||
const d=await r.json();
|
||||
typing.remove();
|
||||
if(d.reply){addMsg(linkify(d.reply),'bot');history.push({role:'assistant',content:d.reply})}
|
||||
else{if(d.fallback)aiDown=true;const a=answer(q);addMsg(a,'bot');history.push({role:'assistant',content:stripHtml(a)})}
|
||||
}catch(e){typing.remove();aiDown=true;const a=answer(q);addMsg(a,'bot');history.push({role:'assistant',content:stripHtml(a)})}
|
||||
}
|
||||
|
||||
let started=false;
|
||||
function open(){
|
||||
|
||||
@@ -44,3 +44,5 @@ a{color:inherit}.wrap{width:min(1160px,calc(100% - 32px));margin:auto}.nav{heigh
|
||||
.chat-foot{padding:8px 14px 12px;font-size:12px;color:var(--muted);text-align:center}
|
||||
.chat-foot a{color:var(--teal)}
|
||||
.chat-hidden{display:none}
|
||||
.chat-typing{letter-spacing:3px;animation:chatpulse 1s infinite}
|
||||
@keyframes chatpulse{50%{opacity:.35}}
|
||||
|
||||
Reference in New Issue
Block a user