Miroir commited on
Commit
e568e66
·
1 Parent(s): e9f4d43

migrated game state to vercel app

Browse files
Files changed (3) hide show
  1. config.py +4 -88
  2. main.py +34 -85
  3. services/game_service.py +0 -238
config.py CHANGED
@@ -39,101 +39,17 @@ class GameConfig(BaseModel):
39
  progression: Dict[str, Any]
40
 
41
  class Settings(BaseSettings):
42
- # Add model config to resolve the warnings
43
  model_config = {
44
- 'protected_namespaces': (), # This disables the protected namespace warning
45
- 'env_prefix': "SEMANTIX_" # Move this from Config class to model_config
46
  }
47
 
48
- # App settings
49
- app_name: str = "Semantix API"
50
  version: str = "1.0.0"
51
  debug: bool = False
52
  model_url: str = "https://huggingface.co/Miroir/cc.fr.300.reduced/resolve/main/cc.fr.300.reduced.vec"
53
-
54
 
55
- # Game settings
56
- current_difficulty: str = "medium"
57
- game_config: Dict[str, Any] = {
58
- "difficulty": {
59
- "easy": {
60
- "jokers_high_similarity": 3,
61
- "jokers_medium_similarity": 3,
62
- "words_per_joker": 5,
63
- "similarity_threshold": 0.99,
64
- "time_limit": 300,
65
- },
66
- "medium": {
67
- "jokers_high_similarity": 2,
68
- "jokers_medium_similarity": 2,
69
- "words_per_joker": 3,
70
- "similarity_threshold": 0.995,
71
- "time_limit": 240,
72
- },
73
- "hard": {
74
- "jokers_high_similarity": 1,
75
- "jokers_medium_similarity": 1,
76
- "words_per_joker": 2,
77
- "similarity_threshold": 0.998,
78
- "time_limit": 180,
79
- }
80
- },
81
- "jokers": {
82
- "similarity_ranges": {
83
- "high": {"min": 0.7, "max": 0.8},
84
- "medium": {"min": 0.6, "max": 0.7}
85
- },
86
- "cooldown": 3,
87
- },
88
- "scoring": {
89
- "base_points": 1000,
90
- "time_bonus": {
91
- "enabled": True,
92
- "points_per_second": 10,
93
- },
94
- "joker_penalty": {
95
- "high_similarity": -100,
96
- "medium_similarity": -50,
97
- },
98
- "streak_bonus": {
99
- "enabled": True,
100
- "threshold": 0.8,
101
- "multiplier": 1.5,
102
- }
103
- },
104
- "rules": {
105
- "max_attempts": 0,
106
- "min_word_length": 3,
107
- "show_target_word": False,
108
- "allow_partial_matches": True,
109
- },
110
- "interface": {
111
- "history_size": 50,
112
- "visualization_auto_toggle": True,
113
- "visualization_moments": ["word_found", "joker_used"],
114
- "feedback_levels": ["very_cold", "cold", "warm", "hot", "very_hot"],
115
- },
116
- "word_selection": {
117
- "categories": ["general", "science", "nature", "technology"],
118
- "difficulty_weights": {
119
- "easy": {"common": 0.8, "uncommon": 0.2},
120
- "medium": {"common": 0.5, "uncommon": 0.5},
121
- "hard": {"common": 0.2, "uncommon": 0.8}
122
- },
123
- },
124
- "progression": {
125
- "levels_enabled": True,
126
- "xp_per_game": 100,
127
- "level_thresholds": [0, 1000, 2500, 5000, 10000],
128
- "rewards": {
129
- "level_2": {"bonus_joker": "high_similarity"},
130
- "level_3": {"bonus_time": 60},
131
- "level_4": {"bonus_joker": "medium_similarity"},
132
- "level_5": {"unlimited_time": True}
133
- }
134
- }
135
- }
136
-
137
  class Config:
138
  env_prefix = "SEMANTIX_"
139
 
 
39
  progression: Dict[str, Any]
40
 
41
  class Settings(BaseSettings):
 
42
  model_config = {
43
+ 'protected_namespaces': (),
44
+ 'env_prefix': "SEMANTIX_"
45
  }
46
 
47
+ # Keep only ML-related settings
48
+ app_name: str = "Semantix ML API"
49
  version: str = "1.0.0"
50
  debug: bool = False
