Skip to content

Commit 95185b6

Browse files
jeremymanningclaude
andcommitted
feat(demo-15): Improve chatbot evolution demo UX and visuals
- Remove emoji from title, add white subtitle text - Update timeline to show actual model names and years (1966 ELIZA, 1972 PARRY, 1995 A.L.I.C.E., 2020 BlenderBot, 2023 LaMini-GPT) - Fix architecture sidebar text color (was dark gray on black) - Add typing animation for neural model responses - Show user messages immediately before model processing - Improve Compare All Bots: maintains conversation history, shows instant feedback with typing indicators, displays all bot responses with proper model names 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent fe1a5ba commit 95185b6

File tree

3 files changed

+166
-59
lines changed

3 files changed

+166
-59
lines changed

demos/15-chatbot-evolution/css/chatbot-evolution.css

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ header h1 {
4242
margin: 0;
4343
opacity: 0.95;
4444
font-size: 1.1em;
45+
color: white;
4546
}
4647

4748
/* Timeline Navigator */
@@ -312,7 +313,46 @@ header h1 {
312313
align-items: center;
313314
justify-content: center;
314315
font-size: 0.9em;
315-
color: #6b7280;
316+
color: var(--text-color);
317+
}
318+
319+
/* Typing animation for neural model responses */
320+
.typing-indicator {
321+
display: flex;
322+
align-items: center;
323+
gap: 4px;
324+
padding: 12px 16px;
325+
}
326+
327+
.typing-indicator span {
328+
width: 8px;
329+
height: 8px;
330+
background: var(--primary-color);
331+
border-radius: 50%;
332+
animation: typing-bounce 1.4s infinite ease-in-out both;
333+
}
334+
335+
.typing-indicator span:nth-child(1) {
336+
animation-delay: -0.32s;
337+
}
338+
339+
.typing-indicator span:nth-child(2) {
340+
animation-delay: -0.16s;
341+
}
342+
343+
.typing-indicator span:nth-child(3) {
344+
animation-delay: 0s;
345+
}
346+
347+
@keyframes typing-bounce {
348+
0%, 80%, 100% {
349+
transform: scale(0.6);
350+
opacity: 0.5;
351+
}
352+
40% {
353+
transform: scale(1);
354+
opacity: 1;
355+
}
316356
}
317357

