172 lines
5.8 KiB
JavaScript
172 lines
5.8 KiB
JavaScript
/* ===================================================
|
|
JAESWIFT.XYZ — JAE-AI Chat Terminal
|
|
Venice API chat interface
|
|
=================================================== */
|
|
|
|
(function () {
|
|
'use strict';
|
|
|
|
const chatMessages = document.getElementById('chatMessages');
|
|
const chatInput = document.getElementById('chatInput');
|
|
const chatSend = document.getElementById('chatSend');
|
|
const chatStatus = document.getElementById('chatStatus');
|
|
|
|
if (!chatMessages || !chatInput || !chatSend) return;
|
|
|
|
let history = [];
|
|
let isWaiting = false;
|
|
|
|
// ─── Render a message bubble ───
|
|
function addMessage(role, text) {
|
|
// Remove welcome screen on first message
|
|
const welcome = chatMessages.querySelector('.chat-welcome');
|
|
if (welcome) welcome.remove();
|
|
|
|
const msg = document.createElement('div');
|
|
msg.className = `chat-msg chat-msg-${role}`;
|
|
|
|
const label = document.createElement('span');
|
|
label.className = 'chat-msg-label';
|
|
label.textContent = role === 'user' ? 'YOU' : 'JAE-AI';
|
|
|
|
const body = document.createElement('div');
|
|
body.className = 'chat-msg-body';
|
|
body.textContent = text;
|
|
|
|
msg.appendChild(label);
|
|
msg.appendChild(body);
|
|
chatMessages.appendChild(msg);
|
|
chatMessages.scrollTop = chatMessages.scrollHeight;
|
|
return body;
|
|
}
|
|
|
|
// ─── Typing indicator ───
|
|
function showTyping() {
|
|
const indicator = document.createElement('div');
|
|
indicator.className = 'chat-msg chat-msg-assistant chat-typing-indicator';
|
|
indicator.id = 'chatTyping';
|
|
indicator.innerHTML = `
|
|
<span class="chat-msg-label">JAE-AI</span>
|
|
<div class="chat-msg-body">
|
|
<span class="typing-dot"></span>
|
|
<span class="typing-dot"></span>
|
|
<span class="typing-dot"></span>
|
|
</div>
|
|
`;
|
|
chatMessages.appendChild(indicator);
|
|
chatMessages.scrollTop = chatMessages.scrollHeight;
|
|
}
|
|
|
|
function hideTyping() {
|
|
const el = document.getElementById('chatTyping');
|
|
if (el) el.remove();
|
|
}
|
|
|
|
// ─── Typewriter effect for AI responses ───
|
|
function typewriterEffect(element, text, speed) {
|
|
speed = speed || 12;
|
|
let i = 0;
|
|
element.textContent = '';
|
|
return new Promise(function (resolve) {
|
|
function tick() {
|
|
if (i < text.length) {
|
|
element.textContent += text.charAt(i);
|
|
i++;
|
|
chatMessages.scrollTop = chatMessages.scrollHeight;
|
|
setTimeout(tick, speed);
|
|
} else {
|
|
resolve();
|
|
}
|
|
}
|
|
tick();
|
|
});
|
|
}
|
|
|
|
// ─── Send message to API ───
|
|
async function sendMessage() {
|
|
const text = chatInput.value.trim();
|
|
if (!text || isWaiting) return;
|
|
|
|
isWaiting = true;
|
|
chatInput.value = '';
|
|
chatStatus.textContent = '● PROCESSING';
|
|
chatStatus.classList.remove('status-green');
|
|
chatStatus.classList.add('status-amber');
|
|
|
|
addMessage('user', text);
|
|
history.push({ role: 'user', content: text });
|
|
|
|
showTyping();
|
|
|
|
try {
|
|
const resp = await fetch('/api/chat', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
message: text,
|
|
history: history.slice(0, -1)
|
|
})
|
|
});
|
|
|
|
hideTyping();
|
|
|
|
if (!resp.ok) {
|
|
const err = await resp.json().catch(function () { return {}; });
|
|
addMessage('assistant', 'ERROR: ' + (err.error || 'Connection failed'));
|
|
chatStatus.textContent = '● ERROR';
|
|
chatStatus.classList.remove('status-amber');
|
|
chatStatus.classList.add('status-red');
|
|
isWaiting = false;
|
|
return;
|
|
}
|
|
|
|
const data = await resp.json();
|
|
const reply = data.reply || 'No response received.';
|
|
|
|
history.push({ role: 'assistant', content: reply });
|
|
|
|
// Keep history manageable
|
|
if (history.length > 40) {
|
|
history = history.slice(-30);
|
|
}
|
|
|
|
const bodyEl = addMessage('assistant', '');
|
|
await typewriterEffect(bodyEl, reply, 10);
|
|
|
|
} catch (e) {
|
|
hideTyping();
|
|
addMessage('assistant', 'ERROR: Network failure — ' + e.message);
|
|
chatStatus.textContent = '● OFFLINE';
|
|
chatStatus.classList.remove('status-amber');
|
|
chatStatus.classList.add('status-red');
|
|
}
|
|
|
|
chatStatus.textContent = '● ONLINE';
|
|
chatStatus.classList.remove('status-amber', 'status-red');
|
|
chatStatus.classList.add('status-green');
|
|
isWaiting = false;
|
|
chatInput.focus();
|
|
}
|
|
|
|
// ─── Event listeners ───
|
|
chatSend.addEventListener('click', sendMessage);
|
|
chatInput.addEventListener('keydown', function (e) {
|
|
if (e.key === 'Enter' && !e.shiftKey) {
|
|
e.preventDefault();
|
|
sendMessage();
|
|
}
|
|
});
|
|
|
|
// ─── Auto-greeting after short delay ───
|
|
setTimeout(function () {
|
|
showTyping();
|
|
setTimeout(function () {
|
|
hideTyping();
|
|
var greeting = 'Welcome to JAESWIFT.XYZ — I\'m JAE-AI, your onboard guide. Ask me anything about this system, or try saying "what can I explore here?"';
|
|
history.push({ role: 'assistant', content: greeting });
|
|
var bodyEl = addMessage('assistant', '');
|
|
typewriterEffect(bodyEl, greeting, 15);
|
|
}, 1500);
|
|
}, 2000);
|
|
|
|
})();
|