vimalk78 commited on
Commit
83b1393
·
1 Parent(s): 4689944

removed old js backend

Browse files

Signed-off-by: Vimal Kumar <[email protected]>

.gitignore CHANGED
@@ -3,12 +3,12 @@ node_modules/
3
  */node_modules/
4
 
5
  # Environment variables
6
- .env
7
- .env.local
8
- .env.development.local
9
- .env.test.local
10
- .env.production.local
11
- .env.backup
12
 
13
  # Additional security - API keys and secrets
14
  *.key
 
3
  */node_modules/
4
 
5
  # Environment variables
6
+ #.env
7
+ # .env.local
8
+ # .env.development.local
9
+ # .env.test.local
10
+ # .env.production.local
11
+ # .env.backup
12
 
13
  # Additional security - API keys and secrets
14
  *.key
Dockerfile CHANGED
@@ -30,7 +30,6 @@ RUN pip install --no-cache-dir --upgrade pip && \
30
 
31
  # Copy all source code
32
  COPY crossword-app/frontend/ ./frontend/
33
- COPY crossword-app/backend/ ./backend/
34
  COPY crossword-app/backend-py/ ./backend-py/
35
 
36
  # Copy cache directory with pre-built models and NLTK data
@@ -44,9 +43,6 @@ RUN cd frontend && npm run build
44
  # Copy built frontend files to Python backend public directory
