parent
9a5975ff97
commit
878f27d9fd
@ -1,94 +1,151 @@
|
||||
// Replace placeholder JS with chat UI client logic
|
||||
// Handles sending messages to backend and updating the UI
|
||||
|
||||
(function(){
|
||||
const messagesEl = document.getElementById('messages');
|
||||
const form = document.getElementById('composer');
|
||||
const input = document.getElementById('input');
|
||||
const sendBtn = document.getElementById('send');
|
||||
const BASE_URL = "https://automatic-space-funicular-954qxp96rgcqjq-5000.app.github.dev/";
|
||||
const API_ENDPOINT = `${BASE_URL}/hello`; // adjust if your backend runs elsewhere
|
||||
|
||||
function escapeHtml(str){
|
||||
if(!str) return '';
|
||||
return str.replace(/&/g,'&')
|
||||
.replace(/</g,'<')
|
||||
.replace(/>/g,'>')
|
||||
.replace(/"/g,'"')
|
||||
.replace(/'/g,''');
|
||||
}
|
||||
|
||||
function formatText(text){
|
||||
return escapeHtml(text).replace(/\n/g,'<br>');
|
||||
}
|
||||
|
||||
function scrollToBottom(){
|
||||
messagesEl.scrollTop = messagesEl.scrollHeight;
|
||||
}
|
||||
|
||||
function appendMessage(role, text){
|
||||
const el = document.createElement('div');
|
||||
el.className = 'message ' + role;
|
||||
el.innerHTML = `<div class="content">${formatText(text)}</div><small>${new Date().toLocaleTimeString()}</small>`;
|
||||
messagesEl.appendChild(el);
|
||||
scrollToBottom();
|
||||
return el;
|
||||
}
|
||||
|
||||
function createTyping(){
|
||||
const el = document.createElement('div');
|
||||
el.className = 'message ai';
|
||||
const typing = document.createElement('div');
|
||||
typing.className = 'typing';
|
||||
for(let i=0;i<3;i++){ const d = document.createElement('span'); d.className = 'dot'; typing.appendChild(d); }
|
||||
el.appendChild(typing);
|
||||
messagesEl.appendChild(el);
|
||||
scrollToBottom();
|
||||
return el;
|
||||
}
|
||||
|
||||
async function sendToApi(text){
|
||||
const res = await fetch(API_ENDPOINT, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ message: text })
|
||||
(function () {
|
||||
const messagesEl = document.getElementById('messages');
|
||||
const form = document.getElementById('composer');
|
||||
const input = document.getElementById('input');
|
||||
const sendBtn = document.getElementById('send');
|
||||
|
||||
const BASE_URL = "http://127.0.0.1:5000/";
|
||||
const API_ENDPOINT = `${BASE_URL}/hello`;
|
||||
|
||||
// Dynamic Send Button Logic: Enable/Disable based on input text
|
||||
input.addEventListener('input', () => {
|
||||
sendBtn.disabled = input.value.trim().length === 0;
|
||||
});
|
||||
if(!res.ok) throw new Error('Network response was not ok');
|
||||
let json = await res.json();
|
||||
return json.response;
|
||||
}
|
||||
|
||||
form.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const text = input.value.trim();
|
||||
if(!text) return;
|
||||
appendMessage('user', text);
|
||||
input.value = '';
|
||||
input.focus();
|
||||
sendBtn.disabled = true;
|
||||
|
||||
const typingEl = createTyping();
|
||||
try{
|
||||
const reply = await sendToApi(text);
|
||||
typingEl.remove();
|
||||
appendMessage('ai', reply || '(no response)');
|
||||
}catch(err){
|
||||
typingEl.remove();
|
||||
appendMessage('ai', 'Error: ' + err.message);
|
||||
console.error(err);
|
||||
}finally{
|
||||
sendBtn.disabled = false;
|
||||
|
||||
// --- CHAT FUNCTIONS ---
|
||||
function escapeHtml(str) {
|
||||
if (!str) return '';
|
||||
return str.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>').replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
function formatText(text) {
|
||||
return escapeHtml(text).replace(/\n/g, '<br>');
|
||||
}
|
||||
});
|
||||
|
||||
// Enter to send, Shift+Enter for newline
|
||||
input.addEventListener('keydown', (e) => {
|
||||
if(e.key === 'Enter' && !e.shiftKey){
|
||||
e.preventDefault();
|
||||
form.dispatchEvent(new Event('submit', { cancelable: true }));
|
||||
function scrollToBottom() {
|
||||
messagesEl.scrollTop = messagesEl.scrollHeight;
|
||||
}
|
||||
});
|
||||
|
||||
// Small welcome message
|
||||
appendMessage('ai', 'Hello! I\'m your AI assistant. Ask me anything.');
|
||||
// Message control handlers (Copy/Delete)
|
||||
function handleDelete(messageEl) {
|
||||
if (messageEl) {
|
||||
messageEl.remove();
|
||||
}
|
||||
}
|
||||
|
||||
function handleCopy(text) {
|
||||
const tempTextArea = document.createElement('textarea');
|
||||
tempTextArea.value = text.replace(/<br>/g, '\n');
|
||||
document.body.appendChild(tempTextArea);
|
||||
tempTextArea.select();
|
||||
try {
|
||||
document.execCommand('copy');
|
||||
} catch (err) {
|
||||
console.error('Could not copy text: ', err);
|
||||
}
|
||||
document.body.removeChild(tempTextArea);
|
||||
}
|
||||
|
||||
function appendMessage(role, text) {
|
||||
const el = document.createElement('div');
|
||||
el.className = 'message ' + role;
|
||||
|
||||
const contentDiv = document.createElement('div');
|
||||
contentDiv.className = 'content';
|
||||
const formattedText = formatText(text);
|
||||
contentDiv.innerHTML = formattedText;
|
||||
|
||||
const smallEl = document.createElement('small');
|
||||
smallEl.textContent = new Date().toLocaleTimeString();
|
||||
|
||||
el.appendChild(contentDiv);
|
||||
el.appendChild(smallEl);
|
||||
|
||||
// Add Controls (AI messages get copy/delete)
|
||||
if (role === 'ai') {
|
||||
const controls = document.createElement('div');
|
||||
controls.className = 'controls';
|
||||
|
||||
const copyBtn = document.createElement('button');
|
||||
copyBtn.innerHTML = '<i class="fas fa-copy"></i>';
|
||||
copyBtn.title = 'Copy message content';
|
||||
copyBtn.onclick = () => handleCopy(text);
|
||||
|
||||
const deleteBtn = document.createElement('button');
|
||||
deleteBtn.innerHTML = '<i class="fas fa-trash-alt"></i>';
|
||||
deleteBtn.title = 'Delete message';
|
||||
deleteBtn.onclick = () => handleDelete(el);
|
||||
|
||||
controls.appendChild(copyBtn);
|
||||
controls.appendChild(deleteBtn);
|
||||
el.appendChild(controls);
|
||||
}
|
||||
|
||||
messagesEl.appendChild(el);
|
||||
scrollToBottom();
|
||||
return el;
|
||||
}
|
||||
|
||||
function createTyping() {
|
||||
const el = document.createElement('div');
|
||||
el.className = 'message ai';
|
||||
const typing = document.createElement('div');
|
||||
typing.className = 'typing';
|
||||
for (let i = 0; i < 3; i++) { const d = document.createElement('span'); d.className = 'dot'; typing.appendChild(d); }
|
||||
el.appendChild(typing);
|
||||
messagesEl.appendChild(el);
|
||||
scrollToBottom();
|
||||
return el;
|
||||
}
|
||||
|
||||
async function sendToApi(text) {
|
||||
const res = await fetch(API_ENDPOINT, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ message: text })
|
||||
});
|
||||
if (!res.ok) throw new Error('Network response was not ok');
|
||||
let json = await res.json();
|
||||
return json.response;
|
||||
}
|
||||
|
||||
form.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const text = input.value.trim();
|
||||
if (!text) return;
|
||||
appendMessage('user', text);
|
||||
input.value = '';
|
||||
sendBtn.disabled = true;
|
||||
input.focus();
|
||||
|
||||
const typingEl = createTyping();
|
||||
try {
|
||||
const reply = await sendToApi(text);
|
||||
typingEl.remove();
|
||||
appendMessage('ai', reply || '(no response)');
|
||||
} catch (err) {
|
||||
typingEl.remove();
|
||||
// Display exact error message: "Error: Failed to fetch"
|
||||
appendMessage('ai', 'Error: '+err.message);
|
||||
console.error(err);
|
||||
} finally {
|
||||
sendBtn.disabled = input.value.trim().length === 0;
|
||||
}
|
||||
});
|
||||
|
||||
// Enter to send, Shift+Enter for newline
|
||||
input.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
form.dispatchEvent(new Event('submit', { cancelable: true }));
|
||||
}
|
||||
});
|
||||
|
||||
// Small welcome message
|
||||
appendMessage('ai', 'Hello! I\'m your AI assistant. Ask me anything.');
|
||||
})();
|
||||
Loading…
Reference in new issue