49c0e5559c
Co-Authored-By: Claude Code <noreply@anthropic.com>
232 lines
7.5 KiB
JavaScript
232 lines
7.5 KiB
JavaScript
// ---- 动态主题色 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();
|
|
})();
|