diff --git a/client/index.html b/client/index.html index c880bd36..e0cea69e 100644 --- a/client/index.html +++ b/client/index.html @@ -4,6 +4,7 @@ PyBe +
diff --git a/client/package-lock.json b/client/package-lock.json index 15e52316..b7f58099 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -17,6 +17,7 @@ } }, "..": { + "name": "pybe-mern-app", "version": "1.0.0", "dependencies": { "concurrently": "^9.1.2" diff --git a/client/src/components/HelpExampleWalkthrough.jsx b/client/src/components/HelpExampleWalkthrough.jsx new file mode 100644 index 00000000..5b3de203 --- /dev/null +++ b/client/src/components/HelpExampleWalkthrough.jsx @@ -0,0 +1,117 @@ +import React, { useState } from "react"; +import { ArrowRight, CheckCircle2, RefreshCw, Lightbulb, Code2 } from "lucide-react"; +import { HELP_CONTENT } from "../data/helpData"; + +export default function HelpExampleWalkthrough() { + const data = HELP_CONTENT["example-walkthrough"]; + const [activeStep, setActiveStep] = useState(0); + + const currentStep = data.steps[activeStep]; + + return ( +
+
+
+ +
+

{data.title}

+

{data.subtitle}

+
+
+
+ Scenario: {data.scenarioText} +
+
+ +
+ {data.steps.map((step, idx) => { + const isActive = idx === activeStep; + const isDone = idx < activeStep; + return ( + + ); + })} +
+ +
+
Step {currentStep.stepNum} of 3
+

{currentStep.title}

+

{currentStep.desc}

+ +
+
+ Real-World Action +
{currentStep.realWorld}
+
+
+ +
+
+ Mental / Code Value +
{currentStep.dataValue}
+
+
+
+ +
+
+ = 0 ? "active" : ""}`}> + 1. Real-world value (5 kg) + + + = 1 ? "active" : ""}`}> + 2. Meaningful name (weight) + + + = 2 ? "active" : ""}`}> + 3. Variable (weight = 5) + +
+
+ +
+ + + {activeStep < data.steps.length - 1 ? ( + + ) : ( + + )} +
+ +
+ + Why Python code exists: Instead of memorizing syntax rules, Python is just a clean, standardized tool for capturing the real-world values and decisions you already make! +
+
+ ); +} diff --git a/client/src/components/HelpManual.jsx b/client/src/components/HelpManual.jsx new file mode 100644 index 00000000..a7a9f7ff --- /dev/null +++ b/client/src/components/HelpManual.jsx @@ -0,0 +1,487 @@ +import React, { useState, useEffect, useRef } from "react"; +import { + Compass, + BookOpen, + Layout, + Sparkles, + CheckSquare, + Lightbulb, + X, + ArrowRight, + MessageSquare, + FileText, + Brain, + Code2, + BarChart2, + Layers, + AlertCircle, + CheckCircle2, + Info +} from "lucide-react"; +import { HELP_SECTIONS, HELP_CONTENT } from "../data/helpData"; +import HelpExampleWalkthrough from "./HelpExampleWalkthrough"; + +const ICON_MAP = { + Compass, + BookOpen, + Layout, + Sparkles, + CheckSquare, + Lightbulb, + FileText, + MessageSquare, + Brain, + BarChart2, + Layers, + Code2, + AlertCircle +}; + +export default function HelpManual({ isOpen, onClose, initialSectionId }) { + const [activeSectionId, setActiveSectionId] = useState("getting-started"); + const modalRef = useRef(null); + + useEffect(() => { + if (initialSectionId && HELP_SECTIONS.some((s) => s.id === initialSectionId)) { + setActiveSectionId(initialSectionId); + } + }, [initialSectionId, isOpen]); + + useEffect(() => { + function handleKeyDown(event) { + if (event.key === "Escape" && isOpen) { + onClose(); + } + } + if (isOpen) { + document.addEventListener("keydown", handleKeyDown); + document.body.style.overflow = "hidden"; + } + return () => { + document.removeEventListener("keydown", handleKeyDown); + document.body.style.overflow = ""; + }; + }, [isOpen, onClose]); + + if (!isOpen) return null; + + function renderSectionIcon(iconName, size = 18) { + const IconComponent = ICON_MAP[iconName] || HelpCircleFallback; + return ; + } + + return ( +
{ + if (e.target === e.currentTarget) onClose(); + }} + role="dialog" + aria-modal="true" + aria-labelledby="help-modal-title" + > +
+ {/* Header Bar */} +
+
+
+ +
+
+

PyBe Help Manual

+ Scenario-driven learning assistant & guide +
+
+ +
+ +
+
+ + {/* Modal Main Body */} +
+ {/* Left Navigation Sidebar */} + + + {/* Right Main Content Panel */} +
+ setActiveSectionId(id)} + /> +
+
+
+
+ ); +} + +function HelpCircleFallback({ size = 18 }) { + return ; +} + +/* ========================================================================== */ +/* SECTION DETAIL VIEW */ +/* ========================================================================== */ +function SectionDetailView({ sectionId, onSelectSection }) { + switch (sectionId) { + case "getting-started": + return ; + case "how-scenarios-work": + return ; + case "learning-session": + return ; + case "mentor-output": + return ; + case "how-to-approach": + return ; + case "example-walkthrough": + return ; + default: + return ; + } +} + +/* 1. Getting Started Content */ +function GettingStartedContent({ onSelectSection }) { + const data = HELP_CONTENT["getting-started"]; + return ( +
+
+

{data.title}

+

{data.subtitle}

+
+ +
+

{data.intro}

+
    + {data.whatIsPybe.map((item, idx) => ( +
  • + + {item} +
  • + ))} +
+
+ +
+

The PyBe Learning Flow

+

Here is how you navigate through every scenario from start to finish:

+ +
+ {data.learningFlow.map((item, idx) => ( + +
+
{item.step}
+
+ {item.label} + {item.desc} +
+
+ {idx < data.learningFlow.length - 1 && ( +
+ +
+ )} +
+ ))} +
+
+ +
+

What Happens After Submitting?

+

{data.afterSubmit}

+ +
+ +
+
+
+ ); +} + +/* 2. How Scenarios Work Content */ +function HowScenariosWorkContent() { + const data = HELP_CONTENT["how-scenarios-work"]; + return ( +
+
+

{data.title}

+

{data.subtitle}

+
+ +
+

{data.intro}

+ +
+ {data.progressionSteps.map((step, idx) => ( + +
+ 0{idx + 1} + {step.step} + {step.detail} +
+ {idx < data.progressionSteps.length - 1 && ( +
↓
+ )} +
+ ))} +
+
+ +
+

Simple Scenario Example

+
+
+ Scenario: +

{data.rainExample.scenario}

+
+
+ Reasoning: +

{data.rainExample.reasoning}

+
+
+ Python Concept: +

{data.rainExample.concept}

+
+
+ Python Construct: +
{data.rainExample.pythonCode}
+
+
+
+ +
+ +

{data.keyTakeaway}

+
+
+ ); +} + +/* 3. Learning Session Content */ +function LearningSessionContent({ onSelectSection }) { + const data = HELP_CONTENT["learning-session"]; + return ( +
+
+

{data.title}

+

{data.subtitle}

+
+ +
+ {data.sections.map((sec) => ( +
+
+ {sec.icon === "Compass" && } + {sec.icon === "Lightbulb" && } + {sec.icon === "FileText" && } + {sec.icon === "MessageSquare" && } + {sec.icon === "Brain" && } +

{sec.title}

+
+ +

"{sec.summary}"

+ + {sec.progression && ( +
+ Hint Progression: + {sec.progression} +
+ )} + + {sec.details &&

{sec.details}

} + + {sec.guidelines && ( +
    + {sec.guidelines.map((g, i) => ( +
  • {g}
  • + ))} +
+ )} + + {sec.example && ( +
+
+ Plain English Reasoning: +

"{sec.example.plainText}"

+
+
➔
+
+ Python Variable: + {sec.example.codeMap} +
+
+ )} + + {sec.promptComparison && ( +
+
+ Weak Prompt +
"{sec.promptComparison.weak.text}"
+

{sec.promptComparison.weak.reason}

+
+
+ Better Prompt +
"{sec.promptComparison.strong.text}"
+

{sec.promptComparison.strong.reason}

+
+
+ )} + + {sec.usefulPromptFactors && ( +
+ What Makes a Prompt Useful? +
    + {sec.usefulPromptFactors.map((f, i) => ( +
  • {f}
  • + ))} +
+
+ )} + + {sec.examples && ( +
+ Reflection Examples: +
    + {sec.examples.map((ex, i) => ( +
  • {ex}
  • + ))} +
+
+ )} +
+ ))} +
+
+ ); +} + +/* 4. Mentor Output Content */ +function MentorOutputContent() { + const data = HELP_CONTENT["mentor-output"]; + return ( +
+
+

{data.title}

+

{data.subtitle}

+
+ +
+ {data.components.map((comp, idx) => ( +
+
+ {comp.icon === "BarChart2" && } + {comp.icon === "Layers" && } + {comp.icon === "Code2" && } + {comp.icon === "AlertCircle" && } +

{comp.title}

+
+ + {comp.scoreExample && ( +
+ Prompt maturity: {comp.scoreExample} +
+ )} + +

{comp.explanation}

+ + {comp.importantNote && ( +
+ Important: {comp.importantNote} +
+ )} + + {comp.exampleFlow && ( +
+ {comp.exampleFlow.map((step, i) => ( +
+ {step.label}: + {step.val} +
+ ))} +
+ )} + + {comp.purpose &&

{comp.purpose}

} +
+ ))} +
+
+ ); +} + +/* 5. How to Approach Content */ +function HowToApproachContent({ onSelectSection }) { + const data = HELP_CONTENT["how-to-approach"]; + return ( +
+
+

{data.title}

+

{data.subtitle}

+
+ +
+ {data.steps.map((s) => ( +
+
{s.number}
+
+

{s.title}

+

{s.detail}

+
+
+ ))} +
+ +
+

Ready to see this approach in action?

+

Check out our interactive walkthrough of the Bag Weight Label scenario.

+ +
+
+ ); +} diff --git a/client/src/components/HelpTooltip.jsx b/client/src/components/HelpTooltip.jsx new file mode 100644 index 00000000..c7e21b34 --- /dev/null +++ b/client/src/components/HelpTooltip.jsx @@ -0,0 +1,73 @@ +import React, { useState, useRef, useEffect } from "react"; +import { HelpCircle, ExternalLink } from "lucide-react"; + +export default function HelpTooltip({ title, content, sectionId, onOpenManual }) { + const [isOpen, setIsOpen] = useState(false); + const containerRef = useRef(null); + + useEffect(() => { + function handleClickOutside(event) { + if (containerRef.current && !containerRef.current.contains(event.target)) { + setIsOpen(false); + } + } + function handleKeyDown(event) { + if (event.key === "Escape") { + setIsOpen(false); + } + } + if (isOpen) { + document.addEventListener("mousedown", handleClickOutside); + document.addEventListener("keydown", handleKeyDown); + } + return () => { + document.removeEventListener("mousedown", handleClickOutside); + document.removeEventListener("keydown", handleKeyDown); + }; + }, [isOpen]); + + return ( + + + + {isOpen && ( +
+
+ {title} + +
+

{content}

+ {sectionId && onOpenManual && ( + + )} +
+ )} +
+ ); +} diff --git a/client/src/data/helpData.js b/client/src/data/helpData.js new file mode 100644 index 00000000..ca0892d2 --- /dev/null +++ b/client/src/data/helpData.js @@ -0,0 +1,241 @@ +export const HELP_SECTIONS = [ + { + id: "getting-started", + title: "Getting Started", + icon: "Compass", + description: "Understand PyBe's scenario-driven approach to learning Python.", + keywords: ["getting started", "overview", "flow", "pybe", "scenario-driven", "submission", "learning flow"] + }, + { + id: "how-scenarios-work", + title: "How Scenarios Work", + icon: "BookOpen", + description: "Learn how real-world reasoning connects to programming concepts before code.", + keywords: ["scenarios", "reasoning", "programming concept", "python construct", "umbrella", "example"] + }, + { + id: "learning-session", + title: "Understanding a Learning Session", + icon: "Layout", + description: "A breakdown of every section on your learning screen.", + keywords: ["learning session", "scenario", "hints", "objectives", "your reasoning", "ai mentor prompt", "reflection"] + }, + { + id: "mentor-output", + title: "AI Mentor Output", + icon: "Sparkles", + description: "How to interpret your prompt maturity score, abstraction mapping, and feedback.", + keywords: ["mentor output", "prompt maturity", "abstraction mapping", "generated python", "misconception signals", "score"] + }, + { + id: "how-to-approach", + title: "How to Approach a Scenario", + icon: "CheckSquare", + description: "An 8-step practical guide to tackle any scenario effectively.", + keywords: ["approach", "guide", "steps", "strategy", "best practices", "how to solve"] + }, + { + id: "example-walkthrough", + title: "Example: Bag Weight Label", + icon: "Lightbulb", + description: "An interactive step-by-step example connecting real-world values to Python variables.", + keywords: ["example", "bag weight", "walkthrough", "variable", "5 kg", "name", "interactive"] + } +]; + +export const HELP_CONTENT = { + "getting-started": { + title: "Getting Started with PyBe", + subtitle: "Scenario-Driven Python Learning for Beginners", + intro: "PyBe is a scenario-driven Python learning platform designed to help you think like a developer before writing a single line of code. Traditional programming courses jump straight into syntax rules, but PyBe focuses on building your analytical reasoning first.", + whatIsPybe: [ + "Real-world context first: Every lesson starts with a familiar situation (like spending pocket money or checking the weather) rather than abstract code snippets.", + "Reasoning over syntax: You describe how to solve problems in plain English, allowing your natural problem-solving ability to lead.", + "AI Mentor guidance: Your reasoning is mapped into programming abstractions and Python code by an intelligent mentor that evaluates your prompt maturity." + ], + learningFlow: [ + { step: 1, label: "Scenario", desc: "Read a real-world situation and goal." }, + { step: 2, label: "Think about the problem", desc: "Analyze what is happening in the situation." }, + { step: 3, label: "Identify important info", desc: "Spot the key numbers, text, or decisions." }, + { step: 4, label: "Explain your reasoning", desc: "Describe your solution in simple plain English." }, + { step: 5, label: "Write AI Mentor prompt", desc: "Ask the AI mentor to guide or explain your logic." }, + { step: 6, label: "Submit", desc: "Click 'Map My Reasoning' to process your input." }, + { step: 7, label: "Receive feedback", desc: "Review prompt maturity, code mapping, and misconception watch." }, + { step: 8, label: "Reflect", desc: "Consolidate your learning by noting what clicked." } + ], + afterSubmit: "After submitting your solution, PyBe processes your input through its rule-based AI engine. You receive a Prompt Maturity Score, an Abstraction Mapping breakdown, generated Python code matching your logic, and misconception watch notes to deepen your understanding." + }, + + "how-scenarios-work": { + title: "How Scenarios Work", + subtitle: "From Real-World Thinking to Python Code", + intro: "PyBe does not begin by simply teaching Python syntax rules. Instead, it guides you through a progressive mental model:", + progressionSteps: [ + { step: "Real-world situation", detail: "A practical day-to-day scenario you already understand." }, + { step: "Reasoning", detail: "The plain-English logic you use to make a decision." }, + { step: "Programming concept", detail: "The underlying computer science idea (e.g. Variable, Condition, List)." }, + { step: "Python construct", detail: "The syntactic building block in Python (e.g. if/else statement)." }, + { step: "Code", detail: "The executable Python snippet." } + ], + rainExample: { + scenario: "It is raining before leaving home.", + reasoning: "If it is raining, I should carry an umbrella.", + concept: "Condition (Decision Making)", + pythonCode: "if raining:\n print(\"Carry an umbrella\")" + }, + keyTakeaway: "Notice how the Python code is just a formal way of writing down the thought you already had! You don't need to memorize code syntax blindly; you just need to express your natural logic clearly." + }, + + "learning-session": { + title: "Understanding a Learning Session", + subtitle: "A Complete Tour of the Workspace Screen", + sections: [ + { + id: "scenario-box", + title: "1. Scenario", + icon: "Compass", + summary: "The scenario gives you a real-world situation that you need to reason about.", + details: "Read the story context carefully. Pay attention to what information is given and what goal or decision needs to be accomplished." + }, + { + id: "hints-chips", + title: "2. Hints / Objectives", + icon: "Lightbulb", + summary: "Hint chips break down the problem into smaller mental stepping stones.", + progression: "Identify one value → Give the value a name → Connect naming to a variable", + details: "Hints should guide you toward the core concept without simply giving you the final answer. Try to think about the situation independently before relying on every hint chip!" + }, + { + id: "your-reasoning", + title: "3. Your Reasoning", + icon: "FileText", + summary: "This is where you describe how you would solve the situation using your own words.", + guidelines: [ + "Focus on your thinking, not code formatting.", + "Do not worry about Python syntax initially.", + "Explain what information matters in the situation.", + "Explain what decision or action should happen.", + "Break the problem into simple, logical steps." + ], + example: { + plainText: "The computer needs to remember the bag's weight. I can give the value a meaningful name so I can refer to it later.", + codeMap: "weight = 5" + } + }, + { + id: "ai-mentor-prompt", + title: "4. AI Mentor Prompt", + icon: "MessageSquare", + summary: "This is the question or instruction you would give to an AI mentor to help guide your learning.", + promptComparison: { + weak: { + text: "Give me the answer.", + reason: "Too vague. Does not explain what you understand or what specific guidance you need." + }, + strong: { + text: "Explain how my reasoning connects to Python variables and show me a simple example.", + reason: "Clear goal, specific request, specifies the desired explanation type." + } + }, + usefulPromptFactors: [ + "Clear goal: State what you want to accomplish.", + "Relevant context: Mention the situation or concept.", + "Specific request: Ask for explanation, comparison, or breakdown.", + "Desired output: Indicate if you want steps, code examples, or analogies." + ] + }, + { + id: "reflection", + title: "5. Reflection", + icon: "Brain", + summary: "Reflection asks you to think about what you learned from the scenario.", + examples: [ + "\"I noticed that I was already thinking in terms of conditions before learning the Python syntax.\"", + "\"I learned that a variable gives a name to a value so it can be used later in the program.\"" + ] + } + ] + }, + + "mentor-output": { + title: "Understanding AI Mentor Output", + subtitle: "How to Read the Right-Side Feedback Panel", + components: [ + { + title: "Prompt Maturity Score", + icon: "BarChart2", + scoreExample: 85, + explanation: "The Prompt Maturity score represents how effectively you communicated your request and reasoning to the AI mentor.", + importantNote: "Note: A lower score does NOT mean you lack Python skill! It simply means your prompt can be more specific, structured, or clear about your learning intent." + }, + { + title: "Abstraction Mapping", + icon: "Layers", + explanation: "Abstraction mapping connects your real-world reasoning directly to computer science abstractions.", + exampleFlow: [ + { label: "Real world", val: "The bag weighs 5 kg." }, + { label: "Abstraction", val: "Store a value and give it a meaningful name." }, + { label: "Python concept", val: "Variable" }, + { label: "Python", val: "weight = 5" } + ] + }, + { + title: "Generated Python", + icon: "Code2", + explanation: "This is one possible Python code representation of your plain-English reasoning.", + purpose: "The generated code is intended to help you bridge the gap between your conceptual thinking and Python syntax. Compare it with your reasoning to see how words translate into code." + }, + { + title: "Misconception Signals", + icon: "AlertCircle", + explanation: "These signals identify possible misunderstandings or missed nuances in your reasoning so you know exactly what to practice next." + } + ] + }, + + "how-to-approach": { + title: "How to Approach Any Scenario", + subtitle: "8 Practical Steps for Every Learner", + steps: [ + { number: 1, title: "Read the scenario carefully", detail: "Understand the context, goal, and constraints of the situation." }, + { number: 2, title: "Ask: 'What information matters?'", detail: "Identify numbers, names, status flags, or items that need to be remembered." }, + { number: 3, title: "Ask: 'What decision, action, or pattern is involved?'", detail: "Check if you are comparing values, repeating an action, or storing items together." }, + { number: 4, title: "Describe your reasoning in plain English", detail: "Write down how a human would solve this step by step without code keywords." }, + { number: 5, title: "Connect to a programming concept", detail: "Identify if this logic resembles a Variable, Condition, Loop, Function, or List." }, + { number: 6, title: "Write a useful AI mentor prompt", detail: "Formulate a clear prompt asking for specific guidance or explanation." }, + { number: 7, title: "Submit and study feedback", detail: "Analyze your Prompt Maturity, Abstraction Mapping, and Misconception Signals." }, + { number: 8, title: "Complete the reflection", detail: "Jot down your key insight to reinforce your long-term memory." } + ] + }, + + "example-walkthrough": { + title: "Example: Bag Weight Label", + subtitle: "Interactive Walkthrough Connecting Values to Variables", + scenarioText: "A student has one school bag with a weight written on a scale.", + goal: "Learn why Python variables exist instead of just memorizing syntax rules.", + steps: [ + { + stepNum: 1, + title: "Identify the value", + realWorld: "The scale reads 5 kg.", + dataValue: "5", + desc: "First, notice the piece of information that the computer needs to remember." + }, + { + stepNum: 2, + title: "Give the value a name", + realWorld: "Instead of calling it 'that number on the scale', we give it a clear label: 'weight'.", + dataValue: "weight", + desc: "Giving a value a meaningful name allows us to refer to it later in our thinking." + }, + { + stepNum: 3, + title: "Connect the name to a Python variable", + realWorld: "We join the name and value using the assignment operator (=).", + dataValue: "weight = 5", + desc: "Now the computer stores 5 in memory under the name 'weight'. Python syntax is complete!" + } + ], + flowSummary: "Real-world value (5) → Meaningful name (weight) → Variable (weight = 5)" + } +}; diff --git a/client/src/main.jsx b/client/src/main.jsx index ef3da3a9..d32c7189 100644 --- a/client/src/main.jsx +++ b/client/src/main.jsx @@ -1,26 +1,31 @@ -import React, { useEffect, useMemo, useState } from 'react'; -import { createRoot } from 'react-dom/client'; +import React, { useEffect, useMemo, useState } from "react"; +import { createRoot } from "react-dom/client"; import { Brain, ChartNoAxesCombined, Code2, Compass, + HelpCircle, Lightbulb, MessageSquareText, + MoonStar, Play, Route, Search, Send, - Sparkles -} from 'lucide-react'; -import './styles.css'; + Sparkles, + SunMedium, +} from "lucide-react"; +import "./styles.css"; +import HelpManual from "./components/HelpManual"; +import HelpTooltip from "./components/HelpTooltip"; -const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:5000/api'; +const API_URL = import.meta.env.VITE_API_URL || "http://localhost:5000/api"; async function api(path, options) { const response = await fetch(`${API_URL}${path}`, { - headers: { 'Content-Type': 'application/json' }, - ...options + headers: { "Content-Type": "application/json" }, + ...options, }); if (!response.ok) throw new Error(await response.text()); return response.json(); @@ -32,22 +37,59 @@ function App() { const [sessions, setSessions] = useState([]); const [analytics, setAnalytics] = useState(null); const [roadmap, setRoadmap] = useState([]); - const [filters, setFilters] = useState({ q: '', difficulty: '', concept: '' }); - const [form, setForm] = useState({ learnerName: 'Guest learner', reasoning: '', promptText: '', reflection: '' }); + const [filters, setFilters] = useState({ + q: "", + difficulty: "", + concept: "", + }); + const [form, setForm] = useState({ + learnerName: "Guest learner", + reasoning: "", + promptText: "", + reflection: "", + }); const [activeResult, setActiveResult] = useState(null); const [loading, setLoading] = useState(true); const [submitting, setSubmitting] = useState(false); + const [helpOpen, setHelpOpen] = useState(true); + const [helpSection, setHelpSection] = useState("getting-started"); + const [theme, setTheme] = useState(() => { + const savedTheme = localStorage.getItem("pybe-theme"); + if (savedTheme) return savedTheme; + return window.matchMedia("(prefers-color-scheme: dark)").matches + ? "dark" + : "light"; + }); + + useEffect(() => { + document.documentElement.setAttribute("data-theme", theme); + localStorage.setItem("pybe-theme", theme); + }, [theme]); - const concepts = useMemo(() => [...new Set(scenarios.flatMap((scenario) => scenario.concepts || []))].sort(), [scenarios]); + function openHelp(sectionId = "getting-started") { + setHelpSection(sectionId); + setHelpOpen(true); + } + + const concepts = useMemo( + () => + [ + ...new Set(scenarios.flatMap((scenario) => scenario.concepts || [])), + ].sort(), + [scenarios], + ); async function refresh() { - const params = new URLSearchParams(Object.entries(filters).filter(([, value]) => value)); - const [scenarioData, sessionData, analyticsData, roadmapData] = await Promise.all([ - api(`/scenarios?${params}`), - api('/sessions'), - api('/analytics'), - api('/roadmap') - ]); + const params = new URLSearchParams( + Object.entries(filters).filter(([, value]) => value), + ); + const [scenarioData, sessionData, analyticsData, roadmapData] = + await Promise.all([ + api(`/scenarios?${params}`), + api("/sessions"), + api("/analytics"), + api("/roadmap"), + ]); setScenarios(scenarioData); setSessions(sessionData); setAnalytics(analyticsData); @@ -65,12 +107,12 @@ function App() { if (!selected || !form.reasoning.trim()) return; setSubmitting(true); try { - const result = await api('/sessions', { - method: 'POST', - body: JSON.stringify({ ...form, scenarioId: selected._id }) + const result = await api("/sessions", { + method: "POST", + body: JSON.stringify({ ...form, scenarioId: selected._id }), }); setActiveResult(result); - setForm({ ...form, reasoning: '', promptText: '', reflection: '' }); + setForm({ ...form, reasoning: "", promptText: "", reflection: "" }); await refresh(); } finally { setSubmitting(false); @@ -82,40 +124,78 @@ function App() { return (