Add model selection and local RAG
This commit is contained in:
1 parent
2b2e5fcb0f
commit
bf39c2e070
6 files changed
+369
-32
No files matched your search
@@ -5,11 +5,16 @@ const sendButton = document.querySelector("#sendButton");
|
||||
const newChatButton = document.querySelector("#newChat");
|
||||
const historyEl = document.querySelector("#history");
|
||||
const chatTitleEl = document.querySelector("#chatTitle");
|
||||
const modelSelect = document.querySelector("#modelSelect");
|
||||
const ragToggle = document.querySelector("#ragToggle");
|
||||
const ragStatus = document.querySelector("#ragStatus");
|
||||
const reindexButton = document.querySelector("#reindexButton");
|
||||
const metaLine = document.querySelector("#metaLine");
|
||||
|
||||
let activeChatId = null;
|
||||
let messages = [];
|
||||
|
||||
function addMessage(role, content) {
|
||||
function addMessage(role, content, meta = "") {
|
||||
const article = document.createElement("article");
|
||||
article.className = `message ${role}`;
|
||||
|
||||
@@ -21,6 +26,13 @@ function addMessage(role, content) {
|
||||
bubble.className = "bubble";
|
||||
bubble.textContent = content;
|
||||
|
||||
if (meta) {
|
||||
const metaEl = document.createElement("div");
|
||||
metaEl.className = "message-meta";
|
||||
metaEl.textContent = meta;
|
||||
bubble.append(document.createElement("br"), metaEl);
|
||||
}
|
||||
|
||||
article.append(avatar, bubble);
|
||||
messagesEl.append(article);
|
||||
messagesEl.scrollTop = messagesEl.scrollHeight;
|
||||
@@ -31,7 +43,7 @@ function renderMessages() {
|
||||
messagesEl.innerHTML = "";
|
||||
|
||||
if (!messages.length) {
|
||||
addMessage("assistant", "안녕하세요. 이 노트북에서 실행되는 로컬 LLM입니다.");
|
||||
addMessage("assistant", "Local LLM is ready. Ask in Korean or English.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -42,6 +54,32 @@ function setTitle(title) {
|
||||
chatTitleEl.textContent = title || "Local LLM";
|
||||
}
|
||||
|
||||
function selectedModel() {
|
||||
return modelSelect.value || "qwen2.5:7b";
|
||||
}
|
||||
|
||||
async function loadModels() {
|
||||
const response = await fetch("/api/models");
|
||||
const data = await response.json();
|
||||
modelSelect.innerHTML = "";
|
||||
|
||||
data.models.forEach((model) => {
|
||||
const option = document.createElement("option");
|
||||
option.value = model;
|
||||
option.textContent = model;
|
||||
if (model === data.defaultModel) {
|
||||
option.selected = true;
|
||||
}
|
||||
modelSelect.append(option);
|
||||
});
|
||||
}
|
||||
|
||||
async function loadRagStatus() {
|
||||
const response = await fetch("/api/rag/status");
|
||||
const data = await response.json();
|
||||
ragStatus.textContent = `RAG: ${data.files} files, ${data.chunks} chunks`;
|
||||
}
|
||||
|
||||
async function loadHistory() {
|
||||
const response = await fetch("/api/chats");
|
||||
const data = await response.json();
|
||||
@@ -62,6 +100,8 @@ async function loadChat(chatId) {
|
||||
const data = await response.json();
|
||||
activeChatId = data.chat.id;
|
||||
messages = data.chat.messages || [];
|
||||
modelSelect.value = data.chat.model || selectedModel();
|
||||
ragToggle.checked = data.chat.useRag !== false;
|
||||
setTitle(data.chat.title);
|
||||
renderMessages();
|
||||
await loadHistory();
|
||||
@@ -91,6 +131,18 @@ promptInput.addEventListener("keydown", (event) => {
|
||||
|
||||
newChatButton.addEventListener("click", resetChat);
|
||||
|
||||
reindexButton.addEventListener("click", async () => {
|
||||
reindexButton.disabled = true;
|
||||
ragStatus.textContent = "RAG: indexing";
|
||||
try {
|
||||
const response = await fetch("/api/rag/reindex", { method: "POST" });
|
||||
const data = await response.json();
|
||||
ragStatus.textContent = `RAG: ${data.files} files, ${data.chunks} chunks`;
|
||||
} finally {
|
||||
reindexButton.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
form.addEventListener("submit", async (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
@@ -105,32 +157,49 @@ form.addEventListener("submit", async (event) => {
|
||||
messages.push({ role: "user", content });
|
||||
|
||||
sendButton.disabled = true;
|
||||
const pending = addMessage("assistant", "생각 중...");
|
||||
const pending = addMessage("assistant", "Thinking...");
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/chat", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ chatId: activeChatId, messages }),
|
||||
body: JSON.stringify({
|
||||
chatId: activeChatId,
|
||||
messages,
|
||||
model: selectedModel(),
|
||||
useRag: ragToggle.checked,
|
||||
}),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || "응답 생성 실패");
|
||||
throw new Error(data.error || "Response failed");
|
||||
}
|
||||
|
||||
const sourceText = data.sources?.length
|
||||
? `sources: ${data.sources.map((item) => item.source).join(", ")}`
|
||||
: "no sources";
|
||||
pending.textContent = data.reply;
|
||||
|
||||
const meta = document.createElement("div");
|
||||
meta.className = "message-meta";
|
||||
meta.textContent = `${data.model} / ${(data.elapsedMs / 1000).toFixed(1)}s / ${sourceText}`;
|
||||
pending.append(document.createElement("br"), meta);
|
||||
|
||||
activeChatId = data.chatId;
|
||||
messages = data.chat.messages || [...messages, { role: "assistant", content: data.reply }];
|
||||
setTitle(data.chat.title);
|
||||
metaLine.textContent = `${data.model} with RAG ${ragToggle.checked ? "on" : "off"}`;
|
||||
await loadHistory();
|
||||
} catch (error) {
|
||||
pending.textContent = `오류: ${error.message}`;
|
||||
pending.textContent = `Error: ${error.message}`;
|
||||
} finally {
|
||||
sendButton.disabled = false;
|
||||
promptInput.focus();
|
||||
}
|
||||
});
|
||||
|
||||
loadModels();
|
||||
loadRagStatus();
|
||||
renderMessages();
|
||||
loadHistory();
|
||||
Reference in new issue
Block a user