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
425 changes: 179 additions & 246 deletions frontend/src/EducationalScratchApp.jsx

Large diffs are not rendered by default.

125 changes: 125 additions & 0 deletions frontend/src/components/ChallengeSelector.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { challenges } from '../data/challenges';

/**
* ChallengeSelector Component
* Let users select different challenges to solve
*/
function ChallengeSelector({ currentChallenge, onSelectChallenge, onClose }) {
const getDifficultyColor = (difficulty) => {
switch (difficulty) {
case 'Beginner':
return 'bg-green-100 text-green-700 border-green-300';
case 'Intermediate':
return 'bg-yellow-100 text-yellow-700 border-yellow-300';
case 'Advanced':
return 'bg-red-100 text-red-700 border-red-300';
default:
return 'bg-blue-100 text-blue-700 border-blue-300';
}
};

return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
<div className="bg-white rounded-xl shadow-2xl max-w-4xl w-full max-h-[80vh] overflow-hidden flex flex-col">
{/* Header */}
<div className="bg-gradient-to-r from-indigo-600 to-purple-600 p-6 text-white">
<div className="flex items-center justify-between">
<div>
<h2 className="text-2xl font-bold mb-1">Select a Challenge</h2>
<p className="text-sm opacity-90">Choose your mission and build your agent</p>
</div>
<button
onClick={onClose}
className="p-2 hover:bg-white hover:bg-opacity-20 rounded-lg transition"
>
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
</div>

{/* Challenges Grid */}
<div className="flex-1 overflow-y-auto p-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{challenges.map((challenge) => (
<div
key={challenge.id}
className={`border-2 rounded-lg p-4 cursor-pointer transition-all hover:shadow-lg ${
currentChallenge?.id === challenge.id
? 'border-indigo-600 bg-indigo-50'
: 'border-gray-200 hover:border-indigo-300'
}`}
onClick={() => {
onSelectChallenge(challenge);
onClose();
}}
>
{/* Challenge Header */}
<div className="flex items-start justify-between mb-3">
<h3 className="text-lg font-bold text-gray-900">{challenge.title}</h3>
<span className={`text-xs px-2 py-1 rounded-full border font-medium ${getDifficultyColor(challenge.difficulty)}`}>
{challenge.difficulty}
</span>
</div>

{/* Description */}
<p className="text-sm text-gray-600 mb-3">{challenge.description}</p>

{/* Stats */}
<div className="flex items-center gap-4 text-xs text-gray-500 mb-3">
<div className="flex items-center gap-1">
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<span>{Object.values(challenge.successCriteria).filter(Boolean).length} criteria</span>
</div>
{challenge.items && challenge.items.length > 0 && (
<div className="flex items-center gap-1">
<span>⭐</span>
<span>{challenge.items.length} items</span>
</div>
)}
{challenge.obstacles && challenge.obstacles.length > 0 && (
<div className="flex items-center gap-1">
<span>🚧</span>
<span>{challenge.obstacles.length} obstacles</span>
</div>
)}
</div>

{/* Recommended Blocks */}
<div className="flex flex-wrap gap-1">
{challenge.recommendedBlocks.map((block, idx) => (
<span
key={idx}
className="text-xs px-2 py-1 bg-gray-100 text-gray-700 rounded font-medium"
>
{block}
</span>
))}
</div>

{/* Current Challenge Badge */}
{currentChallenge?.id === challenge.id && (
<div className="mt-3 pt-3 border-t border-indigo-200">
<span className="text-xs font-bold text-indigo-600">✓ Currently Active</span>
</div>
)}
</div>
))}
</div>
</div>

{/* Footer */}
<div className="bg-gray-50 p-4 border-t border-gray-200">
<p className="text-xs text-gray-600 text-center">
💡 Tip: Start with easier challenges to learn the basics, then progress to advanced ones
</p>
</div>
</div>
</div>
);
}

