guoj5 commited on
Commit
ce10613
·
1 Parent(s): 84b5dd5

added custom puzzle page

Browse files
backend/Example.txt CHANGED
@@ -153,13 +153,13 @@ if s.check() == sat:
153
  block.append(Vars[cat_idx][i] != m[Vars[cat_idx][i]])
154
  s.add(Or(block))
155
  while s.check() == sat:
156
- m = s.model()
157
- cnt += 1
158
- block = []
159
- for cat_idx, (cat_name, item_list) in enumerate(categories):
160
- for i in range(NUM_POSITIONS):
161
- block.append(Vars[cat_idx][i] != m[Vars[cat_idx][i]])
162
- s.add(Or(block))
163
 
164
  if cnt == 1:
165
  # Output the solution as a JSON string.
 
153
  block.append(Vars[cat_idx][i] != m[Vars[cat_idx][i]])
154
  s.add(Or(block))
155
  while s.check() == sat:
156
+ m = s.model()
157
+ cnt += 1
158
+ block = []
159
+ for cat_idx, (cat_name, item_list) in enumerate(categories):
160
+ for i in range(NUM_POSITIONS):
161
+ block.append(Vars[cat_idx][i] != m[Vars[cat_idx][i]])
162
+ s.add(Or(block))
163
 
164
  if cnt == 1:
165
  # Output the solution as a JSON string.
backend/Example_multi.txt ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ This is an example of representing a Zebra Puzzle using a Z3-based Python code:
2
+
3
+ There are 5 Producers in this puzzle.
4
+
5
+ Categories and possible items:
6
+ - Shirt: black, green, orange, pink, red
7
+ - Name: Barbara, Isabella, Jane, Nicole, Rachel
8
+ - Jam: apricot, cherry, fig, raspberry, watermelon
9
+ - Size: 10 oz, 12 oz, 14 oz, 20 oz, 6 oz
10
+ - Source: backyard, farmers' coop, local grocer, organic farm, wild pickings
11
+
12
+ Clues:
13
+ - The producer whose jam comes from Wild pickings is in the fourth position.
14
+ - The producer selling Cherry jam is somewhere to the right of the one wearing a Green shirt.
15
+ - Isabella is selling Cherry jam.
16
+ - The producer wearing a Pink shirt is somewhere between the producer whose jam source is the Backyard and the one selling Watermelon jam, in that order.
17
+ - The producer selling 14 oz jars is somewhere to the right of the one wearing a Green shirt.
18
+ - The producer with 14 oz jars is next to the one selling Raspberry jam.
19
+ - The Raspberry jam producer is positioned at one of the ends.
20
+ - Barbara is next to the producer whose jam source is the Organic farm.
21
+ - Jane is located somewhere between Nicole and Isabella, in that order.
22
+ - The producer of Fig jam sources the fruit from an Organic farm.
23
+ - The producer with 10 oz jars is at one of the ends.
24
+ - The Raspberry jam producer is somewhere to the right of the one wearing a Green shirt.
25
+ - The producer with 12 oz jars is next to the one who has 6 oz jars.
26
+ - Isabella is next to the producer wearing a Black shirt.
27
+ - The producer with 6 oz jars is next to the one whose source is the Local grocer.
28
+ - The producer with 6 oz jars is in the second position.
29
+ - Rachel's source of fruit is the Farmers' coop.
30
+ - Barbara is next to Nicole.
31
+ - The producer wearing an Orange shirt gets her fruit from the Backyard.
32
+ - The producer with 12 oz jars is in the very first position.
33
+
34
+ ```python
35
+ from z3 import *
36
+ import json
37
+ import sys
38
+
39
+ # Define all categories in a single list of tuples:
40
+ # (House is implicit; each row's first column will be the house number.)
41
+ categories = [
42
+ ("Shirt", ["black", "green", "orange", "pink", "red"]),
43
+ ("Name", ["Barbara", "Isabella", "Jane", "Nicole", "Rachel"]),
44
+ ("Jam", ["apricot", "cherry", "fig", "raspberry", "watermelon"]),
45
+ ("Size", ["10 oz", "12 oz", "14 oz", "20 oz", "6 oz"]),
46
+ ("Source", ["backyard", "farmers' coop", "local grocer", "organic farm", "wild pickings"])
47
+ ]
48
+
49
+ # No need to change here, automatically processing
50
+ NUM_POSITIONS = len(categories[0][1])
51
+
52
+ item_to_cat_and_index = {}
53
+ for cat_idx, (cat_str, item_list) in enumerate(categories):
54
+ for item_idx, item_str in enumerate(item_list):
55
+ item_to_cat_and_index[(cat_str, item_str)] = (cat_idx, item_idx)
56
+
57
+ Vars = []
58
+ for cat_idx, (cat_name, item_list) in enumerate(categories):
59
+ var = IntVector(cat_name, len(item_list))
60
+ Vars.append(var)
61
+
62
+ s = Solver()
63
+
64
+ for cat_idx, (cat_name, item_list) in enumerate(categories):
65
+ for item_idx, item_str in enumerate(item_list):
66
+ s.add(Vars[cat_idx][item_idx] >= 1, Vars[cat_idx][item_idx] <= NUM_POSITIONS)
67
+ s.add(Distinct(Vars[cat_idx]))
68
+
69
+ def pos(cat_str, item_str):
70
+ (cat_idx, item_idx) = item_to_cat_and_index[(cat_str, item_str)]
71
+ return Vars[cat_idx][item_idx]
72
+
73
+ # All clues here
74
+ # The producer whose jam comes from Wild pickings is in the fourth position.
75
+ s.add(pos("Source", "wild pickings") == 4)
76
+
77
+ # The producer selling Cherry jam is somewhere to the right of the one wearing a Green shirt.
78
+ s.add(pos("Jam", "cherry") > pos("Shirt", "green"))
79
+
80
+ # Isabella is selling Cherry jam.
81
+ s.add(pos("Name", "Isabella") == pos("Jam", "cherry"))
82
+
83
+ # The producer wearing a Pink shirt is somewhere between the producer whose jam source is the Backyard and the one selling Watermelon jam, in that order.
84
+ s.add(And(pos("Source", "backyard") < pos("Shirt", "pink"), pos("Shirt", "pink") < pos("Jam", "watermelon")))
85
+
86
+ # The producer selling 14 oz jars is somewhere to the right of the one wearing a Green shirt.
87
+ s.add(pos("Size", "14 oz") > pos("Shirt", "green"))
88
+
89
+ # The producer with 14 oz jars is next to the one selling Raspberry jam.
90
+ s.add(Abs(pos("Size", "14 oz") - pos("Jam", "raspberry")) == 1)
91
+
92
+ # The Raspberry jam producer is positioned at one of the ends.
93
+ s.add(Or(pos("Jam", "raspberry") == 1, pos("Jam", "raspberry") == NUM_POSITIONS))
94
+
95
+ # Barbara is next to the producer whose jam source is the Organic farm.
96
+ s.add(Abs(pos("Name", "Barbara") - pos("Source", "organic farm")) == 1)
97
+
98
+ # Jane is located somewhere between Nicole and Isabella, in that order.
99
+ s.add(And(pos("Name", "Nicole") < pos("Name", "Jane"), pos("Name", "Jane") < pos("Name", "Isabella")))
100
+
101
+ # The producer of Fig jam sources the fruit from an Organic farm.
102
+ s.add(pos("Jam", "fig") == pos("Source", "organic farm"))
103
+
104
+ # The producer with 10 oz jars is at one of the ends.
105
+ s.add(Or(pos("Size", "10 oz") == 1, pos("Size", "10 oz") == NUM_POSITIONS))
106
+
107
+ # The Raspberry jam producer is somewhere to the right of the one wearing a Green shirt.
108
+ s.add(pos("Jam", "raspberry") > pos("Shirt", "green"))
109
+
110
+ # The producer with 12 oz jars is next to the one who has 6 oz jars.
111
+ s.add(Abs(pos("Size", "12 oz") - pos("Size", "6 oz")) == 1)
112
+
113
+ # Isabella is next to the producer wearing a Black shirt.
114
+ s.add(Abs(pos("Name", "Isabella") - pos("Shirt", "black")) == 1)
115
+
116
+ # The producer with 6 oz jars is next to the one whose source is the Local grocer.
117
+ s.add(Abs(pos("Size", "6 oz") - pos("Source", "local grocer")) == 1)
118
+
119
+ # The producer with 6 oz jars is in the second position.
120
+ s.add(pos("Size", "6 oz") == 2)
121
+
122
+ # Rachel's source of fruit is the Farmers' coop.
123
+ s.add(pos("Name", "Rachel") == pos("Source", "farmers' coop"))
124
+
125
+ # Barbara is next to Nicole.
126
+ s.add(Abs(pos("Name", "Barbara") - pos("Name", "Nicole")) == 1)
127
+
128
+ # The producer wearing an Orange shirt gets her fruit from the Backyard.
129
+ s.add(pos("Shirt", "orange") == pos("Source", "backyard"))
130
+
131
+ # The producer with 12 oz jars is in the very first position.
132
+ s.add(pos("Size", "12 oz") == 1)
133
+
134
+ # Solve the puzzle
135
+ if s.check() == sat:
136
+ while s.check() == sat:
137
+ m = s.model()
138
+ rows = []
139
+ header = ["House"] + [cat_name for cat_name, _ in categories]
140
+ for position in range(1, NUM_POSITIONS + 1):
141
+ row = [str(position)]
142
+ for cat_idx, (cat_name, item_list) in enumerate(categories):
143
+ for item_idx, item_str in enumerate(item_list):
144
+ if m.evaluate(Vars[cat_idx][item_idx]).as_long() == position:
145
+ row.append(item_str)
146
+ break
147
+ rows.append(row)
148
+ result_dict = {"header": header, "rows": rows}
149
+ print(json.dumps(result_dict))
150
+
151
+ block = []
152
+ for cat_idx, (cat_name, item_list) in enumerate(categories):
153
+ for i in range(NUM_POSITIONS):
154
+ block.append(Vars[cat_idx][i] != m[Vars[cat_idx][i]])
155
+ s.add(Or(block))
156
+
157
+ if len(solutions) >= 10:
158
+ break
159
+ else:
160
+ print("NO_SOLUTION")
161
+ sys.stdout.flush() # Force immediate output
162
+ ```
163
+
164
+ Based on this example, write a similar Z3-based Python code by changing only categories and constraints to represent the puzzle given below.
backend/app.py CHANGED
@@ -3,12 +3,15 @@ from flask_cors import CORS
3
  import os
