@@ -11,6 +11,40 @@ import { GPTBot } from './gpt-bot.js';
1111import { ElizaBreakdownRenderer } from '../../eliza/js/eliza-breakdown-renderer.js' ;
1212import { RulesViewer } from './rules-viewer.js' ;
1313
14+ function formatMarkdown ( text ) {
15+ let html = text ;
16+
17+ html = html . replace ( / ` ` ` ( \w + ) ? \n ( [ \s \S ] * ?) ` ` ` / g, ( match , lang , code ) => {
18+ const language = lang || 'plaintext' ;
19+ const escapedCode = code
20+ . replace ( / & / g, '&' )
21+ . replace ( / < / g, '<' )
22+ . replace ( / > / g, '>' )
23+ . trim ( ) ;
24+ return `<pre class="code-block" data-language="${ language } "><code>${ escapedCode } </code></pre>` ;
25+ } ) ;
26+
27+ html = html . replace ( / ` ( [ ^ ` ] + ) ` / g, '<code class="inline-code">$1</code>' ) ;
28+
29+ html = html . replace ( / \* \* ( [ ^ * ] + ) \* \* / g, '<strong>$1</strong>' ) ;
30+ html = html . replace ( / \* ( [ ^ * ] + ) \* / g, '<em>$1</em>' ) ;
31+
32+ html = html . replace ( / ^ # # # ( .+ ) $ / gm, '<strong style="font-size:1.1em;">$1</strong>' ) ;
33+ html = html . replace ( / ^ # # ( .+ ) $ / gm, '<strong style="font-size:1.2em;">$1</strong>' ) ;
34+ html = html . replace ( / ^ # ( .+ ) $ / gm, '<strong style="font-size:1.3em;">$1</strong>' ) ;
35+
36+ html = html . replace ( / ^ [ - * ] ( .+ ) $ / gm, '• $1' ) ;
37+ html = html . replace ( / ^ \d + \. ( .+ ) $ / gm, ( match , item , offset , str ) => {
38+ const before = str . substring ( 0 , offset ) ;
39+ const num = ( before . match ( / ^ \d + \. / gm) || [ ] ) . length + 1 ;
40+ return `${ num } . ${ item } ` ;
41+ } ) ;
42+
43+ html = html . replace ( / \n / g, '<br>' ) ;
44+
45+ return html ;
46+ }
47+
1448class TimelineApp {
1549 constructor ( ) {
1650 this . bots = {
@@ -754,8 +788,8 @@ class TimelineApp {
754788
755789 if ( useHTML ) {
756790 messageDiv . innerHTML = text ;
757- } else if ( botName === 'gpt' && type === 'bot' && this . bots . gpt . formatResponseAsHTML ) {
758- messageDiv . innerHTML = this . bots . gpt . formatResponseAsHTML ( text ) ;
791+ } else if ( type === 'bot' ) {
792+ messageDiv . innerHTML = formatMarkdown ( text ) ;
759793 } else {
760794 messageDiv . textContent = text ;
761795 }
@@ -774,17 +808,13 @@ class TimelineApp {
774808 }
775809
776810 const resultsDiv = document . getElementById ( 'comparison-results' ) ;
777- resultsDiv . textContent = '' ; // Clear previous results
811+ resultsDiv . textContent = '' ;
778812
779- // Get compare button and set loading state
780813 const compareBtn = document . getElementById ( 'compare-btn' ) ;
781814 compareBtn . disabled = true ;
782815 compareBtn . textContent = 'Comparing...' ;
783816
784- // Ensure ELIZA is initialized before getting response
785- await this . bots . eliza . ensureInitialized ( ) ;
786-
787- // Add user message to display
817+ // Show user message IMMEDIATELY before any async operations
788818 const userMsgDiv = document . createElement ( 'div' ) ;
789819 userMsgDiv . className = 'comparison-item' ;
790820 userMsgDiv . style . background = 'var(--primary-color)' ;
@@ -837,13 +867,14 @@ class TimelineApp {
837867 // Scroll to show new content
838868 resultsDiv . scrollTop = resultsDiv . scrollHeight ;
839869
840- // Get responses - rule-based first (sync), then neural (async)
841870 const updateBotResponse = ( bot , response ) => {
842871 const item = botDivs [ bot ] ;
843- // Remove typing indicator and add response
844- const typing = item . querySelector ( '.typing-indicator' ) ;
872+ const typing = item . querySelector ( '.typing-indicator' ) || item . querySelector ( '.loading-indicator' ) ;
845873 if ( typing ) typing . remove ( ) ;
846- item . appendChild ( document . createTextNode ( response ) ) ;
874+ const responseSpan = document . createElement ( 'span' ) ;
875+ responseSpan . className = 'bot-response' ;
876+ responseSpan . innerHTML = formatMarkdown ( response ) ;
877+ item . appendChild ( responseSpan ) ;
847878 } ;
848879
849880 // ELIZA is async (needs to ensure rules loaded)
@@ -876,9 +907,24 @@ class TimelineApp {
876907 const item = botDivs [ botName ] ;
877908 const typing = item . querySelector ( '.typing-indicator' ) ;
878909 if ( typing ) {
879- typing . innerHTML = '<span class="loading-text">Loading model...</span>' ;
910+ typing . innerHTML = '' ;
911+ typing . className = 'loading-indicator' ;
912+ const spinner = document . createElement ( 'span' ) ;
913+ spinner . className = 'inline-spinner' ;
914+ const text = document . createElement ( 'span' ) ;
915+ text . textContent = 'Loading model...' ;
916+ text . style . marginLeft = '8px' ;
917+ typing . appendChild ( spinner ) ;
918+ typing . appendChild ( text ) ;
880919 }
881920 await bot . loadModel ( ) ;
921+ if ( typing ) {
922+ typing . innerHTML = '' ;
923+ typing . className = 'typing-indicator' ;
924+ for ( let i = 0 ; i < 3 ; i ++ ) {
925+ typing . appendChild ( document . createElement ( 'span' ) ) ;
926+ }
927+ }
882928 }
883929 return bot . getResponse ( prompt ) ;
884930 } ;
@@ -900,24 +946,37 @@ class TimelineApp {
900946 displayArchitecture ( ) {
901947 const vizDiv = document . getElementById ( 'architecture-viz' ) ;
902948
903- const architectures = {
904- 'ELIZA (1966)' : 'Pattern → Rules → Response' ,
905- 'PARRY (1972)' : 'Input → State Machine → Emotional Model → Response' ,
906- 'ALICE (1995)' : 'Input → AIML Parser → Category Match → Response' ,
907- 'BlenderBot (2020)' : 'Input → Encoder → Decoder → Response' ,
908- 'SmolLM2 (2024)' : 'Input → Decoder-Only Transformer → Response'
909- } ;
949+ const architectures = [
950+ { name : 'ELIZA (1966)' , arch : 'Pattern → Rules → Response' , era : '1960s' } ,
951+ { name : 'PARRY (1972)' , arch : 'Input → State Machine → Emotional Model → Response' , era : '1970s' } ,
952+ { name : 'ALICE (1995)' , arch : 'Input → AIML Parser → Category Match → Response' , era : '1990s' } ,
953+ { name : 'BlenderBot (2020)' , arch : 'Input → Encoder → Decoder → Response' , era : '2010s' } ,
954+ { name : 'SmolLM2 (2024)' , arch : 'Input → Decoder-Only Transformer → Response' , era : '2020s' }
955+ ] ;
910956
911- let html = '<div style="padding: 15px; text-align: left;">' ;
912- for ( const [ name , arch ] of Object . entries ( architectures ) ) {
913- html += `<div style="margin-bottom: 12px; font-size: 0.8em;">
914- <strong>${ name } </strong><br>
915- <code style="font-size: 0.85em;">${ arch } </code>
916- </div>` ;
917- }
918- html += '</div>' ;
957+ const container = document . createElement ( 'div' ) ;
958+ container . style . cssText = 'padding: 15px; text-align: left;' ;
919959
920- vizDiv . innerHTML = html ;
960+ for ( const item of architectures ) {
961+ const div = document . createElement ( 'div' ) ;
962+ div . style . cssText = 'margin-bottom: 10px; font-size: 0.8em; cursor: pointer; padding: 8px; border-radius: 6px; transition: background 0.2s; line-height: 1.4;' ;
963+ const strong = document . createElement ( 'strong' ) ;
964+ strong . textContent = item . name ;
965+ strong . style . display = 'block' ;
966+ strong . style . marginBottom = '2px' ;
967+ const code = document . createElement ( 'code' ) ;
968+ code . textContent = item . arch ;
969+ code . style . cssText = 'font-size: 0.8em; word-break: break-word; display: block;' ;
970+ div . appendChild ( strong ) ;
971+ div . appendChild ( code ) ;
972+ div . addEventListener ( 'mouseenter' , ( ) => div . style . background = 'var(--surface-hover)' ) ;
973+ div . addEventListener ( 'mouseleave' , ( ) => div . style . background = 'transparent' ) ;
974+ div . addEventListener ( 'click' , ( ) => this . switchEra ( item . era ) ) ;
975+ container . appendChild ( div ) ;
976+ }
977+
978+ vizDiv . innerHTML = '' ;
979+ vizDiv . appendChild ( container ) ;
921980 }
922981}
923982
0 commit comments