llm/go-client/frontend/static/script.js (108 lines of code) (raw):

const chatMessages = document.getElementById('chatMessages'); const userInput = document.getElementById('userInput'); const imageUpload = document.getElementById('imageUpload'); const previewContainer = document.getElementById('previewContainer'); const modelSelect = document.getElementById('model-select'); let selectedModel = modelSelect.value; let imageFile = null; let imageBlob = null; modelSelect.addEventListener("change", (e) => { selectedModel = e.target.value; const modelChangeMsg = document.createElement("div"); modelChangeMsg.className = "message ai"; modelChangeMsg.innerHTML = ` <div class="avatar"><span class="material-symbols-outlined">smart_toy</span></div> <div class="message-content"><p>Model switched to: <strong>${selectedModel}</strong></p></div> `; chatMessages.appendChild(modelChangeMsg); chatMessages.scrollTop = chatMessages.scrollHeight; }); // ============ Image Handling ============= imageUpload.addEventListener('change', async (e) => { const file = e.target.files[0]; if (!file || !file.type.startsWith('image/')) { alert('Only image files are supported'); return; } imageFile = file; await filesToBlob(file); }); function filesToBlob(file) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.readAsDataURL(file); reader.onload = (e) => { imageBlob = e.target.result; previewContainer.innerHTML = ''; const preview = document.createElement('div'); preview.className = 'preview'; const img = document.createElement('img'); img.src = imageBlob; const deleteBtn = document.createElement('button'); deleteBtn.className = 'delete-btn'; deleteBtn.textContent = '×'; deleteBtn.onclick = clearImage; preview.appendChild(img); preview.appendChild(deleteBtn); previewContainer.appendChild(preview); resolve(); }; reader.onerror = reject; }); } function clearImage() { imageFile = null; imageBlob = null; previewContainer.innerHTML = ''; imageUpload.value = ''; } // ============ Chat Logic ============= function sendMessage() { const message = userInput.value.trim(); if (!message && !imageBlob) return; // Display user message const userMsg = document.createElement('div'); userMsg.className = 'message user'; userMsg.innerHTML = ` <div class="message-content"> ${message ? `<p>${message}</p>` : ''} ${imageBlob ? `<img src="${imageBlob}" style="width:100px;height:100px;margin-top:6px;border-radius:8px;object-fit:cover;" alt="">` : ''} </div>`; chatMessages.appendChild(userMsg); chatMessages.scrollTop = chatMessages.scrollHeight; // Display AI "thinking" message const aiMsg = document.createElement("div"); aiMsg.className = "message ai"; aiMsg.innerHTML = ` <div class="avatar"><span class="material-symbols-outlined">smart_toy</span></div> <div class="message-content"><p id="streaming-response">Thinking...</p></div>`; chatMessages.appendChild(aiMsg); chatMessages.scrollTop = chatMessages.scrollHeight; let b = imageBlob userInput.value = ''; clearImage(); // Set timeout control const TIMEOUT_MS = 5000; // 5 seconds let isTimeout = false; const timeoutId = setTimeout(() => { isTimeout = true; const p = aiMsg.querySelector("#streaming-response"); if (p) p.textContent = "Request timed out, please try again."; }, TIMEOUT_MS); generateResponse(message, b, selectedModel, aiMsg, () => { if (!isTimeout) clearTimeout(timeoutId); }); } function generateResponse(message, imageBlob, model, containerEl, onFinish) { const API_URL = "/api/chat"; const p = containerEl.querySelector("#streaming-response"); p.textContent = ""; let accumulatedResponse = ""; fetch(API_URL, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ message, bin: imageBlob, model }) }) .then(res => { if (!res.ok) throw new Error(`Request failed: ${res.status}`); const reader = res.body.getReader(); const decoder = new TextDecoder(); function read() { return reader.read().then(({ done, value }) => { if (done) { onFinish && onFinish(); return; } const chunk = decoder.decode(value); const events = chunk.split('\n\n'); events.forEach(event => { if (event.startsWith("event:message")) { const dataLine = event.split('\n').find(line => line.startsWith("data:")); if (dataLine) { try { const data = JSON.parse(dataLine.replace("data:", "").trim()); accumulatedResponse += data.content; p.textContent = accumulatedResponse; chatMessages.scrollTop = chatMessages.scrollHeight; } catch (err) { console.warn("Parsing failed:", err); } } } }); return read(); }); } return read(); }) .catch(err => { p.textContent = "An error occurred, please try again later."; p.style.color = "red"; console.error(err); onFinish && onFinish(); }); } // ============ Input Field Events ============= userInput.addEventListener('keypress', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); } });