4
 
5
  from puzzle_dataset import get_puzzle_by_index
6
- from solver import solve_puzzle
7
 
8
  BASE_DIR = os.path.dirname(os.path.abspath(__file__))
9
  example_path = os.path.join(BASE_DIR, "Example.txt")
10
  with open(example_path, "r", encoding="utf-8") as f:
11
  DEFAULT_SYS_CONTENT = f.read()
 
 
 
12
  sat_cnt_path = os.path.join(BASE_DIR, "Sat_cnt.txt")
13
  with open(sat_cnt_path, "r", encoding="utf-8") as f:
14
  SAT_CNT_CONTENT = f.read()
@@ -55,9 +58,25 @@ def solve():
55
  result = solve_puzzle(puzzle_index, puzzle_text, expected_solution, sys_content, SAT_CNT_CONTENT)
56
  return jsonify({"success": True, "result": result})
57
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  @app.route("/default_sys_content", methods=["GET"])
59
  def get_default_sys_content():
60
  return jsonify({"success": True, "sysContent": DEFAULT_SYS_CONTENT})
61
 
 
 
 
 
62
  if __name__ == "__main__":
63
  app.run(host="0.0.0.0", port=7860, debug=False)
 
3
  import os
4
 
5
  from puzzle_dataset import get_puzzle_by_index
6
+ from solver import solve_puzzle, solve_custom_puzzle
7
 
8
  BASE_DIR = os.path.dirname(os.path.abspath(__file__))
9
  example_path = os.path.join(BASE_DIR, "Example.txt")
10
  with open(example_path, "r", encoding="utf-8") as f:
11
  DEFAULT_SYS_CONTENT = f.read()
12
+ example_multi_path = os.path.join(BASE_DIR, "Example_multi.txt")
13
+ with open(example_multi_path, "r", encoding="utf-8") as f:
14
+ DEFAULT_SYS_CONTENT_MULTI = f.read()
15
  sat_cnt_path = os.path.join(BASE_DIR, "Sat_cnt.txt")
16
  with open(sat_cnt_path, "r", encoding="utf-8") as f:
17
  SAT_CNT_CONTENT = f.read()
 
58
  result = solve_puzzle(puzzle_index, puzzle_text, expected_solution, sys_content, SAT_CNT_CONTENT)
59
  return jsonify({"success": True, "result": result})
60
 
61
+ @app.route("/solve_custom", methods=["POST"])
62
+ def solve_custom():
63
+ data = request.get_json()
64
+ puzzle_text = data.get("puzzle")
65
+ sys_content = data.get("sys_content", DEFAULT_SYS_CONTENT_MULTI)
66
+
67
+ if not puzzle_text:
68
+ return jsonify({"success": False, "error": "Missing puzzle text"}), 400
69
+
70
+ result = solve_custom_puzzle(puzzle_text, sys_content, SAT_CNT_CONTENT)
71
+ return jsonify({"success": True, "result": result})
72
+
73
  @app.route("/default_sys_content", methods=["GET"])
74
  def get_default_sys_content():
75
  return jsonify({"success": True, "sysContent": DEFAULT_SYS_CONTENT})
76
 
77
+ @app.route("/default_sys_content_multi", methods=["GET"])
78
+ def get_default_sys_content_multi():
79
+ return jsonify({"success": True, "sysContent": DEFAULT_SYS_CONTENT_MULTI})
80
+
81
  if __name__ == "__main__":
82
  app.run(host="0.0.0.0", port=7860, debug=False)
backend/solver.py CHANGED
@@ -206,3 +206,85 @@ def solve_puzzle(index, puzzle, expected_solution, sys_content, sat_cnt_content)
206
  "modelResponse": content,
207
  "problematicConstraints": problematic_constraints
208
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
206
  "modelResponse": content,
207
  "problematicConstraints": problematic_constraints
208
  }
209
+
210
+ def solve_custom_puzzle(puzzle, sys_content, sat_cnt_content):
211
+ """
212
+ 解决自定义谜题,返回所有可能的解
213
+ """
214
+ load_dotenv()
215
+ client = OpenAI(
216
+ api_key=os.getenv("GEMINI_API_KEY"),
217
+ base_url="https://generativelanguage.googleapis.com/v1beta/openai/"
218
+ )
219
+
220
+ messages = [
221
+ {"role": "user", "content": sys_content},
222
+ {"role": "user", "content": puzzle},
223
+ ]
224
+
225
+ attempts = 0
226
+ while attempts < 3:
227
+ messages.append({"role": "user", "content": "Make sure you stick to the given categories with strictly identical names otherwise there will be a critical error."})
228
+ attempts += 1
229
+ response = client.chat.completions.create(
230
+ model="gemini-2.0-flash",
231
+ messages=messages,
232
+ temperature=0.0,
233
+ stream=False
234
+ )
235
+ content = response.choices[0].message.content
236
+ messages.append(response.choices[0].message)
237
+
238
+ # 提取代码
239
+ code_blocks = re.findall(r"```(?:python)?(.*?)```", content, re.DOTALL)
240
+ if not code_blocks:
241
+ messages.append({"role": "user", "content": "Please write a complete Python code in your response. Try again."})
242
+ continue
243
+
244
+ code_to_run = code_blocks[-1].strip()
245
+ result = subprocess.run(
246
+ [sys.executable, "-c", code_to_run],
247
+ stdout=subprocess.PIPE,
248
+ stderr=subprocess.PIPE,
249
+ text=True
250
+ )
251
+
252
+ if result.stderr.strip():
253
+ messages.append({"role": "user", "content": f"Your code has errors: {result.stderr.strip()}. Please check your code and provide the complete code in the end."})
254
+ continue
255
+
256
+ output = result.stdout.strip()
257
+ try:
258
+ if output.startswith("NO_SOLUTION"):
259
+ return {
260
+ "no_solution": True,
261
+ "message": "No solution exists for this puzzle",
262
+ "generatedCode": code_to_run
263
+ }
264
+ else:
265
+ solutions = []
266
+ for line in output.split('\n'):
267
+ if line.strip():
268
+ try:
269
+ solution = json.loads(line)
270
+ solutions.append(solution)
271
+ except json.JSONDecodeError:
272
+ continue
273
+
274
+ if solutions:
275
+ return {
276
+ "solutions": solutions,
277
+ "generatedCode": code_to_run
278
+ }
279
+ else:
280
+ messages.append({"role": "user", "content": "Your output is not valid JSON. Please ensure your code prints solutions as JSON dictionaries."})
281
+ continue
282
+ except Exception as e:
283
+ messages.append({"role": "user", "content": f"Error processing output: {str(e)}. Please check your code format."})
284
+ continue
285
+
286
+ return {
287
+ "error": "Failed to solve puzzle after 3 attempts",
288
+ "generatedCode": code_to_run if 'code_to_run' in locals() else ""
289
+ }
290
+
frontend/src/App.css CHANGED
@@ -431,6 +431,243 @@
431
  backdrop-filter: blur(10px);
