Skip to content

Commit c04c111

Browse files
jeremymanningclaude
andcommitted
feat(demo-01): Interactive goto resolution in Rule Breakdown
Major improvements to how goto statements are handled: 1. Dynamic Goto Resolution Step: - When selecting a "goto X" template, a new step appears showing: - The goto statement with warning styling - Target keyword info (pattern and response count) - Dropdown to select from target's responses - Resolved template display - Note that original captures are preserved 2. Interactive Template Switching: - Selecting a goto template dynamically adds the resolution step - Selecting a non-goto template removes it - Step numbers update automatically - All downstream steps (post-substitutions, final response) update 3. Goto Template Dropdown: - Users can browse all responses from the target keyword - Selecting a different response updates the final output - Changes propagate through post-substitutions to final response This makes the goto mechanism fully transparent and educational, showing exactly how ELIZA redirects to other keyword rules. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent b1f038d commit c04c111

File tree

2 files changed

+280
-14
lines changed

2 files changed

+280
-14
lines changed

demos/01-eliza/index.html

Lines changed: 269 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -482,13 +482,36 @@ <h3>Synonyms</h3>
482482
</div>
483483
<div class="arrow" style="color: var(--warning-color, #856404);">↪</div>
484484
<div style="flex: 1;">
485-
<div class="io-label">Target: "${escapeHtml(step.targetKeyword)}" (${step.targetTemplates.length} template(s))</div>
485+
<div class="io-label">Target Keyword: "${escapeHtml(step.targetKeyword)}"</div>
486+
<div class="io-box" style="font-size: 0.85rem;">
487+
<div><strong>Pattern:</strong> "${escapeHtml(step.targetPatternString || '*')}"</div>
488+
<div style="margin-top: 4px;"><strong>Responses:</strong> ${step.targetTemplates.length} available</div>
489+
</div>
490+
</div>
491+
</div>
492+
`;
493+
// Show the resolved template selection
494+
stepHTML += `
495+
<div class="step-io" style="margin-top: 12px;">
496+
<div style="flex: 1;">
497+
<div class="io-label">Select Response from "${escapeHtml(step.targetKeyword)}"</div>
486498
<select class="template-dropdown" id="goto-template-selector" style="width: 100%; padding: 10px; border: 2px solid var(--border-color); border-radius: 6px; font-size: 0.9rem; background: var(--surface-color); color: var(--text-primary); cursor: pointer;">
487499
${step.targetTemplates.map((t, i) => `<option value="${i}" ${escapeHtml(t) === escapeHtml(step.output.replace(/^"|"$/g, '')) ? 'selected' : ''}>${escapeHtml(t)}</option>`).join('')}
488500
</select>
489501
</div>
502+
<div class="arrow">&rarr;</div>
503+
<div style="flex: 1;">
504+
<div class="io-label">Resolved Template</div>
505+
<div class="io-box">${escapeHtml(step.output)}</div>
506+
</div>
490507
</div>
491508
`;
509+
// Add note about captures being preserved
510+
if (step.originalCapturesPreserved) {
511+
stepHTML += `<div class="step-details" style="margin-top: 8px; font-style: italic;">
512+
Note: Original captures (${step.capturesAvailable}) are preserved and can be used in the resolved template.
513+
</div>`;
514+
}
492515
stepHTML += `<div class="step-details">${step.details}</div>`;
493516
} else if (step.input !== undefined && step.output !== undefined) {
494517
// Add input/output visualization for other steps
@@ -642,11 +665,51 @@ <h3>Synonyms</h3>
642665
}
643666
}
644667

645-
// Re-assemble the response with the new template
646668
const patternMatcher = eliza.patternMatcher;
647669
const postSubs = eliza.postSubstitutions;
670+
const rules = eliza.rules;
671+
672+
// Check if selected template is a goto statement
673+
let finalTemplate = selectedTemplate;
674+
let gotoResolutionData = null;
675+
676+
if (selectedTemplate.startsWith('goto ')) {
677+
// Resolve the goto
678+
const gotoChain = [];
679+
let currentTemplate = selectedTemplate;
680+
let targetRule = null;
681+
let targetPattern = null;
682+
683+
while (currentTemplate && currentTemplate.startsWith('goto ')) {
684+
const targetKeyword = currentTemplate.substring(5).trim();
685+
gotoChain.push(targetKeyword);
686+
687+
targetRule = rules.find(r => r.keyword === targetKeyword);
688+
if (targetRule && targetRule.patterns && targetRule.patterns.length > 0) {
689+
targetPattern = targetRule.patterns[0];
690+
const targetTemplates = targetPattern.responses;
691+
// Select first template by default (user can change via goto dropdown)
692+
currentTemplate = targetTemplates[0];
693+
} else {
694+
break;
695+
}
648696

649-
let newResponse = selectedTemplate;
697+
if (gotoChain.length >= 10) break;
698+
}
699+
700+
finalTemplate = currentTemplate;
701+
gotoResolutionData = {
702+
gotoChain,
703+
targetKeyword: gotoChain[gotoChain.length - 1],
704+
targetRule,
705+
targetPattern,
706+
targetTemplates: targetPattern ? targetPattern.responses : [],
707+
resolvedTemplate: finalTemplate
708+
};
709+
}
710+
711+
// Re-assemble the response with the final template
712+
let newResponse = finalTemplate;
650713
for (let i = 0; i < captures.length; i++) {
651714
const placeholder = `(${i + 1})`;
652715
if (newResponse.includes(placeholder)) {
@@ -659,23 +722,217 @@ <h3>Synonyms</h3>
659722
// Format response (uppercase to match ELIZA style)
660723
newResponse = newResponse.toUpperCase();
661724

662-
// Update the final response display
663-
const finalResponseContent = document.getElementById('final-response-content');
664-
if (finalResponseContent) {
665-
finalResponseContent.textContent = newResponse;
666-
}
725+
// Update the breakdown steps dynamically
726+
updateBreakdownWithGoto(selectedTemplate, gotoResolutionData, finalTemplate, newResponse, captures);
727+
}
667728

668-
// Also update the Post-substitutions & Assembly step if it exists
729+
// Update breakdown steps when a goto is selected or deselected
730+
function updateBreakdownWithGoto(selectedTemplate, gotoData, finalTemplate, finalResponse, captures) {
669731
const stepsDiv = document.getElementById('breakdown-steps');
732+
const finalResponseStep = document.getElementById('final-response-step');
733+
734+
// Find existing goto resolution step (by id or by title)
735+
let gotoStep = document.getElementById('goto-resolution-step');
736+
if (!gotoStep) {
737+
// Also check by title for steps created during initial render
738+
const stepElements = stepsDiv.querySelectorAll('.breakdown-step');
739+
stepElements.forEach(stepEl => {
740+
const titleEl = stepEl.querySelector('.step-title');
741+
if (titleEl && titleEl.textContent === 'Goto Resolution') {
742+
gotoStep = stepEl;
743+
}
744+
});
745+
}
746+
747+
// Find the Post-substitutions step
748+
let postSubStep = null;
670749
const stepElements = stepsDiv.querySelectorAll('.breakdown-step');
671750
stepElements.forEach(stepEl => {
672751
const titleEl = stepEl.querySelector('.step-title');
673752
if (titleEl && titleEl.textContent === 'Post-substitutions & Assembly') {
674-
const outputBox = stepEl.querySelectorAll('.io-box')[1];
675-
if (outputBox) {
676-
outputBox.textContent = newResponse;
753+
postSubStep = stepEl;
754+
}
755+
});
756+
757+
if (gotoData) {
758+
// Need to show/update goto resolution step
759+
if (!gotoStep) {
760+
// Create the goto resolution step
761+
gotoStep = document.createElement('div');
762+
gotoStep.className = 'breakdown-step';
763+
gotoStep.id = 'goto-resolution-step';
764+
gotoStep.style.borderColor = 'var(--warning-color, #ffc107)';
765+
766+
// Insert before post-substitutions step
767+
if (postSubStep) {
768+
stepsDiv.insertBefore(gotoStep, postSubStep);
769+
// Update step numbers
770+
updateStepNumbers();
677771
}
678772
}
773+
774+
// Update goto step content
775+
gotoStep.innerHTML = `
776+
<div class="step-header">
777+
<div class="step-number" style="background: var(--warning-color, #ffc107);">↪</div>
778+
<div class="step-title">Goto Resolution</div>
779+
</div>
780+
<div class="step-description">Following reference to another keyword's responses</div>
781+
<div class="step-io">
782+
<div style="flex: 1;">
783+
<div class="io-label">Goto Statement</div>
784+
<div class="io-box" style="background: var(--warning-bg, #fff3cd); border-color: var(--warning-border, #ffc107);">"${escapeHtml(selectedTemplate)}"</div>
785+
</div>
786+
<div class="arrow" style="color: var(--warning-color, #856404);">↪</div>
787+
<div style="flex: 1;">
788+
<div class="io-label">Target Keyword: "${escapeHtml(gotoData.targetKeyword)}"</div>
789+
<div class="io-box" style="font-size: 0.85rem;">
790+
<div><strong>Pattern:</strong> "${escapeHtml(gotoData.targetPattern ? gotoData.targetPattern.pattern : '*')}"</div>
791+
<div style="margin-top: 4px;"><strong>Responses:</strong> ${gotoData.targetTemplates.length} available</div>
792+
</div>
793+
</div>
794+
</div>
795+
<div class="step-io" style="margin-top: 12px;">
796+
<div style="flex: 1;">
797+
<div class="io-label">Select Response from "${escapeHtml(gotoData.targetKeyword)}"</div>
798+
<select class="template-dropdown" id="goto-template-selector" style="width: 100%; padding: 10px; border: 2px solid var(--border-color); border-radius: 6px; font-size: 0.9rem; background: var(--surface-color); color: var(--text-primary); cursor: pointer;">
799+
${gotoData.targetTemplates.map((t, i) => `<option value="${i}" ${i === 0 ? 'selected' : ''}>${escapeHtml(t)}</option>`).join('')}
800+
</select>
801+
</div>
802+
<div class="arrow">&rarr;</div>
803+
<div style="flex: 1;">
804+
<div class="io-label">Resolved Template</div>
805+
<div class="io-box" id="goto-resolved-template">"${escapeHtml(finalTemplate)}"</div>
806+
</div>
807+
</div>
808+
${captures.length > 0 ? `<div class="step-details" style="margin-top: 8px; font-style: italic;">
809+
Note: Original captures (${captures.length}) are preserved and can be used in the resolved template.
810+
</div>` : ''}
811+
<div class="step-details">Redirected to keyword "${escapeHtml(gotoData.targetKeyword)}"</div>
812+
`;
813+
814+
// Add event listener for the goto template selector
815+
const gotoSelector = document.getElementById('goto-template-selector');
816+
if (gotoSelector) {
817+
gotoSelector.addEventListener('change', handleGotoTemplateChange);
818+
}
819+
} else {
820+
// Remove goto resolution step if it exists
821+
if (gotoStep) {
822+
gotoStep.remove();
823+
updateStepNumbers();
824+
}
825+
}
826+
827+
// Update Post-substitutions step
828+
if (postSubStep) {
829+
const inputBox = postSubStep.querySelector('.io-box');
830+
const outputBox = postSubStep.querySelectorAll('.io-box')[1];
831+
if (inputBox) inputBox.textContent = finalTemplate;
832+
if (outputBox) outputBox.textContent = finalResponse;
833+
}
834+
835+
// Update final response
836+
const finalResponseContent = document.getElementById('final-response-content');
837+
if (finalResponseContent) {
838+
finalResponseContent.textContent = finalResponse;
839+
}
840+
}
841+
842+
// Handle goto template dropdown change
843+
function handleGotoTemplateChange(e) {
844+
const selectedIndex = parseInt(e.target.value);
845+
846+
// We need to get the target templates from the current goto data
847+
// Re-resolve by looking at the current template selection
848+
const templateSelector = document.getElementById('breakdown-template-selector');
849+
if (!templateSelector) return;
850+
851+
const templateStep = currentBreakdown.steps.find(s => s.name === 'Template Selection');
852+
if (!templateStep) return;
853+
854+
const mainTemplate = templateStep.allTemplates[parseInt(templateSelector.value)];
855+
if (!mainTemplate.startsWith('goto ')) return;
856+
857+
// Resolve the goto chain
858+
const rules = eliza.rules;
859+
let targetKeyword = mainTemplate.substring(5).trim();
860+
let targetRule = rules.find(r => r.keyword === targetKeyword);
861+
862+
// Handle chain
863+
while (targetRule && targetRule.patterns && targetRule.patterns.length > 0) {
864+
const targetPattern = targetRule.patterns[0];
865+
const templates = targetPattern.responses;
866+
const selectedGotoTemplate = templates[selectedIndex];
867+
868+
if (selectedGotoTemplate && selectedGotoTemplate.startsWith('goto ')) {
869+
targetKeyword = selectedGotoTemplate.substring(5).trim();
870+
targetRule = rules.find(r => r.keyword === targetKeyword);
871+
} else {
872+
// Found the final template
873+
const patternMatcher = eliza.patternMatcher;
874+
const postSubs = eliza.postSubstitutions;
875+
876+
// Get captures
877+
const patternStep = currentBreakdown.steps.find(s => s.name === 'Pattern Matching');
878+
const captures = [];
879+
if (patternStep && patternStep.patternTests) {
880+
const matchedTest = patternStep.patternTests.find(t => t.matched);
881+
if (matchedTest && matchedTest.captures) {
882+
captures.push(...matchedTest.captures);
883+
}
884+
}
885+
886+
// Assemble response
887+
let newResponse = selectedGotoTemplate;
888+
for (let i = 0; i < captures.length; i++) {
889+
const placeholder = `(${i + 1})`;
890+
if (newResponse.includes(placeholder)) {
891+
const captureText = Array.isArray(captures[i]) ? captures[i].join(' ') : captures[i];
892+
const { result } = patternMatcher.applyPostSubstitutions(captureText, postSubs);
893+
newResponse = newResponse.replace(placeholder, result);
894+
}
895+
}
896+
newResponse = newResponse.toUpperCase();
897+
898+
// Update displays
899+
const resolvedTemplateBox = document.getElementById('goto-resolved-template');
900+
if (resolvedTemplateBox) {
901+
resolvedTemplateBox.textContent = `"${selectedGotoTemplate}"`;
902+
}
903+
904+
// Update post-sub step
905+
const stepsDiv = document.getElementById('breakdown-steps');
906+
stepsDiv.querySelectorAll('.breakdown-step').forEach(stepEl => {
907+
const titleEl = stepEl.querySelector('.step-title');
908+
if (titleEl && titleEl.textContent === 'Post-substitutions & Assembly') {
909+
const inputBox = stepEl.querySelector('.io-box');
910+
const outputBox = stepEl.querySelectorAll('.io-box')[1];
911+
if (inputBox) inputBox.textContent = selectedGotoTemplate;
912+
if (outputBox) outputBox.textContent = newResponse;
913+
}
914+
});
915+
916+
// Update final response
917+
const finalResponseContent = document.getElementById('final-response-content');
918+
if (finalResponseContent) {
919+
finalResponseContent.textContent = newResponse;
920+
}
921+
922+
break;
923+
}
924+
}
925+
}
926+
927+
// Update step numbers after adding/removing steps
928+
function updateStepNumbers() {
929+
const stepsDiv = document.getElementById('breakdown-steps');
930+
const stepElements = stepsDiv.querySelectorAll('.breakdown-step:not(#final-response-step)');
931+
stepElements.forEach((stepEl, index) => {
932+
const stepNumber = stepEl.querySelector('.step-number');
933+
if (stepNumber && !stepNumber.textContent.includes('✓') && !stepNumber.textContent.includes('↪')) {
934+
stepNumber.textContent = `${index + 1}`;
935+
}
679936
});
680937
}
681938

demos/01-eliza/js/pattern-matcher.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -590,9 +590,14 @@ export class PatternMatcher {
590590

591591
// Add goto resolution step if we followed any gotos
592592
if (gotoChain.length > 0) {
593+
// Check if the selected template uses captures that won't be available from the target
594+
const capturesInOriginal = matchResult ? matchResult.captures.length : 0;
595+
const templateUsesCapturesRegex = /\(\d+\)/g;
596+
const capturesUsedInFinal = finalTemplate ? (finalTemplate.match(templateUsesCapturesRegex) || []) : [];
597+
593598
breakdown.steps.push({
594599
name: 'Goto Resolution',
595-
description: 'Following reference to another keyword',
600+
description: 'Following reference to another keyword\'s responses',
596601
input: `"${selectedTemplate}"`,
597602
output: gotoTargetRule ? `"${finalTemplate}"` : 'Target not found',
598603
details: gotoChain.length === 1
@@ -602,7 +607,11 @@ export class PatternMatcher {
602607
targetKeyword: gotoChain[gotoChain.length - 1],
603608
targetRule: gotoTargetRule,
604609
targetPattern: gotoTargetPattern,
605-
targetTemplates: gotoTargetPattern ? gotoTargetPattern.responses : []
610+
targetPatternString: gotoTargetPattern ? gotoTargetPattern.pattern : null,
611+
targetTemplates: gotoTargetPattern ? gotoTargetPattern.responses : [],
612+
originalCapturesPreserved: capturesInOriginal > 0,
613+
capturesAvailable: capturesInOriginal,
614+
capturesUsedInTemplate: capturesUsedInFinal.length
606615
});
607616

608617
// Update selected template to the resolved one

0 commit comments

Comments
 (0)