| | 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; |