432
  }
433
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
434
  /* Responsive Design */
435
  @media (max-width: 768px) {
436
  .app-header h1 {
@@ -460,6 +697,33 @@
460
  flex-direction: column;
461
  gap: 1rem;
462
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
463
  }
464
 
465
  @media (max-width: 480px) {
@@ -475,4 +739,18 @@
475
  padding: 0.5rem 1rem;
476
  font-size: 0.9rem;
477
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
478
  }
 
431
  backdrop-filter: blur(10px);
432
  }
433
 
434
+ /* Page Navigation */
435
+ .page-navigation {
436
+ display: flex;
437
+ gap: 1rem;
438
+ margin-top: 2rem;
439
+ justify-content: center;
440
+ }
441
+
442
+ .nav-tab {
443
+ padding: 0.75rem 2rem;
444
+ border: 2px solid rgba(255, 255, 255, 0.3);
445
+ border-radius: 25px;
446
+ background: rgba(255, 255, 255, 0.1);
447
+ color: white;
448
+ font-size: 1rem;
449
+ font-weight: 500;
450
+ cursor: pointer;
451
+ transition: all 0.3s ease;
452
+ backdrop-filter: blur(10px);
453
+ }
454
+
455
+ .nav-tab:hover {
456
+ background: rgba(255, 255, 255, 0.2);
457
+ border-color: rgba(255, 255, 255, 0.5);
458
+ }
459
+
460
+ .nav-tab.active {
461
+ background: white;
462
+ color: #667eea;
463
+ border-color: white;
464
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
465
+ }
466
+
467
+ /* Custom Puzzle Page */
468
+ .custom-puzzle-editor h3 {
469
+ margin-bottom: 0.5rem;
470
+ color: #333;
471
+ }
472
+
473
+ .puzzle-input-container {
474
+ margin-bottom: 2rem;
475
+ }
476
+
477
+ .custom-puzzle-textarea {
478
+ width: 100%;
479
+ min-height: 400px;
480
+ padding: 1rem;
481
+ border: 2px solid #e0e0e0;
482
+ border-radius: 8px;
483
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
484
+ font-size: 0.9rem;
485
+ line-height: 1.5;
486
+ resize: vertical;
487
+ transition: border-color 0.3s ease;
488
+ }
489
+
490
+ .custom-puzzle-textarea:focus {
491
+ outline: none;
492
+ border-color: #667eea;
493
+ box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
494
+ }
495
+
496
+ .puzzle-examples {
497
+ background: #f8f9ff;
498
+ border-radius: 8px;
499
+ padding: 1.5rem;
500
+ border-left: 4px solid #667eea;
501
+ }
502
+
503
+ .puzzle-examples h4 {
504
+ margin-bottom: 1rem;
505
+ color: #333;
506
+ }
507
+
508
+ .puzzle-examples ul {
509
+ margin-left: 1.5rem;
510
+ color: #555;
511
+ line-height: 1.6;
512
+ }
513
+
514
+ .puzzle-examples li {
515
+ margin-bottom: 0.5rem;
516
+ }
517
+
518
+ /* Solution Navigator */
519
+ .solution-navigator {
520
+ background: #f8f9fa;
521
+ border-radius: 10px;
522
+ padding: 1.5rem;
523
+ margin-bottom: 2rem;
524
+ }
525
+
526
+ .solution-header {
527
+ display: flex;
528
+ justify-content: space-between;
529
+ align-items: center;
530
+ margin-bottom: 1.5rem;
531
+ }
532
+
533
+ .solution-header h3 {
534
+ margin: 0;
535
+ color: #333;
536
+ }
537
+
538
+ .solution-controls {
539
+ display: flex;
540
+ align-items: center;
541
+ gap: 1rem;
542
+ }
543
+
544
+ .btn-sm {
545
+ padding: 0.5rem 1rem;
546
+ font-size: 0.9rem;
547
+ }
548
+
549
+ .solution-pagination {
550
+ display: flex;
551
+ gap: 0.25rem;
552
+ }
553
+
554
+ .pagination-btn {
555
+ width: 35px;
556
+ height: 35px;
557
+ border: 1px solid #ddd;
558
+ border-radius: 6px;
559
+ background: white;
560
+ color: #666;
561
+ font-size: 0.9rem;
562
+ cursor: pointer;
563
+ transition: all 0.2s ease;
564
+ }
565
+
566
+ .pagination-btn:hover {
567
+ background: #f0f0f0;
568
+ }
569
+
570
+ .pagination-btn.active {
571
+ background: #667eea;
572
+ color: white;
573
+ border-color: #667eea;
574
+ }
575
+
576
+ .solution-display {
577
+ background: white;
578
+ border-radius: 8px;
579
+ padding: 1rem;
580
+ border: 1px solid #e0e0e0;
581
+ }
582
+
583
+ .solution-info {
584
+ margin-bottom: 1rem;
585
+ padding-bottom: 0.5rem;
586
+ border-bottom: 1px solid #eee;
587
+ }
588
+
589
+ .solution-counter {
590
+ color: #666;
591
+ font-weight: 500;
592
+ }
593
+
594
+ .solution-table-container {
595
+ overflow-x: auto;
596
+ }
597
+
598
+ .solution-table {
599
+ width: 100%;
600
+ border-collapse: collapse;
601
+ font-size: 0.9rem;
602
+ }
603
+
604
+ .solution-table th,
605
+ .solution-table td {
606
+ padding: 0.75rem;
607
+ text-align: left;
608
+ border: 1px solid #ddd;
609
+ }
610
+
611
+ .solution-table th {
612
+ background: #f8f9fa;
613
+ font-weight: 600;
614
+ color: #333;
615
+ }
616
+
617
+ .solution-table tr:nth-child(even) {
618
+ background: #f9f9f9;
619
+ }
620
+
621
+ .solution-table tr:hover {
622
+ background: #f0f4ff;
623
+ }
624
+
625
+ .solution-json {
626
+ background: #f8f9fa;
627
+ border-radius: 6px;
628
+ padding: 1rem;
629
+ overflow-x: auto;
630
+ }
631
+
632
+ .solution-json pre {
633
+ margin: 0;
634
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
635
+ font-size: 0.85rem;
636
+ line-height: 1.4;
637
+ }
638
+
639
+ /* Error Section */
640
+ .error-section {
641
+ margin-top: 2rem;
642
+ }
643
+
644
+ .error-section h3 {
645
+ margin-bottom: 1rem;
646
+ color: #dc3545;
647
+ }
648
+
649
+ .error-display {
650
+ background: #fff5f5;
651
+ border: 1px solid #fed7d7;
652
+ border-radius: 8px;
653
+ padding: 1rem;
654
+ max-height: 300px;
655
+ overflow-y: auto;
656
+ }
657
+
658
+ .error-display pre {
659
+ margin: 0;
660
+ color: #c53030;
661
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
662
+ font-size: 0.9rem;
663
+ line-height: 1.5;
664
+ white-space: pre-wrap;
665
+ }
666
+
667
+ .result-value.warning {
668
+ color: #f56500;
669
+ }
670
+
671
  /* Responsive Design */
