gpt / static /script.js
chizk's picture
Upload 2 files
e39cf8d verified
Raw
History Blame Contribute Delete
4.37 kB
/* static/script.js */
function chatApp() {
return {
// ------------------------------
// State
// ------------------------------
messages: [], // {id, role, content}
userInput: '',
loading: false,
dark: false, // dark‑mode flag
// ------------------------------
// Init – runs once when component mounts
// ------------------------------
init() {
// Detect system dark‑mode preference
this.dark = window.matchMedia('(prefers-color-scheme: dark)').matches;
this.applyDarkMode();
// Listen for changes (e.g., user switches OS theme)
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
this.dark = e.matches;
this.applyDarkMode();
});
},
// ------------------------------
// Dark‑mode helper
// ------------------------------
toggleDark() {
this.dark = !this.dark;
this.applyDarkMode();
},
applyDarkMode() {
if (this.dark) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
},
// ------------------------------
// Auto‑grow textarea
// ------------------------------
autoResize(event) {
const el = event.target;
el.style.height = 'auto';
el.style.height = `${el.scrollHeight}px`;
},
// ------------------------------
// Send a message to the back‑end
// ------------------------------
async sendMessage() {
const content = this.userInput.trim();
if (!content) return;
// 1️⃣ Add user bubble
const userMsg = {
id: Date.now(),
role: 'user',
content: this.escapeHtml(content).replace(/\n/g, '<br>')
};
this.messages.push(userMsg);
this.userInput = '';
this.scrollToBottom();
// 2️⃣ Show loading indicator
this.loading = true;
// 3️⃣ Call the OpenAI‑compatible endpoint
try {
const response = await fetch('/v1/chat/completions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model: 'qwen2.5-1.5b-instruct',
messages: [
// Keep the whole conversation – the model can handle a few turns
...this.messages.map(m => ({
role: m.role,
content: this.stripHtml(m.content) // plain text for the model
}))
],
temperature: 0.7,
max_tokens: 512,
top_p: 0.9,
stream: false // set true later if you implement SSE streaming
})
});
if (!response.ok) {
const err = await response.json();
throw new Error(err.detail || 'API error');
}
const data = await response.json();
const assistant = data.choices[0].message.content;
// 4️⃣ Add assistant bubble
const assistantMsg = {
id: Date.now() + 1,
role: 'assistant',
content: this.escapeHtml(assistant).replace(/\n/g, '<br>')
};
this.messages.push(assistantMsg);
this.scrollToBottom();
} catch (e) {
console.error(e);
const errMsg = {
id: Date.now() + 1,
role: 'assistant',
content: `<span class="text-red-500">⚠️ ${this.escapeHtml(e.message)}</span>`
};
this.messages.push(errMsg);
this.scrollToBottom();
} finally {
this.loading = false;
}
},
// ------------------------------
// Helpers
// ------------------------------
scrollToBottom() {
this.$nextTick(() => {
const chat = document.getElementById('chat-window');
chat.scrollTop = chat.scrollHeight;
});
},
// Escape HTML to avoid XSS when we later render raw strings
escapeHtml(str) {
const div = document.createElement('div');
div.appendChild(document.createTextNode(str));
return div.innerHTML;
},
// Strip HTML tags – used when sending the prompt to the model
stripHtml(html) {
const div = document.createElement('div');
div.innerHTML = html;
return div.textContent || div.innerText || '';
}
};
}