// ---- 动态主题色 bootstrap(spec §3):?color= → 派生 → :root 变量 ---- (function () { if (typeof ChatTheme === "undefined") return; // theme.js 加载失败→style.css :root 默认橙兜底 var m = /(?:^|[?&])color=([^&]*)/.exec(location.search); function safeDecode(s) { try { return decodeURIComponent(s); } catch (e) { return ""; } } var color = m ? safeDecode(m[1]).trim() : ""; var palette = ChatTheme.derivePalette(color || ChatTheme.DEFAULT_COLOR); var root = document.documentElement.style; root.setProperty("--w-primary", palette.primary); root.setProperty("--w-primary-rgb", palette.primaryRgb); root.setProperty("--w-gradient-end", palette.gradientEnd); root.setProperty("--w-deep-text", palette.deepText); root.setProperty("--w-caption-text", palette.captionText); root.setProperty("--w-soft-bg", palette.softBg); root.setProperty("--w-border-soft", palette.borderSoft); // 头像兜底(与挂件同机制) Array.prototype.forEach.call(document.querySelectorAll(".avatar-img"), function (img) { function hideEmoji() { var s = img.parentElement && img.parentElement.querySelector(".avatar-emoji"); if (s) s.style.display = "none"; } if (img.complete && img.naturalWidth > 0) { hideEmoji(); return; } img.addEventListener("load", hideEmoji); img.addEventListener("error", function () { img.remove(); }); }); })(); (function () { // 联系方式配置(与 widget.js 的 data-* 机制一致) var chatScript = document.currentScript || document.querySelector('script[src*="chat.js"]'); var whatsapp = (chatScript && chatScript.getAttribute("data-whatsapp")) || "https://wa.me/+8619037179250"; var email = (chatScript && chatScript.getAttribute("data-email")) || "mailto:info@borderlesscar.com"; var waBtn = document.querySelector('.whatsapp-btn'); var emBtn = document.querySelector('.email-btn'); if (waBtn) waBtn.href = whatsapp; if (emBtn) emBtn.href = email; var sessionId = localStorage.getItem("cc_session_id"); var messagesEl = document.getElementById("messages"); var inputEl = document.getElementById("input"); var sendBtn = document.getElementById("sendBtn"); var typingEl = document.getElementById("typing"); var endedEl = document.getElementById("chatEnded"); var popularTopicsEl = document.getElementById("popular-topics"); var ws = null; var ended = false; // Current streaming message element var streamBubble = null; var statusTimer = null; function getWsUrl() { var loc = window.location; var proto = loc.protocol === "https:" ? "wss:" : "ws:"; return proto + "//" + loc.host + "/ws/chat"; } function connect() { ws = new WebSocket(getWsUrl()); ws.onopen = function () { if (sessionId) { ws.send(JSON.stringify({ session_id: sessionId })); } else { ws.send(JSON.stringify({ source: "standalone" })); } }; ws.onmessage = function (e) { var data = JSON.parse(e.data); if (data.type === "init" || data.type === "resumed") { sessionId = data.session_id; localStorage.setItem("cc_session_id", sessionId); if (data.type === "resumed") { loadHistory(); } } if (data.type === "stream_start") { clearStatusTimer(); hideTyping(); streamBubble = addStreamBubble(); } if (data.type === "stream_chunk") { if (streamBubble) { streamBubble.textContent += data.content; messagesEl.scrollTop = messagesEl.scrollHeight; } } if (data.type === "stream_end") { streamBubble = null; } if (data.type === "evaluated") { if (data.ended) { ended = true; endedEl.classList.add("active"); inputEl.disabled = true; sendBtn.disabled = true; } } if (data.type === "error") { clearStatusTimer(); hideTyping(); streamBubble = null; addMessage("assistant", "抱歉,出了点问题,请稍后再试。"); } }; ws.onclose = function () { if (!ended) { setTimeout(connect, 3000); } }; ws.onerror = function () { ws.close(); }; } function send() { var text = inputEl.value.trim(); if (!text || ended || !ws || ws.readyState !== WebSocket.OPEN) return; addMessage("user", text); inputEl.value = ""; hidePopularTopics(); // Immediate feedback: show status then typing showStatusThenTyping(); ws.send(JSON.stringify({ content: text })); } function sendTopicMessage(topic) { if (ended || !ws || ws.readyState !== WebSocket.OPEN) return; addMessage("user", topic); hidePopularTopics(); // Immediate feedback: show status then typing showStatusThenTyping(); ws.send(JSON.stringify({ content: topic })); } function hidePopularTopics() { if (popularTopicsEl) { popularTopicsEl.style.display = "none"; } } function showStatusThenTyping() { clearStatusTimer(); showTyping(); // Auto-hide typing is handled by stream_start replacing it } function clearStatusTimer() { if (statusTimer) { clearTimeout(statusTimer); statusTimer = null; } } function addMessage(role, content) { var div = document.createElement("div"); div.className = "message " + role; var wrapper = document.createElement("div"); var bubble = document.createElement("div"); bubble.className = "bubble"; bubble.textContent = content; wrapper.appendChild(bubble); div.appendChild(wrapper); messagesEl.appendChild(div); messagesEl.scrollTop = messagesEl.scrollHeight; } function addStreamBubble() { var div = document.createElement("div"); div.className = "message assistant"; var wrapper = document.createElement("div"); var bubble = document.createElement("div"); bubble.className = "bubble"; bubble.textContent = ""; wrapper.appendChild(bubble); div.appendChild(wrapper); messagesEl.appendChild(div); messagesEl.scrollTop = messagesEl.scrollHeight; return bubble; } function showTyping() { typingEl.classList.add("active"); messagesEl.scrollTop = messagesEl.scrollHeight; } function hideTyping() { typingEl.classList.remove("active"); } function loadHistory() { } sendBtn.addEventListener("click", send); inputEl.addEventListener("keydown", function (e) { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); send(); } }); // Popular topics buttons var topicBtns = document.querySelectorAll(".popular-topic-btn"); topicBtns.forEach(function (btn) { btn.addEventListener("click", function () { var topic = this.getAttribute("data-topic"); sendTopicMessage(topic); }); }); connect(); })();