51
  model_url: str = "https://huggingface.co/Miroir/cc.fr.300.reduced/resolve/main/cc.fr.300.reduced.vec"
 
52
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  class Config:
54
  env_prefix = "SEMANTIX_"
55
 
main.py CHANGED
@@ -13,88 +13,37 @@ from services.game_service import GameService
13
  from services.visualization_service import VisualizationService
14
  from services.study_service import StudyService
15
 
16
- # Configure logger
17
- logger.remove()
18
- logger.add(sys.stdout, level="INFO")
19
-
20
- @asynccontextmanager
21
- async def lifespan(app: FastAPI):
22
- # Startup
23
- logger.info("=" * 40)
24
- logger.info(f"Starting {settings.app_name}")
25
- logger.info(f"Version: {settings.version}")
26
- logger.info(f"Python version: {platform.python_version()}")
27
- logger.info(f"Host: {socket.gethostname()}")
28
- logger.info(f"Debug mode: {settings.debug}")
29
- logger.info(f"Current difficulty: {settings.current_difficulty}")
30
- logger.info(f"Model URL: {settings.model_url}")
31
- logger.info("=" * 40)
32
-
33
- yield
34
-
35
- # Shutdown
36
- logger.info("Shutting down application")
37
-
38
- def init_services():
39
- """Initialize all services"""
40
- try:
41
- word_service = WordEmbeddingService(settings.model_url)
42
- game_service = GameService(word_service=word_service) # Simplified
43
- visualization_service = VisualizationService(word_service)
44
- study_service = StudyService(word_service)
45
-
46
- logger.info("Services initialized successfully")
47
- return word_service, game_service, visualization_service, study_service
48
-
49
- except Exception as e:
50
- logger.error(f"Failed to initialize services: {str(e)}")
51
- raise
52
-
53
- def create_app():
54
- app = FastAPI(
55
- title=settings.app_name,
56
- version=settings.version,
57
- description="WordVerse Semantic Word Game API",
58
- lifespan=lifespan
59
- )
60
-
61
- # Configure CORS
62
- app.add_middleware(
63
- CORSMiddleware,
64
- allow_origins=["*"], # Adjust this in production
65
- allow_credentials=True,
66
- allow_methods=["*"],
67
- allow_headers=["*"],
68
- )
69
-
70
- # Initialize services
71
- word_service, game_service, visualization_service, study_service = init_services()
72
-
73
- # Include routers
74
- app.include_router(game.init_router(game_service, visualization_service))
75
- app.include_router(health.init_router(word_service))
76
- app.include_router(study.init_router(study_service))
77
-
78
- @app.get("/")
79
- async def root():
80
- """Root endpoint with basic API information"""
81
- return {
82
- "app": settings.app_name,
83
- "version": settings.version,
84
- "status": "running",
85
- "difficulty": settings.current_difficulty
86
- }
87
-
88
- return app
89
-
90
- app = create_app()
91
-
92
- if __name__ == "__main__":
93
- import uvicorn
94
- uvicorn.run(
95
- "main:app",
96
- host="0.0.0.0",
97
- port=8000,
98
- reload=settings.debug,
99
- log_level="info" if settings.debug else "error"
100
- )
 
13
  from services.visualization_service import VisualizationService
14
  from services.study_service import StudyService
15
 