45
  RUN mkdir -p backend-py/public && cp -r frontend/dist/* backend-py/public/
46
 
47
- # Create symlink for shared data (word lists)
48
- RUN cd backend-py && ln -sf ../backend/data data
49
-
50
  # Stage 2: Runtime - Copy only necessary files as non-root user
51
  FROM python:3.11-slim AS runtime
52
 
@@ -67,7 +63,6 @@ WORKDIR /app/backend-py
67
 
68
  # Copy built application files with correct ownership
69
  COPY --from=builder --chown=appuser:appuser /app/backend-py ./
70
- COPY --from=builder --chown=appuser:appuser /app/backend/data ./data
71
 
72
  # Copy cache directory to backend-py/cache for model and NLTK data
73
  COPY --from=builder --chown=appuser:appuser /app/cache-dir /app/backend-py/cache
 
30
 
31
  # Copy all source code
32
  COPY crossword-app/frontend/ ./frontend/
 
33
  COPY crossword-app/backend-py/ ./backend-py/
34
 
35
  # Copy cache directory with pre-built models and NLTK data
 
43
  # Copy built frontend files to Python backend public directory
44
  RUN mkdir -p backend-py/public && cp -r frontend/dist/* backend-py/public/
45
 
 
 
 
46
  # Stage 2: Runtime - Copy only necessary files as non-root user
47
  FROM python:3.11-slim AS runtime
48
 
 
63
 
64
  # Copy built application files with correct ownership
65
  COPY --from=builder --chown=appuser:appuser /app/backend-py ./
 
66
 
67
  # Copy cache directory to backend-py/cache for model and NLTK data
68
  COPY --from=builder --chown=appuser:appuser /app/cache-dir /app/backend-py/cache
crossword-app/backend/.env.example DELETED
@@ -1,68 +0,0 @@
1
- # Crossword App - Environment Configuration Template
2
- # Copy this file to .env and update with your actual values
3
-
4
- # Environment Configuration
5
- NODE_ENV=development
6
- PORT=3001
7
-
8
- # Database Configuration (Optional - using JSON files currently)
9
- DATABASE_URL=postgresql://username:password@localhost:5432/crossword_db
10
- DB_HOST=localhost
11
- DB_PORT=5432
12
- DB_NAME=crossword_db
13
- DB_USER=username
14
- DB_PASSWORD=password
15
-
16
- # CORS Configuration
17
- CORS_ORIGIN=http://localhost:5173,http://localhost:3000
18
-
19
- # Rate Limiting
20
- RATE_LIMIT_WINDOW_MS=900000
21
- RATE_LIMIT_MAX_REQUESTS=100
22
- GENERATE_RATE_LIMIT_MAX=10
23
-
24
- # Security
25
- JWT_SECRET=your-jwt-secret-key-change-in-production
26
-
27
- # Logging
28
- LOG_LEVEL=info
29
-
30
- # Cache Configuration (Optional - for Redis)
31
- REDIS_URL=redis://localhost:6379
32
- CACHE_TTL=3600
33
-
34
- # External APIs (Optional)
35
- DICTIONARY_API_KEY=your-dictionary-api-key
36
- WIKIPEDIA_API_ENDPOINT=https://en.wikipedia.org/api/rest_v1
37
-
38
- # =========================================
39
- # 🤖 AI/HUGGINGFACE CONFIGURATION
40
- # =========================================
41
-
42
- # HuggingFace API Configuration
43
- # Get your free API key at: https://huggingface.co/settings/tokens
44
- HUGGINGFACE_API_KEY=hf_xxxxxxxxxx
45
-
46
- # HuggingFace Model Configuration
47
- HUGGINGFACE_MODEL_ENDPOINT=https://api-inference.huggingface.co/models/sentence-transformers/all-MiniLM-L6-v2
48
- EMBEDDING_MODEL=sentence-transformers/all-MiniLM-L6-v2
49
-
50
- # LLM Configuration for Clue Generation
51
- TEXT_GENERATION_MODEL=gpt2
52
- MAX_WORDS_PER_GENERATION=15
53
- CACHE_EMBEDDINGS=true
54
-
55
- # AI Word Generation Settings
56
- WORD_SIMILARITY_THRESHOLD=0.7
57
- MAX_TOPIC_WORDS=50
58
- FALLBACK_TO_STATIC=true
59
- USE_AI_WORDS=false
60
-
61
- # =========================================
62
- # 🚀 QUICK START INSTRUCTIONS
63
- # =========================================
64
- # 1. Copy this file: cp .env.example .env
65
- # 2. Get HuggingFace API key: https://huggingface.co/settings/tokens
66
- # 3. Replace HUGGINGFACE_API_KEY with your real key
67
- # 4. Optionally set USE_AI_WORDS=true to enable AI by default
68
- # 5. Start server: npm run dev
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
crossword-app/backend/data/word-lists/animals.json DELETED
@@ -1,165 +0,0 @@
1
- [
2
- { "word": "DOG", "clue": "Man's best friend" },
3
- { "word": "CAT", "clue": "Feline pet that purrs" },
4
- { "word": "ELEPHANT", "clue": "Large mammal with a trunk" },
5
- { "word": "TIGER", "clue": "Striped big cat" },
6
- { "word": "WHALE", "clue": "Largest marine mammal" },
7
- { "word": "BUTTERFLY", "clue": "Colorful flying insect" },
8
- { "word": "BIRD", "clue": "Flying creature with feathers" },
9
- { "word": "FISH", "clue": "Aquatic animal with gills" },
10
- { "word": "LION", "clue": "King of the jungle" },
11
- { "word": "BEAR", "clue": "Large mammal that hibernates" },
12
- { "word": "RABBIT", "clue": "Hopping mammal with long ears" },
13
- { "word": "HORSE", "clue": "Riding animal with hooves" },
14
- { "word": "SHEEP", "clue": "Woolly farm animal" },
15
- { "word": "GOAT", "clue": "Horned farm animal" },
16
- { "word": "DUCK", "clue": "Water bird that quacks" },
17
- { "word": "CHICKEN", "clue": "Farm bird that lays eggs" },
18
- { "word": "SNAKE", "clue": "Slithering reptile" },
19
- { "word": "TURTLE", "clue": "Shelled reptile" },
20
- { "word": "FROG", "clue": "Amphibian that croaks" },
21
- { "word": "SHARK", "clue": "Predatory ocean fish" },
22
- { "word": "DOLPHIN", "clue": "Intelligent marine mammal" },
23
- { "word": "PENGUIN", "clue": "Flightless Antarctic bird" },
24
- { "word": "MONKEY", "clue": "Primate that swings in trees" },
25
- { "word": "ZEBRA", "clue": "Striped African animal" },
26
- { "word": "GIRAFFE", "clue": "Tallest land animal" },
27
- { "word": "WOLF", "clue": "Wild canine that howls" },
28
- { "word": "FOX", "clue": "Cunning red-furred animal" },
29
- { "word": "DEER", "clue": "Graceful forest animal with antlers" },
30
- { "word": "MOOSE", "clue": "Large antlered animal" },
31
- { "word": "SQUIRREL", "clue": "Tree-climbing nut gatherer" },
32
- { "word": "RACCOON", "clue": "Masked nocturnal animal" },
33
- { "word": "BEAVER", "clue": "Dam-building rodent" },
34
- { "word": "OTTER", "clue": "Playful water mammal" },
35
- { "word": "SEAL", "clue": "Marine mammal with flippers" },
36
- { "word": "WALRUS", "clue": "Tusked Arctic marine mammal" },
37
- { "word": "RHINO", "clue": "Horned thick-skinned mammal" },
38
- { "word": "HIPPO", "clue": "Large African river mammal" },
39
- { "word": "CHEETAH", "clue": "Fastest land animal" },
40
- { "word": "LEOPARD", "clue": "Spotted big cat" },
41
- { "word": "JAGUAR", "clue": "South American big cat" },
42
- { "word": "PUMA", "clue": "Mountain lion" },
43
- { "word": "LYNX", "clue": "Wild cat with tufted ears" },
44
- { "word": "KANGAROO", "clue": "Hopping Australian marsupial" },
45
- { "word": "KOALA", "clue": "Eucalyptus-eating marsupial" },
46
- { "word": "PANDA", "clue": "Black and white bamboo eater" },
47
- { "word": "SLOTH", "clue": "Slow-moving tree dweller" },
48
- { "word": "ARMADILLO", "clue": "Armored mammal" },
49
- { "word": "ANTEATER", "clue": "Long-snouted insect eater" },
50
- { "word": "PLATYPUS", "clue": "Egg-laying mammal with a bill" },
51
- { "word": "BAT", "clue": "Flying mammal" },
52
- { "word": "MOLE", "clue": "Underground tunnel digger" },
53
- { "word": "HEDGEHOG", "clue": "Spiny small mammal" },
54
- { "word": "PORCUPINE", "clue": "Quill-covered rodent" },
55
- { "word": "SKUNK", "clue": "Black and white scent-spraying mammal" },
56
- { "word": "WEASEL", "clue": "Small carnivorous mammal" },
57
- { "word": "BADGER", "clue": "Burrowing black and white mammal" },
58
- { "word": "FERRET", "clue": "Domesticated hunting animal" },
59
- { "word": "MINK", "clue": "Valuable fur-bearing animal" },
60
- { "word": "EAGLE", "clue": "Majestic bird of prey" },
61
- { "word": "HAWK", "clue": "Sharp-eyed hunting bird" },
62
- { "word": "OWL", "clue": "Nocturnal bird with large eyes" },
63
- { "word": "FALCON", "clue": "Fast diving bird of prey" },
64
- { "word": "VULTURE", "clue": "Scavenging bird" },
65
- { "word": "CROW", "clue": "Black intelligent bird" },
66
- { "word": "RAVEN", "clue": "Large black corvid" },
67
- { "word": "ROBIN", "clue": "Red-breasted songbird" },
68
- { "word": "SPARROW", "clue": "Small brown songbird" },
69
- { "word": "CARDINAL", "clue": "Bright red songbird" },
70
- { "word": "BLUEJAY", "clue": "Blue crested bird" },
71
- { "word": "WOODPECKER", "clue": "Tree-pecking bird" },
72
- { "word": "HUMMINGBIRD", "clue": "Tiny fast-flying bird" },
73
- { "word": "PELICAN", "clue": "Large-billed water bird" },
74
- { "word": "FLAMINGO", "clue": "Pink wading bird" },
75
- { "word": "STORK", "clue": "Long-legged wading bird" },
76
- { "word": "HERON", "clue": "Tall fishing bird" },
77
- { "word": "CRANE", "clue": "Large wading bird" },
78
- { "word": "SWAN", "clue": "Elegant white water bird" },
79
- { "word": "GOOSE", "clue": "Large waterfowl" },
80
- { "word": "TURKEY", "clue": "Large ground bird" },
81
- { "word": "PHEASANT", "clue": "Colorful game bird" },
82
- { "word": "QUAIL", "clue": "Small ground bird" },
83
- { "word": "PEACOCK", "clue": "Bird with spectacular tail feathers" },
84
- { "word": "OSTRICH", "clue": "Largest flightless bird" },
85
- { "word": "EMU", "clue": "Australian flightless bird" },
86
- { "word": "KIWI", "clue": "Small flightless New Zealand bird" },
87
- { "word": "PARROT", "clue": "Colorful talking bird" },
88
- { "word": "TOUCAN", "clue": "Large-billed tropical bird" },
89
- { "word": "MACAW", "clue": "Large colorful parrot" },
90
- { "word": "COCKATOO", "clue": "Crested parrot" },
91
- { "word": "CANARY", "clue": "Yellow singing bird" },
92
- { "word": "FINCH", "clue": "Small seed-eating bird" },
93
- { "word": "PIGEON", "clue": "Common city bird" },
94
- { "word": "DOVE", "clue": "Symbol of peace" },
95
- { "word": "SEAGULL", "clue": "Coastal scavenging bird" },
96
- { "word": "ALBATROSS", "clue": "Large ocean bird" },
97
- { "word": "PUFFIN", "clue": "Colorful-billed seabird" },
98
- { "word": "LIZARD", "clue": "Small scaly reptile" },
99
- { "word": "IGUANA", "clue": "Large tropical lizard" },
100
- { "word": "GECKO", "clue": "Wall-climbing lizard" },
101
- { "word": "CHAMELEON", "clue": "Color-changing reptile" },
102
- { "word": "ALLIGATOR", "clue": "Large American crocodilian" },
103
- { "word": "CROCODILE", "clue": "Large aquatic reptile" },
104
- { "word": "PYTHON", "clue": "Large constricting snake" },
105
- { "word": "COBRA", "clue": "Venomous hooded snake" },
106
- { "word": "VIPER", "clue": "Poisonous snake" },
107
- { "word": "RATTLESNAKE", "clue": "Snake with warning tail" },
108
- { "word": "SALAMANDER", "clue": "Amphibian that can regrow limbs" },
109
- { "word": "NEWT", "clue": "Small aquatic salamander" },
110
- { "word": "TOAD", "clue": "Warty amphibian" },
111
- { "word": "TADPOLE", "clue": "Frog larva" },
112
- { "word": "SALMON", "clue": "Fish that swims upstream" },
113
- { "word": "TROUT", "clue": "Freshwater game fish" },
114
- { "word": "BASS", "clue": "Popular sport fish" },
115
- { "word": "TUNA", "clue": "Large ocean fish" },
116
- { "word": "SWORDFISH", "clue": "Fish with long pointed bill" },
117
- { "word": "MARLIN", "clue": "Large billfish" },
118
- { "word": "MANTA", "clue": "Large ray fish" },
119
- { "word": "STINGRAY", "clue": "Flat fish with barbed tail" },
120
- { "word": "EEL", "clue": "Snake-like fish" },
121
- { "word": "SEAHORSE", "clue": "Horse-shaped fish" },
122
- { "word": "ANGELFISH", "clue": "Colorful tropical fish" },
123
- { "word": "GOLDFISH", "clue": "Common pet fish" },
124
- { "word": "CLOWNFISH", "clue": "Orange and white anemone fish" },
125
- { "word": "JELLYFISH", "clue": "Transparent stinging sea creature" },
126
- { "word": "OCTOPUS", "clue": "Eight-armed sea creature" },
127
- { "word": "SQUID", "clue": "Ten-armed cephalopod" },
128
- { "word": "CRAB", "clue": "Sideways-walking crustacean" },
129
- { "word": "LOBSTER", "clue": "Large marine crustacean" },
130
- { "word": "SHRIMP", "clue": "Small crustacean" },
131
- { "word": "STARFISH", "clue": "Five-armed sea creature" },
132
- { "word": "URCHIN", "clue": "Spiny sea creature" },
133
- { "word": "CORAL", "clue": "Marine organism that builds reefs" },
134
- { "word": "SPONGE", "clue": "Simple marine animal" },
135
- { "word": "OYSTER", "clue": "Pearl-producing mollusk" },
136
- { "word": "CLAM", "clue": "Burrowing shellfish" },
137
- { "word": "MUSSEL", "clue": "Dark-shelled mollusk" },
138
- { "word": "SNAIL", "clue": "Spiral-shelled gastropod" },
139
- { "word": "SLUG", "clue": "Shell-less gastropod" },
140
- { "word": "WORM", "clue": "Segmented invertebrate" },
141
- { "word": "SPIDER", "clue": "Eight-legged web spinner" },
142
- { "word": "SCORPION", "clue": "Arachnid with stinging tail" },
143
- { "word": "ANT", "clue": "Social insect worker" },
144
- { "word": "BEE", "clue": "Honey-making insect" },
145
- { "word": "WASP", "clue": "Stinging flying insect" },
146
- { "word": "HORNET", "clue": "Large aggressive wasp" },
147
- { "word": "FLY", "clue": "Common buzzing insect" },
148
- { "word": "MOSQUITO", "clue": "Blood-sucking insect" },
149
- { "word": "BEETLE", "clue": "Hard-shelled insect" },
150
- { "word": "LADYBUG", "clue": "Red spotted beneficial insect" },
151
- { "word": "DRAGONFLY", "clue": "Large-winged flying insect" },
152
- { "word": "GRASSHOPPER", "clue": "Jumping green insect" },
153
- { "word": "CRICKET", "clue": "Chirping insect" },
154
- { "word": "MANTIS", "clue": "Praying insect predator" },
155
- { "word": "MOTH", "clue": "Nocturnal butterfly relative" },
156
- { "word": "CATERPILLAR", "clue": "Butterfly larva" },
157
- { "word": "COCOON", "clue": "Insect transformation casing" },
158
- { "word": "TERMITE", "clue": "Wood-eating social insect" },
159
- { "word": "TICK", "clue": "Blood-sucking parasite" },
160
- { "word": "FLEA", "clue": "Jumping parasite" },
161
- { "word": "LOUSE", "clue": "Small parasitic insect" },
162
- { "word": "APHID", "clue": "Plant-sucking insect" },
163
- { "word": "MAGGOT", "clue": "Fly larva" },
164
- { "word": "GRUB", "clue": "Beetle larva" }
165
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
crossword-app/backend/data/word-lists/geography.json DELETED
@@ -1,161 +0,0 @@
1
- [
2
- { "word": "MOUNTAIN", "clue": "High elevation landform" },
3
- { "word": "OCEAN", "clue": "Large body of salt water" },
4
- { "word": "DESERT", "clue": "Dry, arid region" },
5
- { "word": "CONTINENT", "clue": "Large landmass" },
6
- { "word": "RIVER", "clue": "Flowing body of water" },
7
- { "word": "ISLAND", "clue": "Land surrounded by water" },
8
- { "word": "FOREST", "clue": "Dense area of trees" },
9
- { "word": "VALLEY", "clue": "Low area between hills" },
10
- { "word": "LAKE", "clue": "Body of freshwater" },
11
- { "word": "BEACH", "clue": "Sandy shore by water" },
12
- { "word": "CLIFF", "clue": "Steep rock face" },
13
- { "word": "PLATEAU", "clue": "Elevated flat area" },
14
- { "word": "CANYON", "clue": "Deep gorge with steep sides" },
15
- { "word": "GLACIER", "clue": "Moving mass of ice" },
16
- { "word": "VOLCANO", "clue": "Mountain that erupts" },
17
- { "word": "PENINSULA", "clue": "Land surrounded by water on three sides" },
18
- { "word": "ARCHIPELAGO", "clue": "Group of islands" },
19
- { "word": "PRAIRIE", "clue": "Grassland plain" },
20
- { "word": "TUNDRA", "clue": "Cold, treeless region" },
21
- { "word": "SAVANNA", "clue": "Tropical grassland" },
22
- { "word": "EQUATOR", "clue": "Earth's middle line" },
23
- { "word": "LATITUDE", "clue": "Distance from equator" },
24
- { "word": "LONGITUDE", "clue": "Distance from prime meridian" },
25
- { "word": "CLIMATE", "clue": "Long-term weather pattern" },
26
- { "word": "MONSOON", "clue": "Seasonal wind pattern" },
27
- { "word": "CAPITAL", "clue": "Main city of country" },
28
- { "word": "BORDER", "clue": "Boundary between countries" },
29
- { "word": "COAST", "clue": "Land meeting the sea" },
30
- { "word": "STRAIT", "clue": "Narrow water passage" },
31
- { "word": "DELTA", "clue": "River mouth formation" },
32
- { "word": "FJORD", "clue": "Narrow inlet between cliffs" },
33
- { "word": "ATOLL", "clue": "Ring-shaped coral island" },
34
- { "word": "MESA", "clue": "Flat-topped hill" },
35
- { "word": "BUTTE", "clue": "Isolated hill with steep sides" },
36
- { "word": "GORGE", "clue": "Deep narrow valley" },
37
- { "word": "RAVINE", "clue": "Small narrow gorge" },
38
- { "word": "RIDGE", "clue": "Long narrow hilltop" },
39
- { "word": "PEAK", "clue": "Mountain summit" },
40
- { "word": "SUMMIT", "clue": "Highest point" },
41
- { "word": "FOOTHILLS", "clue": "Hills at base of mountains" },
42
- { "word": "RANGE", "clue": "Chain of mountains" },
43
- { "word": "BASIN", "clue": "Low-lying area" },
44
- { "word": "WATERSHED", "clue": "Drainage area" },
45
- { "word": "ESTUARY", "clue": "Where river meets sea" },
46
- { "word": "BAY", "clue": "Curved inlet of water" },
47
- { "word": "GULF", "clue": "Large bay" },
48
- { "word": "CAPE", "clue": "Point of land into water" },
49
- { "word": "HEADLAND", "clue": "High point of land" },
50
- { "word": "LAGOON", "clue": "Shallow coastal body of water" },
51
- { "word": "REEF", "clue": "Underwater rock formation" },
52
- { "word": "SHOAL", "clue": "Shallow area in water" },
53
- { "word": "CHANNEL", "clue": "Deep water passage" },
54
- { "word": "SOUND", "clue": "Large sea inlet" },
55
- { "word": "HARBOR", "clue": "Sheltered port area" },
56
- { "word": "INLET", "clue": "Small bay" },
57
- { "word": "COVE", "clue": "Small sheltered bay" },
58
- { "word": "MARSH", "clue": "Wetland area" },
59
- { "word": "SWAMP", "clue": "Forested wetland" },
60
- { "word": "BOG", "clue": "Acidic wetland" },
61
- { "word": "OASIS", "clue": "Fertile spot in desert" },
62
- { "word": "DUNE", "clue": "Sand hill" },
63
- { "word": "PLAIN", "clue": "Flat grassland" },
64
- { "word": "STEPPE", "clue": "Dry grassland" },
65
- { "word": "TAIGA", "clue": "Northern coniferous forest" },
66
- { "word": "RAINFOREST", "clue": "Dense tropical forest" },
67
- { "word": "JUNGLE", "clue": "Dense tropical vegetation" },
68
- { "word": "WOODLAND", "clue": "Area with scattered trees" },
69
- { "word": "GROVE", "clue": "Small group of trees" },
70
- { "word": "MEADOW", "clue": "Grassy field" },
71
- { "word": "PASTURE", "clue": "Grazing land" },
72
- { "word": "FIELD", "clue": "Open area of land" },
73
- { "word": "MOOR", "clue": "Open uncultivated land" },
74
- { "word": "HEATH", "clue": "Shrubland area" },
75
- { "word": "ARCTIC", "clue": "Cold northern region" },
76
- { "word": "ANTARCTIC", "clue": "Cold southern region" },
77
- { "word": "POLAR", "clue": "Of the poles" },
78
- { "word": "TROPICAL", "clue": "Hot humid climate zone" },
79
- { "word": "TEMPERATE", "clue": "Moderate climate zone" },
80
- { "word": "ARID", "clue": "Very dry" },
81
- { "word": "HUMID", "clue": "Moist air" },
82
- { "word": "ALTITUDE", "clue": "Height above sea level" },
83
- { "word": "ELEVATION", "clue": "Height of land" },
84
- { "word": "TERRAIN", "clue": "Physical features of land" },
85
- { "word": "TOPOGRAPHY", "clue": "Surface features of area" },
86
- { "word": "GEOGRAPHY", "clue": "Study of Earth's features" },
87
- { "word": "CARTOGRAPHY", "clue": "Map making" },
88
- { "word": "MERIDIAN", "clue": "Longitude line" },
89
- { "word": "PARALLEL", "clue": "Latitude line" },
90
- { "word": "HEMISPHERE", "clue": "Half of Earth" },
91
- { "word": "TROPICS", "clue": "Hot climate zone" },
92
- { "word": "POLES", "clue": "Earth's endpoints" },
93
- { "word": "AXIS", "clue": "Earth's rotation line" },
94
- { "word": "ORBIT", "clue": "Path around sun" },
95
- { "word": "SEASON", "clue": "Time of year" },
96
- { "word": "SOLSTICE", "clue": "Longest or shortest day" },
97
- { "word": "EQUINOX", "clue": "Equal day and night" },
98
- { "word": "COMPASS", "clue": "Direction-finding tool" },
99
- { "word": "NAVIGATION", "clue": "Finding your way" },
100
- { "word": "BEARING", "clue": "Direction or course" },
101
- { "word": "AZIMUTH", "clue": "Compass direction" },
102
- { "word": "SCALE", "clue": "Map size ratio" },
103
- { "word": "LEGEND", "clue": "Map symbol key" },
104
- { "word": "CONTOUR", "clue": "Elevation line on map" },
105
- { "word": "GRID", "clue": "Map reference system" },
106
- { "word": "PROJECTION", "clue": "Map flattening method" },
107
- { "word": "SURVEY", "clue": "Land measurement" },
108
- { "word": "BOUNDARY", "clue": "Dividing line" },
109
- { "word": "FRONTIER", "clue": "Border region" },
110
- { "word": "TERRITORY", "clue": "Area of land" },
111
- { "word": "REGION", "clue": "Geographic area" },
112
- { "word": "ZONE", "clue": "Designated area" },
113
- { "word": "DISTRICT", "clue": "Administrative area" },
114
- { "word": "PROVINCE", "clue": "Political subdivision" },
115
- { "word": "STATE", "clue": "Political entity" },
116
- { "word": "COUNTY", "clue": "Local government area" },
117
- { "word": "CITY", "clue": "Large urban area" },
118
- { "word": "TOWN", "clue": "Small urban area" },
119
- { "word": "VILLAGE", "clue": "Small rural community" },
120
- { "word": "HAMLET", "clue": "Very small village" },
121
- { "word": "SUBURB", "clue": "Residential area outside city" },
122
- { "word": "URBAN", "clue": "City-like" },
123
- { "word": "RURAL", "clue": "Countryside" },
124
- { "word": "METROPOLITAN", "clue": "Large city area" },
125
- { "word": "POPULATION", "clue": "Number of people" },
126
- { "word": "DENSITY", "clue": "Crowdedness" },
127
- { "word": "SETTLEMENT", "clue": "Place where people live" },
128
- { "word": "COLONY", "clue": "Overseas territory" },
129
- { "word": "NATION", "clue": "Country" },
130
- { "word": "REPUBLIC", "clue": "Democratic state" },
131
- { "word": "KINGDOM", "clue": "Monarchy" },
132
- { "word": "EMPIRE", "clue": "Large political entity" },
133
- { "word": "FEDERATION", "clue": "Union of states" },
134
- { "word": "ALLIANCE", "clue": "Partnership of nations" },
135
- { "word": "TREATY", "clue": "International agreement" },
136
- { "word": "TRADE", "clue": "Commercial exchange" },
137
- { "word": "EXPORT", "clue": "Goods sent abroad" },
138
- { "word": "IMPORT", "clue": "Goods brought in" },
139
- { "word": "COMMERCE", "clue": "Business activity" },
140
- { "word": "INDUSTRY", "clue": "Manufacturing" },
141
- { "word": "AGRICULTURE", "clue": "Farming" },
142
- { "word": "MINING", "clue": "Extracting minerals" },
143
- { "word": "FORESTRY", "clue": "Tree management" },
144
- { "word": "FISHING", "clue": "Catching fish" },
145
- { "word": "TOURISM", "clue": "Travel industry" },
146
- { "word": "TRANSPORTATION", "clue": "Moving people and goods" },
147
- { "word": "INFRASTRUCTURE", "clue": "Basic facilities" },
148
- { "word": "COMMUNICATION", "clue": "Information exchange" },
149
- { "word": "CULTURE", "clue": "Way of life" },
150
- { "word": "LANGUAGE", "clue": "Communication system" },
151
- { "word": "RELIGION", "clue": "Belief system" },
152
- { "word": "ETHNICITY", "clue": "Cultural group" },
153
- { "word": "MIGRATION", "clue": "Movement of people" },
154
- { "word": "IMMIGRATION", "clue": "Moving into country" },
155
- { "word": "EMIGRATION", "clue": "Moving out of country" },
156
- { "word": "DIASPORA", "clue": "Scattered population" },
157
- { "word": "NOMAD", "clue": "Wandering person" },
158
- { "word": "REFUGEE", "clue": "Displaced person" },
159
- { "word": "CENSUS", "clue": "Population count" },
160
- { "word": "DEMOGRAPHIC", "clue": "Population characteristic" }
161
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
crossword-app/backend/data/word-lists/science.json DELETED
@@ -1,170 +0,0 @@
1
- [
2
- { "word": "ATOM", "clue": "Smallest unit of matter" },
3
- { "word": "GRAVITY", "clue": "Force that pulls objects down" },
4
- { "word": "MOLECULE", "clue": "Group of atoms bonded together" },
5
- { "word": "PHOTON", "clue": "Particle of light" },
6
- { "word": "CHEMISTRY", "clue": "Study of matter and reactions" },
7
- { "word": "PHYSICS", "clue": "Study of matter and energy" },
8
- { "word": "BIOLOGY", "clue": "Study of living organisms" },
9
- { "word": "ELEMENT", "clue": "Pure chemical substance" },
10
- { "word": "OXYGEN", "clue": "Gas essential for breathing" },
11
- { "word": "CARBON", "clue": "Element found in all life" },
12
- { "word": "HYDROGEN", "clue": "Lightest chemical element" },
13
- { "word": "ENERGY", "clue": "Capacity to do work" },
14
- { "word": "FORCE", "clue": "Push or pull on an object" },
15
- { "word": "VELOCITY", "clue": "Speed with direction" },
16
- { "word": "MASS", "clue": "Amount of matter in object" },
17
- { "word": "VOLUME", "clue": "Amount of space occupied" },
18
- { "word": "DENSITY", "clue": "Mass per unit volume" },
19
- { "word": "PRESSURE", "clue": "Force per unit area" },
20
- { "word": "TEMPERATURE", "clue": "Measure of heat" },
21
- { "word": "ELECTRON", "clue": "Negatively charged particle" },
22
- { "word": "PROTON", "clue": "Positively charged particle" },
23
- { "word": "NEUTRON", "clue": "Neutral atomic particle" },
24
- { "word": "NUCLEUS", "clue": "Center of an atom" },
25
- { "word": "CELL", "clue": "Basic unit of life" },
26
- { "word": "DNA", "clue": "Genetic blueprint molecule" },
27
- { "word": "PROTEIN", "clue": "Complex biological molecule" },
28
- { "word": "ENZYME", "clue": "Biological catalyst" },
29
- { "word": "VIRUS", "clue": "Infectious agent" },
30
- { "word": "BACTERIA", "clue": "Single-celled organisms" },
31
- { "word": "EVOLUTION", "clue": "Change in species over time" },
32
- { "word": "ISOTOPE", "clue": "Atom variant with different neutrons" },
33
- { "word": "ION", "clue": "Charged atom or molecule" },
34
- { "word": "COMPOUND", "clue": "Chemical combination of elements" },
35
- { "word": "MIXTURE", "clue": "Combined substances retaining properties" },
36
- { "word": "SOLUTION", "clue": "Dissolved mixture" },
37
- { "word": "ACID", "clue": "Sour chemical with low pH" },
38
- { "word": "BASE", "clue": "Alkaline substance with high pH" },
39
- { "word": "SALT", "clue": "Ionic compound from acid-base reaction" },
40
- { "word": "CATALYST", "clue": "Substance that speeds reactions" },
41
- { "word": "RNA", "clue": "Genetic messenger molecule" },
42
- { "word": "GENE", "clue": "Heredity unit on chromosome" },
43
- { "word": "CHROMOSOME", "clue": "Gene-carrying structure" },
44
- { "word": "TISSUE", "clue": "Group of similar cells" },
45
- { "word": "ORGAN", "clue": "Body part with specific function" },
46
- { "word": "SYSTEM", "clue": "Group of organs working together" },
47
- { "word": "ORGANISM", "clue": "Living individual entity" },
48
- { "word": "SPECIES", "clue": "Group of similar organisms" },
49
- { "word": "ADAPTATION", "clue": "Survival-enhancing change" },
50
- { "word": "MUTATION", "clue": "Genetic change in DNA" },
51
- { "word": "HEREDITY", "clue": "Passing traits to offspring" },
52
- { "word": "ECOSYSTEM", "clue": "Community and environment" },
53
- { "word": "HABITAT", "clue": "Natural living environment" },
54
- { "word": "BIODIVERSITY", "clue": "Variety of life forms" },
55
- { "word": "PHOTOSYNTHESIS", "clue": "Plant energy-making process" },
56
- { "word": "RESPIRATION", "clue": "Cellular breathing process" },
57
- { "word": "METABOLISM", "clue": "Chemical processes in body" },
58
- { "word": "HOMEOSTASIS", "clue": "Body's internal balance" },
59
- { "word": "MITOSIS", "clue": "Cell division for growth" },
60
- { "word": "MEIOSIS", "clue": "Cell division for reproduction" },
61
- { "word": "EMBRYO", "clue": "Early development stage" },
62
- { "word": "FOSSIL", "clue": "Preserved ancient remains" },
63
- { "word": "GEOLOGY", "clue": "Study of Earth's structure" },
64
- { "word": "MINERAL", "clue": "Natural inorganic crystal" },
65
- { "word": "ROCK", "clue": "Solid earth material" },
66
- { "word": "SEDIMENT", "clue": "Settled particles" },
67
- { "word": "EROSION", "clue": "Gradual wearing away" },
68
- { "word": "VOLCANO", "clue": "Earth opening spewing lava" },
69
- { "word": "EARTHQUAKE", "clue": "Ground shaking from plate movement" },
70
- { "word": "PLATE", "clue": "Earth's crust section" },
71
- { "word": "MAGMA", "clue": "Molten rock beneath surface" },
72
- { "word": "LAVA", "clue": "Molten rock on surface" },
73
- { "word": "CRYSTAL", "clue": "Ordered atomic structure" },
74
- { "word": "ATMOSPHERE", "clue": "Layer of gases around Earth" },
75
- { "word": "CLIMATE", "clue": "Long-term weather pattern" },
76
- { "word": "WEATHER", "clue": "Short-term atmospheric conditions" },
77
- { "word": "PRECIPITATION", "clue": "Water falling from clouds" },
78
- { "word": "HUMIDITY", "clue": "Moisture in air" },
79
- { "word": "WIND", "clue": "Moving air mass" },
80
- { "word": "STORM", "clue": "Violent weather event" },
81
- { "word": "HURRICANE", "clue": "Powerful tropical cyclone" },
82
- { "word": "TORNADO", "clue": "Rotating column of air" },
83
- { "word": "LIGHTNING", "clue": "Electrical discharge in sky" },
84
- { "word": "THUNDER", "clue": "Sound of lightning" },
85
- { "word": "RAINBOW", "clue": "Spectrum of light in sky" },
86
- { "word": "ASTRONOMY", "clue": "Study of celestial objects" },
87
- { "word": "GALAXY", "clue": "Collection of stars and planets" },
88
- { "word": "PLANET", "clue": "Large orbiting celestial body" },
89
- { "word": "STAR", "clue": "Self-luminous celestial body" },
90
- { "word": "MOON", "clue": "Natural satellite of planet" },
91
- { "word": "COMET", "clue": "Icy body with tail" },
92
- { "word": "ASTEROID", "clue": "Rocky space object" },
93
- { "word": "METEOR", "clue": "Space rock entering atmosphere" },
94
- { "word": "ORBIT", "clue": "Curved path around object" },
95
- { "word": "LIGHT", "clue": "Electromagnetic radiation" },
96
- { "word": "SPECTRUM", "clue": "Range of electromagnetic radiation" },
97
- { "word": "WAVELENGTH", "clue": "Distance between wave peaks" },
98
- { "word": "FREQUENCY", "clue": "Waves per unit time" },
99
- { "word": "AMPLITUDE", "clue": "Wave height or intensity" },
100
- { "word": "SOUND", "clue": "Vibrations in air" },
101
- { "word": "ECHO", "clue": "Reflected sound" },
102
- { "word": "RESONANCE", "clue": "Vibration amplification" },
103
- { "word": "DOPPLER", "clue": "Wave frequency shift effect" },
104
- { "word": "MOTION", "clue": "Change in position" },
105
- { "word": "ACCELERATION", "clue": "Change in velocity" },
106
- { "word": "MOMENTUM", "clue": "Mass times velocity" },
107
- { "word": "INERTIA", "clue": "Resistance to motion change" },
108
- { "word": "FRICTION", "clue": "Resistance to sliding" },
109
- { "word": "HEAT", "clue": "Thermal energy transfer" },
110
- { "word": "COMBUSTION", "clue": "Burning chemical reaction" },
111
- { "word": "OXIDATION", "clue": "Reaction with oxygen" },
112
- { "word": "REDUCTION", "clue": "Gain of electrons" },
113
- { "word": "ELECTROLYSIS", "clue": "Chemical breakdown by electricity" },
114
- { "word": "CONDUCTIVITY", "clue": "Ability to transfer energy" },
115
- { "word": "INSULATOR", "clue": "Material blocking energy flow" },
116
- { "word": "SEMICONDUCTOR", "clue": "Partial electrical conductor" },
117
- { "word": "MAGNETISM", "clue": "Force of magnetic attraction" },
118
- { "word": "FIELD", "clue": "Region of force influence" },
119
- { "word": "CIRCUIT", "clue": "Closed electrical path" },
120
- { "word": "CURRENT", "clue": "Flow of electric charge" },
121
- { "word": "VOLTAGE", "clue": "Electric potential difference" },
122
- { "word": "RESISTANCE", "clue": "Opposition to current flow" },
123
- { "word": "CAPACITOR", "clue": "Device storing electric charge" },
124
- { "word": "INDUCTOR", "clue": "Device storing magnetic energy" },
125
- { "word": "TRANSISTOR", "clue": "Electronic switching device" },
126
- { "word": "LASER", "clue": "Focused beam of light" },
127
- { "word": "RADAR", "clue": "Radio detection system" },
128
- { "word": "SONAR", "clue": "Sound detection system" },
129
- { "word": "TELESCOPE", "clue": "Instrument for viewing distant objects" },
130
- { "word": "MICROSCOPE", "clue": "Instrument for viewing small objects" },
131
- { "word": "HYPOTHESIS", "clue": "Testable scientific prediction" },
132
- { "word": "THEORY", "clue": "Well-tested scientific explanation" },
133
- { "word": "LAW", "clue": "Consistently observed scientific rule" },
134
- { "word": "EXPERIMENT", "clue": "Controlled scientific test" },
135
- { "word": "OBSERVATION", "clue": "Careful scientific watching" },
136
- { "word": "MEASUREMENT", "clue": "Quantified observation" },
137
- { "word": "ANALYSIS", "clue": "Detailed examination of data" },
138
- { "word": "SYNTHESIS", "clue": "Combining elements into whole" },
139
- { "word": "VARIABLE", "clue": "Factor that can change" },
140
- { "word": "CONTROL", "clue": "Unchanged comparison group" },
141
- { "word": "DATA", "clue": "Information collected from tests" },
142
- { "word": "STATISTICS", "clue": "Mathematical analysis of data" },
143
- { "word": "PROBABILITY", "clue": "Likelihood of occurrence" },
144
- { "word": "PRECISION", "clue": "Exactness of measurement" },
145
- { "word": "ACCURACY", "clue": "Correctness of measurement" },
146
- { "word": "ERROR", "clue": "Difference from true value" },
147
- { "word": "UNCERTAINTY", "clue": "Range of doubt in measurement" },
148
- { "word": "CALIBRATION", "clue": "Adjusting instrument accuracy" },
149
- { "word": "STANDARD", "clue": "Reference for measurement" },
150
- { "word": "UNIT", "clue": "Base measure of quantity" },
151
- { "word": "METRIC", "clue": "Decimal measurement system" },
152
- { "word": "WEIGHT", "clue": "Force of gravity on mass" },
153
- { "word": "CONCENTRATION", "clue": "Amount of substance per volume" },
154
- { "word": "MOLARITY", "clue": "Moles of solute per liter" },
155
- { "word": "EQUILIBRIUM", "clue": "State of balanced forces" },
156
- { "word": "STABILITY", "clue": "Resistance to change" },
157
- { "word": "DECAY", "clue": "Gradual breakdown process" },
158
- { "word": "RADIATION", "clue": "Energy emitted from source" },
159
- { "word": "RADIOACTIVE", "clue": "Emitting nuclear radiation" },
160
- { "word": "HALFLIFE", "clue": "Time for half to decay" },
161
- { "word": "FUSION", "clue": "Nuclear combining reaction" },
162
- { "word": "FISSION", "clue": "Nuclear splitting reaction" },
163
- { "word": "QUANTUM", "clue": "Discrete packet of energy" },
164
- { "word": "PARTICLE", "clue": "Tiny piece of matter" },
165
- { "word": "WAVE", "clue": "Energy transfer disturbance" },
166
- { "word": "INTERFERENCE", "clue": "Wave interaction effect" },
167
- { "word": "DIFFRACTION", "clue": "Wave bending around obstacle" },
168
- { "word": "REFLECTION", "clue": "Bouncing back of waves" },
169
- { "word": "REFRACTION", "clue": "Bending of waves through medium" }
170
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
crossword-app/backend/data/word-lists/technology.json DELETED
@@ -1,221 +0,0 @@
1
- [
2
- { "word": "COMPUTER", "clue": "Electronic processing device" },
3
- { "word": "INTERNET", "clue": "Global computer network" },
4
- { "word": "ALGORITHM", "clue": "Set of rules for solving problems" },
5
- { "word": "DATABASE", "clue": "Organized collection of data" },
6
- { "word": "SOFTWARE", "clue": "Computer programs" },
7
- { "word": "HARDWARE", "clue": "Physical computer components" },
8
- { "word": "NETWORK", "clue": "Connected system of computers" },
9
- { "word": "CODE", "clue": "Programming instructions" },
10
- { "word": "ROBOT", "clue": "Automated machine" },
11
- { "word": "ARTIFICIAL", "clue": "Made by humans, not natural" },
12
- { "word": "DIGITAL", "clue": "Using binary data" },
13
- { "word": "BINARY", "clue": "Base-2 number system" },
14
- { "word": "PROCESSOR", "clue": "Computer's brain" },
15
- { "word": "MEMORY", "clue": "Data storage component" },
16
- { "word": "KEYBOARD", "clue": "Input device with keys" },
17
- { "word": "MONITOR", "clue": "Computer display screen" },
18
- { "word": "MOUSE", "clue": "Pointing input device" },
19
- { "word": "PRINTER", "clue": "Device that prints documents" },
20
- { "word": "SCANNER", "clue": "Device that digitizes images" },
21
- { "word": "CAMERA", "clue": "Device that captures images" },
22
- { "word": "SMARTPHONE", "clue": "Portable computing device" },
23
- { "word": "TABLET", "clue": "Touchscreen computing device" },
24
- { "word": "LAPTOP", "clue": "Portable computer" },
25
- { "word": "SERVER", "clue": "Computer that serves data" },
26
- { "word": "CLOUD", "clue": "Internet-based computing" },
27
- { "word": "WEBSITE", "clue": "Collection of web pages" },
28
- { "word": "EMAIL", "clue": "Electronic mail" },
29
- { "word": "BROWSER", "clue": "Web navigation software" },
30
- { "word": "SEARCH", "clue": "Look for information" },
31
- { "word": "DOWNLOAD", "clue": "Transfer data to device" },
32
- { "word": "UPLOAD", "clue": "Transfer data from device" },
33
- { "word": "BANDWIDTH", "clue": "Data transfer capacity" },
34
- { "word": "PROTOCOL", "clue": "Communication rules" },
35
- { "word": "FIREWALL", "clue": "Network security barrier" },
36
- { "word": "ENCRYPTION", "clue": "Data scrambling for security" },
37
- { "word": "PASSWORD", "clue": "Secret access code" },
38
- { "word": "SECURITY", "clue": "Protection from threats" },
39
- { "word": "VIRUS", "clue": "Malicious computer program" },
40
- { "word": "MALWARE", "clue": "Harmful software" },
41
- { "word": "ANTIVIRUS", "clue": "Protection software" },
42
- { "word": "BACKUP", "clue": "Data safety copy" },
43
- { "word": "RECOVERY", "clue": "Data restoration process" },
44
- { "word": "STORAGE", "clue": "Data keeping capacity" },
45
- { "word": "HARDDRIVE", "clue": "Magnetic storage device" },
46
- { "word": "FLASH", "clue": "Solid state storage" },
47
- { "word": "RAM", "clue": "Random access memory" },
48
- { "word": "ROM", "clue": "Read-only memory" },
49
- { "word": "CPU", "clue": "Central processing unit" },
50
- { "word": "GPU", "clue": "Graphics processing unit" },
51
- { "word": "MOTHERBOARD", "clue": "Main circuit board" },
52
- { "word": "CHIP", "clue": "Integrated circuit" },
53
- { "word": "CIRCUIT", "clue": "Electronic pathway" },
54
- { "word": "TRANSISTOR", "clue": "Electronic switch" },
55
- { "word": "SILICON", "clue": "Semiconductor material" },
56
- { "word": "NANOTECHNOLOGY", "clue": "Extremely small scale tech" },
57
- { "word": "AUTOMATION", "clue": "Self-operating technology" },
58
- { "word": "MACHINE", "clue": "Mechanical device" },
59
- { "word": "SENSOR", "clue": "Detection device" },
60
- { "word": "ACTUATOR", "clue": "Movement device" },
61
- { "word": "FEEDBACK", "clue": "System response information" },
62
- { "word": "PROGRAMMING", "clue": "Writing computer instructions" },
63
- { "word": "FUNCTION", "clue": "Reusable code block" },
64
- { "word": "VARIABLE", "clue": "Data storage container" },
65
- { "word": "LOOP", "clue": "Repeating code structure" },
66
- { "word": "CONDITION", "clue": "Decision-making logic" },
67
- { "word": "DEBUG", "clue": "Find and fix errors" },
68
- { "word": "COMPILE", "clue": "Convert code to executable" },
69
- { "word": "RUNTIME", "clue": "Program execution time" },
70
- { "word": "API", "clue": "Application programming interface" },
71
- { "word": "FRAMEWORK", "clue": "Code structure foundation" },
72
- { "word": "LIBRARY", "clue": "Reusable code collection" },
73
- { "word": "MODULE", "clue": "Self-contained code unit" },
74
- { "word": "OBJECT", "clue": "Data and methods container" },
75
- { "word": "CLASS", "clue": "Object blueprint" },
76
- { "word": "INHERITANCE", "clue": "Code reuse mechanism" },
77
- { "word": "INTERFACE", "clue": "System interaction boundary" },
78
- { "word": "PROTOCOL", "clue": "Communication standard" },
79
- { "word": "FORMAT", "clue": "Data structure standard" },
80
- { "word": "SYNTAX", "clue": "Language rules" },
81
- { "word": "SEMANTIC", "clue": "Meaning in code" },
82
- { "word": "PARSING", "clue": "Analyzing code structure" },
83
- { "word": "COMPILER", "clue": "Code translation program" },
84
- { "word": "INTERPRETER", "clue": "Code execution program" },
85
- { "word": "VIRTUAL", "clue": "Simulated environment" },
86
- { "word": "SIMULATION", "clue": "Computer modeling" },
87
- { "word": "EMULATION", "clue": "System imitation" },
88
- { "word": "OPTIMIZATION", "clue": "Performance improvement" },
89
- { "word": "EFFICIENCY", "clue": "Resource usage effectiveness" },
90
- { "word": "PERFORMANCE", "clue": "System speed and quality" },
91
- { "word": "BENCHMARK", "clue": "Performance measurement" },
92
- { "word": "TESTING", "clue": "Quality verification process" },
93
- { "word": "VALIDATION", "clue": "Correctness checking" },
94
- { "word": "VERIFICATION", "clue": "Accuracy confirmation" },
95
- { "word": "QUALITY", "clue": "Standard of excellence" },
96
- { "word": "MAINTENANCE", "clue": "System upkeep" },
97
- { "word": "UPDATE", "clue": "Software improvement" },
98
- { "word": "PATCH", "clue": "Software fix" },
99
- { "word": "VERSION", "clue": "Software release number" },
100
- { "word": "RELEASE", "clue": "Software distribution" },
101
- { "word": "DEPLOYMENT", "clue": "Software installation" },
102
- { "word": "CONFIGURATION", "clue": "System setup" },
103
- { "word": "INSTALLATION", "clue": "Software setup process" },
104
- { "word": "MIGRATION", "clue": "System transition" },
105
- { "word": "INTEGRATION", "clue": "System combination" },
106
- { "word": "COMPATIBILITY", "clue": "System cooperation ability" },
107
- { "word": "INTEROPERABILITY", "clue": "Cross-system communication" },
108
- { "word": "SCALABILITY", "clue": "Growth accommodation ability" },
109
- { "word": "RELIABILITY", "clue": "Consistent performance" },
110
- { "word": "AVAILABILITY", "clue": "System accessibility" },
111
- { "word": "REDUNDANCY", "clue": "Backup system duplication" },
112
- { "word": "FAULT", "clue": "System error condition" },
113
- { "word": "TOLERANCE", "clue": "Error handling ability" },
114
- { "word": "RECOVERY", "clue": "System restoration" },
115
- { "word": "MONITORING", "clue": "System observation" },
116
- { "word": "LOGGING", "clue": "Event recording" },
117
- { "word": "ANALYTICS", "clue": "Data analysis" },
118
- { "word": "METRICS", "clue": "Measurement data" },
119
- { "word": "DASHBOARD", "clue": "Information display panel" },
120
- { "word": "INTERFACE", "clue": "User interaction design" },
121
- { "word": "EXPERIENCE", "clue": "User interaction quality" },
122
- { "word": "USABILITY", "clue": "Ease of use" },
123
- { "word": "ACCESSIBILITY", "clue": "Universal design principle" },
124
- { "word": "RESPONSIVE", "clue": "Adaptive design" },
125
- { "word": "MOBILE", "clue": "Portable device category" },
126
- { "word": "TOUCHSCREEN", "clue": "Touch-sensitive display" },
127
- { "word": "GESTURE", "clue": "Touch movement command" },
128
- { "word": "VOICE", "clue": "Speech interaction" },
129
- { "word": "RECOGNITION", "clue": "Pattern identification" },
130
- { "word": "LEARNING", "clue": "Adaptive improvement" },
131
- { "word": "INTELLIGENCE", "clue": "Artificial reasoning" },
132
- { "word": "NEURAL", "clue": "Brain-inspired network" },
133
- { "word": "DEEP", "clue": "Multi-layered learning" },
134
- { "word": "MACHINE", "clue": "Automated learning system" },
135
- { "word": "DATA", "clue": "Information collection" },
136
- { "word": "BIG", "clue": "Large scale data" },
137
- { "word": "MINING", "clue": "Data pattern extraction" },
138
- { "word": "ANALYSIS", "clue": "Data examination" },
139
- { "word": "VISUALIZATION", "clue": "Data graphic representation" },
140
- { "word": "DASHBOARD", "clue": "Data monitoring panel" },
141
- { "word": "REPORT", "clue": "Data summary document" },
142
- { "word": "QUERY", "clue": "Data search request" },
143
- { "word": "INDEX", "clue": "Data location reference" },
144
- { "word": "SCHEMA", "clue": "Data structure blueprint" },
145
- { "word": "TABLE", "clue": "Data organization structure" },
146
- { "word": "RECORD", "clue": "Data entry" },
147
- { "word": "FIELD", "clue": "Data element" },
148
- { "word": "PRIMARY", "clue": "Main identifier key" },
149
- { "word": "FOREIGN", "clue": "Reference relationship key" },
150
- { "word": "RELATION", "clue": "Data connection" },
151
- { "word": "JOIN", "clue": "Data combination operation" },
152
- { "word": "TRANSACTION", "clue": "Data operation sequence" },
153
- { "word": "COMMIT", "clue": "Data change confirmation" },
154
- { "word": "ROLLBACK", "clue": "Data change reversal" },
155
- { "word": "CONCURRENCY", "clue": "Simultaneous access handling" },
156
- { "word": "LOCK", "clue": "Data access control" },
157
- { "word": "SYNCHRONIZATION", "clue": "Timing coordination" },
158
- { "word": "THREAD", "clue": "Execution sequence" },
159
- { "word": "PROCESS", "clue": "Running program instance" },
160
- { "word": "MULTITASKING", "clue": "Multiple process handling" },
161
- { "word": "PARALLEL", "clue": "Simultaneous execution" },
162
- { "word": "DISTRIBUTED", "clue": "Spread across multiple systems" },
163
- { "word": "CLUSTER", "clue": "Group of connected computers" },
164
- { "word": "GRID", "clue": "Distributed computing network" },
165
- { "word": "PEER", "clue": "Equal network participant" },
166
- { "word": "CLIENT", "clue": "Service requesting system" },
167
- { "word": "SERVICE", "clue": "System functionality provider" },
168
- { "word": "MICROSERVICE", "clue": "Small independent service" },
169
- { "word": "CONTAINER", "clue": "Isolated application environment" },
170
- { "word": "DOCKER", "clue": "Containerization platform" },
171
- { "word": "KUBERNETES", "clue": "Container orchestration" },
172
- { "word": "DEVOPS", "clue": "Development operations practice" },
173
- { "word": "AGILE", "clue": "Flexible development method" },
174
- { "word": "SCRUM", "clue": "Iterative development framework" },
175
- { "word": "SPRINT", "clue": "Short development cycle" },
176
- { "word": "KANBAN", "clue": "Visual workflow management" },
177
- { "word": "CONTINUOUS", "clue": "Ongoing integration practice" },
178
- { "word": "PIPELINE", "clue": "Automated workflow" },
179
- { "word": "BUILD", "clue": "Software compilation process" },
180
- { "word": "TESTING", "clue": "Quality assurance process" },
181
- { "word": "AUTOMATION", "clue": "Manual task elimination" },
182
- { "word": "SCRIPT", "clue": "Automated task sequence" },
183
- { "word": "BATCH", "clue": "Group processing" },
184
- { "word": "STREAMING", "clue": "Continuous data flow" },
185
- { "word": "REALTIME", "clue": "Immediate processing" },
186
- { "word": "LATENCY", "clue": "Response delay time" },
187
- { "word": "THROUGHPUT", "clue": "Processing capacity" },
188
- { "word": "BOTTLENECK", "clue": "Performance limitation point" },
189
- { "word": "CACHE", "clue": "Fast temporary storage" },
190
- { "word": "BUFFER", "clue": "Temporary data holder" },
191
- { "word": "QUEUE", "clue": "Ordered waiting line" },
192
- { "word": "STACK", "clue": "Last-in-first-out structure" },
193
- { "word": "HEAP", "clue": "Dynamic memory area" },
194
- { "word": "POINTER", "clue": "Memory address reference" },
195
- { "word": "REFERENCE", "clue": "Object location indicator" },
196
- { "word": "GARBAGE", "clue": "Unused memory collection" },
197
- { "word": "ALLOCATION", "clue": "Memory assignment" },
198
- { "word": "DEALLOCATION", "clue": "Memory release" },
199
- { "word": "LEAK", "clue": "Memory usage error" },
200
- { "word": "OVERFLOW", "clue": "Capacity exceeding error" },
201
- { "word": "UNDERFLOW", "clue": "Insufficient data error" },
202
- { "word": "EXCEPTION", "clue": "Error handling mechanism" },
203
- { "word": "INTERRUPT", "clue": "Process suspension signal" },
204
- { "word": "SIGNAL", "clue": "Process communication" },
205
- { "word": "EVENT", "clue": "System occurrence" },
206
- { "word": "HANDLER", "clue": "Event processing function" },
207
- { "word": "CALLBACK", "clue": "Function reference" },
208
- { "word": "PROMISE", "clue": "Future value placeholder" },
209
- { "word": "ASYNC", "clue": "Non-blocking operation" },
210
- { "word": "AWAIT", "clue": "Pause for completion" },
211
- { "word": "YIELD", "clue": "Temporary function pause" },
212
- { "word": "GENERATOR", "clue": "Value sequence producer" },
213
- { "word": "ITERATOR", "clue": "Sequential access pattern" },
214
- { "word": "RECURSION", "clue": "Self-calling function" },
215
- { "word": "CLOSURE", "clue": "Function scope retention" },
216
- { "word": "LAMBDA", "clue": "Anonymous function" },
217
- { "word": "FUNCTIONAL", "clue": "Function-based programming" },
218
- { "word": "PROCEDURAL", "clue": "Step-by-step programming" },
219
- { "word": "DECLARATIVE", "clue": "What-not-how programming" },
220
- { "word": "IMPERATIVE", "clue": "Command-based programming" }
221
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
crossword-app/backend/package-lock.json DELETED
The diff for this file is too large to render. See raw diff
 
crossword-app/backend/package.json DELETED
@@ -1,53 +0,0 @@
1
- {
2
- "name": "crossword-backend",
3
- "version": "1.0.0",
4
- "description": "Node.js backend for crossword puzzle generator",
5
- "main": "src/app.js",
6
- "type": "commonjs",
7
- "scripts": {
8
- "start": "node src/app.js",
9
- "dev": "nodemon src/app.js",
10
- "build:frontend": "cd ../frontend && npm run build",
11
- "build": "npm run build:frontend && mkdir -p public && cp -r ../frontend/dist/* public/",
12
- "test": "jest",
13
- "test:watch": "jest --watch",
14
- "lint": "eslint src/",
15
- "lint:fix": "eslint src/ --fix",
16
- "format": "prettier --write \"src/**/*.js\"",
17
- "db:migrate": "node scripts/migrate.js",
18
- "db:seed": "node scripts/seed.js",
19
- "db:reset": "npm run db:migrate && npm run db:seed"
20
- },
21
- "dependencies": {
22
- "@huggingface/inference": "^4.7.1",
23
- "compression": "^1.7.4",
24
- "cors": "^2.8.5",
25
- "dotenv": "^16.3.1",
26
- "express": "^4.18.2",
27
- "express-rate-limit": "^7.1.5",
28
- "helmet": "^7.1.0",
29
- "pg": "^8.11.3"
30
- },
31
- "devDependencies": {
32
- "axios": "^1.11.0",
33
- "eslint": "^8.55.0",
34
- "jest": "^29.7.0",
35
- "nodemon": "^3.0.2",
36
- "prettier": "^3.1.1",
37
- "supertest": "^6.3.3"
38
- },
39
- "engines": {
40
- "node": ">=18.0.0",
41
- "npm": ">=9.0.0"
42
- },
43
- "keywords": [
44
- "crossword",
45
- "puzzle",
46
- "word-game",
47
- "api",
48
- "nodejs",
49
- "express"
50
- ],
51
- "author": "Crossword App Team",
52
- "license": "MIT"
53
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
crossword-app/backend/public/assets/index-Bkj8ir_U.js DELETED
@@ -1,10 +0,0 @@
1
- import{r as m,a as R,R as k}from"./vendor-nf7bT_Uh.js";(function(){const c=document.createElement("link").relList;if(c&&c.supports&&c.supports("modulepreload"))return;for(const s of document.querySelectorAll('link[rel="modulepreload"]'))n(s);new MutationObserver(s=>{for(const r of s)if(r.type==="childList")for(const a of r.addedNodes)a.tagName==="LINK"&&a.rel==="modulepreload"&&n(a)}).observe(document,{childList:!0,subtree:!0});function i(s){const r={};return s.integrity&&(r.integrity=s.integrity),s.referrerPolicy&&(r.referrerPolicy=s.referrerPolicy),s.crossOrigin==="use-credentials"?r.credentials="include":s.crossOrigin==="anonymous"?r.credentials="omit":r.credentials="same-origin",r}function n(s){if(s.ep)return;s.ep=!0;const r=i(s);fetch(s.href,r)}})();var S={exports:{}},b={};/**
2
- * @license React
3
- * react-jsx-runtime.production.min.js
4
- *
5
- * Copyright (c) Facebook, Inc. and its affiliates.
6
- *
7
- * This source code is licensed under the MIT license found in the
8
- * LICENSE file in the root directory of this source tree.
9
- */var E=m,_=Symbol.for("react.element"),$=Symbol.for("react.fragment"),T=Object.prototype.hasOwnProperty,O=E.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,A={key:!0,ref:!0,__self:!0,__source:!0};function C(t,c,i){var n,s={},r=null,a=null;i!==void 0&&(r=""+i),c.key!==void 0&&(r=""+c.key),c.ref!==void 0&&(a=c.ref);for(n in c)T.call(c,n)&&!A.hasOwnProperty(n)&&(s[n]=c[n]);if(t&&t.defaultProps)for(n in c=t.defaultProps,c)s[n]===void 0&&(s[n]=c[n]);return{$$typeof:_,type:t,key:r,ref:a,props:s,_owner:O.current}}b.Fragment=$;b.jsx=C;b.jsxs=C;S.exports=b;var e=S.exports,v={},w=R;v.createRoot=w.createRoot,v.hydrateRoot=w.hydrateRoot;const L=({onTopicsChange:t,availableTopics:c=[],selectedTopics:i=[],useAI:n=!1,onAIToggle:s})=>{const r=a=>{const g=i.includes(a)?i.filter(o=>o!==a):[...i,a];t(g)};return e.jsxs("div",{className:"topic-selector",children:[e.jsx("h3",{children:"Select Topics"}),e.jsx("div",{className:"topic-buttons",children:c.map(a=>e.jsx("button",{className:`topic-btn ${i.includes(a.name)?"selected":""}`,onClick:()=>r(a.name),children:a.name},a.id))}),e.jsxs("div",{className:"ai-toggle-container",children:[e.jsxs("label",{className:"ai-toggle",children:[e.jsx("input",{type:"checkbox",checked:n,onChange:a=>s(a.target.checked),className:"ai-checkbox"}),e.jsxs("span",{className:"ai-label",children:["🤖 Use AI-powered word generation",n&&e.jsx("span",{className:"ai-status",children:" (Dynamic content)"})]})]}),e.jsx("p",{className:"ai-description",children:n?"AI will generate unique words based on semantic relationships":"Using curated word lists with quality clues"})]}),e.jsxs("p",{className:"selected-count",children:[i.length," topic",i.length!==1?"s":""," selected"]})]})},D=({grid:t,clues:c,showSolution:i,onCellChange:n})=>{const[s,r]=m.useState({}),a=(u,l,d)=>{const p=`${u}-${l}`,h={...s,[p]:d.toUpperCase()};r(h),n&&n(u,l,d)},g=(u,l)=>{if(i&&!o(u,l))return t[u][l];const d=`${u}-${l}`;return s[d]||""},o=(u,l)=>t[u][l]===".",f=(u,l)=>{if(!c)return null;const d=c.find(p=>p.position.row===u&&p.position.col===l);return d?d.number:null};if(!t||t.length===0)return e.jsx("div",{className:"puzzle-grid",children:"No puzzle loaded"});const x=t.length,j=t[0]?t[0].length:0;return e.jsx("div",{className:"puzzle-container",children:e.jsx("div",{className:"puzzle-grid",style:{gridTemplateColumns:`repeat(${j}, 35px)`,gridTemplateRows:`repeat(${x}, 35px)`},children:t.map((u,l)=>u.map((d,p)=>{const h=f(l,p);return o(l,p)?e.jsx("div",{className:"grid-cell empty-cell",style:{visibility:"hidden"}},`${l}-${p}`):e.jsxs("div",{className:"grid-cell white-cell",children:[h&&e.jsx("span",{className:"cell-number",children:h}),e.jsx("input",{type:"text",maxLength:"1",value:g(l,p),onChange:z=>a(l,p,z.target.value),className:`cell-input ${i?"solution-text":""}`,disabled:i})]},`${l}-${p}`)}))})})},G=({clues:t=[]})=>{const c=t.filter(s=>s.direction==="across"),i=t.filter(s=>s.direction==="down"),n=({title:s,clueList:r})=>e.jsxs("div",{className:"clue-section",children:[e.jsx("h4",{children:s}),e.jsx("ol",{children:r.map(a=>e.jsxs("li",{className:"clue-item",children:[e.jsx("span",{className:"clue-number",children:a.number}),e.jsx("span",{className:"clue-text",children:a.text})]},`${a.number}-${a.direction}`))})]});return e.jsxs("div",{className:"clue-list",children:[e.jsx(n,{title:"Across",clueList:c}),e.jsx(n,{title:"Down",clueList:i})]})},U=({message:t="Generating puzzle..."})=>e.jsxs("div",{className:"loading-spinner",children:[e.jsx("div",{className:"spinner"}),e.jsx("p",{className:"loading-message",children:t})]}),F=()=>{const[t,c]=m.useState(null),[i,n]=m.useState(!1),[s,r]=m.useState(null),[a,g]=m.useState([]),o="",f=m.useCallback(async()=>{try{n(!0);const l=await fetch(`${o}/api/topics`);if(!l.ok)throw new Error("Failed to fetch topics");const d=await l.json();g(d)}catch(l){r(l.message)}finally{n(!1)}},[o]),x=m.useCallback(async(l,d="medium",p=!1)=>{try{n(!0),r(null);const h=await fetch(`${o}/api/generate`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({topics:l,difficulty:d,useAI:p})});if(!h.ok){const z=await h.json().catch(()=>({}));throw new Error(z.message||"Failed to generate puzzle")}const y=await h.json();return c(y),y}catch(h){return r(h.message),null}finally{n(!1)}},[o]),j=m.useCallback(async l=>{try{const d=await fetch(`${o}/api/validate`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({puzzle:t,answers:l})});if(!d.ok)throw new Error("Failed to validate answers");return await d.json()}catch(d){return r(d.message),null}},[o,t]),u=m.useCallback(()=>{c(null),r(null)},[]);return{puzzle:t,loading:i,error:s,topics:a,fetchTopics:f,generatePuzzle:x,validateAnswers:j,resetPuzzle:u}};function B(){const[t,c]=m.useState([]),[i,n]=m.useState("medium"),[s,r]=m.useState(!1),[a,g]=m.useState(!1),{puzzle:o,loading:f,error:x,topics:j,fetchTopics:u,generatePuzzle:l,resetPuzzle:d}=F();m.useEffect(()=>{u()},[u]);const p=async()=>{if(t.length===0){alert("Please select at least one topic");return}await l(t,i,a)},h=N=>{c(N)},y=N=>{g(N)},z=()=>{d(),c([]),r(!1),g(!1),n("medium")},P=()=>{r(!0)};return e.jsxs("div",{className:"crossword-app",children:[e.jsxs("header",{className:"app-header",children:[e.jsx("h1",{className:"app-title",children:"Crossword Puzzle Generator"}),e.jsx("p",{children:"Select topics and generate your custom crossword puzzle!"})]}),e.jsx(L,{onTopicsChange:h,availableTopics:j,selectedTopics:t,useAI:a,onAIToggle:y}),e.jsxs("div",{className:"puzzle-controls",children:[e.jsxs("select",{value:i,onChange:N=>n(N.target.value),className:"control-btn",disabled:!0,title:"Difficulty selection temporarily disabled - using Medium difficulty",children:[e.jsx("option",{value:"easy",children:"Easy"}),e.jsx("option",{value:"medium",children:"Medium"}),e.jsx("option",{value:"hard",children:"Hard"})]}),e.jsx("button",{onClick:p,disabled:f||t.length===0,className:"control-btn generate-btn",children:f?"Generating...":"Generate Puzzle"}),e.jsx("button",{onClick:z,className:"control-btn reset-btn",children:"Reset"}),o&&!s&&e.jsx("button",{onClick:P,className:"control-btn reveal-btn",children:"Reveal Solution"})]}),x&&e.jsxs("div",{className:"error-message",children:["Error: ",x]}),f&&e.jsx(U,{}),o&&!f&&e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"puzzle-info",children:[e.jsxs("span",{className:"puzzle-stats",children:[o.metadata.wordCount," words • ",o.metadata.size,"×",o.metadata.size," grid"]}),o.metadata.aiGenerated&&e.jsx("span",{className:"ai-generated-badge",children:"🤖 AI-Enhanced"})]}),e.jsxs("div",{className:"puzzle-layout",children:[e.jsx(D,{grid:o.grid,clues:o.clues,showSolution:s}),e.jsx(G,{clues:o.clues})]})]}),!o&&!f&&!x&&e.jsx("div",{style:{textAlign:"center",padding:"40px",color:"#7f8c8d"},children:'Select topics and click "Generate Puzzle" to start!'})]})}v.createRoot(document.getElementById("root")).render(e.jsx(k.StrictMode,{children:e.jsx(B,{})}));
10
- //# sourceMappingURL=index-Bkj8ir_U.js.map
 
 
 
 
 
 
 
 
 
 
 