672
  @media (max-width: 768px) {
673
  .app-header h1 {
 
697
  flex-direction: column;
698
  gap: 1rem;
699
  }
700
+
701
+ .page-navigation {
702
+ flex-direction: column;
703
+ align-items: center;
704
+ }
705
+
706
+ .nav-tab {
707
+ width: 200px;
708
+ text-align: center;
709
+ }
710
+
711
+ .solution-header {
712
+ flex-direction: column;
713
+ gap: 1rem;
714
+ align-items: stretch;
715
+ }
716
+
717
+ .solution-controls {
718
+ justify-content: center;
719
+ flex-wrap: wrap;
720
+ }
721
+
722
+ .solution-pagination {
723
+ max-width: 100%;
724
+ overflow-x: auto;
725
+ padding: 0.5rem 0;
726
+ }
727
  }
728
 
729
  @media (max-width: 480px) {
 
739
  padding: 0.5rem 1rem;
740
  font-size: 0.9rem;
741
  }
742
+
743
+ .custom-puzzle-textarea {
744
+ min-height: 300px;
745
+ font-size: 0.8rem;
746
+ }
747
+
748
+ .solution-table {
749
+ font-size: 0.8rem;
750
+ }
751
+
752
+ .solution-table th,
753
+ .solution-table td {
754
+ padding: 0.5rem;
755
+ }
756
  }
frontend/src/App.js CHANGED
@@ -1,125 +1,19 @@
1
- import React, { useState, useEffect } from 'react';
2
  import './App.css';
 
 
3
 
4
  function App() {
5
- // For puzzle index and puzzle data
6
- const [puzzleIndex, setPuzzleIndex] = useState(0);
7
- const [puzzleText, setPuzzleText] = useState("");
8
- const [expectedSolution, setExpectedSolution] = useState(null);
9
-
10
- // sysContent can be editted, default using Example.txt
11
- const [sysContent, setSysContent] = useState("");
12
-
13
- // Interaction results
14
- const [generatedCode, setGeneratedCode] = useState("");
15
- const [executionSuccess, setExecutionSuccess] = useState(null);
16
- const [attempts, setAttempts] = useState(0);
17
- const [isSolving, setIsSolving] = useState(false);
18
- const [problematicConstraints, setProblematicConstraints] = useState("");
19
-
20
- // UI state
21
- const [currentStep, setCurrentStep] = useState(1);
22
- const [expandedSections, setExpandedSections] = useState({
23
- puzzle: true,
24
- sysContent: false,
25
- result: false
26
- });
27
-
28
- // Frontend fetch sysContent in default
29
- useEffect(() => {
30
- fetch(`/default_sys_content`)
31
- .then(res => res.json())
32
- .then(data => {
33
- if(data.success) {
34
- setSysContent(data.sysContent);
35
- }
36
- })
37
- .catch(e => console.error(e));
38
- }, []);
39
-
40
- // When puzzleIndex changing,auto get puzzle
41
- useEffect(() => {
42
- fetch(`/get_puzzle?index=${puzzleIndex}`)
43
- .then(res => res.json())
44
- .then(data => {
45
- if(data.success) {
46
- setPuzzleText(data.puzzle);
47
- setExpectedSolution(data.expected_solution);
48
- } else {
49
- console.error("Failed to fetch puzzle", data.error);
50
- setPuzzleText("");
51
- setExpectedSolution(null);
52
- }
53
- })
54
- .catch(e => console.error(e));
55
- }, [puzzleIndex]);
56
-
57
- const handleSolve = () => {
58
- if(!puzzleText || !expectedSolution) {
59
- alert("puzzle or expectedSolution incomplete");
60
- return;
61
- }
62
- const payload = {
63
- index: puzzleIndex,
64
- puzzle: puzzleText,
65
- expected_solution: expectedSolution,
66
- sys_content: sysContent,
67
- problematic_constraints: problematicConstraints
68
- };
69
-
70
- setIsSolving(true);
71
- setCurrentStep(3);
72
- setExpandedSections({...expandedSections, result: true});
73
-
74
- fetch(`/solve`, {
75
- method: "POST",
76
- headers: { "Content-Type": "application/json" },
77
- body: JSON.stringify(payload)
78
- })
79
- .then(res => res.json())
80
- .then(data => {
81
- if(!data.success) {
82
- alert("Backend error: " + data.error);
83
- return;
84
- }
85
- const result = data.result;
86
- setGeneratedCode(result.generatedCode || "");
87
- setExecutionSuccess(result.success);
88
- setAttempts(result.attempts || 0);
89
- setProblematicConstraints(result.problematicConstraints || "");
90
- })
91
- .catch(e => console.error(e))
92
- .finally(() => {
93
- setIsSolving(false);
94
- });
95
- };
96
-
97
- const toggleSection = (section) => {
98
- setExpandedSections({
99
- ...expandedSections,
100
- [section]: !expandedSections[section]
101
- });
102
- };
103
-
104
- const nextStep = () => {
105
- if (currentStep < 3) {
106
- setCurrentStep(currentStep + 1);
107
- if (currentStep === 1) {
108
- setExpandedSections({puzzle: false, sysContent: true, result: false});
109
- } else if (currentStep === 2) {
110
- setExpandedSections({puzzle: false, sysContent: false, result: true});
111
- }
112
- }
113
- };
114
-
115
- const prevStep = () => {
116
- if (currentStep > 1) {
117
- setCurrentStep(currentStep - 1);
118
- if (currentStep === 2) {
119
- setExpandedSections({puzzle: true, sysContent: false, result: false});
120
- } else if (currentStep === 3) {
121
- setExpandedSections({puzzle: false, sysContent: true, result: false});
122
- }
123
  }
124
  };
125
 
@@ -128,181 +22,25 @@ function App() {
128
  <header className="app-header">
129
  <h1>🦓 Zebra Puzzle Solver</h1>
130
  <p>Solve complex logic puzzles using AI-powered constraint satisfaction</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
  </header>
132
 
133
- {/* Progress Steps */}
134
- <div className="progress-container">
135
- <div className="progress-steps">
136
- <div className={`step ${currentStep >= 1 ? 'active' : ''}`}>
137
- <div className="step-number">1</div>
138
- <div className="step-label">Select Puzzle</div>
139
- </div>
140
- <div className={`step ${currentStep >= 2 ? 'active' : ''}`}>
141
- <div className="step-number">2</div>
142
- <div className="step-label">Configure System</div>
143
- </div>
144
- <div className={`step ${currentStep >= 3 ? 'active' : ''}`}>
145
- <div className="step-number">3</div>
146
- <div className="step-label">Solve & Results</div>
147
- </div>
148
- </div>
149
- </div>
150
-
151
- <main className="main-content">
152
- {/* Step 1: Puzzle Selection */}
153
- <section className={`content-section ${expandedSections.puzzle ? 'expanded' : 'collapsed'}`}>
154
- <div className="section-header" onClick={() => toggleSection('puzzle')}>
155
- <h2>📋 Puzzle Selection</h2>
156
- <span className="toggle-icon">{expandedSections.puzzle ? '▼' : '▶'}</span>
157
- </div>
158
-
159
- {expandedSections.puzzle && (
160
- <div className="section-content">
161
- <div className="puzzle-selector">
162
- <label htmlFor="puzzle-index">Choose puzzle index (0 - 999):</label>
163
- <div className="input-group">
164
- <input
165
- id="puzzle-index"
166
- type="number"
167
- value={puzzleIndex}
168
- onChange={(e) => setPuzzleIndex(Number(e.target.value))}
169
- min={0}
170
- max={999}
171
- className="number-input"
172
- />
173
- <button
174
- onClick={() => setPuzzleIndex(puzzleIndex)}
175
- className="btn btn-secondary"
176
- >
177
- Load Puzzle
178
- </button>
179
- </div>
180
- </div>
181
-
182
- <div className="puzzle-display">
183
- <div className="puzzle-text">
184
- <h3>📄 Puzzle Text</h3>
185
- <div className="text-display">
186
- {puzzleText || "Loading puzzle..."}
187
- </div>
188
- </div>
189
-
190
- <div className="expected-solution">
191
- <h3>🎯 Expected Solution</h3>
192
- <div className="json-display">
193
- {expectedSolution ? (
194
- <pre>{JSON.stringify(expectedSolution, null, 2)}</pre>
195
- ) : (
196
- "Loading solution..."
197
- )}
198
- </div>
199
- </div>
200
- </div>
201
- </div>
202
- )}
203
- </section>
204
-
205
- {/* Step 2: System Configuration */}
206
- <section className={`content-section ${expandedSections.sysContent ? 'expanded' : 'collapsed'}`}>
207
- <div className="section-header" onClick={() => toggleSection('sysContent')}>
208
- <h2>⚙️ System Configuration</h2>
209
- <span className="toggle-icon">{expandedSections.sysContent ? '▼' : '▶'}</span>
210
- </div>
211
-
212
- {expandedSections.sysContent && (
213
- <div className="section-content">
214
- <div className="sys-content-editor">
215
- <h3>📝 System Content</h3>
216
- <p className="description">
217
- Edit the system prompt that will guide the AI in solving the puzzle:
218
- </p>
219
- <textarea
220
- value={sysContent}
221
- onChange={(e) => setSysContent(e.target.value)}
222
- className="sys-content-textarea"
223
- placeholder="Enter system content..."
224
- />
225
- </div>
226
- </div>
227
- )}
228
- </section>
229
-
230
- {/* Step 3: Solve & Results */}
231
- <section className={`content-section ${expandedSections.result ? 'expanded' : 'collapsed'}`}>
232
- <div className="section-header" onClick={() => toggleSection('result')}>
233
- <h2>🚀 Solve & Results</h2>
234
- <span className="toggle-icon">{expandedSections.result ? '▼' : '▶'}</span>
235
- </div>
236
-
237
- {expandedSections.result && (
238
- <div className="section-content">
239
- <div className="solve-section">
240
- <button
241
- onClick={handleSolve}
242
- disabled={isSolving || !puzzleText || !expectedSolution}
243
- className={`btn btn-primary solve-btn ${isSolving ? 'loading' : ''}`}
244
- >
245
- {isSolving ? '🔄 Solving...' : '🧠 Solve Puzzle with AI'}
246
- </button>
247
- </div>
248
-
249
- <div className="results-section">
250
- <div className="result-summary">
251
- <div className="result-item">
252
- <span className="result-label">Status:</span>
253
- <span className={`result-value ${executionSuccess === true ? 'success' : executionSuccess === false ? 'error' : 'pending'}`}>
254
- {executionSuccess === null ? "⏳ Pending" : executionSuccess ? "✅ Success" : "❌ Failed"}
255
- </span>
256
- </div>
257
- <div className="result-item">
258
- <span className="result-label">Attempts:</span>
259
- <span className="result-value">{attempts}</span>
260
- </div>
261
- </div>
262
-
263
- {problematicConstraints && (
264
- <div className="issues-section">
265
- <h3>⚠️ Issues & Analysis</h3>
266
- <div className="issues-display">
267
- <pre>{problematicConstraints}</pre>
268
- </div>
269
- </div>
270
- )}
271
-
272
- {generatedCode && (
273
- <div className="code-section">
274
- <h3>💻 Generated Code</h3>
275
- <div className="code-display">
276
- <pre><code>{generatedCode}</code></pre>
277
- </div>
278
- </div>
279
- )}
280
- </div>
281
- </div>
282
- )}
283
- </section>
284
- </main>
285
-
286
- {/* Navigation */}
287
- <nav className="navigation">
288
- <button
289
- onClick={prevStep}
290
- disabled={currentStep <= 1}
291
- className="btn btn-outline"
292
- >
293
- ← Previous
294
- </button>
295
- <span className="step-indicator">
296
- Step {currentStep} of 3
297
- </span>
298
- <button
299
- onClick={nextStep}
300
- disabled={currentStep >= 3}
301
- className="btn btn-outline"
302
- >
303
- Next →
304
- </button>
305
- </nav>
306
  </div>
307
  );
308
  }
 