16
+ from typing import List
17
+
18
+ app = FastAPI()
19
+
20
+ # Configure CORS
21
+ app.add_middleware(
22
+ CORSMiddleware,
23
+ allow_origins=["*"],
24
+ allow_credentials=True,
25
+ allow_methods=["*"],
26
+ allow_headers=["*"],
27
+ )
28
+
29
+ # Initialize services
30
+ word_service = WordEmbeddingService(settings.model_url)
31
+ visualization_service = VisualizationService(word_service)
32
+ study_service = StudyService(word_service)
33
+
34
+ # ML-focused endpoints
35
+ @app.post("/api/similarity")
36
+ async def calculate_similarity(word1: str, word2: str):
37
+ return {"similarity": await word_service.calculate_similarity(word1, word2)}
38
+
39
+ @app.post("/api/similar-words/{word}")
40
+ async def get_similar_words(word: str, n: int = 5):
41
+ return {"words": await word_service.get_most_similar_words(word, n)}
42
+
43
+ @app.get("/api/visualization/{word}")
44
+ async def get_visualization(word: str, context_words: List[str] = []):
45
+ return await visualization_service.prepare_3d_visualization(word, context_words)
46
+
47
+ @app.post("/api/words-in-range")
48
+ async def get_words_in_range(word: str, min_sim: float, max_sim: float, n: int = 5):
49
+ return {"words": await word_service.get_words_in_range(word, min_sim, max_sim, n)}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
services/game_service.py DELETED
@@ -1,238 +0,0 @@
1
- # semantix-api/services/game_service.py
2
- import json
3
- from pathlib import Path
4
- from loguru import logger
5
- import random
6
- from typing import Dict, List
7
- from config import settings
8
-
9
- class GameService:
10
- def __init__(self, word_service):
11
- self.data_file = Path('data/game_state.json')
12
- self.words_file = Path('data/word_list.json')
13
- self.word_service = word_service
14
- self.config = settings.get_current_game_config()
15
- self.difficulty = settings.get_current_difficulty_config()
16
- self._ensure_data_file()
17
-
18
- def _ensure_data_file(self):
19
- """Initialize game state file if it doesn't exist."""
20
- if not self.data_file.exists():
21
- self.data_file.parent.mkdir(exist_ok=True)
22
- self._save_state(self._create_initial_state())
23
-
24
- def _create_initial_state(self) -> Dict:
25
- """Create a new game state with default values from config."""
26
- return {
27
- 'target_word': self._get_random_word(),
28
- 'attempts': [],
29
- 'word_found': False,
30
- 'similar_words': [],
31
- 'jokers': {
32
- 'high_similarity': {
33
- 'remaining': self.difficulty.jokers_high_similarity,
34
- 'words_per_use': self.difficulty.words_per_joker
35
- },
36
- 'medium_similarity': {
37
- 'remaining': self.difficulty.jokers_medium_similarity,
38
- 'words_per_use': self.difficulty.words_per_joker
39
- }
40
- }
41
- }
42
-
43
- def reset_game(self) -> Dict:
44
- """Reset the game with a new random word and fresh jokers."""
45
- try:
46
- new_state = self._create_initial_state()
47
- self._save_state(new_state)
48
- return new_state
49
- except Exception:
50
- logger.exception("Error resetting game")
51
- raise
52
-
53
- def use_joker(self, joker_type: str) -> Dict:
54
- """Use a joker to get words within a specific similarity range."""
55
- try:
56
- logger.info(f"Using joker of type: {joker_type}")
57
- state = self._load_state()
58
-
59
- # Validate joker type and availability
60
- if joker_type not in ['high_similarity', 'medium_similarity']:
61
- logger.error(f"Invalid joker type: {joker_type}")
62
- raise ValueError("Invalid joker type")
63
-
64
- joker = state['jokers'][joker_type]
65
- if joker['remaining'] <= 0:
66
- logger.warning(f"No {joker_type} jokers remaining")
67
- raise ValueError("No jokers remaining of this type")
68
-
69
- # Get similarity range from config
70
- joker_ranges = self.config.jokers.similarity_ranges
71
- sim_range = (
72
- joker_ranges['high'].min if joker_type == 'high_similarity' else joker_ranges['medium'].min,
73
- joker_ranges['high'].max if joker_type == 'high_similarity' else joker_ranges['medium'].max
74
- )
75
-
76
- target = state['target_word']
77
- logger.info(f"Target word: {target}, range: {sim_range}")
78
-
79
- # Get words in range
80
- similar_words = self.word_service.get_words_in_range(
81
- target,
82
- sim_range[0],
83
- sim_range[1],
84
- n=joker['words_per_use']
85
- )
86
-
87
- # Log the results
88
- logger.info(f"Found {len(similar_words)} words using joker:")
89
- for w in similar_words:
90
- logger.info(f"- {w['word']} (similarity: {w['similarity']:.3f})")
91
-
92
- # Update joker count
93
- joker['remaining'] -= 1
94
- self._save_state(state)
95
-
96
- logger.info(f"Remaining {joker_type} jokers: {joker['remaining']}")
97
-
98
- return {'joker_words': similar_words, 'jokers': state['jokers']}
99
-
100
- except Exception:
101
- logger.exception("Error using joker")
102
- raise
103
-
104
- def get_center_word_power(self, chosen_words: List[str]) -> Dict[str, float]:
105
- """Compute and return the "center word" based on the user's chosen words."""
106
- try:
107
- state = self._load_state()
108
- target_word = state['target_word']
109
-
110
- result = self.word_service.get_center_word(chosen_words, target_word)
111
- if not result:
112
- logger.warning("Center word power returned no result.")
113
- return {}
114
-
115
- logger.info(f"Center word found: {result['word']} (sim={result['similarity']:.3f})")
116
- return result
117
-
118
- except Exception:
119
- logger.exception("Error computing center word power")
120
- return {}
121
-
122
- def _get_random_word(self) -> str:
123
- """Get a random word from the game's word list."""
124
- try:
125
- with open(self.words_file, 'r', encoding='utf-8') as f:
126
- words_data = json.load(f)
127
- return random.choice(words_data['words'])
128
- except Exception:
129
- logger.exception("Error loading word list")
130
- return "mathématiques" # fallback word
131
-
132
- def save_attempt(self, word: str, similarity: float) -> Dict:
133
- """Save a word attempt and update game state."""
134
- try:
135
- if not word or similarity <= 0:
136
- return self._load_state()
137
-
138
- state = self._load_state()
139
- state['attempts'].append({'word': word, 'similarity': similarity})
140
-
141
- # Check if word is found using config threshold
142
- if similarity > self.difficulty.similarity_threshold:
143
- state['word_found'] = True
144
- # Get similar words when the target is found
145
- state['similar_words'] = self.word_service.get_most_similar_words(
146
- state['target_word'],
147
- n=self.config.interface['history_size']
148
- )
149
-
150
- self._save_state(state)
151
- return state
152
- except Exception:
153
- logger.exception("Error saving attempt")
154
- raise
155
-
156
- def _save_state(self, state: Dict) -> None:
157
- """Save game state to file."""
158
- try:
159
- self.data_file.parent.mkdir(exist_ok=True)
160
- with open(self.data_file, 'w', encoding='utf-8') as f:
161
- json.dump(state, f, ensure_ascii=False, indent=2)
162
- except Exception:
163
- logger.exception("Error saving game state")
164
- raise
165
-
166
- def _load_state(self) -> Dict:
167
- """Load game state from file."""
168
- try:
169
- if not self.data_file.exists():
170
- self._ensure_data_file()
171
- with open(self.data_file, 'r', encoding='utf-8') as f:
172
- return json.load(f)
173
- except Exception:
174
- logger.exception("Error loading game state")
175
- raise
176
-
177
- def get_state(self) -> Dict:
178
- """Get current game state."""
179
- try:
180
- return self._load_state()
181
- except Exception:
182
- logger.exception("Error getting game state")
183
- raise
184
-
185
- def get_history(self) -> List[Dict]:
186
- """Get history of attempts."""
187
- try:
188
- state = self._load_state()
189
- return state['attempts']
190
- except Exception:
191
- logger.exception("Error getting history")
192
- return []
193
-
194
- def set_target_word(self, word: str) -> Dict:
195
- """Set a specific target word for testing."""
196
- try:
197
- if not self.word_service.get_vector(word):
198
- raise ValueError(f"Word '{word}' not found in vocabulary")
199
-
200
- state = self._create_initial_state()
201
- state['target_word'] = word
202
- self._save_state(state)
203
-
204
- return self.get_state()
205
- except Exception:
206
- logger.exception(f"Error setting target word: {word}")
207
- raise
208
-
209
- def check_word(self, word: str) -> Dict:
210
- """Check a word against the target word and save the attempt."""
211
- try:
212
- state = self._load_state()
213
- target_word = state['target_word']
214
-
215
- # Calculate similarity
216
- similarity = self.word_service.calculate_similarity(word, target_word)
217
- logger.info(f"Word: {word}, Target: {target_word}, Similarity: {similarity:.3f}")
218
-
219
- # Validate word length
220
- if len(word) < self.config.rules.min_word_length:
221
- raise ValueError(f"Word must be at least {self.config.rules.min_word_length} characters long")
222
-
223
- # Save attempt and get updated state
224
- updated_state = self.save_attempt(word, similarity)
225
-
226
- response = {
227
- "similarity": similarity,
228
- "history": updated_state['attempts'][-self.config.interface['history_size']:],
229
- "word_found": updated_state['word_found'],
230
- "similar_words": updated_state.get('similar_words', [])
231
- }
232
-
233
- logger.info(f"Sending response: {response}")
234
- return response
235
-
236
- except Exception:
237
- logger.exception(f"Error checking word: {word}")
238
- raise