|
import React, { useState, useEffect } from 'react'; |
|
import SolutionNavigator from '../components/SolutionNavigator'; |
|
|
|
function CustomPuzzlePage() { |
|
|
|
const [customPuzzleText, setCustomPuzzleText] = useState(""); |
|
const [sysContent, setSysContent] = useState(""); |
|
|
|
|
|
const [solutions, setSolutions] = useState([]); |
|
const [currentSolutionIndex, setCurrentSolutionIndex] = useState(0); |
|
const [generatedCode, setGeneratedCode] = useState(""); |
|
const [isSolving, setIsSolving] = useState(false); |
|
const [solutionStatus, setSolutionStatus] = useState(""); |
|
const [errorMessage, setErrorMessage] = useState(""); |
|
|
|
|
|
const [currentStep, setCurrentStep] = useState(1); |
|
const [expandedSections, setExpandedSections] = useState({ |
|
puzzle: true, |
|
sysContent: false, |
|
result: false |
|
}); |
|
|
|
|
|
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; |