项目文件夹

文件

143 行
3.9 KiB
JavaScript

const API_BASE = "/api/cloud";
const STARTERS = [
"今晚沿岸有雾,怎么判断能不能开航?",
"用一段话解释什么是上下文窗口。",
"帮我把这段需求收成三条验收标准:做一个能记住对话的助手。"
];
const thread = document.getElementById("thread");
const form = document.getElementById("composer");
const prompt = document.getElementById("prompt");
const send = document.getElementById("send");
const linkStatus = document.getElementById("link-status");
const starters = document.getElementById("starters");
const KEY = "beacon-watch.thread";
function esc(value) {
return String(value ?? "")
.replaceAll("&", "&")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll('"', "&quot;");
}
function loadHistory() {
try {
const raw = localStorage.getItem(KEY);
const parsed = raw ? JSON.parse(raw) : [];
return Array.isArray(parsed) ? parsed : [];
} catch {
return [];
}
}
function saveHistory(items) {
localStorage.setItem(KEY, JSON.stringify(items.slice(-24)));
}
function render(items) {
if (!items.length) {
thread.innerHTML =
'<li class="entry"><span class="who">灯塔</span><p class="text">本班还是空白。从左侧笔记开始,或直接在下面写下第一句。</p></li>';
return;
}
thread.innerHTML = items
.map(function (item) {
const cls = item.role === "user" ? "entry user" : item.role === "error" ? "entry error" : "entry";
const who = item.role === "user" ? "来访" : item.role === "error" ? "断线" : "夜航员";
return (
'<li class="' +
cls +
'"><span class="who">' +
who +
'</span><p class="text">' +
esc(item.content) +
"</p></li>"
);
})
.join("");
thread.scrollTop = thread.scrollHeight;
}
async function ping() {
try {
const res = await fetch(API_BASE + "/health");
const data = await res.json();
if (data && data.bindings && data.bindings.model) {
linkStatus.textContent = "灯芯已接上";
} else {
linkStatus.textContent = "灯还在,模型令牌未接通";
}
} catch {
linkStatus.textContent = "岸上链路未通";
}
}
async function ask(question) {
const history = loadHistory();
history.push({ role: "user", content: question });
saveHistory(history);
render(history);
document.body.classList.add("is-thinking");
send.disabled = true;
try {
const res = await fetch(API_BASE + "/chat", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
messages: history.filter(function (item) {
return item.role === "user" || item.role === "assistant";
})
})
});
const data = await res.json();
if (!res.ok || data.ok === false) {
throw new Error(data.error || "HTTP " + res.status);
}
history.push({ role: "assistant", content: data.answer });
} catch (error) {
history.push({ role: "error", content: error.message || "发灯失败" });
} finally {
saveHistory(history);
render(history);
document.body.classList.remove("is-thinking");
send.disabled = false;
prompt.focus();
}
}
STARTERS.forEach(function (text) {
const button = document.createElement("button");
button.type = "button";
button.textContent = text;
button.addEventListener("click", function () {
ask(text);
});
starters.appendChild(button);
});
form.addEventListener("submit", function (event) {
event.preventDefault();
const question = prompt.value.trim();
if (!question) {
return;
}
prompt.value = "";
ask(question);
});
prompt.addEventListener("keydown", function (event) {
if (event.key === "Enter" && !event.shiftKey) {
event.preventDefault();
form.requestSubmit();
}
});
document.getElementById("clear").addEventListener("click", function () {
saveHistory([]);
render([]);
});
render(loadHistory());
ping();