1
+ import React, { useState } from 'react';
2
  import './App.css';
3
+ import PredefinedPuzzlePage from './pages/PredefinedPuzzlePage';
4
+ import CustomPuzzlePage from './pages/CustomPuzzlePage';
5
 
6
  function App() {
7
+ const [currentPage, setCurrentPage] = useState('predefined');
8
+
9
+ const renderPage = () => {
10
+ switch (currentPage) {
11
+ case 'predefined':
12
+ return <PredefinedPuzzlePage />;
13
+ case 'custom':
14
+ return <CustomPuzzlePage />;
15
+ default:
16
+ return <PredefinedPuzzlePage />;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  }
18
  };
19
 
 
22
  <header className="app-header">
23
  <h1>🦓 Zebra Puzzle Solver</h1>
24
  <p>Solve complex logic puzzles using AI-powered constraint satisfaction</p>
25
+
26
+ {/* Navigation Tabs */}
27
+ <nav className="page-navigation">
28
+ <button
29
+ className={`nav-tab ${currentPage === 'predefined' ? 'active' : ''}`}
30
+ onClick={() => setCurrentPage('predefined')}
31
+ >
32
+ 📚 Predefined Puzzles
33
+ </button>
34
+ <button
35
+ className={`nav-tab ${currentPage === 'custom' ? 'active' : ''}`}
36
+ onClick={() => setCurrentPage('custom')}
37
+ >
38
+ ✏️ Custom Puzzle
39
+ </button>
40
+ </nav>
41
  </header>
42
 
43
+ {renderPage()}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  </div>
45
  );
46
  }
