diff --git a/public/index.html b/public/index.html index 659a1bd..9483b3e 100644 --- a/public/index.html +++ b/public/index.html @@ -21,8 +21,7 @@
diff --git a/public/script.js b/public/script.js index 5e69869..8de68af 100644 --- a/public/script.js +++ b/public/script.js @@ -12,6 +12,28 @@ const sendBtn = document.getElementById("send"); let isProcessing = false; let chatHistory = JSON.parse(localStorage.getItem('chatHistory')) || []; +const displayModels = async () => { + const select = document.getElementById("model"); + + fetch("/models") + .then(response => response.json()) + .then(data => { + // Clear loading option + select.innerHTML = ""; + + data.models.forEach(model => { + const option = document.createElement("option"); + option.value = model.model; + option.textContent = model.name; + select.appendChild(option); + }); + }) + .catch(err => { + console.error("Failed to load models:", err); + select.innerHTML = ``; + }); +} + async function post(url, data) { try { const res = await fetch(url, { @@ -172,7 +194,7 @@ async function sendMessage() { const loadingMsg = addMessage('', false, true, false); try { const model = document.getElementById("model").value; - const res = await post("/chat", { message: msg, model }); + const res = await post("/chat", {message: msg, model}); if (messagesDiv.contains(loadingMsg)) { messagesDiv.removeChild(loadingMsg); } @@ -230,4 +252,5 @@ msgInput.addEventListener('input', function () { window.addEventListener('load', () => { checkSession(); + displayModels() }); \ No newline at end of file diff --git a/server.js b/server.js index 8f3491d..dac7386 100644 --- a/server.js +++ b/server.js @@ -64,38 +64,83 @@ const requireLogin = (req, res, next) => { next(); }; +app.get("/models", async (req, res) => { + const response = await fetch("https://ai.krishd.ch/api/tags"); + res.json(await response.json()); +}) + app.post("/chat", requireLogin, async (req, res) => { const user = req.session.user; const { message, model } = req.body; const selectedModel = model || "mistral:latest"; + try { if (!chats[user]) chats[user] = []; chats[user].push({ role: "user", content: message }); if (chats[user].length > 20) chats[user] = chats[user].slice(-20); + const messagesPayload = chats[user].map(m => ({ role: m.role === "bot" ? "assistant" : "user", content: m.content })); + const response = await fetch("https://ai.krishd.ch/api/chat", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ model: selectedModel, messages: messagesPayload, - stream: false + stream: true }) }); - const data = await response.json(); - const reply = data?.message?.content || "[No response]"; - chats[user].push({ role: "bot", content: reply }); + + if (!response.ok) { + throw new Error(`AI HTTP ${response.status}`); + } + + const reader = response.body.getReader(); + const decoder = new TextDecoder(); + + let buffer = ""; + let fullReply = ""; + + while (true) { + const { value, done } = await reader.read(); + if (done) break; + + buffer += decoder.decode(value, { stream: true }); + + let lines = buffer.split("\n"); + buffer = lines.pop(); // keep incomplete JSON + + for (const line of lines) { + if (!line.trim()) continue; + + const json = JSON.parse(line); + + if (json.done) { + break; + } + + if (json.message?.content) { + fullReply += json.message.content; + } + } + } + + chats[user].push({ role: "bot", content: fullReply }); saveChats(); - res.json({ reply }); + console.log("Reply length:", fullReply.length); + + res.json({ reply: fullReply }); + } catch (err) { console.error("AI chat error:", err); res.status(500).json({ error: "AI request failed" }); } }); + async function checkSession() { const res = await get('/session'); if (res.user) {