crossword-app/backend/public/assets/index-Bkj8ir_U.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"index-Bkj8ir_U.js","sources":["../../node_modules/react/cjs/react-jsx-runtime.production.min.js","../../node_modules/react/jsx-runtime.js","../../node_modules/react-dom/client.js","../../src/components/TopicSelector.jsx","../../src/components/PuzzleGrid.jsx","../../src/components/ClueList.jsx","../../src/components/LoadingSpinner.jsx","../../src/hooks/useCrossword.js","../../src/App.jsx","../../src/main.jsx"],"sourcesContent":["/**\n * @license React\n * react-jsx-runtime.production.min.js\n *\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n'use strict';var f=require(\"react\"),k=Symbol.for(\"react.element\"),l=Symbol.for(\"react.fragment\"),m=Object.prototype.hasOwnProperty,n=f.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,p={key:!0,ref:!0,__self:!0,__source:!0};\nfunction q(c,a,g){var b,d={},e=null,h=null;void 0!==g&&(e=\"\"+g);void 0!==a.key&&(e=\"\"+a.key);void 0!==a.ref&&(h=a.ref);for(b in a)m.call(a,b)&&!p.hasOwnProperty(b)&&(d[b]=a[b]);if(c&&c.defaultProps)for(b in a=c.defaultProps,a)void 0===d[b]&&(d[b]=a[b]);return{$$typeof:k,type:c,key:e,ref:h,props:d,_owner:n.current}}exports.Fragment=l;exports.jsx=q;exports.jsxs=q;\n","'use strict';\n\nif (process.env.NODE_ENV === 'production') {\n module.exports = require('./cjs/react-jsx-runtime.production.min.js');\n} else {\n module.exports = require('./cjs/react-jsx-runtime.development.js');\n}\n","'use strict';\n\nvar m = require('react-dom');\nif (process.env.NODE_ENV === 'production') {\n exports.createRoot = m.createRoot;\n exports.hydrateRoot = m.hydrateRoot;\n} else {\n var i = m.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;\n exports.createRoot = function(c, o) {\n i.usingClientEntryPoint = true;\n try {\n return m.createRoot(c, o);\n } finally {\n i.usingClientEntryPoint = false;\n }\n };\n exports.hydrateRoot = function(c, h, o) {\n i.usingClientEntryPoint = true;\n try {\n return m.hydrateRoot(c, h, o);\n } finally {\n i.usingClientEntryPoint = false;\n }\n };\n}\n","import React from 'react';\n\nconst TopicSelector = ({ \n onTopicsChange, \n availableTopics = [], \n selectedTopics = [],\n useAI = false,\n onAIToggle\n}) => {\n const handleTopicToggle = (topic) => {\n const newSelectedTopics = selectedTopics.includes(topic)\n ? selectedTopics.filter(t => t !== topic)\n : [...selectedTopics, topic];\n \n onTopicsChange(newSelectedTopics);\n };\n\n return (\n <div className=\"topic-selector\">\n <h3>Select Topics</h3>\n <div className=\"topic-buttons\">\n {availableTopics.map(topic => (\n <button\n key={topic.id}\n className={`topic-btn ${selectedTopics.includes(topic.name) ? 'selected' : ''}`}\n onClick={() => handleTopicToggle(topic.name)}\n >\n {topic.name}\n </button>\n ))}\n </div>\n \n <div className=\"ai-toggle-container\">\n <label className=\"ai-toggle\">\n <input\n type=\"checkbox\"\n checked={useAI}\n onChange={(e) => onAIToggle(e.target.checked)}\n className=\"ai-checkbox\"\n />\n <span className=\"ai-label\">\n 🤖 Use AI-powered word generation\n {useAI && <span className=\"ai-status\"> (Dynamic content)</span>}\n </span>\n </label>\n <p className=\"ai-description\">\n {useAI \n ? \"AI will generate unique words based on semantic relationships\" \n : \"Using curated word lists with quality clues\"\n }\n </p>\n </div>\n \n <p className=\"selected-count\">\n {selectedTopics.length} topic{selectedTopics.length !== 1 ? 's' : ''} selected\n </p>\n </div>\n );\n};\n\nexport default TopicSelector;","import React, { useState } from 'react';\n\nconst PuzzleGrid = ({ grid, clues, showSolution, onCellChange }) => {\n const [userAnswers, setUserAnswers] = useState({});\n\n const handleCellInput = (row, col, value) => {\n const key = `${row}-${col}`;\n const newAnswers = { ...userAnswers, [key]: value.toUpperCase() };\n setUserAnswers(newAnswers);\n onCellChange && onCellChange(row, col, value);\n };\n\n const getCellValue = (row, col) => {\n if (showSolution && !isBlackCell(row, col)) {\n return grid[row][col];\n }\n const key = `${row}-${col}`;\n return userAnswers[key] || '';\n };\n\n const isBlackCell = (row, col) => {\n return grid[row][col] === '.';\n };\n\n const getCellNumber = (row, col) => {\n if (!clues) return null;\n const clue = clues.find(c => c.position.row === row && c.position.col === col);\n return clue ? clue.number : null;\n };\n\n if (!grid || grid.length === 0) {\n return <div className=\"puzzle-grid\">No puzzle loaded</div>;\n }\n\n const gridRows = grid.length;\n const gridCols = grid[0] ? grid[0].length : 0;\n\n return (\n <div className=\"puzzle-container\">\n <div \n className=\"puzzle-grid\"\n style={{\n gridTemplateColumns: `repeat(${gridCols}, 35px)`,\n gridTemplateRows: `repeat(${gridRows}, 35px)`\n }}\n >\n {grid.map((row, rowIndex) =>\n row.map((cell, colIndex) => {\n const cellNumber = getCellNumber(rowIndex, colIndex);\n const isBlack = isBlackCell(rowIndex, colIndex);\n \n // Only render cells that contain letters (not black/unused cells)\n if (isBlack) {\n return (\n <div\n key={`${rowIndex}-${colIndex}`}\n className=\"grid-cell empty-cell\"\n style={{ visibility: 'hidden' }}\n >\n </div>\n );\n }\n \n return (\n <div\n key={`${rowIndex}-${colIndex}`}\n className=\"grid-cell white-cell\"\n >\n {cellNumber && <span className=\"cell-number\">{cellNumber}</span>}\n <input\n type=\"text\"\n maxLength=\"1\"\n value={getCellValue(rowIndex, colIndex)}\n onChange={(e) => handleCellInput(rowIndex, colIndex, e.target.value)}\n className={`cell-input ${showSolution ? 'solution-text' : ''}`}\n disabled={showSolution}\n />\n </div>\n );\n })\n )}\n </div>\n </div>\n );\n};\n\nexport default PuzzleGrid;","import React from 'react';\n\nconst ClueList = ({ clues = [] }) => {\n const acrossClues = clues.filter(clue => clue.direction === 'across');\n const downClues = clues.filter(clue => clue.direction === 'down');\n\n const ClueSection = ({ title, clueList }) => (\n <div className=\"clue-section\">\n <h4>{title}</h4>\n <ol>\n {clueList.map(clue => (\n <li key={`${clue.number}-${clue.direction}`} className=\"clue-item\">\n <span className=\"clue-number\">{clue.number}</span>\n <span className=\"clue-text\">{clue.text}</span>\n </li>\n ))}\n </ol>\n </div>\n );\n\n return (\n <div className=\"clue-list\">\n <ClueSection title=\"Across\" clueList={acrossClues} />\n <ClueSection title=\"Down\" clueList={downClues} />\n </div>\n );\n};\n\nexport default ClueList;","import React from 'react';\n\nconst LoadingSpinner = ({ message = \"Generating puzzle...\" }) => {\n return (\n <div className=\"loading-spinner\">\n <div className=\"spinner\"></div>\n <p className=\"loading-message\">{message}</p>\n </div>\n );\n};\n\nexport default LoadingSpinner;","import { useState, useCallback } from 'react';\n\nconst useCrossword = () => {\n const [puzzle, setPuzzle] = useState(null);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState(null);\n const [topics, setTopics] = useState([]);\n\n const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || (import.meta.env.PROD ? '' : 'http://localhost:3000');\n\n const fetchTopics = useCallback(async () => {\n try {\n setLoading(true);\n const response = await fetch(`${API_BASE_URL}/api/topics`);\n if (!response.ok) throw new Error('Failed to fetch topics');\n const data = await response.json();\n setTopics(data);\n } catch (err) {\n setError(err.message);\n } finally {\n setLoading(false);\n }\n }, [API_BASE_URL]);\n\n const generatePuzzle = useCallback(async (selectedTopics, difficulty = 'medium', useAI = false) => {\n try {\n setLoading(true);\n setError(null);\n \n const response = await fetch(`${API_BASE_URL}/api/generate`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n topics: selectedTopics,\n difficulty,\n useAI\n })\n });\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({}));\n throw new Error(errorData.message || 'Failed to generate puzzle');\n }\n \n const puzzleData = await response.json();\n setPuzzle(puzzleData);\n return puzzleData;\n } catch (err) {\n setError(err.message);\n return null;\n } finally {\n setLoading(false);\n }\n }, [API_BASE_URL]);\n\n const validateAnswers = useCallback(async (userAnswers) => {\n try {\n const response = await fetch(`${API_BASE_URL}/api/validate`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n puzzle: puzzle,\n answers: userAnswers\n })\n });\n\n if (!response.ok) throw new Error('Failed to validate answers');\n \n return await response.json();\n } catch (err) {\n setError(err.message);\n return null;\n }\n }, [API_BASE_URL, puzzle]);\n\n const resetPuzzle = useCallback(() => {\n setPuzzle(null);\n setError(null);\n }, []);\n\n return {\n puzzle,\n loading,\n error,\n topics,\n fetchTopics,\n generatePuzzle,\n validateAnswers,\n resetPuzzle\n };\n};\n\nexport default useCrossword;","import React, { useState, useEffect } from 'react';\nimport TopicSelector from './components/TopicSelector';\nimport PuzzleGrid from './components/PuzzleGrid';\nimport ClueList from './components/ClueList';\nimport LoadingSpinner from './components/LoadingSpinner';\nimport useCrossword from './hooks/useCrossword';\nimport './styles/puzzle.css';\n\nfunction App() {\n const [selectedTopics, setSelectedTopics] = useState([]);\n const [difficulty, setDifficulty] = useState('medium');\n const [showSolution, setShowSolution] = useState(false);\n const [useAI, setUseAI] = useState(false);\n \n const {\n puzzle,\n loading,\n error,\n topics,\n fetchTopics,\n generatePuzzle,\n resetPuzzle\n } = useCrossword();\n\n useEffect(() => {\n fetchTopics();\n }, [fetchTopics]);\n\n const handleGeneratePuzzle = async () => {\n if (selectedTopics.length === 0) {\n alert('Please select at least one topic');\n return;\n }\n \n await generatePuzzle(selectedTopics, difficulty, useAI);\n };\n\n const handleTopicsChange = (topics) => {\n setSelectedTopics(topics);\n };\n\n const handleAIToggle = (aiEnabled) => {\n setUseAI(aiEnabled);\n };\n\n const handleReset = () => {\n resetPuzzle();\n setSelectedTopics([]);\n setShowSolution(false);\n setUseAI(false);\n setDifficulty('medium'); // Always reset to medium\n };\n\n const handleRevealSolution = () => {\n setShowSolution(true);\n };\n\n return (\n <div className=\"crossword-app\">\n <header className=\"app-header\">\n <h1 className=\"app-title\">Crossword Puzzle Generator</h1>\n <p>Select topics and generate your custom crossword puzzle!</p>\n </header>\n\n <TopicSelector \n onTopicsChange={handleTopicsChange}\n availableTopics={topics}\n selectedTopics={selectedTopics}\n useAI={useAI}\n onAIToggle={handleAIToggle}\n />\n\n <div className=\"puzzle-controls\">\n <select \n value={difficulty} \n onChange={(e) => setDifficulty(e.target.value)}\n className=\"control-btn\"\n disabled\n title=\"Difficulty selection temporarily disabled - using Medium difficulty\"\n >\n <option value=\"easy\">Easy</option>\n <option value=\"medium\">Medium</option>\n <option value=\"hard\">Hard</option>\n </select>\n \n <button\n onClick={handleGeneratePuzzle}\n disabled={loading || selectedTopics.length === 0}\n className=\"control-btn generate-btn\"\n >\n {loading ? 'Generating...' : 'Generate Puzzle'}\n </button>\n \n <button\n onClick={handleReset}\n className=\"control-btn reset-btn\"\n >\n Reset\n </button>\n \n {puzzle && !showSolution && (\n <button\n onClick={handleRevealSolution}\n className=\"control-btn reveal-btn\"\n >\n Reveal Solution\n </button>\n )}\n </div>\n\n {error && (\n <div className=\"error-message\">\n Error: {error}\n </div>\n )}\n\n {loading && <LoadingSpinner />}\n\n {puzzle && !loading && (\n <>\n <div className=\"puzzle-info\">\n <span className=\"puzzle-stats\">\n {puzzle.metadata.wordCount} words • {puzzle.metadata.size}×{puzzle.metadata.size} grid\n </span>\n {puzzle.metadata.aiGenerated && (\n <span className=\"ai-generated-badge\">🤖 AI-Enhanced</span>\n )}\n </div>\n <div className=\"puzzle-layout\">\n <PuzzleGrid \n grid={puzzle.grid} \n clues={puzzle.clues}\n showSolution={showSolution}\n />\n <ClueList clues={puzzle.clues} />\n </div>\n </>\n )}\n\n {!puzzle && !loading && !error && (\n <div style={{ textAlign: 'center', padding: '40px', color: '#7f8c8d' }}>\n Select topics and click \"Generate Puzzle\" to start!\n </div>\n )}\n </div>\n );\n}\n\nexport default App;","import React from 'react'\nimport ReactDOM from 'react-dom/client'\nimport App from './App.jsx'\n\nReactDOM.createRoot(document.getElementById('root')).render(\n <React.StrictMode>\n <App />\n </React.StrictMode>,\n)"],"names":["f","require$$0","k","l","m","n","p","q","c","a","g","b","d","e","h","reactJsxRuntime_production_min","jsxRuntimeModule","client","TopicSelector","onTopicsChange","availableTopics","selectedTopics","useAI","onAIToggle","handleTopicToggle","topic","newSelectedTopics","t","jsxs","jsx","PuzzleGrid","grid","clues","showSolution","onCellChange","userAnswers","setUserAnswers","useState","handleCellInput","row","col","value","key","newAnswers","getCellValue","isBlackCell","getCellNumber","clue","gridRows","gridCols","rowIndex","cell","colIndex","cellNumber","ClueList","acrossClues","downClues","ClueSection","title","clueList","LoadingSpinner","message","useCrossword","puzzle","setPuzzle","loading","setLoading","error","setError","topics","setTopics","API_BASE_URL","fetchTopics","useCallback","response","data","err","generatePuzzle","difficulty","errorData","puzzleData","validateAnswers","resetPuzzle","App","setSelectedTopics","setDifficulty","setShowSolution","setUseAI","useEffect","handleGeneratePuzzle","handleTopicsChange","handleAIToggle","aiEnabled","handleReset","handleRevealSolution","Fragment","ReactDOM","React"],"mappings":"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","x_google_ignoreList":[0,1,2]}
 
 
crossword-app/backend/public/assets/index-V4v18wFW.css DELETED
@@ -1 +0,0 @@
1
- .crossword-app{max-width:1200px;margin:0 auto;padding:20px;font-family:Segoe UI,Tahoma,Geneva,Verdana,sans-serif}.app-header{text-align:center;margin-bottom:30px}.app-title{color:#2c3e50;font-size:2.5rem;margin-bottom:10px}.topic-selector{background:#f8f9fa;padding:20px;border-radius:8px;margin-bottom:20px}.topic-selector h3{margin-top:0;color:#2c3e50}.topic-buttons{display:flex;flex-wrap:wrap;gap:10px;margin-bottom:15px}.topic-btn{padding:8px 16px;border:2px solid #3498db;background:#fff;color:#3498db;border-radius:20px;cursor:pointer;transition:all .3s ease;font-weight:500}.topic-btn:hover,.topic-btn.selected{background:#3498db;color:#fff}.selected-count{color:#7f8c8d;font-size:.9rem;margin:0}.ai-toggle-container{margin:20px 0;padding:15px;background:#f8f9fa;border-radius:8px;border:2px solid #e9ecef;transition:all .3s ease}.ai-toggle-container:has(.ai-checkbox:checked){background:linear-gradient(135deg,#e3f2fd,#f3e5f5);border-color:#3498db}.ai-toggle{display:flex;align-items:center;cursor:pointer;font-weight:500;margin-bottom:8px}.ai-checkbox{width:20px;height:20px;margin-right:12px;cursor:pointer;accent-color:#3498db}.ai-label{font-size:1rem;color:#2c3e50;-webkit-user-select:none;user-select:none}.ai-status{color:#27ae60;font-weight:600;font-size:.9rem}.ai-description{margin:0;font-size:.85rem;color:#6c757d;line-height:1.4;padding-left:32px}.puzzle-controls{display:flex;gap:15px;margin-bottom:20px;justify-content:center}.control-btn{padding:10px 20px;border:none;border-radius:5px;cursor:pointer;font-weight:600;transition:background-color .3s ease}.control-btn:disabled{background:#bdc3c7!important;color:#7f8c8d!important;cursor:not-allowed;opacity:.7}.generate-btn{background:#27ae60;color:#fff}.generate-btn:hover{background:#229954}.generate-btn:disabled{background:#bdc3c7;cursor:not-allowed}.reset-btn{background:#e74c3c;color:#fff}.reset-btn:hover{background:#c0392b}.reveal-btn{background:#f39c12;color:#fff}.reveal-btn:hover{background:#e67e22}.loading-spinner{display:flex;flex-direction:column;align-items:center;padding:40px}.spinner{width:40px;height:40px;border:4px solid #f3f3f3;border-top:4px solid #3498db;border-radius:50%;animation:spin 1s linear infinite;margin-bottom:15px}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.loading-message{color:#7f8c8d;font-size:1.1rem}.puzzle-info{display:flex;justify-content:space-between;align-items:center;margin:20px 0 10px;padding:10px 15px;background:#f8f9fa;border-radius:6px;border-left:4px solid #3498db}.puzzle-stats{font-size:.9rem;color:#6c757d;font-weight:500}.ai-generated-badge{background:linear-gradient(135deg,#667eea,#764ba2);color:#fff;padding:4px 12px;border-radius:15px;font-size:.8rem;font-weight:600;text-shadow:0 1px 2px rgba(0,0,0,.2);box-shadow:0 2px 4px #0000001a}.puzzle-layout{display:grid;grid-template-columns:1fr 300px;gap:30px;margin-top:20px}@media (max-width: 768px){.puzzle-layout{grid-template-columns:1fr;gap:20px}.puzzle-info{flex-direction:column;gap:8px;text-align:center}.ai-toggle-container{padding:12px}.ai-description{padding-left:0;text-align:center}}.puzzle-container{display:flex;justify-content:center}.puzzle-grid{display:grid;gap:0;margin:0 auto;width:fit-content;height:fit-content}.grid-cell{width:35px;height:35px;position:relative;display:flex;align-items:center;justify-content:center;box-sizing:border-box;background:#fff}.grid-cell:before{content:"";position:absolute;top:0;left:0;right:-1px;bottom:-1px;border:1px solid #2c3e50;pointer-events:none;z-index:10}.black-cell{background:#f0f0f0}.black-cell:before{background:#f0f0f0;border:1px solid #2c3e50}.white-cell{background:#fff}.empty-cell{background:transparent;border:none;visibility:hidden}.empty-cell:before{display:none}.cell-input{width:100%;height:100%;border:none!important;text-align:center;font-size:16px;font-weight:700;background:transparent;outline:none;text-transform:uppercase;position:relative;z-index:5}.cell-input:focus{background:#e8f4fd;box-shadow:inset 0 0 0 2px #3498db}.cell-number{position:absolute;top:1px;left:2px;font-size:10px;font-weight:700;color:#2c3e50;line-height:1;z-index:15;pointer-events:none}.solution-text{color:#2c3e50!important;font-weight:700!important;background:#fff!important}.solution-text:disabled{opacity:1!important;cursor:default}.grid-cell .solution-text{border:none!important;background:#fff!important}.clue-list{background:#f8f9fa;padding:20px;border-radius:8px;max-height:600px;overflow-y:auto}.clue-section{margin-bottom:25px}.clue-section h4{color:#2c3e50;margin-bottom:15px;font-size:1.2rem;border-bottom:2px solid #3498db;padding-bottom:5px}.clue-section ol{padding-left:0;list-style:none}.clue-item{display:flex;margin-bottom:8px;padding:8px;border-radius:4px;cursor:pointer;transition:background-color .2s ease}.clue-item:hover{background:#e9ecef}.clue-number{font-weight:700;color:#3498db;margin-right:10px;min-width:25px}.clue-text{flex:1;color:#2c3e50}.error-message{background:#f8d7da;color:#721c24;padding:15px;border-radius:5px;margin:20px 0;border:1px solid #f5c6cb}.success-message{background:#d4edda;color:#155724;padding:15px;border-radius:5px;margin:20px 0;border:1px solid #c3e6cb;text-align:center;font-weight:600}
 
 
crossword-app/backend/public/assets/vendor-nf7bT_Uh.js DELETED
The diff for this file is too large to render. See raw diff
 
crossword-app/backend/public/assets/vendor-nf7bT_Uh.js.map DELETED
The diff for this file is too large to render. See raw diff
 
crossword-app/backend/public/index.html DELETED
@@ -1,16 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <meta name="description" content="Generate custom crossword puzzles by selecting topics" />
7
- <meta name="keywords" content="crossword, puzzle, word game, brain teaser" />
8
- <title>Crossword Puzzle Generator</title>
9
- <script type="module" crossorigin src="/assets/index-Bkj8ir_U.js"></script>
10
- <link rel="modulepreload" crossorigin href="/assets/vendor-nf7bT_Uh.js">
11
- <link rel="stylesheet" crossorigin href="/assets/index-V4v18wFW.css">
12
- </head>
13
- <body>
14
- <div id="root"></div>
15
- </body>
16
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
crossword-app/backend/setup-env.sh DELETED
@@ -1,33 +0,0 @@
1
- #!/bin/bash
2
-
3
- # Crossword App - Environment Setup Script
4
- echo "🛠️ Setting up Crossword App environment..."
5
-
6
- # Check if .env already exists
7
- if [ -f ".env" ]; then
8
- echo "⚠️ .env file already exists!"
9
- echo " Backup created as .env.backup"
10
- cp .env .env.backup
11
- fi
12
-
13
- # Copy template
14
- echo "📋 Copying .env.example to .env..."
15
- cp .env.example .env
16
-
17
- echo "✅ Environment file created!"
18
- echo ""
19
- echo "🔑 Next steps:"
20
- echo " 1. Edit .env file with your settings:"
21
- echo " nano .env"
22
- echo ""
23
- echo " 2. Add your HuggingFace API key:"
24
- echo " HUGGINGFACE_API_KEY=hf_your_real_key_here"
25
- echo ""
26
- echo " 3. Optionally enable AI by default:"
27
- echo " USE_AI_WORDS=true"
28
- echo ""
29
- echo " 4. Start the development server:"
30
- echo " npm run dev"
31
- echo ""
32
- echo "📚 Get HuggingFace API key: https://huggingface.co/settings/tokens"
33
- echo "🔒 Your .env file is gitignored and secure!"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
crossword-app/backend/src/app.js DELETED
@@ -1,175 +0,0 @@
1
- require('dotenv').config();
2
-
3
- // Suppress HuggingFace verbose logging
4
- process.env.HF_HUB_VERBOSITY = 'error';
5
- process.env.TRANSFORMERS_VERBOSITY = 'error';
6
-
7
- const express = require('express');
8
- const cors = require('cors');
9
- const helmet = require('helmet');
10
- const rateLimit = require('express-rate-limit');
11
- const path = require('path');
12
- const apiRoutes = require('./routes/api');
13
-
14
- const app = express();
15
- const PORT = process.env.PORT || 7860;
16
-
17
- // Trust proxy for platforms like Hugging Face Spaces, Heroku, etc.
18
- if (process.env.NODE_ENV === 'production') {
19
- app.set('trust proxy', 1);
20
- }
21
-
22
- app.use(helmet());
23
-
24
- // CORS configuration
25
- let corsOptions;
26
- if (process.env.NODE_ENV === 'production') {
27
- // In production, allow same origin (since frontend is served from same server)
28
- corsOptions = {
29
- origin: true,
30
- credentials: true,
31
- optionsSuccessStatus: 200
32
- };
33
- } else {
34
- // Development mode - allow dev servers
35
- corsOptions = {
36
- origin: process.env.CORS_ORIGIN || ['http://localhost:5173', 'http://localhost:3000'],
37
- credentials: true,
38
- optionsSuccessStatus: 200
39
- };
40
- }
41
- app.use(cors(corsOptions));
42
-
43
- const limiter = rateLimit({
44
- windowMs: 15 * 60 * 1000,
45
- max: 100,
46
- message: 'Too many requests from this IP, please try again later.',
47
- standardHeaders: true,
48
- legacyHeaders: false,
49
- });
50
- app.use(limiter);
51
-
52
- const generateLimiter = rateLimit({
53
- windowMs: 5 * 60 * 1000,
54
- max: 50,
55
- message: 'Too many puzzle generation requests, please wait before trying again.',
56
- });
57
-
58
- app.use(express.json({ limit: '10mb' }));
59
- app.use(express.urlencoded({ extended: true, limit: '10mb' }));
60
-
61
- // Basic request logging without detailed headers
62
- app.use((req, res, next) => {
63
- console.log(`${new Date().toISOString()} - ${req.method} ${req.path}`);
64
- next();
65
- });
66
-
67
- app.use('/api/generate', generateLimiter);
68
- app.use('/api', apiRoutes);
69
-
70
- // Serve static files in production
71
- if (process.env.NODE_ENV === 'production') {
72
- const staticPath = path.join(__dirname, '../public');
73
- console.log(`Static files path: ${staticPath}`);
74
-
75
- // Check if static files exist
76
- const fs = require('fs');
77
- try {
78
- const files = fs.readdirSync(staticPath);
79
- console.log(`Static files found:`, files);
80
-
81
- // Check assets directory specifically
82
- const assetsPath = path.join(staticPath, 'assets');
83
- if (fs.existsSync(assetsPath)) {
84
- const assetFiles = fs.readdirSync(assetsPath);
85
- console.log(`Assets directory contents:`, assetFiles);
86
- } else {
87
- console.error(`Assets directory not found at: ${assetsPath}`);
88
- }
89
- } catch (error) {
90
- console.error(`Error reading static files directory:`, error.message);
91
- }
92
-
93
- app.use(express.static(staticPath));
94
-
95
- // Test route to check if static serving works
96
- app.get('/test-static', (req, res) => {
97
- const fs = require('fs');
98
- const assetsPath = path.join(staticPath, 'assets');
99
- try {
100
- const files = fs.readdirSync(assetsPath);
101
- res.json({ staticPath, assetsPath, files });
102
- } catch (error) {
103
- res.status(500).json({ error: error.message, staticPath, assetsPath });
104
- }
105
- });
106
-
107
-
108
- // Handle React Router routes - serve index.html for non-API routes
109
- app.get('*', (req, res) => {
110
- // Ensure we're sending the right content type
111
- res.setHeader('Content-Type', 'text/html; charset=utf-8');
112
- res.sendFile(path.join(staticPath, 'index.html'));
113
- });
114
- } else {
115
- // Development mode - show API info
116
- app.get('/', (req, res) => {
117
- res.json({
118
- message: 'Crossword Puzzle API',
119
- version: '1.0.0',
120
- mode: 'development',
121
- endpoints: {
122
- topics: 'GET /api/topics',
123
- generate: 'POST /api/generate',
124
- validate: 'POST /api/validate',
125
- words: 'GET /api/words/:topic',
126
- health: 'GET /api/health'
127
- }
128
- });
129
- });
130
- }
131
-
132
- app.use((req, res) => {
133
- res.status(404).json({
134
- error: 'Not Found',
135
- message: `Route ${req.method} ${req.path} not found`,
136
- timestamp: new Date().toISOString()
137
- });
138
- });
139
-
140
- app.use((error, req, res, next) => {
141
- console.error('Error:', error);
142
-
143
- if (error.type === 'entity.parse.failed') {
144
- return res.status(400).json({
145
- error: 'Invalid JSON',
146
- message: 'Request body contains invalid JSON'
147
- });
148
- }
149
-
150
- res.status(500).json({
151
- error: 'Internal Server Error',
152
- message: process.env.NODE_ENV === 'production'
153
- ? 'Something went wrong'
154
- : error.message,
155
- timestamp: new Date().toISOString()
156
- });
157
- });
158
-
159
- if (require.main === module) {
160
- const HOST = process.env.NODE_ENV === 'production' ? '0.0.0.0' : 'localhost';
161
- app.listen(PORT, HOST, () => {
162
- console.log(`Server running on ${HOST}:${PORT}`);
163
- console.log(`Environment: ${process.env.NODE_ENV || 'development'}`);
164
- console.log(`Working directory: ${process.cwd()}`);
165
- console.log(`App file location: ${__dirname}`);
166
- if (process.env.NODE_ENV === 'production') {
167
- console.log(`Serving React app from /public directory`);
168
- console.log(`CORS enabled for same origin`);
169
- } else {
170
- console.log(`CORS enabled for: ${JSON.stringify(corsOptions.origin)}`);
171
- }
172
- });
173
- }
174
-
175
- module.exports = app;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
crossword-app/backend/src/controllers/puzzleController.js DELETED
@@ -1,65 +0,0 @@
1
- const CrosswordGenerator = require('../services/crosswordGenerator');
2
- const WordService = require('../services/wordService');
3
-
4
- class PuzzleController {
5
- static async getTopics(req, res) {
6
- try {
7
- const topics = await WordService.getAllTopics();
8
- res.json(topics);
9
- } catch (error) {
10
- console.error('Error fetching topics:', error);
11
- res.status(500).json({ error: 'Failed to fetch topics' });
12
- }
13
- }
14
-
15
- static async generatePuzzle(req, res) {
16
- try {
17
- const { topics, difficulty = 'medium', useAI = false } = req.body;
18
-
19
- if (!topics || !Array.isArray(topics) || topics.length === 0) {
20
- return res.status(400).json({ error: 'Topics array is required' });
21
- }
22
-
23
- const generator = new CrosswordGenerator();
24
- const puzzle = await generator.generatePuzzle(topics, difficulty, { useAI });
25
-
26
- if (!puzzle) {
27
- return res.status(400).json({ error: 'Could not generate puzzle with selected topics' });
28
- }
29
-
30
- res.json(puzzle);
31
- } catch (error) {
32
- console.error('Error generating puzzle:', error);
33
- res.status(500).json({ error: 'Failed to generate puzzle' });
34
- }
35
- }
36
-
37
- static async validateAnswers(req, res) {
38
- try {
39
- const { puzzle, answers } = req.body;
40
-
41
- if (!puzzle || !answers) {
42
- return res.status(400).json({ error: 'Puzzle and answers are required' });
43
- }
44
-
45
- const validation = CrosswordGenerator.validateAnswers(puzzle, answers);
46
- res.json(validation);
47
- } catch (error) {
48
- console.error('Error validating answers:', error);
49
- res.status(500).json({ error: 'Failed to validate answers' });
50
- }
51
- }
52
-
53
- static async getWords(req, res) {
54
- try {
55
- const { topic } = req.params;
56
- const words = await WordService.getWordsByTopic(topic);
57
- res.json(words);
58
- } catch (error) {
59
- console.error('Error fetching words:', error);
60
- res.status(500).json({ error: 'Failed to fetch words' });
61
- }
62
- }
63
- }
64
-
65
- module.exports = PuzzleController;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
crossword-app/backend/src/models/Topic.js DELETED
@@ -1,144 +0,0 @@
1
- class Topic {
2
- constructor({ id, name, description, wordCount = 0, difficulty = 'medium' }) {
3
- this.id = id;
4
- this.name = name;
5
- this.description = description;
6
- this.wordCount = wordCount;
7
- this.difficulty = difficulty;
8
- this.createdAt = new Date();
9
- this.updatedAt = new Date();
10
- }
11
-
12
- static validateTopic(topicData) {
13
- const errors = [];
14
-
15
- if (!topicData.name || typeof topicData.name !== 'string') {
16
- errors.push('Topic name is required and must be a string');
17
- } else if (topicData.name.length < 2 || topicData.name.length > 50) {
18
- errors.push('Topic name must be between 2 and 50 characters');
19
- }
20
-
21
- if (topicData.description && typeof topicData.description !== 'string') {
22
- errors.push('Description must be a string');
23
- }
24
-
25
- if (topicData.difficulty && !['easy', 'medium', 'hard'].includes(topicData.difficulty)) {
26
- errors.push('Difficulty must be easy, medium, or hard');
27
- }
28
-
29
- return {
30
- isValid: errors.length === 0,
31
- errors
32
- };
33
- }
34
-
35
- static fromJSON(data) {
36
- return new Topic(data);
37
- }
38
-
39
- toJSON() {
40
- return {
41
- id: this.id,
42
- name: this.name,
43
- description: this.description,
44
- wordCount: this.wordCount,
45
- difficulty: this.difficulty,
46
- createdAt: this.createdAt,
47
- updatedAt: this.updatedAt
48
- };
49
- }
50
-
51
- updateWordCount(count) {
52
- this.wordCount = count;
53
- this.updatedAt = new Date();
54
- }
55
-
56
- getDisplayName() {
57
- return this.name
58
- .split(/[-_\s]+/)
59
- .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
60
- .join(' ');
61
- }
62
-
63
- getSlug() {
64
- return this.name
65
- .toLowerCase()
66
- .replace(/[^a-z0-9]+/g, '-')
67
- .replace(/^-+|-+$/g, '');
68
- }
69
-
70
- isPopular() {
71
- return this.wordCount >= 20;
72
- }
73
-
74
- getDifficultyLevel() {
75
- const levels = {
76
- easy: 1,
77
- medium: 2,
78
- hard: 3
79
- };
80
-
81
- return levels[this.difficulty] || 2;
82
- }
83
-
84
- static getDefaultTopics() {
85
- return [
86
- new Topic({
87
- id: 'animals',
88
- name: 'Animals',
89
- description: 'Various animals from around the world',
90
- wordCount: 25,
91
- difficulty: 'easy'
92
- }),
93
- new Topic({
94
- id: 'science',
95
- name: 'Science',
96
- description: 'Scientific terms and concepts',
97
- wordCount: 30,
98
- difficulty: 'medium'
99
- }),
100
- new Topic({
101
- id: 'geography',
102
- name: 'Geography',
103
- description: 'Countries, cities, and geographical features',
104
- wordCount: 28,
105
- difficulty: 'medium'
106
- }),
107
- new Topic({
108
- id: 'technology',
109
- name: 'Technology',
110
- description: 'Computer and technology terms',
111
- wordCount: 35,
112
- difficulty: 'hard'
113
- }),
114
- new Topic({
115
- id: 'history',
116
- name: 'History',
117
- description: 'Historical events and figures',
118
- wordCount: 22,
119
- difficulty: 'hard'
120
- }),
121
- new Topic({
122
- id: 'sports',
123
- name: 'Sports',
124
- description: 'Sports and athletic activities',
125
- wordCount: 20,
126
- difficulty: 'easy'
127
- })
128
- ];
129
- }
130
-
131
- static sortByDifficulty(topics) {
132
- return topics.sort((a, b) => a.getDifficultyLevel() - b.getDifficultyLevel());
133
- }
134
-
135
- static sortByPopularity(topics) {
136
- return topics.sort((a, b) => b.wordCount - a.wordCount);
137
- }
138
-
139
- static filterByDifficulty(topics, difficulty) {
140
- return topics.filter(topic => topic.difficulty === difficulty);
141
- }
142
- }
143
-
144
- module.exports = Topic;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
crossword-app/backend/src/models/Word.js DELETED
@@ -1,112 +0,0 @@
1
- class Word {
2
- constructor({ id, word, clue, topic, difficulty = 'medium', length }) {
3
- this.id = id;
4
- this.word = word.toUpperCase();
5
- this.clue = clue;
6
- this.topic = topic;
7
- this.difficulty = difficulty;
8
- this.length = length || word.length;
9
- this.createdAt = new Date();
10
- }
11
-
12
- static validateWord(wordData) {
13
- const errors = [];
14
-
15
- if (!wordData.word || typeof wordData.word !== 'string') {
16
- errors.push('Word is required and must be a string');
17
- } else if (wordData.word.length < 2 || wordData.word.length > 15) {
18
- errors.push('Word must be between 2 and 15 characters');
19
- } else if (!/^[A-Za-z]+$/.test(wordData.word)) {
20
- errors.push('Word must contain only letters');
21
- }
22
-
23
- if (!wordData.clue || typeof wordData.clue !== 'string') {
24
- errors.push('Clue is required and must be a string');
25
- }
26
-
27
- if (!wordData.topic || typeof wordData.topic !== 'string') {
28
- errors.push('Topic is required and must be a string');
29
- }
30
-
31
- if (wordData.difficulty && !['easy', 'medium', 'hard'].includes(wordData.difficulty)) {
32
- errors.push('Difficulty must be easy, medium, or hard');
33
- }
34
-
35
- return {
36
- isValid: errors.length === 0,
37
- errors
38
- };
39
- }
40
-
41
- static fromJSON(data) {
42
- return new Word(data);
43
- }
44
-
45
- toJSON() {
46
- return {
47
- id: this.id,
48
- word: this.word,
49
- clue: this.clue,
50
- topic: this.topic,
51
- difficulty: this.difficulty,
52
- length: this.length,
53
- createdAt: this.createdAt
54
- };
55
- }
56
-
57
- getDifficultyScore() {
58
- const baseScore = this.length;
59
- const difficultyMultiplier = {
60
- easy: 1,
61
- medium: 1.2,
62
- hard: 1.5
63
- };
64
-
65
- return Math.round(baseScore * (difficultyMultiplier[this.difficulty] || 1));
66
- }
67
-
68
- isCompatibleWith(otherWord) {
69
- if (!otherWord || !(otherWord instanceof Word)) {
70
- return false;
71
- }
72
-
73
- const thisWord = this.word;
74
- const otherWordStr = otherWord.word;
75
-
76
- for (let i = 0; i < thisWord.length; i++) {
77
- for (let j = 0; j < otherWordStr.length; j++) {
78
- if (thisWord[i] === otherWordStr[j]) {
79
- return true;
80
- }
81
- }
82
- }
83
-
84
- return false;
85
- }
86
-
87
- getIntersectionPoints(otherWord) {
88
- if (!this.isCompatibleWith(otherWord)) {
89
- return [];
90
- }
91
-
92
- const intersections = [];
93
- const thisWord = this.word;
94
- const otherWordStr = otherWord.word;
95
-
96
- for (let i = 0; i < thisWord.length; i++) {
97
- for (let j = 0; j < otherWordStr.length; j++) {
98
- if (thisWord[i] === otherWordStr[j]) {
99
- intersections.push({
100
- thisIndex: i,
101
- otherIndex: j,
102
- letter: thisWord[i]
103
- });
104
- }
105
- }
106
- }
107
-
108
- return intersections;
109
- }
110
- }
111
-
112
- module.exports = Word;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
crossword-app/backend/src/routes/api.js DELETED
@@ -1,82 +0,0 @@
1
- const express = require('express');
2
- const PuzzleController = require('../controllers/puzzleController');
3
- const EmbeddingWordService = require('../services/embeddingWordService');
4
-
5
- const router = express.Router();
6
-
7
- router.get('/topics', PuzzleController.getTopics);
8
-
9
- router.post('/generate', PuzzleController.generatePuzzle);
10
-
11
- router.post('/validate', PuzzleController.validateAnswers);
12
-
13
- router.get('/words/:topic', PuzzleController.getWords);
14
-
15
- router.get('/health', (req, res) => {
16
- res.json({
17
- status: 'OK',
18
- timestamp: new Date().toISOString(),
19
- service: 'Crossword API'
20
- });
21
- });
22
-
23
- // AI/Embedding service endpoints
24
- router.get('/ai/status', async (req, res) => {
25
- try {
26
- const stats = EmbeddingWordService.getCacheStats();
27
- res.json({
28
- status: 'OK',
29
- aiService: {
30
- initialized: stats.isInitialized,
31
- fallbackEnabled: stats.fallbackEnabled,
32
- cacheStats: {
33
- embeddingCache: stats.embeddingCacheSize,
34
- wordCache: stats.wordCacheSize
35
- }
36
- },
37
- timestamp: new Date().toISOString()
38
- });
39
- } catch (error) {
40
- res.status(500).json({
41
- status: 'ERROR',
42
- error: error.message,
43
- timestamp: new Date().toISOString()
44
- });
45
- }
46
- });
47
-
48
- router.post('/ai/generate-words', async (req, res) => {
49
- try {
50
- const { topics, difficulty = 'medium', count = 12 } = req.body;
51
-
52
- if (!topics || !Array.isArray(topics) || topics.length === 0) {
53
- return res.status(400).json({
54
- error: 'Topics array is required',
55
- example: { topics: ['animals', 'science'], difficulty: 'medium', count: 12 }
56
- });
57
- }
58
-
59
- const words = await EmbeddingWordService.generateWordsForTopics(topics, difficulty, count);
60
-
61
- res.json({
62
- success: true,
63
- topics,
64
- difficulty,
65
- requestedCount: count,
66
- generatedCount: words.length,
67
- words: words,
68
- timestamp: new Date().toISOString(),
69
- aiGenerated: EmbeddingWordService.getCacheStats().isInitialized
70
- });
71
-
72
- } catch (error) {
73
- console.error('Error generating AI words:', error);
74
- res.status(500).json({
75
- error: 'Failed to generate words',
76
- message: error.message,
77
- timestamp: new Date().toISOString()
78
- });
79
- }
80
- });
81
-
82
- module.exports = router;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
crossword-app/backend/src/services/crosswordGenerator.js DELETED
@@ -1,982 +0,0 @@
1
- const WordService = require('./wordService');
2
- const EmbeddingWordService = require('./embeddingWordService');
3
-
4
- class CrosswordGenerator {
5
- constructor() {
6
- this.maxAttempts = 100;
7
- this.minWords = 6;
8
- this.maxWords = 12;
9
- this.useAI = process.env.USE_AI_WORDS === 'true';
10
- }
11
-
12
- async generatePuzzle(topics, difficulty = 'medium', options = {}) {
13
- try {
14
- const useAI = options.useAI !== undefined ? options.useAI : this.useAI;
15
-
16
- const words = await this.selectWords(topics, difficulty, useAI);
17
-
18
- // Log word->clue mapping for debugging
19
- this.logWordClueMapping(words, useAI, topics);
20
-
21
- if (words.length < this.minWords) {
22
- console.error(`❌ Not enough words: ${words.length} < ${this.minWords}`);
23
- throw new Error('Not enough words available for selected topics');
24
- }
25
-
26
- const gridResult = this.createGrid(words);
27
-
28
- if (!gridResult) {
29
- console.error(`❌ Grid creation failed - could not place words`);
30
- throw new Error('Could not place words in grid');
31
- }
32
-
33
- const clues = this.generateClues(words, gridResult.placedWords);
34
-
35
- return {
36
- grid: gridResult.grid,
37
- clues: clues,
38
- metadata: {
39
- topics,
40
- difficulty,
41
- wordCount: words.length,
42
- size: gridResult.size,
43
- aiGenerated: useAI && EmbeddingWordService.getCacheStats().isInitialized
44
- }
45
- };
46
- } catch (error) {
47
- console.error('❌ Error generating puzzle:', error);
48
- return null;
49
- }
50
- }
51
-
52
- async selectWords(topics, difficulty, useAI = false) {
53
- try {
54
- if (useAI) {
55
- console.log(`🤖 Using AI-powered word generation for topics: ${topics.join(', ')}`);
56
- const aiWords = await EmbeddingWordService.generateWordsForTopics(topics, difficulty, this.maxWords);
57
-
58
- if (aiWords.length >= this.minWords) {
59
- console.log(`✅ AI generated ${aiWords.length} words successfully`);
60
- return aiWords;
61
- } else {
62
- console.log(`⚠️ AI only generated ${aiWords.length} words, falling back to static`);
63
- }
64
- }
65
-
66
- // Fallback to static word selection
67
- console.log(`📚 Using static word selection for topics: ${topics.join(', ')}`);
68
- const allWords = [];
69
-
70
- for (const topic of topics) {
71
- const topicWords = await WordService.getWordsByTopic(topic);
72
- allWords.push(...topicWords);
73
- }
74
-
75
- const filteredWords = this.filterWordsByDifficulty(allWords, difficulty);
76
-
77
- // Sort words to prioritize those with good intersection potential
78
- const sortedWords = this.sortWordsForCrossword(filteredWords);
79
-
80
- return sortedWords.slice(0, this.maxWords);
81
-
82
- } catch (error) {
83
- console.error('❌ Error in word selection:', error.message);
84
- // Final fallback to basic static selection
85
- return await this.getBasicStaticWords(topics, difficulty);
86
- }
87
- }
88
-
89
- async getBasicStaticWords(topics, difficulty) {
90
- try {
91
- const allWords = [];
92
- for (const topic of topics) {
93
- const topicWords = await WordService.getWordsByTopic(topic);
94
- allWords.push(...topicWords.slice(0, 20)); // Limit per topic
95
- }
96
- const filtered = this.filterWordsByDifficulty(allWords, difficulty);
97
- return filtered.slice(0, this.maxWords);
98
- } catch (error) {
99
- console.error('❌ Even basic word selection failed:', error);
100
- return [];
101
- }
102
- }
103
-
104
- sortWordsForCrossword(words) {
105
- // Score words based on their crossword potential
106
- const scoredWords = words.map(wordObj => {
107
- const word = wordObj.word;
108
- let score = 0;
109
-
110
- // Moderate preference for medium-length words (5-7 chars optimal)
111
- if (word.length >= 5 && word.length <= 7) {
112
- score += 10; // Good base score for medium words
113
- } else if (word.length >= 3 && word.length <= 4) {
114
- score += 5; // Short words get some points
115
- } else if (word.length >= 8) {
116
- score += 3; // Long words get fewer points
117
- }
118
-
119
- // Words with common letters get bonus points
120
- const commonLetters = ['E', 'A', 'R', 'I', 'O', 'T', 'N', 'S'];
121
- for (const letter of word) {
122
- if (commonLetters.includes(letter)) {
123
- score += 1;
124
- }
125
- }
126
-
127
- // Vowel distribution bonus
128
- const vowels = ['A', 'E', 'I', 'O', 'U'];
129
- const vowelCount = word.split('').filter(letter => vowels.includes(letter)).length;
130
- score += vowelCount;
131
-
132
- return { ...wordObj, crosswordScore: score };
133
- });
134
-
135
- // Sort by score (descending) then shuffle within score groups
136
- scoredWords.sort((a, b) => {
137
- if (Math.abs(a.crosswordScore - b.crosswordScore) <= 3) {
138
- return Math.random() - 0.5; // Shuffle similar scores
139
- }
140
- return b.crosswordScore - a.crosswordScore;
141
- });
142
-
143
- return scoredWords;
144
- }
145
-
146
- filterWordsByDifficulty(words, difficulty) {
147
- const difficultyMap = {
148
- easy: { minLen: 3, maxLen: 8 },
149
- medium: { minLen: 4, maxLen: 10 },
150
- hard: { minLen: 5, maxLen: 15 }
151
- };
152
-
153
- const { minLen, maxLen } = difficultyMap[difficulty] || difficultyMap.medium;
154
-
155
- return words.filter(word =>
156
- word.word.length >= minLen && word.word.length <= maxLen
157
- );
158
- }
159
-
160
- createGrid(words) {
161
- if (!words || words.length === 0) {
162
- console.error(`❌ No words provided to createGrid`);
163
- return null;
164
- }
165
-
166
- const wordList = words.map(w => w.word.toUpperCase()).sort((a, b) => b.length - a.length);
167
- const size = this.calculateGridSize(wordList);
168
-
169
- // Try with different grid sizes and word counts
170
- for (let attempt = 0; attempt < 3; attempt++) {
171
- const currentSize = size + attempt;
172
-
173
- // First try with all words
174
- let result = this.placeWordsInGrid(wordList, currentSize);
175
- if (result) {
176
- return { grid: result.grid, size: currentSize, placedWords: result.placedWords };
177
- }
178
-
179
- // If that fails, try with 1-2 fewer words (but not too aggressive)
180
- if (wordList.length > 7) {
181
- const reducedWords = wordList.slice(0, wordList.length - 1);
182
- result = this.placeWordsInGrid(reducedWords, currentSize);
183
- if (result) {
184
- return { grid: result.grid, size: currentSize, placedWords: result.placedWords };
185
- }
186
- }
187
- }
188
-
189
- // Only as last resort, try with minimum words
190
- for (let attempt = 0; attempt < 2; attempt++) {
191
- const currentSize = size + attempt;
192
- if (wordList.length >= 6) {
193
- const minWords = wordList.slice(0, 6);
194
- const result = this.placeWordsInGrid(minWords, currentSize);
195
- if (result) {
196
- return { grid: result.grid, size: currentSize, placedWords: result.placedWords };
197
- }
198
- }
199
- }
200
-
201
- // Absolute last resort: simple cross pattern with just 2 words
202
- if (wordList.length >= 2) {
203
- const result = this.createSimpleCross(wordList.slice(0, 2));
204
- if (result) {
205
- return result;
206
- }
207
- }
208
-
209
- console.error(`❌ All grid creation attempts failed`);
210
- return null;
211
- }
212
-
213
- calculateGridSize(words) {
214
- const totalChars = words.reduce((sum, word) => sum + word.length, 0);
215
- const longestWord = Math.max(...words.map(word => word.length));
216
- return Math.max(
217
- Math.ceil(Math.sqrt(totalChars * 1.5)),
218
- longestWord,
219
- 8
220
- );
221
- }
222
-
223
- trimGrid(grid, placedWords) {
224
- if (!placedWords || placedWords.length === 0) return grid;
225
-
226
- // Find the bounds of the actual puzzle
227
- let minRow = grid.length, maxRow = -1;
228
- let minCol = grid[0].length, maxCol = -1;
229
-
230
- placedWords.forEach(word => {
231
- const { row, col, direction } = word;
232
- const wordLength = word.word.length;
233
-
234
- minRow = Math.min(minRow, row);
235
- minCol = Math.min(minCol, col);
236
-
237
- if (direction === 'horizontal') {
238
- maxRow = Math.max(maxRow, row);
239
- maxCol = Math.max(maxCol, col + wordLength - 1);
240
- } else {
241
- maxRow = Math.max(maxRow, row + wordLength - 1);
242
- maxCol = Math.max(maxCol, col);
243
- }
244
- });
245
-
246
- // Add small padding
247
- minRow = Math.max(0, minRow - 1);
248
- minCol = Math.max(0, minCol - 1);
249
- maxRow = Math.min(grid.length - 1, maxRow + 1);
250
- maxCol = Math.min(grid[0].length - 1, maxCol + 1);
251
-
252
- // Create trimmed grid
253
- const trimmedGrid = [];
254
- for (let r = minRow; r <= maxRow; r++) {
255
- const row = [];
256
- for (let c = minCol; c <= maxCol; c++) {
257
- row.push(grid[r][c]);
258
- }
259
- trimmedGrid.push(row);
260
- }
261
-
262
- // Update placed words positions to match trimmed grid
263
- const updatedPlacedWords = placedWords.map(word => ({
264
- ...word,
265
- row: word.row - minRow,
266
- col: word.col - minCol
267
- }));
268
-
269
- return { grid: trimmedGrid, placedWords: updatedPlacedWords };
270
- }
271
-
272
- placeWordsInGrid(words, size) {
273
- const grid = Array(size).fill().map(() => Array(size).fill('.'));
274
- const placedWords = [];
275
-
276
- const startTime = Date.now();
277
- const timeout = 5000; // Reduced to 5 second timeout
278
-
279
- if (this.backtrackPlacement(grid, words, 0, placedWords, startTime, timeout)) {
280
- const trimmed = this.trimGrid(grid, placedWords);
281
- return { grid: trimmed.grid, placedWords: trimmed.placedWords };
282
- }
283
-
284
- return null;
285
- }
286
-
287
- backtrackPlacement(grid, words, wordIndex, placedWords, startTime = Date.now(), timeout = 5000, callCount = 0) {
288
- // Check timeout more frequently
289
- if (callCount % 50 === 0 && Date.now() - startTime > timeout) {
290
- console.error(`⏰ Placement timeout reached after ${Date.now() - startTime}ms`);
291
- return false;
292
- }
293
-
294
- if (wordIndex >= words.length) {
295
- return true;
296
- }
297
-
298
- const word = words[wordIndex];
299
- const size = grid.length;
300
-
301
- // For the first word, place the longest word in the center horizontally
302
- if (wordIndex === 0) {
303
- const centerRow = Math.floor(size / 2);
304
- const centerCol = Math.floor((size - word.length) / 2);
305
-
306
- if (this.canPlaceWord(grid, word, centerRow, centerCol, 'horizontal')) {
307
- const originalState = this.placeWord(grid, word, centerRow, centerCol, 'horizontal');
308
- placedWords.push({ word, row: centerRow, col: centerCol, direction: 'horizontal', number: 1 });
309
-
310
- if (this.backtrackPlacement(grid, words, wordIndex + 1, placedWords, startTime, timeout, callCount + 1)) {
311
- return true;
312
- }
313
-
314
- this.removeWord(grid, originalState);
315
- placedWords.pop();
316
- }
317
- return false;
318
- }
319
-
320
- // For the second word, try to create a central cross with smart selection
321
- if (wordIndex === 1) {
322
- const firstWord = placedWords[0];
323
-
324
- // Try all remaining words to find one that can intersect
325
- const remainingWords = words.slice(1); // All words except the first
326
- const wordsWithIntersections = [];
327
-
328
- // Score each remaining word by intersection potential
329
- for (const candidateWord of remainingWords) {
330
- const intersections = this.findWordIntersections(candidateWord, firstWord.word);
331
- if (intersections.length > 0) {
332
- wordsWithIntersections.push({
333
- word: candidateWord,
334
- intersectionCount: intersections.length,
335
- intersections: intersections
336
- });
337
- }
338
- }
339
-
340
- // Sort by intersection count (more intersections = better)
341
- wordsWithIntersections.sort((a, b) => b.intersectionCount - a.intersectionCount);
342
-
343
- // Try each word with intersections
344
- for (const candidateInfo of wordsWithIntersections) {
345
- const candidateWord = candidateInfo.word;
346
-
347
- for (const intersection of candidateInfo.intersections) {
348
- const placement = this.calculateIntersectionPlacement(candidateWord, intersection.wordPos, firstWord, intersection.placedPos);
349
-
350
- if (placement && this.canPlaceWord(grid, candidateWord, placement.row, placement.col, placement.direction)) {
351
- const originalState = this.placeWord(grid, candidateWord, placement.row, placement.col, placement.direction);
352
- placedWords.push({ word: candidateWord, ...placement, number: 2 });
353
-
354
- // Create new word order with the selected second word
355
- const newWordOrder = [words[0], candidateWord, ...words.slice(1).filter(w => w !== candidateWord)];
356
-
357
- if (this.backtrackPlacement(grid, newWordOrder, wordIndex + 1, placedWords, startTime, timeout, callCount + 1)) {
358
- return true;
359
- }
360
-
361
- this.removeWord(grid, originalState);
362
- placedWords.pop();
363
- }
364
- }
365
- }
366
-
367
- return false;
368
- }
369
-
370
- // For subsequent words, find the best intersection opportunities
371
- const allPlacements = this.findAllIntersectionPlacements(grid, word, placedWords);
372
-
373
- // Sort by intersection quality score
374
- allPlacements.sort((a, b) => b.score - a.score);
375
-
376
- // Try the best placements first
377
- for (const placement of allPlacements) {
378
- const { row, col, direction } = placement;
379
-
380
- if (this.canPlaceWord(grid, word, row, col, direction)) {
381
- const originalState = this.placeWord(grid, word, row, col, direction);
382
- placedWords.push({ word, row, col, direction, number: wordIndex + 1 });
383
-
384
- if (this.backtrackPlacement(grid, words, wordIndex + 1, placedWords, startTime, timeout, callCount + 1)) {
385
- return true;
386
- }
387
-
388
- this.removeWord(grid, originalState);
389
- placedWords.pop();
390
- }
391
- }
392
-
393
- return false;
394
- }
395
-
396
- canPlaceWord(grid, word, row, col, direction) {
397
- const size = grid.length;
398
-
399
- // Check boundaries first
400
- if (row < 0 || col < 0 || row >= size || col >= size) {
401
- return false;
402
- }
403
-
404
- if (direction === 'horizontal') {
405
- if (col + word.length > size) return false;
406
-
407
- for (let i = 0; i < word.length; i++) {
408
- if (row >= size || col + i >= size || !grid[row] || grid[row][col + i] === undefined) {
409
- return false;
410
- }
411
- const currentCell = grid[row][col + i];
412
- if (currentCell !== '.' && currentCell !== word[i]) {
413
- return false;
414
- }
415
- }
416
- } else {
417
- if (row + word.length > size) return false;
418
-
419
- for (let i = 0; i < word.length; i++) {
420
- if (row + i >= size || col >= size || !grid[row + i] || grid[row + i][col] === undefined) {
421
- return false;
422
- }
423
- const currentCell = grid[row + i][col];
424
- if (currentCell !== '.' && currentCell !== word[i]) {
425
- return false;
426
- }
427
- }
428
- }
429
-
430
- return this.validateWordPlacement(grid, word, row, col, direction);
431
- }
432
-
433
- validateWordPlacement(grid, word, row, col, direction) {
434
- const size = grid.length;
435
-
436
- if (direction === 'horizontal') {
437
- // CRITICAL: Check word boundaries - no letters immediately before/after
438
- if (col > 0 && grid[row][col - 1] !== '.') {
439
- return false; // Word would have a preceding letter
440
- }
441
- if (col + word.length < size && grid[row][col + word.length] !== '.') {
442
- return false; // Word would have a trailing letter
443
- }
444
-
445
- // Check each letter position
446
- for (let i = 0; i < word.length; i++) {
447
- const currentCol = col + i;
448
- const currentCell = grid[row][currentCol];
449
-
450
- // If cell is occupied, it must match the letter we're placing
451
- if (currentCell !== '.' && currentCell !== word[i]) {
452
- return false;
453
- }
454
-
455
- // For empty cells, check perpendicular validity
456
- if (currentCell === '.') {
457
- // Check that placing this letter won't create invalid perpendicular words
458
- if (!this.isValidPerpendicularPlacement(grid, word[i], row, currentCol, 'vertical')) {
459
- return false;
460
- }
461
- }
462
- }
463
- } else { // vertical
464
- // CRITICAL: Check word boundaries - no letters immediately before/after
465
- if (row > 0 && grid[row - 1][col] !== '.') {
466
- return false; // Word would have a preceding letter
467
- }
468
- if (row + word.length < size && grid[row + word.length][col] !== '.') {
469
- return false; // Word would have a trailing letter
470
- }
471
-
472
- // Check each letter position
473
- for (let i = 0; i < word.length; i++) {
474
- const currentRow = row + i;
475
- const currentCell = grid[currentRow][col];
476
-
477
- // If cell is occupied, it must match the letter we're placing
478
- if (currentCell !== '.' && currentCell !== word[i]) {
479
- return false;
480
- }
481
-
482
- // For empty cells, check perpendicular validity
483
- if (currentCell === '.') {
484
- // Check that placing this letter won't create invalid perpendicular words
485
- if (!this.isValidPerpendicularPlacement(grid, word[i], currentRow, col, 'horizontal')) {
486
- return false;
487
- }
488
- }
489
- }
490
- }
491
-
492
- return true;
493
- }
494
-
495
- placeWord(grid, word, row, col, direction) {
496
- const originalState = [];
497
-
498
- if (direction === 'horizontal') {
499
- for (let i = 0; i < word.length; i++) {
500
- originalState.push({
501
- row: row,
502
- col: col + i,
503
- value: grid[row][col + i]
504
- });
505
- grid[row][col + i] = word[i];
506
- }
507
- } else {
508
- for (let i = 0; i < word.length; i++) {
509
- originalState.push({
510
- row: row + i,
511
- col: col,
512
- value: grid[row + i][col]
513
- });
514
- grid[row + i][col] = word[i];
515
- }
516
- }
517
-
518
- return originalState;
519
- }
520
-
521
- removeWord(grid, originalState) {
522
- originalState.forEach(state => {
523
- grid[state.row][state.col] = state.value;
524
- });
525
- }
526
-
527
- generateClues(words, placedWords) {
528
- return placedWords.map((placedWord, index) => {
529
- // Find the word object that matches this placed word
530
- const wordObj = words.find(w => w.word.toUpperCase() === placedWord.word);
531
-
532
- return {
533
- number: index + 1,
534
- word: placedWord.word,
535
- text: wordObj ? (wordObj.clue || `Clue for ${placedWord.word}`) : `Clue for ${placedWord.word}`,
536
- direction: placedWord.direction === 'horizontal' ? 'across' : 'down',
537
- position: { row: placedWord.row, col: placedWord.col }
538
- };
539
- });
540
- }
541
-
542
- static validateAnswers(puzzle, userAnswers) {
543
- const correct = {};
544
- const incorrect = {};
545
- let totalCorrect = 0;
546
- let totalCells = 0;
547
-
548
- puzzle.grid.forEach((row, rowIndex) => {
549
- row.forEach((cell, colIndex) => {
550
- if (cell !== '.') {
551
- totalCells++;
552
- const key = `${rowIndex}-${colIndex}`;
553
- const userAnswer = userAnswers[key];
554
-
555
- if (userAnswer === cell) {
556
- correct[key] = true;
557
- totalCorrect++;
558
- } else if (userAnswer) {
559
- incorrect[key] = true;
560
- }
561
- }
562
- });
563
- });
564
-
565
- return {
566
- correct,
567
- incorrect,
568
- isComplete: totalCorrect === totalCells,
569
- progress: totalCells > 0 ? (totalCorrect / totalCells) * 100 : 0
570
- };
571
- }
572
-
573
- findAllIntersectionPlacements(grid, word, placedWords) {
574
- const placements = [];
575
-
576
- // For each placed word, find potential intersections
577
- for (const placedWord of placedWords) {
578
- const intersections = this.findWordIntersections(word, placedWord.word);
579
-
580
- for (const intersection of intersections) {
581
- const { wordPos, placedPos } = intersection;
582
-
583
- // Calculate where the new word would be placed
584
- const placementInfo = this.calculateIntersectionPlacement(
585
- word, wordPos, placedWord, placedPos
586
- );
587
-
588
- if (placementInfo) {
589
- // Calculate score for this placement
590
- const score = this.calculatePlacementScore(grid, word, placementInfo, placedWords);
591
-
592
- placements.push({
593
- ...placementInfo,
594
- score,
595
- intersectingWord: placedWord.word,
596
- intersectionPoint: { wordPos, placedPos }
597
- });
598
- }
599
- }
600
- }
601
-
602
- return placements;
603
- }
604
-
605
- calculatePlacementScore(grid, word, placement, placedWords) {
606
- const { row, col, direction } = placement;
607
- let score = 0;
608
-
609
- // Base score for creating an intersection
610
- score += 100;
611
-
612
- // Bonus for multiple intersections with the same word
613
- let intersectionCount = 0;
614
-
615
- if (direction === 'horizontal') {
616
- for (let i = 0; i < word.length; i++) {
617
- const cellRow = row;
618
- const cellCol = col + i;
619
-
620
- if (cellCol >= 0 && cellCol < grid.length && grid[cellRow] && grid[cellRow][cellCol] === word[i]) {
621
- intersectionCount++;
622
- }
623
- }
624
- } else {
625
- for (let i = 0; i < word.length; i++) {
626
- const cellRow = row + i;
627
- const cellCol = col;
628
-
629
- if (cellRow >= 0 && cellRow < grid.length && grid[cellRow] && grid[cellRow][cellCol] === word[i]) {
630
- intersectionCount++;
631
- }
632
- }
633
- }
634
-
635
- // Multiple intersections are MUCH better
636
- score += intersectionCount * 200;
637
-
638
- // Bonus for placing near the center
639
- const center = Math.floor(grid.length / 2);
640
- const distanceFromCenter = Math.abs(row - center) + Math.abs(col - center);
641
- score -= distanceFromCenter * 5;
642
-
643
- // Bonus for connecting to multiple existing words
644
- const adjacentWords = this.countAdjacentWords(grid, word, row, col, direction, placedWords);
645
- score += adjacentWords * 50;
646
-
647
- // Prefer compact layouts - bonus for being close to existing words
648
- const proximityBonus = this.calculateProximityBonus(grid, row, col, direction, word.length);
649
- score += proximityBonus;
650
-
651
- return score;
652
- }
653
-
654
- countAdjacentWords(grid, word, row, col, direction, placedWords) {
655
- let adjacentCount = 0;
656
- const size = grid.length;
657
-
658
- if (direction === 'horizontal') {
659
- for (let i = 0; i < word.length; i++) {
660
- const cellRow = row;
661
- const cellCol = col + i;
662
-
663
- // Check boundaries before accessing array
664
- if (cellRow < 0 || cellRow >= size || cellCol < 0 || cellCol >= size) continue;
665
-
666
- // Check above and below
667
- if (cellRow > 0 && grid[cellRow - 1] && grid[cellRow - 1][cellCol] && grid[cellRow - 1][cellCol] !== '.') {
668
- adjacentCount++;
669
- }
670
- if (cellRow < size - 1 && grid[cellRow + 1] && grid[cellRow + 1][cellCol] && grid[cellRow + 1][cellCol] !== '.') {
671
- adjacentCount++;
672
- }
673
- }
674
- } else {
675
- for (let i = 0; i < word.length; i++) {
676
- const cellRow = row + i;
677
- const cellCol = col;
678
-
679
- // Check boundaries before accessing array
680
- if (cellRow < 0 || cellRow >= size || cellCol < 0 || cellCol >= size) continue;
681
-
682
- // Check left and right
683
- if (cellCol > 0 && grid[cellRow] && grid[cellRow][cellCol - 1] && grid[cellRow][cellCol - 1] !== '.') {
684
- adjacentCount++;
685
- }
686
- if (cellCol < size - 1 && grid[cellRow] && grid[cellRow][cellCol + 1] && grid[cellRow][cellCol + 1] !== '.') {
687
- adjacentCount++;
688
- }
689
- }
690
- }
691
-
692
- return adjacentCount;
693
- }
694
-
695
- calculateProximityBonus(grid, row, col, direction, wordLength) {
696
- let cellsNearOtherWords = 0;
697
- const size = grid.length;
698
-
699
- if (direction === 'horizontal') {
700
- for (let i = 0; i < wordLength; i++) {
701
- const cellCol = col + i;
702
-
703
- // Check surrounding cells for letters
704
- for (let dr = -1; dr <= 1; dr++) {
705
- for (let dc = -1; dc <= 1; dc++) {
706
- const checkRow = row + dr;
707
- const checkCol = cellCol + dc;
708
-
709
- if (checkRow >= 0 && checkRow < size &&
710
- checkCol >= 0 && checkCol < size &&
711
- grid[checkRow] && grid[checkRow][checkCol] &&
712
- grid[checkRow][checkCol] !== '.') {
713
- cellsNearOtherWords++;
714
- }
715
- }
716
- }
717
- }
718
- } else {
719
- for (let i = 0; i < wordLength; i++) {
720
- const cellRow = row + i;
721
-
722
- // Check surrounding cells for letters
723
- for (let dr = -1; dr <= 1; dr++) {
724
- for (let dc = -1; dc <= 1; dc++) {
725
- const checkRow = cellRow + dr;
726
- const checkCol = col + dc;
727
-
728
- if (checkRow >= 0 && checkRow < size &&
729
- checkCol >= 0 && checkCol < size &&
730
- grid[checkRow] && grid[checkRow][checkCol] &&
731
- grid[checkRow][checkCol] !== '.') {
732
- cellsNearOtherWords++;
733
- }
734
- }
735
- }
736
- }
737
- }
738
-
739
- return cellsNearOtherWords * 10;
740
- }
741
-
742
- findIntersectionPlacements(grid, word, placedWords) {
743
- return this.findAllIntersectionPlacements(grid, word, placedWords);
744
- }
745
-
746
- findWordIntersections(word1, word2) {
747
- const intersections = [];
748
-
749
- for (let i = 0; i < word1.length; i++) {
750
- for (let j = 0; j < word2.length; j++) {
751
- if (word1[i] === word2[j]) {
752
- intersections.push({
753
- wordPos: i, // Position in the new word
754
- placedPos: j // Position in the already placed word
755
- });
756
- }
757
- }
758
- }
759
-
760
- return intersections;
761
- }
762
-
763
- calculateIntersectionPlacement(newWord, newWordPos, placedWord, placedWordPos) {
764
- const { row: placedRow, col: placedCol, direction: placedDirection } = placedWord;
765
-
766
- // Calculate the position of the intersecting letter in the grid
767
- let intersectionRow, intersectionCol;
768
-
769
- if (placedDirection === 'horizontal') {
770
- intersectionRow = placedRow;
771
- intersectionCol = placedCol + placedWordPos;
772
- } else {
773
- intersectionRow = placedRow + placedWordPos;
774
- intersectionCol = placedCol;
775
- }
776
-
777
- // Calculate where the new word should start
778
- const newDirection = placedDirection === 'horizontal' ? 'vertical' : 'horizontal';
779
-
780
- let newRow, newCol;
781
-
782
- if (newDirection === 'horizontal') {
783
- newRow = intersectionRow;
784
- newCol = intersectionCol - newWordPos;
785
- } else {
786
- newRow = intersectionRow - newWordPos;
787
- newCol = intersectionCol;
788
- }
789
-
790
- return {
791
- row: newRow,
792
- col: newCol,
793
- direction: newDirection
794
- };
795
- }
796
-
797
- findFallbackPlacements(grid, word, size) {
798
- const placements = [];
799
-
800
- // Generate some positions near the center
801
- const center = Math.floor(size / 2);
802
- const searchRadius = Math.min(3, Math.floor(size / 4));
803
-
804
- for (let r = center - searchRadius; r <= center + searchRadius; r++) {
805
- for (let c = center - searchRadius; c <= center + searchRadius; c++) {
806
- if (r >= 0 && c >= 0 && r < size && c < size) {
807
- for (const direction of ['horizontal', 'vertical']) {
808
- placements.push({ row: r, col: c, direction });
809
- }
810
- }
811
- }
812
- }
813
-
814
- return placements;
815
- }
816
-
817
- countIntersections(grid, word, row, col, direction) {
818
- let intersections = 0;
819
-
820
- if (direction === 'horizontal') {
821
- for (let i = 0; i < word.length; i++) {
822
- if (grid[row][col + i] === word[i]) {
823
- intersections++;
824
- }
825
- }
826
- } else {
827
- for (let i = 0; i < word.length; i++) {
828
- if (grid[row + i][col] === word[i]) {
829
- intersections++;
830
- }
831
- }
832
- }
833
-
834
- return intersections;
835
- }
836
-
837
- wouldConnect(grid, word, row, col, direction, placedWords) {
838
- if (placedWords.length === 0) return true; // First word always connects
839
-
840
- // Check if this word would intersect with any placed word
841
- if (direction === 'horizontal') {
842
- for (let i = 0; i < word.length; i++) {
843
- if (grid[row][col + i] !== '.') {
844
- return true; // Intersects with existing letter
845
- }
846
- // Check if adjacent to existing letters (forming a connection)
847
- if ((row > 0 && grid[row - 1][col + i] !== '.') ||
848
- (row < grid.length - 1 && grid[row + 1][col + i] !== '.')) {
849
- return true;
850
- }
851
- }
852
- } else {
853
- for (let i = 0; i < word.length; i++) {
854
- if (grid[row + i][col] !== '.') {
855
- return true; // Intersects with existing letter
856
- }
857
- // Check if adjacent to existing letters (forming a connection)
858
- if ((col > 0 && grid[row + i][col - 1] !== '.') ||
859
- (col < grid[0].length - 1 && grid[row + i][col + 1] !== '.')) {
860
- return true;
861
- }
862
- }
863
- }
864
-
865
- return false;
866
- }
867
-
868
- isValidPerpendicularPlacement(grid, letter, row, col, checkDirection) {
869
- const size = grid.length;
870
-
871
- if (checkDirection === 'vertical') {
872
- // Check if placing this letter would create an invalid vertical sequence
873
- let hasAbove = row > 0 && grid[row - 1] && grid[row - 1][col] !== '.';
874
- let hasBelow = row < size - 1 && grid[row + 1] && grid[row + 1][col] !== '.';
875
-
876
- // STRICT: Don't allow this letter to be placed if it would extend an existing vertical word
877
- // unless it's exactly at an intersection point with matching letters
878
- if (hasAbove || hasBelow) {
879
- // Only allow if this letter is exactly matching existing intersections
880
- return grid[row][col] === letter;
881
- }
882
- } else { // horizontal
883
- // Check if placing this letter would create an invalid horizontal sequence
884
- let hasLeft = col > 0 && grid[row] && grid[row][col - 1] !== '.';
885
- let hasRight = col < size - 1 && grid[row] && grid[row][col + 1] !== '.';
886
-
887
- // STRICT: Don't allow this letter to be placed if it would extend an existing horizontal word
888
- // unless it's exactly at an intersection point with matching letters
889
- if (hasLeft || hasRight) {
890
- // Only allow if this letter is exactly matching existing intersections
891
- return grid[row][col] === letter;
892
- }
893
- }
894
-
895
- return true;
896
- }
897
-
898
- isValidCrossing(grid, letter, row, col) {
899
- // This method should only be called for exact intersections
900
- // where the letter already exists in the grid
901
- return grid[row] && grid[row][col] === letter;
902
- }
903
-
904
- createSimpleCross(words) {
905
- // Find the best intersection between the two words
906
- const word1 = words[0];
907
- const word2 = words[1];
908
- const intersections = this.findWordIntersections(word1, word2);
909
-
910
- if (intersections.length === 0) {
911
- console.error(`❌ No intersections found between ${word1} and ${word2}`);
912
- return null;
913
- }
914
-
915
- // Use the first intersection
916
- const intersection = intersections[0];
917
- const size = Math.max(word1.length, word2.length) + 4;
918
- const grid = Array(size).fill().map(() => Array(size).fill('.'));
919
-
920
- // Place first word horizontally in center
921
- const centerRow = Math.floor(size / 2);
922
- const centerCol = Math.floor((size - word1.length) / 2);
923
-
924
- for (let i = 0; i < word1.length; i++) {
925
- grid[centerRow][centerCol + i] = word1[i];
926
- }
927
-
928
- // Place second word vertically at intersection
929
- const intersectionCol = centerCol + intersection.wordPos;
930
- const word2StartRow = centerRow - intersection.placedPos;
931
-
932
- for (let i = 0; i < word2.length; i++) {
933
- grid[word2StartRow + i][intersectionCol] = word2[i];
934
- }
935
-
936
- const placedWords = [
937
- { word: word1, row: centerRow, col: centerCol, direction: 'horizontal', number: 1 },
938
- { word: word2, row: word2StartRow, col: intersectionCol, direction: 'vertical', number: 2 }
939
- ];
940
-
941
- const trimmed = this.trimGrid(grid, placedWords);
942
- return { grid: trimmed.grid, size: trimmed.grid.length, placedWords: trimmed.placedWords };
943
- }
944
-
945
- shuffleArray(array) {
946
- const shuffled = [...array];
947
- for (let i = shuffled.length - 1; i > 0; i--) {
948
- const j = Math.floor(Math.random() * (i + 1));
949
- [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
950
- }
951
- return shuffled;
952
- }
953
-
954
- logWordClueMapping(words, useAI, topics) {
955
- const source = useAI ? 'AI-generated' : 'Static';
956
- const topicsList = Array.isArray(topics) ? topics.join(', ') : topics;
957
- console.log(`\n📝 Word->Clue Mapping (${source}) for topics: ${topicsList}`);
958
- console.log('════════════════════════════════════════');
959
-
960
- // Create a formatted table of word->clue mappings
961
- const wordClueMap = {};
962
- words.forEach(wordObj => {
963
- const word = wordObj.word || wordObj;
964
- const clue = wordObj.clue || 'No clue available';
965
- wordClueMap[word] = clue;
966
- });
967
-
968
- // Sort by word length for better readability
969
- const sortedWords = Object.keys(wordClueMap).sort((a, b) => a.length - b.length);
970
-
971
- sortedWords.forEach((word, index) => {
972
- const clue = wordClueMap[word];
973
- const paddedWord = word.padEnd(15, ' ');
974
- console.log(`${(index + 1).toString().padStart(2, ' ')}. ${paddedWord} -> ${clue}`);
975
- });
976
-
977
- console.log('════════════════════════════════════════');
978
- console.log(`Total words: ${words.length}\n`);
979
- }
980
- }
981
-
982
- module.exports = CrosswordGenerator;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
crossword-app/backend/src/services/embeddingWordService.js DELETED
@@ -1,635 +0,0 @@
1
- const { HfInference } = require('@huggingface/inference');
2
- const WordService = require('./wordService');
3
-
4
- // Store original console methods
5
- const originalLog = console.log;
6
- const originalWarn = console.warn;
7
-
8
- // Helper to suppress HF verbose messages
9
- function suppressHFLogs(fn) {
10
- return async (...args) => {
11
- console.log = (...msgs) => {
12
- const msg = msgs.join(' ');
13
- if (!msg.includes('Defaulting to') && !msg.includes('Auto selected provider')) {
14
- originalLog(...msgs);
15
- }
16
- };
17
- console.warn = (...msgs) => {
18
- const msg = msgs.join(' ');
19
- if (!msg.includes('Defaulting to') && !msg.includes('Auto selected provider')) {
20
- originalWarn(...msgs);
21
- }
22
- };
23
-
24
- try {
25
- const result = await fn(...args);
26
- return result;
27
- } finally {
28
- console.log = originalLog;
29
- console.warn = originalWarn;
30
- }
31
- };
32
- }
33
-
34
- class EmbeddingWordService {
35
- constructor() {
36
- this.hf = null;
37
- this.isInitialized = false;
38
- this.embeddingCache = new Map();
39
- this.wordCache = new Map();
40
- this.fallbackToStatic = process.env.FALLBACK_TO_STATIC === 'true';
41
- this.maxWordsPerGeneration = parseInt(process.env.MAX_WORDS_PER_GENERATION) || 15;
42
- this.similarityThreshold = parseFloat(process.env.WORD_SIMILARITY_THRESHOLD) || 0.65;
43
- this.maxTopicWords = parseInt(process.env.MAX_TOPIC_WORDS) || 50;
44
-
45
- // console.log(`🔧 EmbeddingWordService initialized with fallback: ${this.fallbackToStatic}`);
46
-
47
- // Initialize HuggingFace client
48
- this.initializeHF();
49
- }
50
-
51
- async initializeHF() {
52
- try {
53
- const apiKey = process.env.HUGGINGFACE_API_KEY;
54
- if (!apiKey || apiKey === 'hf_xxxxxxxxxx') {
55
- console.warn('⚠️ HuggingFace API key not configured, falling back to static words');
56
- this.isInitialized = false;
57
- return;
58
- }
59
-
60
- // Configure HuggingFace client with minimal logging
61
- this.hf = new HfInference(apiKey, {
62
- use_cache: true,
63
- dont_load_model: false
64
- });
65
-
66
- // Test the connection with a simple embedding request
67
- await this.testConnection();
68
- this.isInitialized = true;
69
- console.log('✅ HuggingFace Embedding Service initialized successfully');
70
-
71
- } catch (error) {
72
- console.error('❌ Failed to initialize HuggingFace service:', error.message);
73
- this.isInitialized = false;
74
- }
75
- }
76
-
77
- async testConnection() {
78
- const testConnectionInternal = async () => {
79
- return await this.hf.featureExtraction({
80
- model: process.env.EMBEDDING_MODEL || 'sentence-transformers/all-MiniLM-L6-v2',
81
- inputs: 'test'
82
- });
83
- };
84
-
85
- try {
86
- const testEmbedding = await suppressHFLogs(testConnectionInternal)();
87
-
88
- if (!testEmbedding || testEmbedding.length === 0) {
89
- throw new Error('Empty embedding response');
90
- }
91
- console.log(`✅ HF Embedding test successful - vector dimension: ${testEmbedding.length}`);
92
- return true;
93
- } catch (error) {
94
- console.error('❌ HuggingFace connection test failed:', error.message);
95
- throw error;
96
- }
97
- }
98
-
99
- async getEmbedding(text) {
100
- if (!this.isInitialized || !this.hf) {
101
- throw new Error('HuggingFace service not initialized');
102
- }
103
-
104
- // Check cache first
105
- const cacheKey = text.toLowerCase().trim();
106
- if (this.embeddingCache.has(cacheKey)) {
107
- return this.embeddingCache.get(cacheKey);
108
- }
109
-
110
- const getEmbeddingInternal = async () => {
111
- return await this.hf.featureExtraction({
112
- model: process.env.EMBEDDING_MODEL || 'sentence-transformers/all-MiniLM-L6-v2',
113
- inputs: text
114
- });
115
- };
116
-
117
- try {
118
- const embedding = await suppressHFLogs(getEmbeddingInternal)();
119
-
120
- // Cache the embedding
121
- if (process.env.CACHE_EMBEDDINGS === 'true') {
122
- this.embeddingCache.set(cacheKey, embedding);
123
-
124
- // Limit cache size to prevent memory issues
125
- if (this.embeddingCache.size > 1000) {
126
- const firstKey = this.embeddingCache.keys().next().value;
127
- this.embeddingCache.delete(firstKey);
128
- }
129
- }
130
-
131
- return embedding;
132
-
133
- } catch (error) {
134
- console.error(`❌ Failed to get embedding for "${text}":`, error.message);
135
- throw error;
136
- }
137
- }
138
-
139
- calculateCosineSimilarity(vecA, vecB) {
140
- if (!vecA || !vecB || vecA.length !== vecB.length) {
141
- return 0;
142
- }
143
-
144
- let dotProduct = 0;
145
- let normA = 0;
146
- let normB = 0;
147
-
148
- for (let i = 0; i < vecA.length; i++) {
149
- dotProduct += vecA[i] * vecB[i];
150
- normA += vecA[i] * vecA[i];
151
- normB += vecB[i] * vecB[i];
152
- }
153
-
154
- if (normA === 0 || normB === 0) {
155
- return 0;
156
- }
157
-
158
- return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
159
- }
160
-
161
- async generateWordsForTopics(topics, difficulty = 'medium', targetCount = 12) {
162
- try {
163
- // If HF is not initialized, fall back to static words
164
- if (!this.isInitialized && this.fallbackToStatic) {
165
- console.log('🔄 Using static word fallback');
166
- return await this.getStaticWordsForTopics(topics, difficulty, targetCount);
167
- }
168
-
169
- if (!this.isInitialized) {
170
- throw new Error('HuggingFace service not available and fallback disabled');
171
- }
172
-
173
- console.log(`🔍 Generating words for topics: ${topics.join(', ')} (difficulty: ${difficulty})`);
174
-
175
- // Get topic embeddings
176
- const topicEmbeddings = await Promise.all(
177
- topics.map(async (topic) => ({
178
- topic,
179
- embedding: await this.getEmbedding(topic)
180
- }))
181
- );
182
-
183
- // Generate diverse word candidates
184
- const candidates = await this.generateWordCandidates(topicEmbeddings, difficulty);
185
-
186
- // Score and filter words for crossword suitability
187
- const scoredWords = this.scoreWordsForCrossword(candidates);
188
-
189
- // Select best words up to target count
190
- const selectedWords = scoredWords.slice(0, targetCount);
191
-
192
- // If we don't have enough words, supplement with static words
193
- if (selectedWords.length < targetCount && this.fallbackToStatic) {
194
- console.log(`🔄 Only found ${selectedWords.length} AI words, supplementing with static words`);
195
- const staticWords = await this.getStaticWordsForTopics(topics, difficulty, targetCount - selectedWords.length);
196
- selectedWords.push(...staticWords);
197
- }
198
-
199
- console.log(`✅ Generated ${selectedWords.length} words for crossword`);
200
- return selectedWords.slice(0, targetCount);
201
-
202
- } catch (error) {
203
- console.error('❌ Error generating words with embeddings:', error.message);
204
-
205
- if (this.fallbackToStatic) {
206
- console.log('🔄 Falling back to static words due to error');
207
- return await this.getStaticWordsForTopics(topics, difficulty, targetCount);
208
- }
209
-
210
- throw error;
211
- }
212
- }
213
-
214
- async generateWordCandidates(topicEmbeddings, difficulty) {
215
- const candidates = new Set();
216
-
217
- // TRUE AI GENERATION: Generate words directly using text generation
218
- for (const { topic } of topicEmbeddings) {
219
- const generatedWords = await this.generateWordsForTopic(topic, difficulty);
220
- generatedWords.forEach(wordObj => candidates.add(wordObj));
221
- }
222
-
223
- console.log(`🤖 Generated ${candidates.size} AI words for topics`);
224
-
225
- // Only if AI generation completely fails, fall back to static words
226
- if (candidates.size === 0) {
227
- console.log(`⚠️ AI generation failed, falling back to static words`);
228
- for (const { topic, embedding } of topicEmbeddings) {
229
- const staticWords = await this.findSimilarWords(topic, embedding, difficulty);
230
- staticWords.forEach(word => candidates.add({ word, clue: `Static clue for ${word}` }));
231
- }
232
- }
233
-
234
- return Array.from(candidates);
235
- }
236
-
237
- async generateWordsForTopic(topic, difficulty) {
238
- try {
239
- // Use comprehensive topic-specific word banks instead of LLM generation
240
- const topicWords = this.getTopicSpecificWords(topic, difficulty);
241
-
242
- if (topicWords.length === 0) {
243
- console.log(`⚠️ No words found for topic "${topic}", falling back to prompts`);
244
- // Fallback to LLM generation if needed
245
- return await this.generateWordsFromPrompts(topic, difficulty);
246
- }
247
-
248
- // Randomly select and shuffle words for variety
249
- const shuffled = this.shuffleArray(topicWords);
250
- const selected = shuffled.slice(0, Math.min(15, topicWords.length));
251
-
252
- console.log(`🎯 Generated ${selected.length} topic-specific words for "${topic}"`);
253
- return selected;
254
- } catch (error) {
255
- console.error(`❌ Failed to generate words for topic "${topic}":`, error.message);
256
- return [];
257
- }
258
- }
259
-
260
- getTopicSpecificWords(topic, difficulty) {
261
- // Comprehensive topic-specific word banks - TRUE GENERATION!
262
- const topicBanks = {
263
- 'Animals': {
264
- easy: [
265
- { word: 'CAT', clue: 'Feline pet' },
266
- { word: 'DOG', clue: 'Canine companion' },
267
- { word: 'BIRD', clue: 'Flying creature' },
268
- { word: 'FISH', clue: 'Swimming creature' },
269
- { word: 'BEAR', clue: 'Large forest mammal' },
270
- { word: 'LION', clue: 'King of jungle' },
271
- { word: 'WOLF', clue: 'Pack hunter' },
272
- { word: 'DEER', clue: 'Antlered mammal' },
273
- { word: 'FROG', clue: 'Pond jumper' },
274
- { word: 'SNAKE', clue: 'Slithering reptile' }
275
- ],
276
- medium: [
277
- { word: 'TIGER', clue: 'Striped big cat' },
278
- { word: 'WHALE', clue: 'Largest marine mammal' },
279
- { word: 'EAGLE', clue: 'Soaring predator' },
280
- { word: 'SHARK', clue: 'Ocean predator' },
281
- { word: 'ZEBRA', clue: 'Striped African animal' },
282
- { word: 'GIRAFFE', clue: 'Tallest mammal' },
283
- { word: 'ELEPHANT', clue: 'Largest land mammal' },
284
- { word: 'PENGUIN', clue: 'Antarctic bird' },
285
- { word: 'OCTOPUS', clue: 'Eight-armed sea creature' },
286
- { word: 'DOLPHIN', clue: 'Intelligent marine mammal' },
287
- { word: 'RABBIT', clue: 'Hopping mammal' },
288
- { word: 'TURTLE', clue: 'Shelled reptile' },
289
- { word: 'MONKEY', clue: 'Primate swinger' },
290
- { word: 'PARROT', clue: 'Colorful talking bird' }
291
- ],
292
- hard: [
293
- { word: 'RHINOCEROS', clue: 'Horned thick-skinned mammal' },
294
- { word: 'HIPPOPOTAMUS', clue: 'River horse' },
295
- { word: 'CHIMPANZEE', clue: 'Human-like primate' },
296
- { word: 'ORANGUTAN', clue: 'Red-haired ape' },
297
- { word: 'CROCODILE', clue: 'Large aquatic reptile' },
298
- { word: 'CHAMELEON', clue: 'Color-changing lizard' },
299
- { word: 'FLAMINGO', clue: 'Pink wading bird' },
300
- { word: 'KANGAROO', clue: 'Hopping marsupial' },
301
- { word: 'PLATYPUS', clue: 'Egg-laying mammal' }
302
- ]
303
- },
304
- 'Technology': {
305
- easy: [
306
- { word: 'PHONE', clue: 'Mobile device' },
307
- { word: 'MOUSE', clue: 'Computer pointer' },
308
- { word: 'SCREEN', clue: 'Display surface' },
309
- { word: 'CABLE', clue: 'Connecting wire' },
310
- { word: 'VIRUS', clue: 'Malicious software' },
311
- { word: 'EMAIL', clue: 'Digital message' },
312
- { word: 'WIFI', clue: 'Wireless internet' },
313
- { word: 'CHIP', clue: 'Computer processor' }
314
- ],
315
- medium: [
316
- { word: 'COMPUTER', clue: 'Electronic processor' },
317
- { word: 'KEYBOARD', clue: 'Input device with keys' },
318
- { word: 'MONITOR', clue: 'Computer display screen' },
319
- { word: 'SOFTWARE', clue: 'Computer programs' },
320
- { word: 'HARDWARE', clue: 'Physical components' },
321
- { word: 'DATABASE', clue: 'Organized data storage' },
322
- { word: 'NETWORK', clue: 'Connected systems' },
323
- { word: 'INTERNET', clue: 'Global network' },
324
- { word: 'BROWSER', clue: 'Web navigation tool' },
325
- { word: 'SERVER', clue: 'Data hosting computer' },
326
- { word: 'LAPTOP', clue: 'Portable computer' },
327
- { word: 'TABLET', clue: 'Touch screen device' },
328
- { word: 'ROUTER', clue: 'Network traffic director' },
329
- { word: 'PRINTER', clue: 'Document output device' }
330
- ],
331
- hard: [
332
- { word: 'ALGORITHM', clue: 'Problem-solving procedure' },
333
- { word: 'CYBERSECURITY', clue: 'Digital protection field' },
334
- { word: 'BLOCKCHAIN', clue: 'Distributed ledger technology' },
335
- { word: 'ARTIFICIAL', clue: 'Man-made intelligence type' },
336
- { word: 'PROGRAMMING', clue: 'Code writing process' },
337
- { word: 'ENCRYPTION', clue: 'Data scrambling method' },
338
- { word: 'SEMICONDUCTOR', clue: 'Electronic component material' }
339
- ]
340
- },
341
- 'Science': {
342
- easy: [
343
- { word: 'ATOM', clue: 'Smallest unit of matter' },
344
- { word: 'GENE', clue: 'Heredity unit' },
345
- { word: 'ACID', clue: 'Chemical solution' },
346
- { word: 'LENS', clue: 'Light focusing tool' },
347
- { word: 'WAVE', clue: 'Energy transmission' },
348
- { word: 'MOON', clue: 'Earth\'s satellite' },
349
- { word: 'STAR', clue: 'Celestial light source' }
350
- ],
351
- medium: [
352
- { word: 'MOLECULE', clue: 'Chemical compound unit' },
353
- { word: 'GRAVITY', clue: 'Attractive force' },
354
- { word: 'ELECTRON', clue: 'Negative particle' },
355
- { word: 'PROTEIN', clue: 'Complex biological molecule' },
356
- { word: 'CARBON', clue: 'Element in all life' },
357
- { word: 'OXYGEN', clue: 'Breathing gas' },
358
- { word: 'NEUTRON', clue: 'Neutral atomic particle' },
359
- { word: 'ENERGY', clue: 'Capacity to do work' },
360
- { word: 'GALAXY', clue: 'Star system collection' },
361
- { word: 'PLANET', clue: 'Orbiting celestial body' },
362
- { word: 'CRYSTAL', clue: 'Ordered solid structure' },
363
- { word: 'ENZYME', clue: 'Biological catalyst' }
364
- ],
365
- hard: [
366
- { word: 'PHOTOSYNTHESIS', clue: 'Plant energy conversion' },
367
- { word: 'CHROMOSOME', clue: 'DNA carrying structure' },
368
- { word: 'THERMODYNAMICS', clue: 'Heat and energy study' },
369
- { word: 'ELECTROMAGNETIC', clue: 'Electric and magnetic field' },
370
- { word: 'QUANTUM', clue: 'Smallest energy unit' },
371
- { word: 'BIOCHEMISTRY', clue: 'Chemical life processes' }
372
- ]
373
- },
374
- 'Geography': {
375
- easy: [
376
- { word: 'HILL', clue: 'Small elevation' },
377
- { word: 'LAKE', clue: 'Body of water' },
378
- { word: 'RIVER', clue: 'Flowing water' },
379
- { word: 'OCEAN', clue: 'Large sea' },
380
- { word: 'ISLAND', clue: 'Land surrounded by water' },
381
- { word: 'BEACH', clue: 'Sandy shore' },
382
- { word: 'FOREST', clue: 'Dense tree area' }
383
- ],
384
- medium: [
385
- { word: 'MOUNTAIN', clue: 'High elevation landform' },
386
- { word: 'VOLCANO', clue: 'Erupting mountain' },
387
- { word: 'DESERT', clue: 'Arid landscape' },
388
- { word: 'CANYON', clue: 'Deep valley' },
389
- { word: 'PLATEAU', clue: 'Elevated flatland' },
390
- { word: 'GLACIER', clue: 'Moving ice mass' },
391
- { word: 'PENINSULA', clue: 'Land jutting into water' },
392
- { word: 'CONTINENT', clue: 'Large landmass' },
393
- { word: 'ARCHIPELAGO', clue: 'Island chain' },
394
- { word: 'TUNDRA', clue: 'Arctic plains' },
395
- { word: 'SAVANNA', clue: 'Tropical grassland' },
396
- { word: 'ESTUARY', clue: 'River mouth' }
397
- ],
398
- hard: [
399
- { word: 'TOPOGRAPHY', clue: 'Land surface features' },
400
- { word: 'CARTOGRAPHY', clue: 'Map making science' },
401
- { word: 'PRECIPITATION', clue: 'Weather moisture' },
402
- { word: 'CONTINENTAL', clue: 'Large landmass related' },
403
- { word: 'ECOSYSTEM', clue: 'Environmental system' }
404
- ]
405
- }
406
- };
407
-
408
- const topicKey = Object.keys(topicBanks).find(key =>
409
- key.toLowerCase() === topic.toLowerCase()
410
- );
411
-
412
- if (!topicKey) {
413
- console.log(`⚠️ No word bank found for topic: ${topic}`);
414
- return [];
415
- }
416
-
417
- const difficultyWords = topicBanks[topicKey][difficulty] || topicBanks[topicKey]['medium'] || [];
418
- return difficultyWords;
419
- }
420
-
421
- async generateWordsFromPrompt(prompt, topic) {
422
- const generateTextInternal = async () => {
423
- return await this.hf.textGeneration({
424
- model: 'microsoft/DialoGPT-medium',
425
- inputs: prompt,
426
- parameters: {
427
- max_new_tokens: 150,
428
- temperature: 0.8,
429
- do_sample: true,
430
- repetition_penalty: 1.2
431
- }
432
- });
433
- };
434
-
435
- try {
436
- const response = await suppressHFLogs(generateTextInternal)();
437
- const generatedText = response.generated_text || '';
438
-
439
- // Extract words from the generated text
440
- const words = this.extractWordsFromGeneration(generatedText, topic);
441
- return words;
442
- } catch (error) {
443
- console.error(`❌ Text generation failed for prompt:`, error.message);
444
- return [];
445
- }
446
- }
447
-
448
- extractWordsFromGeneration(text, topic) {
449
- const words = [];
450
-
451
- // Extract potential words from the generated text
452
- const lines = text.split('\n');
453
- const wordPattern = /\b[A-Z]{3,15}\b/g;
454
-
455
- for (const line of lines) {
456
- const matches = line.match(wordPattern) || [];
457
- for (const word of matches) {
458
- if (this.isValidCrosswordWord(word)) {
459
- words.push({
460
- word: word.toUpperCase(),
461
- clue: this.generateSimpleClue(word, topic)
462
- });
463
- }
464
- }
465
- }
466
-
467
- // Remove duplicates and limit to reasonable number
468
- const uniqueWords = Array.from(new Set(words.map(w => w.word)))
469
- .slice(0, 10)
470
- .map(word => words.find(w => w.word === word));
471
-
472
- return uniqueWords;
473
- }
474
-
475
- isValidCrosswordWord(word) {
476
- // Check if word is suitable for crossword
477
- return (
478
- word.length >= 3 &&
479
- word.length <= 15 &&
480
- /^[A-Z]+$/.test(word) &&
481
- !['THE', 'AND', 'FOR', 'ARE', 'BUT', 'NOT', 'YOU', 'ALL'].includes(word)
482
- );
483
- }
484
-
485
- generateSimpleClue(word, topic) {
486
- // Generate basic clues based on word and topic
487
- const clueTemplates = {
488
- 'Animals': `${word.toLowerCase()} (animal)`,
489
- 'Technology': `${word.toLowerCase()} (tech term)`,
490
- 'Science': `${word.toLowerCase()} (scientific term)`,
491
- 'Geography': `${word.toLowerCase()} (geographic feature)`
492
- };
493
-
494
- return clueTemplates[topic] || `${word.toLowerCase()} (${topic.toLowerCase()})`;
495
- }
496
-
497
- async findSimilarWords(topic, topicEmbedding, difficulty) {
498
- const similarWords = [];
499
-
500
- // Get static words for this topic as candidates
501
- const staticWords = await WordService.getWordsByTopic(topic);
502
-
503
- for (const wordObj of staticWords.slice(0, this.maxTopicWords)) {
504
- try {
505
- const word = wordObj.word;
506
- const wordEmbedding = await this.getEmbedding(word);
507
- const similarity = this.calculateCosineSimilarity(topicEmbedding, wordEmbedding);
508
-
509
- if (similarity >= this.similarityThreshold) {
510
- similarWords.push({
511
- word: word,
512
- clue: wordObj.clue,
513
- similarity: similarity,
514
- topic: topic
515
- });
516
- }
517
- } catch (error) {
518
- // Skip words that fail embedding generation
519
- continue;
520
- }
521
- }
522
-
523
- // Sort by similarity and return more top words for better variety
524
- return similarWords
525
- .sort((a, b) => b.similarity - a.similarity)
526
- .slice(0, 20)
527
- .map(item => item.word);
528
- }
529
-
530
- scoreWordsForCrossword(words) {
531
- return words.map(wordItem => {
532
- let score = 0;
533
-
534
- // Handle both string words and word objects
535
- const wordObj = typeof wordItem === 'string' ? { word: wordItem, clue: `Generated clue for ${wordItem}` } : wordItem;
536
- const wordUpper = wordObj.word.toUpperCase();
537
-
538
- // Length scoring (prefer 4-8 character words)
539
- if (wordUpper.length >= 4 && wordUpper.length <= 8) {
540
- score += 10;
541
- } else if (wordUpper.length >= 3 && wordUpper.length <= 10) {
542
- score += 5;
543
- }
544
-
545
- // Common letters bonus
546
- const commonLetters = ['E', 'A', 'R', 'I', 'O', 'T', 'N', 'S'];
547
- for (const letter of wordUpper) {
548
- if (commonLetters.includes(letter)) {
549
- score += 1;
550
- }
551
- }
552
-
553
- // Vowel distribution
554
- const vowels = ['A', 'E', 'I', 'O', 'U'];
555
- const vowelCount = wordUpper.split('').filter(letter => vowels.includes(letter)).length;
556
- score += vowelCount * 2;
557
-
558
- // Avoid problematic characters
559
- if (!/^[A-Z]+$/.test(wordUpper)) {
560
- score -= 20; // Penalty for non-alphabetic characters
561
- }
562
-
563
- return {
564
- word: wordUpper,
565
- clue: wordObj.clue || `Generated clue for ${wordUpper}`,
566
- score: score
567
- };
568
- })
569
- .filter(item => item.score > 0) // Remove words with negative scores
570
- .sort((a, b) => b.score - a.score); // Sort by score descending
571
- }
572
-
573
- async getStaticWordsForTopics(topics, difficulty, targetCount) {
574
- try {
575
- const allWords = [];
576
-
577
- for (const topic of topics) {
578
- const topicWords = await WordService.getWordsByTopic(topic);
579
- allWords.push(...topicWords);
580
- }
581
-
582
- // Filter by difficulty
583
- const filteredWords = this.filterWordsByDifficulty(allWords, difficulty);
584
-
585
- // Shuffle and select target count
586
- const shuffled = this.shuffleArray(filteredWords);
587
- return shuffled.slice(0, targetCount);
588
-
589
- } catch (error) {
590
- console.error('❌ Error getting static words:', error.message);
591
- throw error;
592
- }
593
- }
594
-
595
- filterWordsByDifficulty(words, difficulty) {
596
- const difficultyMap = {
597
- easy: { minLen: 3, maxLen: 8 },
598
- medium: { minLen: 4, maxLen: 10 },
599
- hard: { minLen: 5, maxLen: 15 }
600
- };
601
-
602
- const { minLen, maxLen } = difficultyMap[difficulty] || difficultyMap.medium;
603
-
604
- return words.filter(word =>
605
- word.word.length >= minLen && word.word.length <= maxLen
606
- );
607
- }
608
-
609
- shuffleArray(array) {
610
- const shuffled = [...array];
611
- for (let i = shuffled.length - 1; i > 0; i--) {
612
- const j = Math.floor(Math.random() * (i + 1));
613
- [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
614
- }
615
- return shuffled;
616
- }
617
-
618
- // Utility methods for cache management
619
- clearCache() {
620
- this.embeddingCache.clear();
621
- this.wordCache.clear();
622
- console.log('🧹 Embedding and word caches cleared');
623
- }
624
-
625
- getCacheStats() {
626
- return {
627
- embeddingCacheSize: this.embeddingCache.size,
628
- wordCacheSize: this.wordCache.size,
629
- isInitialized: this.isInitialized,
630
- fallbackEnabled: this.fallbackToStatic
631
- };
632
- }
633
- }
634
-
635
- module.exports = new EmbeddingWordService();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
crossword-app/backend/src/services/wordService.js DELETED
@@ -1,131 +0,0 @@
1
- const fs = require('fs').promises;
2
- const path = require('path');
3
-
4
- class WordService {
5
- constructor() {
6
- this.wordsCache = new Map();
7
- this.topicsCache = null;
8
- }
9
-
10
- async getAllTopics() {
11
- if (this.topicsCache) {
12
- return this.topicsCache;
13
- }
14
-
15
- try {
16
- const dataDir = path.join(__dirname, '../../data/word-lists');
17
- const files = await fs.readdir(dataDir);
18
-
19
- const topics = files
20
- .filter(file => file.endsWith('.json'))
21
- .map(file => ({
22
- id: file.replace('.json', ''),
23
- name: this.formatTopicName(file.replace('.json', ''))
24
- }));
25
-
26
- this.topicsCache = topics;
27
- return topics;
28
- } catch (error) {
29
- console.error('Error loading topics:', error);
30
- return this.getDefaultTopics();
31
- }
32
- }
33
-
34
- async getWordsByTopic(topicName) {
35
- const cacheKey = topicName.toLowerCase();
36
-
37
- if (this.wordsCache.has(cacheKey)) {
38
- return this.wordsCache.get(cacheKey);
39
- }
40
-
41
- try {
42
- const filePath = path.join(__dirname, '../../data/word-lists', `${topicName.toLowerCase()}.json`);
43
- const fileContent = await fs.readFile(filePath, 'utf8');
44
- const words = JSON.parse(fileContent);
45
-
46
- this.wordsCache.set(cacheKey, words);
47
- return words;
48
- } catch (error) {
49
- console.error(`Error loading words for topic ${topicName}:`, error);
50
- return this.getDefaultWords(topicName);
51
- }
52
- }
53
-
54
- formatTopicName(fileName) {
55
- return fileName
56
- .split('-')
57
- .map(word => word.charAt(0).toUpperCase() + word.slice(1))
58
- .join(' ');
59
- }
60
-
61
- getDefaultTopics() {
62
- return [
63
- { id: 'animals', name: 'Animals' },
64
- { id: 'geography', name: 'Geography' },
65
- { id: 'science', name: 'Science' },
66
- { id: 'technology', name: 'Technology' },
67
- { id: 'sports', name: 'Sports' },
68
- { id: 'history', name: 'History' },
69
- { id: 'food', name: 'Food' },
70
- { id: 'entertainment', name: 'Entertainment' },
71
- { id: 'nature', name: 'Nature' },
72
- { id: 'transportation', name: 'Transportation' },
73
- { id: 'art', name: 'Art' },
74
- { id: 'medicine', name: 'Medicine' }
75
- ];
76
- }
77
-
78
- getDefaultWords(topic) {
79
- const defaultWordSets = {
80
- animals: [
81
- { word: 'DOG', clue: 'Man\'s best friend' },
82
- { word: 'CAT', clue: 'Feline pet' },
83
- { word: 'ELEPHANT', clue: 'Large mammal with trunk' },
84
- { word: 'TIGER', clue: 'Striped big cat' },
85
- { word: 'WHALE', clue: 'Largest marine mammal' },
86
- { word: 'BUTTERFLY', clue: 'Colorful flying insect' },
87
- { word: 'BIRD', clue: 'Flying creature with feathers' },
88
- { word: 'FISH', clue: 'Aquatic animal with gills' }
89
- ],
90
- science: [
91
- { word: 'ATOM', clue: 'Smallest unit of matter' },
92
- { word: 'GRAVITY', clue: 'Force that pulls objects down' },
93
- { word: 'MOLECULE', clue: 'Group of atoms bonded together' },
94
- { word: 'PHOTON', clue: 'Particle of light' },
95
- { word: 'CHEMISTRY', clue: 'Study of matter and reactions' },
96
- { word: 'PHYSICS', clue: 'Study of matter and energy' },
97
- { word: 'BIOLOGY', clue: 'Study of living organisms' },
98
- { word: 'ELEMENT', clue: 'Pure chemical substance' }
99
- ],
100
- geography: [
101
- { word: 'MOUNTAIN', clue: 'High elevation landform' },
102
- { word: 'OCEAN', clue: 'Large body of salt water' },
103
- { word: 'DESERT', clue: 'Dry, arid region' },
104
- { word: 'CONTINENT', clue: 'Large landmass' },
105
- { word: 'RIVER', clue: 'Flowing body of water' },
106
- { word: 'ISLAND', clue: 'Land surrounded by water' },
107
- { word: 'FOREST', clue: 'Dense area of trees' },
108
- { word: 'VALLEY', clue: 'Low area between hills' }
109
- ],
110
- technology: [
111
- { word: 'COMPUTER', clue: 'Electronic processing device' },
112
- { word: 'INTERNET', clue: 'Global computer network' },
113
- { word: 'ALGORITHM', clue: 'Set of rules for solving problems' },
114
- { word: 'DATABASE', clue: 'Organized collection of data' },
115
- { word: 'SOFTWARE', clue: 'Computer programs' },
116
- { word: 'ROBOT', clue: 'Automated machine' },
117
- { word: 'NETWORK', clue: 'Connected system of computers' },
118
- { word: 'CODE', clue: 'Programming instructions' }
119
- ]
120
- };
121
-
122
- return defaultWordSets[topic.toLowerCase()] || [];
123
- }
124
-
125
- clearCache() {
126
- this.wordsCache.clear();
127
- this.topicsCache = null;
128
- }
129
- }
130
-
131
- module.exports = new WordService();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
crossword-app/backend/src/test-ai-integration.js DELETED
@@ -1,116 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // Test script to demonstrate AI integration capabilities
4
- require('dotenv').config();
5
-
6
- const axios = require('axios').default || require('axios');
7
-
8
- const BASE_URL = 'http://localhost:3001/api';
9
-
10
- async function testAIIntegration() {
11
- console.log('🧪 Testing AI Integration - Phase 6.1 Complete!\n');
12
-
13
- try {
14
- // Test 1: Check AI service status
15
- console.log('1️⃣ Testing AI Service Status...');
16
- const statusResponse = await axios.get(`${BASE_URL}/ai/status`);
17
- const status = statusResponse.data;
18
-
19
- console.log(` ✅ AI Service Status: ${status.status}`);
20
- console.log(` 🤖 HF Initialized: ${status.aiService.initialized}`);
21
- console.log(` 🔄 Fallback Enabled: ${status.aiService.fallbackEnabled}`);
22
- console.log(` 📊 Cache Size: ${status.aiService.cacheStats.embeddingCache} embeddings\n`);
23
-
24
- // Test 2: AI Word Generation
25
- console.log('2️⃣ Testing AI Word Generation...');
26
- const wordsResponse = await axios.post(`${BASE_URL}/ai/generate-words`, {
27
- topics: ['animals', 'science'],
28
- difficulty: 'medium',
29
- count: 8
30
- });
31
-
32
- const wordsData = wordsResponse.data;
33
- console.log(` ✅ Generated ${wordsData.generatedCount} words`);
34
- console.log(` 🧠 AI Generated: ${wordsData.aiGenerated}`);
35
- console.log(` 📝 Sample words:`, wordsData.words.slice(0, 3).map(w => w.word).join(', '));
36
- console.log();
37
-
38
- // Test 3: Enhanced Puzzle Generation (Static)
39
- console.log('3️⃣ Testing Enhanced Puzzle Generation (Static)...');
40
- const puzzleStaticResponse = await axios.post(`${BASE_URL}/generate`, {
41
- topics: ['technology'],
42
- difficulty: 'medium',
43
- useAI: false
44
- });
45
-
46
- const puzzleStatic = puzzleStaticResponse.data;
47
- console.log(` ✅ Generated puzzle with ${puzzleStatic.metadata.wordCount} words`);
48
- console.log(` 🧠 AI Generated: ${puzzleStatic.metadata.aiGenerated}`);
49
- console.log(` 📐 Grid Size: ${puzzleStatic.metadata.size}x${puzzleStatic.metadata.size}`);
50
- console.log();
51
-
52
- // Test 4: Enhanced Puzzle Generation (AI Fallback)
53
- console.log('4️⃣ Testing Enhanced Puzzle Generation (AI with Fallback)...');
54
- const puzzleAIResponse = await axios.post(`${BASE_URL}/generate`, {
55
- topics: ['geography'],
56
- difficulty: 'medium',
57
- useAI: true
58
- });
59
-
60
- const puzzleAI = puzzleAIResponse.data;
61
- console.log(` ✅ Generated puzzle with ${puzzleAI.metadata.wordCount} words`);
62
- console.log(` 🧠 AI Generated: ${puzzleAI.metadata.aiGenerated}`);
63
- console.log(` 📐 Grid Size: ${puzzleAI.metadata.size}x${puzzleAI.metadata.size}`);
64
- console.log();
65
-
66
- // Test 5: Performance Comparison
67
- console.log('5️⃣ Performance Test...');
68
- const startTime = Date.now();
69
-
70
- await axios.post(`${BASE_URL}/ai/generate-words`, {
71
- topics: ['animals'],
72
- difficulty: 'easy',
73
- count: 6
74
- });
75
-
76
- const endTime = Date.now();
77
- console.log(` ⚡ Word generation took: ${endTime - startTime}ms`);
78
- console.log();
79
-
80
- console.log('✅ All AI Integration Tests Passed!\n');
81
-
82
- console.log('📋 Phase 6.1 Summary:');
83
- console.log(' ✅ HuggingFace dependencies installed');
84
- console.log(' ✅ Environment variables configured');
85
- console.log(' ✅ EmbeddingWordService class created');
86
- console.log(' ✅ Graceful fallback to static words');
87
- console.log(' ✅ New AI endpoints working');
88
- console.log(' ✅ Enhanced puzzle generation');
89
- console.log(' ✅ Performance and error handling');
90
- console.log();
91
-
92
- console.log('🚀 Next Steps (Phase 6.2):');
93
- console.log(' 1. Sign up for HuggingFace account');
94
- console.log(' 2. Get API token and update HUGGINGFACE_API_KEY');
95
- console.log(' 3. Test real AI-powered word generation');
96
- console.log(' 4. Implement dynamic clue generation');
97
- console.log(' 5. Add Redis caching layer');
98
-
99
- } catch (error) {
100
- console.error('❌ Test failed:', error.message);
101
- if (error.response) {
102
- console.error(' Status:', error.response.status);
103
- console.error(' Data:', error.response.data);
104
- }
105
- console.error('\n💡 Make sure the server is running on port 3001');
106
- console.error(' Run: npm run dev');
107
- }
108
- }
109
-
110
- // Check if axios is available
111
- if (typeof axios === 'undefined') {
112
- console.log('⚠️ axios not installed - install with: npm install axios');
113
- console.log(' or test manually with curl commands');
114
- } else {
115
- testAIIntegration();
116
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
crossword-app/backend/src/test-embedding.js DELETED
@@ -1,63 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // Test script for EmbeddingWordService
4
- require('dotenv').config();
5
- const EmbeddingWordService = require('./services/embeddingWordService');
6
-
7
- async function testEmbeddingService() {
8
- console.log('🧪 Testing EmbeddingWordService...\n');
9
-
10
- try {
11
- // Test 1: Check service initialization
12
- console.log('1️⃣ Testing service initialization...');
13
- const stats = EmbeddingWordService.getCacheStats();
14
- console.log(' Service stats:', stats);
15
-
16
- if (stats.isInitialized) {
17
- console.log(' ✅ HuggingFace service initialized successfully');
18
- } else {
19
- console.log(' ⚠️ HuggingFace service not initialized (likely missing API key)');
20
- console.log(' 📝 This is expected if HUGGINGFACE_API_KEY is not set');
21
- }
22
-
23
- // Test 2: Generate words for topics
24
- console.log('\n2️⃣ Testing word generation...');
25
- const topics = ['animals', 'science'];
26
- const words = await EmbeddingWordService.generateWordsForTopics(topics, 'medium', 8);
27
-
28
- console.log(` Generated ${words.length} words for topics: ${topics.join(', ')}`);
29
- words.forEach((word, index) => {
30
- console.log(` ${index + 1}. ${word.word} - "${word.clue}"`);
31
- });
32
-
33
- // Test 3: Test different difficulties
34
- console.log('\n3️⃣ Testing different difficulties...');
35
- const difficulties = ['easy', 'medium', 'hard'];
36
-
37
- for (const difficulty of difficulties) {
38
- const diffWords = await EmbeddingWordService.generateWordsForTopics(['technology'], difficulty, 3);
39
- console.log(` ${difficulty.toUpperCase()}: ${diffWords.map(w => w.word).join(', ')}`);
40
- }
41
-
42
- // Test 4: Cache stats
43
- console.log('\n4️⃣ Final cache stats...');
44
- const finalStats = EmbeddingWordService.getCacheStats();
45
- console.log(' Final stats:', finalStats);
46
-
47
- console.log('\n✅ All tests completed successfully!');
48
-
49
- // Instructions for next steps
50
- console.log('\n📋 Next Steps:');
51
- console.log(' 1. Sign up for HuggingFace account at https://huggingface.co/join');
52
- console.log(' 2. Generate API token at https://huggingface.co/settings/tokens');
53
- console.log(' 3. Update HUGGINGFACE_API_KEY in .env file');
54
- console.log(' 4. Run this test again to verify AI-powered word generation');
55
-
56
- } catch (error) {
57
- console.error('❌ Test failed:', error.message);
58
- console.error(' Stack trace:', error.stack);
59
- }
60
- }
61
-
62
- // Run the test
63
- testEmbeddingService();