137 lines
3.7 KiB
JavaScript
137 lines
3.7 KiB
JavaScript
const form = document.querySelector("#chatForm");
|
|
const promptInput = document.querySelector("#prompt");
|
|
const messagesEl = document.querySelector("#messages");
|
|
const sendButton = document.querySelector("#sendButton");
|
|
const newChatButton = document.querySelector("#newChat");
|
|
const historyEl = document.querySelector("#history");
|
|
const chatTitleEl = document.querySelector("#chatTitle");
|
|
|
|
let activeChatId = null;
|
|
let messages = [];
|
|
|
|
function addMessage(role, content) {
|
|
const article = document.createElement("article");
|
|
article.className = `message ${role}`;
|
|
|
|
const avatar = document.createElement("div");
|
|
avatar.className = "avatar";
|
|
avatar.textContent = role === "user" ? "U" : "L";
|
|
|
|
const bubble = document.createElement("div");
|
|
bubble.className = "bubble";
|
|
bubble.textContent = content;
|
|
|
|
article.append(avatar, bubble);
|
|
messagesEl.append(article);
|
|
messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
return bubble;
|
|
}
|
|
|
|
function renderMessages() {
|
|
messagesEl.innerHTML = "";
|
|
|
|
if (!messages.length) {
|
|
addMessage("assistant", "안녕하세요. 이 노트북에서 실행되는 로컬 LLM입니다.");
|
|
return;
|
|
}
|
|
|
|
messages.forEach((message) => addMessage(message.role, message.content));
|
|
}
|
|
|
|
function setTitle(title) {
|
|
chatTitleEl.textContent = title || "Local LLM";
|
|
}
|
|
|
|
async function loadHistory() {
|
|
const response = await fetch("/api/chats");
|
|
const data = await response.json();
|
|
historyEl.innerHTML = "";
|
|
|
|
data.chats.forEach((chat) => {
|
|
const item = document.createElement("button");
|
|
item.className = `history-item ${chat.id === activeChatId ? "active" : ""}`;
|
|
item.type = "button";
|
|
item.textContent = chat.title;
|
|
item.addEventListener("click", () => loadChat(chat.id));
|
|
historyEl.append(item);
|
|
});
|
|
}
|
|
|
|
async function loadChat(chatId) {
|
|
const response = await fetch(`/api/chats/${chatId}`);
|
|
const data = await response.json();
|
|
activeChatId = data.chat.id;
|
|
messages = data.chat.messages || [];
|
|
setTitle(data.chat.title);
|
|
renderMessages();
|
|
await loadHistory();
|
|
promptInput.focus();
|
|
}
|
|
|
|
function resetChat() {
|
|
activeChatId = null;
|
|
messages = [];
|
|
setTitle("Local LLM");
|
|
renderMessages();
|
|
loadHistory();
|
|
promptInput.focus();
|
|
}
|
|
|
|
promptInput.addEventListener("input", () => {
|
|
promptInput.style.height = "auto";
|
|
promptInput.style.height = `${promptInput.scrollHeight}px`;
|
|
});
|
|
|
|
promptInput.addEventListener("keydown", (event) => {
|
|
if (event.key === "Enter" && !event.shiftKey) {
|
|
event.preventDefault();
|
|
form.requestSubmit();
|
|
}
|
|
});
|
|
|
|
newChatButton.addEventListener("click", resetChat);
|
|
|
|
form.addEventListener("submit", async (event) => {
|
|
event.preventDefault();
|
|
|
|
const content = promptInput.value.trim();
|
|
if (!content || sendButton.disabled) {
|
|
return;
|
|
}
|
|
|
|
promptInput.value = "";
|
|
promptInput.style.height = "auto";
|
|
addMessage("user", content);
|
|
messages.push({ role: "user", content });
|
|
|
|
sendButton.disabled = true;
|
|
const pending = addMessage("assistant", "생각 중...");
|
|
|
|
try {
|
|
const response = await fetch("/api/chat", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ chatId: activeChatId, messages }),
|
|
});
|
|
|
|
const data = await response.json();
|
|
if (!response.ok) {
|
|
throw new Error(data.error || "응답 생성 실패");
|
|
}
|
|
|
|
pending.textContent = data.reply;
|
|
activeChatId = data.chatId;
|
|
messages = data.chat.messages || [...messages, { role: "assistant", content: data.reply }];
|
|
setTitle(data.chat.title);
|
|
await loadHistory();
|
|
} catch (error) {
|
|
pending.textContent = `오류: ${error.message}`;
|
|
} finally {
|
|
sendButton.disabled = false;
|
|
promptInput.focus();
|
|
}
|
|
});
|
|
|
|
renderMessages();
|
|
loadHistory();
|