frontend/src/components/SolutionNavigator.js ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+
3
+ function SolutionNavigator({ solutions, currentIndex, onIndexChange }) {
4
+ if (!solutions || solutions.length === 0) {
5
+ return null;
6
+ }
7
+
8
+ const currentSolution = solutions[currentIndex];
9
+
10
+ const nextSolution = () => {
11
+ if (currentIndex < solutions.length - 1) {
12
+ onIndexChange(currentIndex + 1);
13
+ }
14
+ };
15
+
16
+ const prevSolution = () => {
17
+ if (currentIndex > 0) {
18
+ onIndexChange(currentIndex - 1);
19
+ }
20
+ };
21
+
22
+ const goToSolution = (index) => {
23
+ onIndexChange(index);
24
+ };
25
+
26
+ return (
27
+ <div className="solution-navigator">
28
+ <div className="solution-header">
29
+ <h3>🎯 Solutions ({solutions.length} found)</h3>
30
+
31
+ {solutions.length > 1 && (
32
+ <div className="solution-controls">
33
+ <button
34
+ onClick={prevSolution}
35
+ disabled={currentIndex <= 0}
36
+ className="btn btn-sm btn-outline"
37
+ >
38
+ ← Previous
39
+ </button>
40
+
41
+ <div className="solution-pagination">
42
+ {solutions.map((_, index) => (
43
+ <button
44
+ key={index}
45
+ onClick={() => goToSolution(index)}
46
+ className={`pagination-btn ${index === currentIndex ? 'active' : ''}`}
47
+ >
48
+ {index + 1}
49
+ </button>
50
+ ))}
51
+ </div>
52
+
53
+ <button
54
+ onClick={nextSolution}
55
+ disabled={currentIndex >= solutions.length - 1}
56
+ className="btn btn-sm btn-outline"
57
+ >
58
+ Next →
59
+ </button>
60
+ </div>
61
+ )}
62
+ </div>
63
+
64
+ <div className="solution-display">
65
+ <div className="solution-info">
66
+ <span className="solution-counter">
67
+ Solution {currentIndex + 1} of {solutions.length}
68
+ </span>
69
+ </div>
70
+
71
+ <div className="solution-table-container">
72
+ {currentSolution.header && currentSolution.rows ? (
73
+ <table className="solution-table">
74
+ <thead>
75
+ <tr>
76
+ {currentSolution.header.map((header, index) => (
77
+ <th key={index}>{header}</th>
78
+ ))}
79
+ </tr>
80
+ </thead>
81
+ <tbody>
82
+ {currentSolution.rows.map((row, rowIndex) => (
83
+ <tr key={rowIndex}>
84
+ {row.map((cell, cellIndex) => (
85
+ <td key={cellIndex}>{cell}</td>
86
+ ))}
87
+ </tr>
88
+ ))}
89
+ </tbody>
90
+ </table>
91
+ ) : (
92
+ <div className="solution-json">
93
+ <pre>{JSON.stringify(currentSolution, null, 2)}</pre>
94
+ </div>
95
+ )}
96
+ </div>
97
+ </div>
98
+ </div>
99
+ );
100
+ }
101
+
102
+ export default SolutionNavigator;
frontend/src/pages/CustomPuzzlePage.js ADDED
@@ -0,0 +1,321 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useEffect } from 'react';
2
+ import SolutionNavigator from '../components/SolutionNavigator';
3
+
4
+ function CustomPuzzlePage() {
5
+ // Custom puzzle input
6
+ const [customPuzzleText, setCustomPuzzleText] = useState("");
7
+ const [sysContent, setSysContent] = useState("");
8
+
9
+ // Results
10
+ const [solutions, setSolutions] = useState([]);
11
+ const [currentSolutionIndex, setCurrentSolutionIndex] = useState(0);
12
+ const [generatedCode, setGeneratedCode] = useState("");
13
+ const [isSolving, setIsSolving] = useState(false);
14
+ const [solutionStatus, setSolutionStatus] = useState(""); // "success", "no_solution", "error"
15
+ const [errorMessage, setErrorMessage] = useState("");
16
+
17
+ // UI state
18
+ const [currentStep, setCurrentStep] = useState(1);
19
+ const [expandedSections, setExpandedSections] = useState({
20
+ puzzle: true,
21
+ sysContent: false,
22
+ result: false
23
+ });
24
+
25
+ // Load default system content
26
+ useEffect(() => {
27
+ fetch(`/default_sys_content_multi`)
28
+ .then(res => res.json())
29
+ .then(data => {
30
+ if(data.success) {
31
+ setSysContent(data.sysContent);
32
+ }
33
+ })
34
+ .catch(e => console.error(e));
35
+ }, []);
36
+
37
+ const handleSolveCustom = () => {
38
+ if(!customPuzzleText.trim()) {
39
+ alert("Please enter a puzzle text");
40
+ return;
41
+ }
42
+
43
+ const payload = {
44
+ puzzle: customPuzzleText,
45
+ sys_content: sysContent,
46
+ custom: true
47
+ };
48
+
49
+ setIsSolving(true);
50
+ setCurrentStep(3);
51
+ setExpandedSections({puzzle: false, sysContent: false, result: true});
52
+ setSolutions([]);
53
+ setCurrentSolutionIndex(0);
54
+ setSolutionStatus("");
55
+ setErrorMessage("");
56
+
57
+ fetch(`/solve_custom`, {
58
+ method: "POST",
59
+ headers: { "Content-Type": "application/json" },
60
+ body: JSON.stringify(payload)
61
+ })
62
+ .then(res => res.json())
63
+ .then(data => {
64
+ if(!data.success) {
65
+ setErrorMessage("Backend error: " + data.error);
66
+ setSolutionStatus("error");
67
+ return;
68
+ }
69
+
70
+ const result = data.result;
71
+ setGeneratedCode(result.generatedCode || "");
72
+
73
+ if (result.solutions && result.solutions.length > 0) {
74
+ setSolutions(result.solutions);
75
+ setSolutionStatus("success");
76
+ setCurrentSolutionIndex(0);
77
+ } else if (result.no_solution) {
78
+ setSolutionStatus("no_solution");
79
+ setErrorMessage(result.message || "No solution found for this puzzle");
80
+ } else {
81
+ setSolutionStatus("error");
82
+ setErrorMessage(result.error || "Unknown error occurred");
83
+ }
84
+ })
85
+ .catch(e => {
86
+ console.error(e);
87
+ setErrorMessage("Network error: " + e.message);
88
+ setSolutionStatus("error");
89
+ })
90
+ .finally(() => {
91
+ setIsSolving(false);
92
+ });
93
+ };
94
+
95
+ const toggleSection = (section) => {
96
+ setExpandedSections({
97
+ ...expandedSections,
98
+ [section]: !expandedSections[section]
99
+ });
100
+ };
101
+
102
+ const nextStep = () => {
103
+ if (currentStep < 3) {
104
+ setCurrentStep(currentStep + 1);
105
+ if (currentStep === 1) {
106
+ setExpandedSections({puzzle: false, sysContent: true, result: false});
107
+ } else if (currentStep === 2) {
108
+ setExpandedSections({puzzle: false, sysContent: false, result: true});
109
+ }
110
+ }
111
+ };
112
+
113
+ const prevStep = () => {
114
+ if (currentStep > 1) {
115
+ setCurrentStep(currentStep - 1);
116
+ if (currentStep === 2) {
117
+ setExpandedSections({puzzle: true, sysContent: false, result: false});
118
+ } else if (currentStep === 3) {
119
+ setExpandedSections({puzzle: false, sysContent: true, result: false});
120
+ }
121
+ }
122
+ };
123
+
124
+ return (
125
+ <>
126
+ {/* Progress Steps */}
127
+ <div className="progress-container">
128
+ <div className="progress-steps">
129
+ <div className={`step ${currentStep >= 1 ? 'active' : ''}`}>
130
+ <div className="step-number">1</div>
131
+ <div className="step-label">Create Puzzle</div>
132
+ </div>
133
+ <div className={`step ${currentStep >= 2 ? 'active' : ''}`}>
134
+ <div className="step-number">2</div>
135
+ <div className="step-label">Configure System</div>
136
+ </div>
137
+ <div className={`step ${currentStep >= 3 ? 'active' : ''}`}>
138
+ <div className="step-number">3</div>
139
+ <div className="step-label">Solve & Results</div>
140
+ </div>
141
+ </div>
142
+ </div>
143
+
144
+ <main className="main-content">
145
+ {/* Step 1: Custom Puzzle Input */}
146
+ <section className={`content-section ${expandedSections.puzzle ? 'expanded' : 'collapsed'}`}>
147
+ <div className="section-header" onClick={() => toggleSection('puzzle')}>
148
+ <h2>✏️ Custom Puzzle Creation</h2>
149
+ <span className="toggle-icon">{expandedSections.puzzle ? '▼' : '▶'}</span>
150
+ </div>
151
+
152
+ {expandedSections.puzzle && (
153
+ <div className="section-content">
154
+ <div className="custom-puzzle-editor">
155
+ <h3>📝 Puzzle Description</h3>
156
+ <p className="description">
157
+ Enter your custom zebra puzzle. Include categories, items, and constraints:
158
+ </p>
159
+ <div className="puzzle-input-container">
160
+ <textarea
161
+ value={customPuzzleText}
162
+ onChange={(e) => setCustomPuzzleText(e.target.value)}
163
+ className="custom-puzzle-textarea"
164
+ placeholder="Example:
165
+ There are 5 houses in a row.
166
+
167
+ Categories:
168
+ - Color: red, blue, green, yellow, white
169
+ - Name: Alice, Bob, Carol, Dave, Eve
170
+ - Pet: cat, dog, fish, bird, rabbit
171
+ - Drink: tea, coffee, milk, juice, water
172
+ - Sport: tennis, soccer, golf, swimming, running
173
+
174
+ Constraints:
175
+ 1. The person in the red house owns a cat.
176
+ 2. Alice drinks tea.
177
+ 3. The green house is to the left of the white house.
178
+ ..."
179
+ />
180
+ </div>
181
+
182
+ <div className="puzzle-examples">
183
+ <h4>💡 Tips for Creating Puzzles:</h4>
184
+ <ul>
185
+ <li>Clearly define all categories and their possible values</li>
186
+ <li>Number your constraints for clarity</li>
187
+ <li>Use spatial relationships like "to the left of", "next to", "between"</li>
188
+ <li>Include direct assignments like "Alice lives in the red house"</li>
189
+ <li>It will be better to have your expected results</li>
190
+ </ul>
191
+ </div>
192
+ </div>
193
+ </div>
194
+ )}
195
+ </section>
196
+
197
+ {/* Step 2: System Configuration */}
198
+ <section className={`content-section ${expandedSections.sysContent ? 'expanded' : 'collapsed'}`}>
199
+ <div className="section-header" onClick={() => toggleSection('sysContent')}>
200
+ <h2>⚙️ System Configuration</h2>
201
+ <span className="toggle-icon">{expandedSections.sysContent ? '▼' : '▶'}</span>
202
+ </div>
203
+
204
+ {expandedSections.sysContent && (
205
+ <div className="section-content">
206
+ <div className="sys-content-editor">
207
+ <h3>📝 System Content</h3>
208
+ <p className="description">
209
+ Edit the system prompt that will guide the AI in solving your custom puzzle:
210
+ </p>
211
+ <textarea
212
+ value={sysContent}
213
+ onChange={(e) => setSysContent(e.target.value)}
214
+ className="sys-content-textarea"
215
+ placeholder="Enter system content..."
216
+ />
217
+ </div>
218
+ </div>
219
+ )}
220
+ </section>
221
+
222
+ {/* Step 3: Solve & Results */}
223
+ <section className={`content-section ${expandedSections.result ? 'expanded' : 'collapsed'}`}>
224
+ <div className="section-header" onClick={() => toggleSection('result')}>
225
+ <h2>🚀 Solve & Results</h2>
226
+ <span className="toggle-icon">{expandedSections.result ? '▼' : '▶'}</span>
227
+ </div>
228
+
229
+ {expandedSections.result && (
230
+ <div className="section-content">
231
+ <div className="solve-section">
232
+ <button
233
+ onClick={handleSolveCustom}
234
+ disabled={isSolving || !customPuzzleText.trim()}
235
+ className={`btn btn-primary solve-btn ${isSolving ? 'loading' : ''}`}
236
+ >
237
+ {isSolving ? '🔄 Solving Custom Puzzle...' : '🧠 Solve Custom Puzzle'}
238
+ </button>
239
+ </div>
240
+
241
+ <div className="results-section">
242
+ <div className="result-summary">
243
+ <div className="result-item">
244
+ <span className="result-label">Status:</span>
245
+ <span className={`result-value ${
246
+ solutionStatus === 'success' ? 'success' :
247
+ solutionStatus === 'no_solution' ? 'warning' :
248
+ solutionStatus === 'error' ? 'error' : 'pending'
249
+ }`}>
250
+ {solutionStatus === 'success' ? "✅ Solved" :
251
+ solutionStatus === 'no_solution' ? "⚠️ No Solution" :
252
+ solutionStatus === 'error' ? "❌ Error" : "⏳ Pending"}
253
+ </span>
254
+ </div>
255
+ {solutions.length > 0 && (
256
+ <div className="result-item">
257
+ <span className="result-label">Solutions Found:</span>
258
+ <span className="result-value">{solutions.length}</span>
259
+ </div>
260
+ )}
261
+ </div>
262
+
263
+ {/* Solution Navigator */}
264
+ {solutions.length > 0 && (
265
+ <SolutionNavigator
266
+ solutions={solutions}
267
+ currentIndex={currentSolutionIndex}
268
+ onIndexChange={setCurrentSolutionIndex}
269
+ />
270
+ )}
271
+
272
+ {/* Error Message */}
273
+ {(solutionStatus === 'error' || solutionStatus === 'no_solution') && errorMessage && (
274
+ <div className="error-section">
275
+ <h3>⚠️ {solutionStatus === 'no_solution' ? 'No Solution Found' : 'Error'}</h3>
276
+ <div className="error-display">
277
+ <pre>{errorMessage}</pre>
278
+ </div>
279
+ </div>
280
+ )}
281
+
282
+ {/* Generated Code */}
283
+ {generatedCode && (
284
+ <div className="code-section">
285
+ <h3>💻 Generated Code</h3>
286
+ <div className="code-display">
287
+ <pre><code>{generatedCode}</code></pre>
288
+ </div>
289
+ </div>
290
+ )}
291
+ </div>
292
+ </div>
293
+ )}
294
+ </section>
295
+ </main>
296
+
297
+ {/* Navigation */}
298
+ <nav className="navigation">
299
+ <button
300
+ onClick={prevStep}
301
+ disabled={currentStep <= 1}
302
+ className="btn btn-outline"
303
+ >
304
+ ← Previous
305
+ </button>
306
+ <span className="step-indicator">
307
+ Step {currentStep} of 3
308
+ </span>
309
+ <button
310
+ onClick={nextStep}
311
+ disabled={currentStep >= 3}
312
+ className="btn btn-outline"
313
+ >
314
+ Next →
315
+ </button>
316
+ </nav>
317
+ </>
318
+ );
319
+ }
320
+
321
+ export default CustomPuzzlePage;
frontend/src/pages/PredefinedPuzzlePage.js ADDED
@@ -0,0 +1,304 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useEffect } from 'react';
2
+
3
+ function PredefinedPuzzlePage() {
4
+ // For puzzle index and puzzle data
5
+ const [puzzleIndex, setPuzzleIndex] = useState(0);
6
+ const [puzzleText, setPuzzleText] = useState("");
7
+ const [expectedSolution, setExpectedSolution] = useState(null);
8
+
9
+ // sysContent can be editted, default using Example.txt
10
+ const [sysContent, setSysContent] = useState("");
11
+
12
+ // Interaction results
13
+ const [generatedCode, setGeneratedCode] = useState("");
14
+ const [executionSuccess, setExecutionSuccess] = useState(null);
15
+ const [attempts, setAttempts] = useState(0);
16
+ const [isSolving, setIsSolving] = useState(false);
17
+ const [problematicConstraints, setProblematicConstraints] = useState("");
18
+
19
+ // UI state
20
+ const [currentStep, setCurrentStep] = useState(1);
21
+ const [expandedSections, setExpandedSections] = useState({
22
+ puzzle: true,
23
+ sysContent: false,
24
+ result: false
25
+ });
26
+
27
+ // Frontend fetch sysContent in default
28
+ useEffect(() => {
29
+ fetch(`/default_sys_content`)
30
+ .then(res => res.json())
31
+ .then(data => {
32
+ if(data.success) {
33
+ setSysContent(data.sysContent);
34
+ }
35
+ })
36
+ .catch(e => console.error(e));
37
+ }, []);
38
+
39
+ // When puzzleIndex changing,auto get puzzle
40
+ useEffect(() => {
41
+ fetch(`/get_puzzle?index=${puzzleIndex}`)
42
+ .then(res => res.json())
43
+ .then(data => {
44
+ if(data.success) {
45
+ setPuzzleText(data.puzzle);
46
+ setExpectedSolution(data.expected_solution);
47
+ } else {
48
+ console.error("Failed to fetch puzzle", data.error);
49
+ setPuzzleText("");
50
+ setExpectedSolution(null);
51
+ }
52
+ })
53
+ .catch(e => console.error(e));
54
+ }, [puzzleIndex]);
55
+
56
+ const handleSolve = () => {
57
+ if(!puzzleText || !expectedSolution) {
58
+ alert("puzzle or expectedSolution incomplete");
59
+ return;
60
+ }
61
+ const payload = {
62
+ index: puzzleIndex,
63
+ puzzle: puzzleText,
64
+ expected_solution: expectedSolution,
65
+ sys_content: sysContent,
66
+ problematic_constraints: problematicConstraints
67
+ };
68
+
69
+ setIsSolving(true);
70
+ setCurrentStep(3);
71
+ setExpandedSections({...expandedSections, result: true});
72
+
73
+ fetch(`/solve`, {
74
+ method: "POST",
75
+ headers: { "Content-Type": "application/json" },
76
+ body: JSON.stringify(payload)
77
+ })
78
+ .then(res => res.json())
79
+ .then(data => {
80
+ if(!data.success) {
81
+ alert("Backend error: " + data.error);
82
+ return;
83
+ }
84
+ const result = data.result;
85
+ setGeneratedCode(result.generatedCode || "");
86
+ setExecutionSuccess(result.success);
87
+ setAttempts(result.attempts || 0);
88
+ setProblematicConstraints(result.problematicConstraints || "");
89
+ })
90
+ .catch(e => console.error(e))
91
+ .finally(() => {
92
+ setIsSolving(false);
93
+ });
94
+ };
95
+
96
+ const toggleSection = (section) => {
97
+ setExpandedSections({
98
+ ...expandedSections,
99
+ [section]: !expandedSections[section]
100
+ });
101
+ };
102
+
103
+ const nextStep = () => {
104
+ if (currentStep < 3) {
105
+ setCurrentStep(currentStep + 1);
106
+ if (currentStep === 1) {
107
+ setExpandedSections({puzzle: false, sysContent: true, result: false});
108
+ } else if (currentStep === 2) {
109
+ setExpandedSections({puzzle: false, sysContent: false, result: true});
110
+ }
111
+ }
112
+ };
113
+
114
+ const prevStep = () => {
115
+ if (currentStep > 1) {
116
+ setCurrentStep(currentStep - 1);
117
+ if (currentStep === 2) {
118
+ setExpandedSections({puzzle: true, sysContent: false, result: false});
119
+ } else if (currentStep === 3) {
120
+ setExpandedSections({puzzle: false, sysContent: true, result: false});
121
+ }
122
+ }
123
+ };
124
+
125
+ return (
126
+ <>
127
+ {/* Progress Steps */}
128
+ <div className="progress-container">
129
+ <div className="progress-steps">
130
+ <div className={`step ${currentStep >= 1 ? 'active' : ''}`}>
131
+ <div className="step-number">1</div>
132
+ <div className="step-label">Select Puzzle</div>
133
+ </div>
134
+ <div className={`step ${currentStep >= 2 ? 'active' : ''}`}>
135
+ <div className="step-number">2</div>
136
+ <div className="step-label">Configure System</div>
137
+ </div>
138
+ <div className={`step ${currentStep >= 3 ? 'active' : ''}`}>
139
+ <div className="step-number">3</div>
140
+ <div className="step-label">Solve & Results</div>
141
+ </div>
142
+ </div>
143
+ </div>
144
+
145
+ <main className="main-content">
146
+ {/* Step 1: Puzzle Selection */}
147
+ <section className={`content-section ${expandedSections.puzzle ? 'expanded' : 'collapsed'}`}>
148
+ <div className="section-header" onClick={() => toggleSection('puzzle')}>
149
+ <h2>📋 Puzzle Selection</h2>
150
+ <span className="toggle-icon">{expandedSections.puzzle ? '▼' : '▶'}</span>
151
+ </div>
152
+
153
+ {expandedSections.puzzle && (
154
+ <div className="section-content">
155
+ <div className="puzzle-selector">
156
+ <label htmlFor="puzzle-index">Choose puzzle index (0 - 999):</label>
157
+ <div className="input-group">
158
+ <input
159
+ id="puzzle-index"
160
+ type="number"
161
+ value={puzzleIndex}
162
+ onChange={(e) => setPuzzleIndex(Number(e.target.value))}
163
+ min={0}
164
+ max={999}
165
+ className="number-input"
166
+ />
167
+ <button
168
+ onClick={() => setPuzzleIndex(puzzleIndex)}
169
+ className="btn btn-secondary"
170
+ >
171
+ Load Puzzle
172
+ </button>
173
+ </div>
174
+ </div>
175
+
176
+ <div className="puzzle-display">
177
+ <div className="puzzle-text">
178
+ <h3>📄 Puzzle Text</h3>
179
+ <div className="text-display">
180
+ {puzzleText || "Loading puzzle..."}
181
+ </div>
182
+ </div>
183
+
184
+ <div className="expected-solution">
185
+ <h3>🎯 Expected Solution</h3>
186
+ <div className="json-display">
187
+ {expectedSolution ? (
188
+ <pre>{JSON.stringify(expectedSolution, null, 2)}</pre>
189
+ ) : (
190
+ "Loading solution..."
191
+ )}
192
+ </div>
193
+ </div>
194
+ </div>
195
+ </div>
196
+ )}
197
+ </section>
198
+
199
+ {/* Step 2: System Configuration */}
200
+ <section className={`content-section ${expandedSections.sysContent ? 'expanded' : 'collapsed'}`}>
201
+ <div className="section-header" onClick={() => toggleSection('sysContent')}>
202
+ <h2>⚙️ System Configuration</h2>
203
+ <span className="toggle-icon">{expandedSections.sysContent ? '▼' : '▶'}</span>
204
+ </div>
205
+
206
+ {expandedSections.sysContent && (
207
+ <div className="section-content">
208
+ <div className="sys-content-editor">
209
+ <h3>📝 System Content</h3>
210
+ <p className="description">
211
+ Edit the system prompt that will guide the AI in solving the puzzle:
212
+ </p>
213
+ <textarea
214
+ value={sysContent}
215
+ onChange={(e) => setSysContent(e.target.value)}
216
+ className="sys-content-textarea"
217
+ placeholder="Enter system content..."
218
+ />
219
+ </div>
220
+ </div>
221
+ )}
222
+ </section>
223
+
224
+ {/* Step 3: Solve & Results */}
225
+ <section className={`content-section ${expandedSections.result ? 'expanded' : 'collapsed'}`}>
226
+ <div className="section-header" onClick={() => toggleSection('result')}>
227
+ <h2>🚀 Solve & Results</h2>
228
+ <span className="toggle-icon">{expandedSections.result ? '▼' : '▶'}</span>
229
+ </div>
230
+
231
+ {expandedSections.result && (
232
+ <div className="section-content">
233
+ <div className="solve-section">
234
+ <button
235
+ onClick={handleSolve}
236
+ disabled={isSolving || !puzzleText || !expectedSolution}
237
+ className={`btn btn-primary solve-btn ${isSolving ? 'loading' : ''}`}
238
+ >
239
+ {isSolving ? '🔄 Solving...' : '🧠 Solve Puzzle with AI'}
240
+ </button>
241
+ </div>
242
+
243
+ <div className="results-section">
244
+ <div className="result-summary">
245
+ <div className="result-item">
246
+ <span className="result-label">Status:</span>
247
+ <span className={`result-value ${executionSuccess === true ? 'success' : executionSuccess === false ? 'error' : 'pending'}`}>
248
+ {executionSuccess === null ? "⏳ Pending" : executionSuccess ? "✅ Success" : "❌ Failed"}
249
+ </span>
250
+ </div>
251
+ <div className="result-item">
252
+ <span className="result-label">Attempts:</span>
253
+ <span className="result-value">{attempts}</span>
254
+ </div>
255
+ </div>
256
+
257
+ {problematicConstraints && (
258
+ <div className="issues-section">
259
+ <h3>⚠️ Issues & Analysis</h3>
260
+ <div className="issues-display">
261
+ <pre>{problematicConstraints}</pre>
262
+ </div>
263
+ </div>
264
+ )}
265
+
266
+ {generatedCode && (
267
+ <div className="code-section">
268
+ <h3>💻 Generated Code</h3>
269
+ <div className="code-display">
270
+ <pre><code>{generatedCode}</code></pre>
271
+ </div>
272
+ </div>
273
+ )}
274
+ </div>
275
+ </div>
276
+ )}
277
+ </section>
278
+ </main>
279
+
280
+ {/* Navigation */}
281
+ <nav className="navigation">
282
+ <button
283
+ onClick={prevStep}
284
+ disabled={currentStep <= 1}
285
+ className="btn btn-outline"
286
+ >
287
+ ← Previous
288
+ </button>
289
+ <span className="step-indicator">
290
+ Step {currentStep} of 3
291
+ </span>
292
+ <button
293
+ onClick={nextStep}
294
+ disabled={currentStep >= 3}
295
+ className="btn btn-outline"
296
+ >
297
+ Next →
298
+ </button>
299
+ </nav>
300
+ </>
301
+ );
302
+ }
303
+
304
+ export default PredefinedPuzzlePage;