export default ChallengeSelector;
14 changes: 11 additions & 3 deletions frontend/src/components/ComponentDetailSidebar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -208,12 +208,20 @@ export default function ComponentDetailSidebar({ component, lessons, progress, o

{/* Lesson Name */}
<td className="py-3 px-3">
<div className="flex items-center gap-2">
<a
href="/builder"
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-2 hover:text-blue-400 transition-colors group"
>
<div>
<div className="font-medium text-sm">{lesson.title}</div>
<div className="font-medium text-sm group-hover:underline">{lesson.title}</div>
<div className="text-xs text-gray-400">{lesson.focus}</div>
</div>
</div>
<svg className="w-4 h-4 text-gray-500 opacity-0 group-hover:opacity-100 transition-opacity" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
</svg>
</a>
</td>

{/* Difficulty */}
Expand Down
13 changes: 5 additions & 8 deletions frontend/src/components/LessonCard.jsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
'use client'

import { useState } from 'react';
import { useRouter } from 'next/navigation';

/**
* LessonCard Component
* Displays a single lesson with progress tracking
*/
export default function LessonCard({ lesson, progress, onProgressUpdate, allLessons }) {
const [isExpanded, setIsExpanded] = useState(false);
const router = useRouter();

// Check if prerequisites are met
const prerequisitesMet = lesson.prerequisites.every(prereqId => {
const prereqProgress = allLessons.find(l => l.id === prereqId)?.id;
return prereqProgress && (progress[prereqId] === 'completed' || progress[prereqId] === 'in-progress');
const prerequisitesMet = lesson.prerequisites.length === 0 || lesson.prerequisites.every(prereqId => {
return progress[prereqId] === 'completed' || progress[prereqId] === 'in-progress';
});

// Get progress badge color
Expand Down Expand Up @@ -153,12 +150,12 @@ export default function LessonCard({ lesson, progress, onProgressUpdate, allLess
{prerequisitesMet && (
<div className="flex gap-1">
<button
onClick={() => router.push('/builder')}
onClick={() => window.open('/builder', '_blank')}
className="px-3 py-2 text-sm bg-green-600 text-white rounded-lg hover:bg-green-700 transition flex items-center gap-1"
title="Open Agent Builder"
title="Open Agent Builder in new tab"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M14 5l7 7m0 0l-7 7m7-7H3" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
</svg>
Try It
</button>
Expand Down
192 changes: 192 additions & 0 deletions frontend/src/components/LessonContent.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
/**
* LessonContent Component
* Displays educational content explaining Agentic AI concepts
*/
function LessonContent({ nodes }) {
const lessons = {
sense: {
title: '👁️ Sense',
tagline: 'Perceive the environment',
description: 'Agents gather information from their surroundings through sensors, APIs, and data inputs.',
keyPoints: [
'Collects data from environment',
'Detects obstacles and targets',
'Real-time monitoring'
],
example: 'Self-driving cars use cameras to detect obstacles',
tryThis: 'Drag a SENSE block to help your agent see!'
},
plan: {
title: '🧠 Plan',
tagline: 'Think and decide',
description: 'Agents analyze data, evaluate options, and choose the best action. This is where intelligence happens.',
keyPoints: [
'Breaks goals into steps',
'Weighs different options',
'Predicts outcomes'
],
example: 'Chess AI evaluates millions of moves to find the best one',
tryThis: 'Connect SENSE → PLAN for smart decisions!'
},
act: {
title: '⚡ Act',
tagline: 'Execute the plan',
description: 'Agents execute their decisions and interact with the environment to achieve goals.',
keyPoints: [
'Converts plans to actions',
'Manipulates the environment',
'Creates feedback loops'
],
example: 'Robot arm picks and places objects based on its plan',
tryThis: 'Add ACT to see your agent move in the sandbox!'
},
reflect: {
title: '💭 Reflect',
tagline: 'Learn and improve',
description: 'Agents analyze outcomes, update memory, and improve future performance through learning.',
keyPoints: [
'Evaluates what worked',
'Stores successful strategies',
'Adapts based on experience'
],
example: 'AlphaGo learns from game outcomes to play better',
tryThis: 'Complete the cycle with REFLECT for learning!'
}
};

// Determine primary lesson to display
const getCurrentLesson = () => {
if (nodes.length === 0) {
return {
title: '🎓 Get Started',
tagline: 'Build your first agent',
description: 'Agents perceive, think, act, and learn in a continuous cycle.',
keyPoints: [
'Drag blocks from the sidebar',
'Connect them in order',
'Click Run to see it work'
],
example: 'Try building: SENSE → PLAN → ACT → REFLECT',
tryThis: 'Drag your first block to begin!'
};
}

// Show lesson for most recently added block
const lastBlock = nodes[nodes.length - 1];
const blockType = lastBlock.label.toLowerCase();

if (lessons[blockType]) {
return lessons[blockType];
}

// Default to sense if block type doesn't match
return lessons.sense;
};

const currentLesson = getCurrentLesson();

// Additional guidance based on current setup
const getGuidance = () => {
const blockTypes = nodes.map(n => n.label);
const hasSense = blockTypes.includes('SENSE');
const hasPlan = blockTypes.includes('PLAN');
const hasAct = blockTypes.includes('ACT');
const hasReflect = blockTypes.includes('REFLECT');

if (hasSense && hasPlan && hasAct && hasReflect) {
return { type: 'success', message: 'Complete! Click "Run Agent" to test' };
}
if (hasAct && !hasSense) {
return { type: 'warning', message: 'Add SENSE before ACT' };
}
if (hasSense && hasAct && !hasPlan) {
return { type: 'info', message: 'Add PLAN for smarter decisions' };
}
if (hasAct && !hasReflect) {
return { type: 'info', message: 'Add REFLECT to enable learning' };
}

return null;
};

const guidance = getGuidance();
const guidanceStyles = {
success: 'bg-green-50 border-l-4 border-green-500 text-green-800',
warning: 'bg-orange-50 border-l-4 border-orange-500 text-orange-800',
info: 'bg-blue-50 border-l-4 border-blue-500 text-blue-800'
};

return (
<div className="flex-1 p-4 overflow-y-auto bg-gradient-to-br from-white to-gray-50">
{/* Lesson Header */}
<div className="mb-4 pb-3 border-b-2 border-indigo-100">
<h2 className="text-xl font-bold text-gray-900 mb-1">{currentLesson.title}</h2>
<p className="text-sm text-indigo-600">{currentLesson.tagline}</p>
</div>

{/* Description */}
<p className="text-sm text-gray-700 mb-4 leading-relaxed">{currentLesson.description}</p>

{/* Key Points */}
<div className="mb-4 bg-white rounded-lg p-3 shadow-sm border border-gray-100">
<h3 className="text-xs font-bold text-gray-700 mb-2 uppercase tracking-wide">What it does:</h3>
<ul className="space-y-1.5">
{currentLesson.keyPoints.map((point, idx) => (
<li key={idx} className="text-sm text-gray-700 flex items-start gap-2">
<span className="text-indigo-500 mt-0.5">•</span>
<span>{point}</span>
</li>
))}
</ul>
</div>

{/* Example */}
<div className="mb-4 bg-gradient-to-r from-blue-50 to-indigo-50 rounded-lg p-3 border border-blue-200">
<h3 className="text-xs font-bold text-gray-700 mb-1.5 uppercase tracking-wide">Example:</h3>
<p className="text-sm text-gray-700 italic">{currentLesson.example}</p>
</div>

{/* Try This */}
<div className="mb-4 bg-indigo-600 text-white rounded-lg p-3 shadow-md">
<p className="text-xs font-bold mb-1">💡 Try This:</p>
<p className="text-sm">{currentLesson.tryThis}</p>
</div>

{/* Current Progress Guidance */}
{guidance && (
<div className={`rounded-lg p-3 mb-4 ${guidanceStyles[guidance.type]}`}>
<p className="text-sm font-medium">{guidance.message}</p>
</div>
)}

{/* SPAR Cycle Diagram - Compact */}
<div className="bg-white rounded-lg p-3 border-2 border-gray-200">
<h3 className="text-xs font-bold text-gray-700 mb-2 text-center uppercase tracking-wide">Agent Cycle</h3>
<div className="space-y-1">
<div className={`flex items-center gap-2 p-2 rounded-md transition-all ${nodes.some(n => n.label === 'SENSE') ? 'bg-blue-500 text-white shadow-md' : 'bg-gray-100 text-gray-500'}`}>
<span className="text-base">👁️</span>
<span className="text-xs font-medium">SENSE</span>
</div>
<div className="text-center text-gray-300 text-xs">↓</div>
<div className={`flex items-center gap-2 p-2 rounded-md transition-all ${nodes.some(n => n.label === 'PLAN') ? 'bg-yellow-500 text-white shadow-md' : 'bg-gray-100 text-gray-500'}`}>
<span className="text-base">🧠</span>
<span className="text-xs font-medium">PLAN</span>
</div>
<div className="text-center text-gray-300 text-xs">↓</div>
<div className={`flex items-center gap-2 p-2 rounded-md transition-all ${nodes.some(n => n.label === 'ACT') ? 'bg-green-500 text-white shadow-md' : 'bg-gray-100 text-gray-500'}`}>
<span className="text-base">⚡</span>
<span className="text-xs font-medium">ACT</span>
</div>
<div className="text-center text-gray-300 text-xs">↓</div>
<div className={`flex items-center gap-2 p-2 rounded-md transition-all ${nodes.some(n => n.label === 'REFLECT') ? 'bg-purple-500 text-white shadow-md' : 'bg-gray-100 text-gray-500'}`}>
<span className="text-base">💭</span>
<span className="text-xs font-medium">REFLECT</span>
</div>
<div className="text-center text-gray-400 text-xs mt-1">↻ Repeat</div>
</div>
</div>
</div>
);
}

export default LessonContent;
Loading