Zebra / frontend /src /pages /CustomPuzzlePage.js
guoj5's picture
added custom puzzle page
ce10613
import React, { useState, useEffect } from 'react';
import SolutionNavigator from '../components/SolutionNavigator';
function CustomPuzzlePage() {
// Custom puzzle input
const [customPuzzleText, setCustomPuzzleText] = useState("");
const [sysContent, setSysContent] = useState("");
// Results
const [solutions, setSolutions] = useState([]);
const [currentSolutionIndex, setCurrentSolutionIndex] = useState(0);
const [generatedCode, setGeneratedCode] = useState("");
const [isSolving, setIsSolving] = useState(false);
const [solutionStatus, setSolutionStatus] = useState(""); // "success", "no_solution", "error"
const [errorMessage, setErrorMessage] = useState("");
// UI state
const [currentStep, setCurrentStep] = useState(1);
const [expandedSections, setExpandedSections] = useState({
puzzle: true,
sysContent: false,
result: false
});
// Load default system content
useEffect(() => {
fetch(`/default_sys_content_multi`)
.then(res => res.json())
.then(data => {
if(data.success) {
setSysContent(data.sysContent);
}
})
.catch(e => console.error(e));
}, []);
const handleSolveCustom = () => {
if(!customPuzzleText.trim()) {
alert("Please enter a puzzle text");
return;
}
const payload = {
puzzle: customPuzzleText,
sys_content: sysContent,
custom: true
};
setIsSolving(true);
setCurrentStep(3);
setExpandedSections({puzzle: false, sysContent: false, result: true});
setSolutions([]);
setCurrentSolutionIndex(0);
setSolutionStatus("");
setErrorMessage("");
fetch(`/solve_custom`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload)
})
.then(res => res.json())
.then(data => {
if(!data.success) {
setErrorMessage("Backend error: " + data.error);
setSolutionStatus("error");
return;
}
const result = data.result;
setGeneratedCode(result.generatedCode || "");
if (result.solutions && result.solutions.length > 0) {
setSolutions(result.solutions);
setSolutionStatus("success");
setCurrentSolutionIndex(0);
} else if (result.no_solution) {
setSolutionStatus("no_solution");
setErrorMessage(result.message || "No solution found for this puzzle");
} else {
setSolutionStatus("error");
setErrorMessage(result.error || "Unknown error occurred");
}
})
.catch(e => {
console.error(e);
setErrorMessage("Network error: " + e.message);
setSolutionStatus("error");
})
.finally(() => {
setIsSolving(false);
});
};
const toggleSection = (section) => {
setExpandedSections({
...expandedSections,
[section]: !expandedSections[section]
});
};
const nextStep = () => {
if (currentStep < 3) {
setCurrentStep(currentStep + 1);
if (currentStep === 1) {
setExpandedSections({puzzle: false, sysContent: true, result: false});
} else if (currentStep === 2) {
setExpandedSections({puzzle: false, sysContent: false, result: true});
}
}
};
const prevStep = () => {
if (currentStep > 1) {
setCurrentStep(currentStep - 1);
if (currentStep === 2) {
setExpandedSections({puzzle: true, sysContent: false, result: false});
} else if (currentStep === 3) {
setExpandedSections({puzzle: false, sysContent: true, result: false});
}
}
};
return (
<>
{/* Progress Steps */}
<div className="progress-container">
<div className="progress-steps">
<div className={`step ${currentStep >= 1 ? 'active' : ''}`}>
<div className="step-number">1</div>
<div className="step-label">Create Puzzle</div>
</div>
<div className={`step ${currentStep >= 2 ? 'active' : ''}`}>
<div className="step-number">2</div>
<div className="step-label">Configure System</div>
</div>
<div className={`step ${currentStep >= 3 ? 'active' : ''}`}>
<div className="step-number">3</div>
<div className="step-label">Solve & Results</div>
</div>
</div>
</div>
<main className="main-content">
{/* Step 1: Custom Puzzle Input */}
<section className={`content-section ${expandedSections.puzzle ? 'expanded' : 'collapsed'}`}>
<div className="section-header" onClick={() => toggleSection('puzzle')}>
<h2>✏️ Custom Puzzle Creation</h2>
<span className="toggle-icon">{expandedSections.puzzle ? 'β–Ό' : 'β–Ά'}</span>
</div>
{expandedSections.puzzle && (
<div className="section-content">
<div className="custom-puzzle-editor">
<h3>πŸ“ Puzzle Description</h3>
<p className="description">
Enter your custom zebra puzzle. Include categories, items, and constraints:
</p>
<div className="puzzle-input-container">
<textarea
value={customPuzzleText}
onChange={(e) => setCustomPuzzleText(e.target.value)}
className="custom-puzzle-textarea"
placeholder="Example:
There are 5 houses in a row.
Categories:
- Color: red, blue, green, yellow, white
- Name: Alice, Bob, Carol, Dave, Eve
- Pet: cat, dog, fish, bird, rabbit
- Drink: tea, coffee, milk, juice, water
- Sport: tennis, soccer, golf, swimming, running
Constraints:
1. The person in the red house owns a cat.
2. Alice drinks tea.
3. The green house is to the left of the white house.
..."
/>
</div>
<div className="puzzle-examples">
<h4>πŸ’‘ Tips for Creating Puzzles:</h4>
<ul>
<li>Clearly define all categories and their possible values</li>
<li>Number your constraints for clarity</li>
<li>Use spatial relationships like "to the left of", "next to", "between"</li>
<li>Include direct assignments like "Alice lives in the red house"</li>
<li>It will be better to have your expected results</li>
</ul>
</div>
</div>
</div>
)}
</section>
{/* Step 2: System Configuration */}
<section className={`content-section ${expandedSections.sysContent ? 'expanded' : 'collapsed'}`}>
<div className="section-header" onClick={() => toggleSection('sysContent')}>
<h2>βš™οΈ System Configuration</h2>
<span className="toggle-icon">{expandedSections.sysContent ? 'β–Ό' : 'β–Ά'}</span>
</div>
{expandedSections.sysContent && (
<div className="section-content">
<div className="sys-content-editor">
<h3>πŸ“ System Content</h3>
<p className="description">
Edit the system prompt that will guide the AI in solving your custom puzzle:
</p>
<textarea
value={sysContent}
onChange={(e) => setSysContent(e.target.value)}
className="sys-content-textarea"
placeholder="Enter system content..."
/>
</div>
</div>
)}
</section>
{/* Step 3: Solve & Results */}
<section className={`content-section ${expandedSections.result ? 'expanded' : 'collapsed'}`}>
<div className="section-header" onClick={() => toggleSection('result')}>
<h2>πŸš€ Solve & Results</h2>
<span className="toggle-icon">{expandedSections.result ? 'β–Ό' : 'β–Ά'}</span>
</div>
{expandedSections.result && (
<div className="section-content">
<div className="solve-section">
<button
onClick={handleSolveCustom}
disabled={isSolving || !customPuzzleText.trim()}
className={`btn btn-primary solve-btn ${isSolving ? 'loading' : ''}`}
>
{isSolving ? 'πŸ”„ Solving Custom Puzzle...' : '🧠 Solve Custom Puzzle'}
</button>
</div>
<div className="results-section">
<div className="result-summary">
<div className="result-item">
<span className="result-label">Status:</span>
<span className={`result-value ${
solutionStatus === 'success' ? 'success' :
solutionStatus === 'no_solution' ? 'warning' :
solutionStatus === 'error' ? 'error' : 'pending'
}`}>
{solutionStatus === 'success' ? "βœ… Solved" :
solutionStatus === 'no_solution' ? "⚠️ No Solution" :
solutionStatus === 'error' ? "❌ Error" : "⏳ Pending"}
</span>
</div>
{solutions.length > 0 && (
<div className="result-item">
<span className="result-label">Solutions Found:</span>
<span className="result-value">{solutions.length}</span>
</div>
)}
</div>
{/* Solution Navigator */}
{solutions.length > 0 && (
<SolutionNavigator
solutions={solutions}
currentIndex={currentSolutionIndex}
onIndexChange={setCurrentSolutionIndex}
/>
)}
{/* Error Message */}
{(solutionStatus === 'error' || solutionStatus === 'no_solution') && errorMessage && (
<div className="error-section">
<h3>⚠️ {solutionStatus === 'no_solution' ? 'No Solution Found' : 'Error'}</h3>
<div className="error-display">
<pre>{errorMessage}</pre>
</div>
</div>
)}
{/* Generated Code */}
{generatedCode && (
<div className="code-section">
<h3>πŸ’» Generated Code</h3>
<div className="code-display">
<pre><code>{generatedCode}</code></pre>
</div>
</div>
)}
</div>
</div>
)}
</section>
</main>
{/* Navigation */}
<nav className="navigation">
<button
onClick={prevStep}
disabled={currentStep <= 1}
className="btn btn-outline"
>
← Previous
</button>
<span className="step-indicator">
Step {currentStep} of 3
</span>
<button
onClick={nextStep}
disabled={currentStep >= 3}
className="btn btn-outline"
>
Next β†’
</button>
</nav>
</>
);
}
export default CustomPuzzlePage;