Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
229 changes: 143 additions & 86 deletions 9-chat-project/solution/frontend/app.js
Original file line number Diff line number Diff line change
@@ -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,'&lt;')
.replace(/>/g,'&gt;')
.replace(/"/g,'&quot;')
.replace(/'/g,'&#039;');
}

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, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '>').replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}

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