318358
.papers-list {

demos/15-chatbot-evolution/index.html

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,27 +12,27 @@
1212
<body>
1313
<div class="container">
1414
<header>
15-
<h1>🤖 Chatbot Evolution Timeline</h1>
15+
<h1>Chatbot Evolution Timeline</h1>
1616
<p class="subtitle">Journey through 60 years of conversational AI - from pattern matching to neural language models</p>
1717
</header>
1818

1919
<!-- Timeline Navigator -->
2020
<section class="timeline-navigator">
2121
<div class="timeline-bar">
2222
<div class="era era-1960s" data-era="1960s">
23-
<span class="era-label">1960s<br>Rule-Based</span>
23+
<span class="era-label">1966<br>ELIZA</span>
2424
</div>
2525
<div class="era era-1970s" data-era="1970s">
26-
<span class="era-label">1970s<br>Psychiatry</span>
26+
<span class="era-label">1972<br>PARRY</span>
2727
</div>
2828
<div class="era era-1990s" data-era="1990s">
29-
<span class="era-label">1990s-2000s<br>AIML</span>
29+
<span class="era-label">1995<br>A.L.I.C.E.</span>
3030
</div>
3131
<div class="era era-2010s" data-era="2010s">
32-
<span class="era-label">2010s<br>Neural</span>
32+
<span class="era-label">2020<br>BlenderBot</span>
3333
</div>
3434
<div class="era era-2020s active" data-era="2020s">
35-
<span class="era-label">2020s<br>Transformers</span>
35+
<span class="era-label">2023<br>LaMini-GPT</span>
3636
</div>
3737
</div>
3838
</section>

demos/15-chatbot-evolution/js/timeline-app.js

Lines changed: 119 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -494,7 +494,7 @@ class TimelineApp {
494494

495495
if (!message) return;
496496

497-
// Add user message
497+
// Add user message immediately
498498
this.addMessage(botName, message, 'user');
499499
input.value = '';
500500

@@ -503,11 +503,11 @@ class TimelineApp {
503503
let response;
504504
// Neural models (seq2seq and gpt) use async
505505
if (botName === 'gpt' || botName === 'seq2seq') {
506-
// Show typing indicator for neural models
507-
this.addMessage(botName, 'Generating response...', 'bot-typing');
506+
// Show animated typing indicator for neural models
507+
this.addTypingIndicator(botName);
508508
response = await this.bots[botName].getResponse(message);
509509
// Remove typing indicator
510-
this.removeLastMessage(botName);
510+
this.removeTypingIndicator(botName);
511511
} else {
512512
// Rule-based bots are synchronous
513513
response = this.bots[botName].getResponse(message);
@@ -520,7 +520,7 @@ class TimelineApp {
520520

521521
} catch (error) {
522522
console.error(`Error getting response from ${botName}:`, error);
523-
this.removeLastMessage(botName); // Remove typing indicator if present
523+
this.removeTypingIndicator(botName); // Remove typing indicator if present
524524
this.addMessage(botName, "Sorry, I encountered an error.", 'bot');
525525
}
526526
}
@@ -532,6 +532,26 @@ class TimelineApp {
532532
}
533533
}
534534

535+
addTypingIndicator(botName) {
536+
const messagesContainer = document.getElementById(`${botName}-messages`);
537+
const typingDiv = document.createElement('div');
538+
typingDiv.className = 'message bot typing-indicator';
539+
typingDiv.id = `${botName}-typing`;
540+
// Create three animated dots using DOM methods
541+
for (let i = 0; i < 3; i++) {
542+
typingDiv.appendChild(document.createElement('span'));
543+
}
544+
messagesContainer.appendChild(typingDiv);
545+
messagesContainer.scrollTop = messagesContainer.scrollHeight;
546+
}
547+
548+
removeTypingIndicator(botName) {
549+
const typingDiv = document.getElementById(`${botName}-typing`);
550+
if (typingDiv) {
551+
typingDiv.remove();
552+
}
553+
}
554+
535555
addMessage(botName, text, type) {
536556
const messagesContainer = document.getElementById(`${botName}-messages`);
537557
const messageDiv = document.createElement('div');
@@ -542,71 +562,118 @@ class TimelineApp {
542562
}
543563

544564
async compareAllBots() {
545-
const prompt = document.getElementById('compare-prompt').value.trim();
565+
const promptInput = document.getElementById('compare-prompt');
566+
const prompt = promptInput.value.trim();
546567

547568
if (!prompt) {
548569
alert('Please enter a prompt to compare');
549570
return;
550571
}
551572

552573
const resultsDiv = document.getElementById('comparison-results');
553-
resultsDiv.innerHTML = '<p style="color: #999; text-align: center;">Processing...</p>';
554574

555-
const responses = {};
575+
// Initialize conversation history if not exists
576+
if (!this.comparisonHistory) {
577+
this.comparisonHistory = [];
578+
}
556579

557-
// Get responses from all bots with individual error handling
558-
try {
559-
// Rule-based bots (synchronous) - handle individually
560-
try {
561-
responses.eliza = this.bots.eliza.getResponse(prompt);
562-
} catch (error) {
563-
console.error('Error from ELIZA:', error);
564-
responses.eliza = 'Error: Unable to get response';
565-
}
580+
// Add user message to history and display immediately
581+
const userMsgDiv = document.createElement('div');
582+
userMsgDiv.className = 'comparison-item';
583+
userMsgDiv.style.background = 'var(--primary-color)';
584+
userMsgDiv.style.color = 'white';
585+
userMsgDiv.style.borderLeftColor = 'var(--secondary-color)';
586+
const userStrong = document.createElement('strong');
587+
userStrong.textContent = 'YOU:';
588+
userMsgDiv.appendChild(userStrong);
589+
userMsgDiv.appendChild(document.createElement('br'));
590+
userMsgDiv.appendChild(document.createTextNode(prompt));
591+
resultsDiv.appendChild(userMsgDiv);
592+
593+
// Clear input
594+
promptInput.value = '';
595+
596+
// Create bot response containers with loading indicators
597+
const botNames = ['eliza', 'parry', 'alice', 'seq2seq', 'gpt'];
598+
const botLabels = {
599+
eliza: 'ELIZA (1966)',
600+
parry: 'PARRY (1972)',
601+
alice: 'A.L.I.C.E. (1995)',
602+
seq2seq: 'BlenderBot (2020)',
603+
gpt: 'LaMini-GPT (2023)'
604+
};
605+
const botDivs = {};
566606

567-
try {
568-
responses.parry = this.bots.parry.getResponse(prompt);
569-
} catch (error) {
570-
console.error('Error from PARRY:', error);
571-
responses.parry = 'Error: Unable to get response';
607+
// Create containers with typing indicators
608+
for (const bot of botNames) {
609+
const item = document.createElement('div');
610+
item.className = 'comparison-item';
611+
item.id = `compare-${bot}`;
612+
613+
const strong = document.createElement('strong');
614+
strong.textContent = botLabels[bot] + ':';
615+
item.appendChild(strong);
616+
item.appendChild(document.createElement('br'));
617+
618+
// Add typing indicator
619+
const typingSpan = document.createElement('span');
620+
typingSpan.className = 'typing-indicator';
621+
typingSpan.style.display = 'inline-flex';
622+
for (let i = 0; i < 3; i++) {
623+
typingSpan.appendChild(document.createElement('span'));
572624
}
625+
item.appendChild(typingSpan);
573626

574-
try {
575-
responses.alice = this.bots.alice.getResponse(prompt);
576-
} catch (error) {
577-
console.error('Error from ALICE:', error);
578-
responses.alice = 'Error: Unable to get response';
579-
}
627+
resultsDiv.appendChild(item);
628+
botDivs[bot] = item;
629+
}
580630

581-
// Neural models (asynchronous) - handle individually
582-
try {
583-
responses.seq2seq = await this.bots.seq2seq.getResponse(prompt);
584-
} catch (error) {
585-
console.error('Error from Seq2Seq:', error);
586-
responses.seq2seq = 'Error: Unable to get response';
587-
}
631+
// Scroll to show new content
632+
resultsDiv.scrollTop = resultsDiv.scrollHeight;
588633

589-
try {
590-
responses.gpt = await this.bots.gpt.getResponse(prompt);
591-
} catch (error) {
592-
console.error('Error from GPT:', error);
593-
responses.gpt = 'Error: Unable to get response';
594-
}
634+
// Get responses - rule-based first (sync), then neural (async)
635+
const updateBotResponse = (bot, response) => {
636+
const item = botDivs[bot];
637+
// Remove typing indicator and add response
638+
const typing = item.querySelector('.typing-indicator');
639+
if (typing) typing.remove();
640+
item.appendChild(document.createTextNode(response));
641+
};
642+
643+
// Rule-based bots (synchronous)
644+
try {
645+
updateBotResponse('eliza', this.bots.eliza.getResponse(prompt));
595646
} catch (error) {
596-
console.error('Unexpected error in comparison:', error);
647+
console.error('Error from ELIZA:', error);
648+
updateBotResponse('eliza', 'Error: Unable to get response');
597649
}
598650

599-
// Display results
600-
resultsDiv.innerHTML = '';
601-
for (const [bot, response] of Object.entries(responses)) {
602-
const item = document.createElement('div');
603-
item.className = 'comparison-item';
604-
item.innerHTML = `
605-
<strong>${bot.toUpperCase()}:</strong><br>
606-
${response}
607-
`;
608-
resultsDiv.appendChild(item);
651+
try {
652+
updateBotResponse('parry', this.bots.parry.getResponse(prompt));
653+
} catch (error) {
654+
console.error('Error from PARRY:', error);
655+
updateBotResponse('parry', 'Error: Unable to get response');
656+
}
657+
658+
try {
659+
updateBotResponse('alice', this.bots.alice.getResponse(prompt));
660+
} catch (error) {
661+
console.error('Error from ALICE:', error);
662+
updateBotResponse('alice', 'Error: Unable to get response');
609663
}
664+
665+
// Neural models (asynchronous) - run in parallel
666+
const neuralPromises = [
667+
this.bots.seq2seq.getResponse(prompt).then(r => updateBotResponse('seq2seq', r))
668+
.catch(e => { console.error('Error from Seq2Seq:', e); updateBotResponse('seq2seq', 'Error: Unable to get response'); }),
669+
this.bots.gpt.getResponse(prompt).then(r => updateBotResponse('gpt', r))
670+
.catch(e => { console.error('Error from GPT:', e); updateBotResponse('gpt', 'Error: Unable to get response'); })
671+
];
672+
673+
await Promise.all(neuralPromises);
674+
675+
// Save to history
676+
this.comparisonHistory.push({ prompt, timestamp: Date.now() });
610677
}
611678

612679
displayArchitecture() {

0 commit comments

Comments
 (0)