Files
glance/internal/glance/static/js/ai-chat.js
Tanmay Karande 1ab88b3758 feat: add personal-finance and ai-chat widgets
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 01:39:16 -04:00

98 lines
3.2 KiB
JavaScript

export default function setupAIChat(el) {
const model = el.dataset.model || 'llama3.2';
const ollamaURL = el.dataset.ollamaUrl || 'http://localhost:11434';
const messagesEl = el.querySelector('.ai-chat-messages');
const inputEl = el.querySelector('.ai-chat-input');
const sendBtn = el.querySelector('.ai-chat-send');
const history = [];
let busy = false;
function appendMessage(role, text) {
const div = document.createElement('div');
div.className = `ai-chat-msg ai-chat-msg-${role}`;
div.textContent = text;
messagesEl.appendChild(div);
messagesEl.scrollTop = messagesEl.scrollHeight;
return div;
}
async function send() {
const text = inputEl.value.trim();
if (!text || busy) return;
busy = true;
sendBtn.disabled = true;
inputEl.value = '';
inputEl.style.height = 'auto';
history.push({ role: 'user', content: text });
appendMessage('user', text);
const assistantEl = appendMessage('assistant', '');
assistantEl.classList.add('ai-chat-msg-loading');
let reply = '';
try {
const res = await fetch('/api/ai-chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ollama_url: ollamaURL, model, messages: history }),
});
if (!res.ok) {
assistantEl.textContent = `Error: ${res.statusText}`;
assistantEl.classList.remove('ai-chat-msg-loading');
history.pop();
return;
}
const reader = res.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const lines = decoder.decode(value, { stream: true }).split('\n');
for (const line of lines) {
if (!line.trim()) continue;
try {
const chunk = JSON.parse(line);
if (chunk.message && chunk.message.content) {
reply += chunk.message.content;
assistantEl.textContent = reply;
messagesEl.scrollTop = messagesEl.scrollHeight;
}
} catch (_) {}
}
}
} catch (e) {
assistantEl.textContent = 'Failed to reach Ollama.';
} finally {
assistantEl.classList.remove('ai-chat-msg-loading');
if (reply) history.push({ role: 'assistant', content: reply });
busy = false;
sendBtn.disabled = false;
inputEl.focus();
}
}
sendBtn.addEventListener('click', send);
inputEl.addEventListener('keydown', e => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
send();
}
});
// Auto-grow textarea
inputEl.addEventListener('input', () => {
inputEl.style.height = 'auto';
inputEl.style.height = Math.min(inputEl.scrollHeight, 120) + 'px';
});
}