dcrey7 commited on
Commit
2d5ac34
·
verified ·
1 Parent(s): 205ca02

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +105 -262
app.py CHANGED
@@ -1,5 +1,6 @@
1
  from flask import Flask, render_template, request, jsonify
2
- from flask_socketio import SocketIO, emit, join_room, leave_room
 
3
  import os
4
  import requests
5
  import json
@@ -9,340 +10,182 @@ from dotenv import load_dotenv
9
  import logging
10
  from werkzeug.utils import secure_filename
11
  import random
12
- import asyncio
13
 
14
- # Initialize Flask and configure core settings
15
  app = Flask(__name__)
 
16
  app.config['SECRET_KEY'] = os.urandom(24)
17
  app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024
18
 
19
- # Initialize SocketIO with CORS support and logging
20
- socketio = SocketIO(app, cors_allowed_origins="*", logger=True, engineio_logger=True, async_mode='eventlet')
 
 
 
 
 
21
 
22
  # Load environment variables
23
  load_dotenv()
24
  MISTRAL_API_KEY = os.getenv('MISTRAL_API_KEY')
25
  ELEVENLABS_API_KEY = os.getenv('ELEVENLABS_API_KEY')
26
 
27
- # Configure logging with more detailed format
28
- logging.basicConfig(
29
- level=logging.INFO,
30
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
31
- )
32
  logger = logging.getLogger(__name__)
33
 
34
  class GameState:
35
- """Manages the state of all active game sessions."""
36
 
37
  def __init__(self):
38
  self.games = {}
39
  self.cleanup_interval = 3600
40
-
41
- def create_game(self):
42
- """Creates a new game session with proper initialization."""
43
- try:
44
- game_id = str(uuid.uuid4())
45
- self.games[game_id] = {
46
- 'players': [],
47
- 'current_phase': 'setup',
48
- 'recordings': {},
49
- 'impostor': None,
50
- 'votes': {},
51
- 'question': None,
52
- 'impostor_answer': None,
53
- 'modified_recording': None,
54
- 'round_number': 1,
55
- 'start_time': datetime.now().isoformat(),
56
- 'completed_rounds': [],
57
- 'score': {'impostor_wins': 0, 'player_wins': 0},
58
- 'room': game_id # Add room for socket management
59
- }
60
- logger.info(f"Successfully created game with ID: {game_id}")
61
- return game_id
62
- except Exception as e:
63
- logger.error(f"Error creating game: {str(e)}")
64
- raise
65
 
66
- def get_game(self, game_id):
67
- """Safely retrieves a game by ID."""
68
- game = self.games.get(game_id)
69
- if not game:
70
- logger.error(f"Game not found: {game_id}")
71
- raise ValueError("Game not found")
72
- return game
 
 
 
 
 
 
 
 
 
 
 
73
 
74
  def cleanup_inactive_games(self):
75
- """Removes inactive game sessions."""
76
  current_time = datetime.now()
77
  for game_id, game in list(self.games.items()):
78
- start_time = datetime.fromisoformat(game['start_time'])
79
- if (current_time - start_time).total_seconds() > 7200: # 2 hours
80
  del self.games[game_id]
81
- logger.info(f"Cleaned up inactive game: {game_id}")
82
 
83
- # Initialize global game state
84
  game_state = GameState()
85
 
86
- @app.route('/')
87
- def home():
88
- """Serves the main game page."""
89
- return render_template('index.html')
90
-
91
  @socketio.on('connect')
92
  def handle_connect():
93
- """Handles client connection."""
94
- logger.info(f"Client connected: {request.sid}")
95
- emit('connection_success', {'status': 'connected'})
96
 
97
  @socketio.on('disconnect')
98
  def handle_disconnect():
99
- """Handles client disconnection."""
100
- logger.info(f"Client disconnected: {request.sid}")
101
 
102
  @socketio.on('create_game')
103
  def handle_create_game():
104
- """Handles game creation request."""
105
  try:
106
  game_id = game_state.create_game()
107
- join_room(game_id) # Create socket room for the game
108
- logger.info(f"Created and joined game room: {game_id}")
109
  emit('game_created', {
 
110
  'gameId': game_id,
111
- 'status': 'success'
112
  })
 
113
  except Exception as e:
114
- logger.error(f"Error in game creation: {str(e)}")
115
- emit('game_error', {
116
- 'error': 'Failed to create game',
 
117
  'details': str(e)
118
  })
119
 
120
  @socketio.on('join_game')
121
  def handle_join_game(data):
122
- """Handles player joining a game."""
123
  try:
124
- game_id = data.get('gameId')
125
- player_name = data.get('playerName')
126
-
127
  if not game_id or not player_name:
128
- raise ValueError("Missing game ID or player name")
129
-
130
- game = game_state.get_game(game_id)
 
 
 
131
 
132
- # Validate player count
133
  if len(game['players']) >= 5:
134
- raise ValueError("Game is full")
135
 
136
- # Add player to game
137
  player_id = len(game['players']) + 1
138
- player = {
139
  'id': player_id,
140
  'name': player_name,
141
  'socket_id': request.sid
142
  }
143
- game['players'].append(player)
 
144
 
145
- # Join socket room
146
- join_room(game_id)
147
- logger.info(f"Player {player_name} (ID: {player_id}) joined game {game_id}")
148
-
149
- # Broadcast to all players in the game
150
  emit('player_joined', {
151
- 'playerId': player_id,
152
- 'playerName': player_name,
153
- 'status': 'success'
154
- }, room=game_id)
155
-
156
- except Exception as e:
157
- error_msg = str(e)
158
- logger.error(f"Error in handle_join_game: {error_msg}")
159
- emit('game_error', {'error': error_msg})
160
-
161
- @app.route('/api/start_game', methods=['POST'])
162
- async def start_game():
163
- """Initializes a new game round."""
164
- try:
165
- data = request.get_json()
166
- game_id = data.get('gameId')
167
-
168
- if not game_id:
169
- raise ValueError("Missing game ID")
170
-
171
- game = game_state.get_game(game_id)
172
-
173
- # Validate player count
174
- if len(game['players']) < 3:
175
- raise ValueError("Need at least 3 players to start")
176
-
177
- # Generate question using Mistral AI
178
- question = await generate_question()
179
- game['question'] = question
180
- game['current_phase'] = 'recording'
181
 
182
- logger.info(f"Started game {game_id} with question: {question}")
183
 
184
- # Notify all players in the game room
185
- socketio.emit('round_started', {
186
- 'question': question
187
- }, room=game_id)
188
-
189
- return jsonify({
190
- 'status': 'success',
191
- 'question': question
192
- })
193
-
194
  except Exception as e:
195
- error_msg = str(e)
196
- logger.error(f"Error starting game: {error_msg}")
197
- return jsonify({
198
  'status': 'error',
199
- 'error': error_msg
200
- }), 500
201
-
202
- async def generate_question():
203
- """Generates an engaging question using Mistral AI."""
204
- try:
205
- headers = {
206
- 'Authorization': f'Bearer {MISTRAL_API_KEY}',
207
- 'Content-Type': 'application/json'
208
- }
209
-
210
- payload = {
211
- 'messages': [{
212
- 'role': 'user',
213
- 'content': '''Generate an engaging personal question for a social game.
214
- The question should:
215
- 1. Encourage creative and unique responses
216
- 2. Be open-ended but not too philosophical
217
- 3. Be answerable in 15-30 seconds
218
- 4. Be appropriate for all ages
219
- 5. Spark interesting conversation
220
 
221
- Generate only the question, without any additional text.'''
222
- }]
223
- }
224
-
225
- response = requests.post(
226
- 'https://api.mistral.ai/v1/chat/completions',
227
- headers=headers,
228
- json=payload,
229
- timeout=10
230
- )
231
-
232
- if response.status_code == 200:
233
- question = response.json()['choices'][0]['message']['content'].strip()
234
- logger.info(f"Generated question: {question}")
235
- return question
236
-
237
- logger.error(f"Mistral API error: {response.status_code}")
238
- return random.choice([
239
- "What's your favorite childhood memory?",
240
- "What's the most interesting place you've ever visited?",
241
- "What's a skill you'd love to master and why?",
242
- "What's the best piece of advice you've ever received?"
243
- ])
244
-
245
- except Exception as e:
246
- logger.error(f"Error generating question: {str(e)}")
247
- return "What is your favorite memory from your childhood?"
248
 
249
- @app.route('/api/submit_recording', methods=['POST'])
250
- async def submit_recording():
251
- """Handles voice recording submissions."""
252
  try:
253
- game_id = request.form.get('gameId')
254
- player_id = request.form.get('playerId')
255
- audio_file = request.files.get('audio')
256
 
257
- if not all([game_id, player_id, audio_file]):
258
- raise ValueError("Missing required data")
259
 
260
- game = game_state.get_game(game_id)
261
-
262
- # Save the recording
263
- filename = secure_filename(f"recording_{game_id}_{player_id}.wav")
264
- filepath = os.path.join('temp', filename)
265
- audio_file.save(filepath)
266
-
267
- game['recordings'][player_id] = filepath
268
- logger.info(f"Saved recording for player {player_id} in game {game_id}")
269
 
270
- # Notify all players about the new recording
271
- socketio.emit('recording_submitted', {
272
- 'playerId': player_id,
273
- 'status': 'success'
274
- }, room=game_id)
275
 
276
- return jsonify({'status': 'success'})
277
 
278
  except Exception as e:
279
- error_msg = str(e)
280
- logger.error(f"Error submitting recording: {error_msg}")
281
- return jsonify({
282
- 'status': 'error',
283
- 'error': error_msg
284
- }), 500
285
 
286
- @socketio.on('submit_vote')
287
- def handle_vote(data):
288
- """Processes player votes and determines round outcome."""
289
- try:
290
- game_id = data.get('gameId')
291
- voter_id = data.get('voterId')
292
- vote_for = data.get('voteFor')
293
-
294
- if not all([game_id, voter_id, vote_for]):
295
- raise ValueError("Missing vote data")
296
-
297
- game = game_state.get_game(game_id)
298
- game['votes'][voter_id] = vote_for
299
-
300
- # Check if all players have voted
301
- if len(game['votes']) == len(game['players']):
302
- # Calculate results
303
- votes_count = {}
304
- for vote in game['votes'].values():
305
- votes_count[vote] = votes_count.get(vote, 0) + 1
306
-
307
- most_voted = max(votes_count.items(), key=lambda x: x[1])[0]
308
-
309
- # Update scores
310
- if most_voted == game['impostor']:
311
- game['score']['player_wins'] += 1
312
- result = 'players_win'
313
- else:
314
- game['score']['impostor_wins'] += 1
315
- result = 'impostor_wins'
316
-
317
- # Store round results
318
- game['completed_rounds'].append({
319
- 'round_number': game['round_number'],
320
- 'impostor': game['impostor'],
321
- 'votes': game['votes'].copy(),
322
- 'most_voted': most_voted,
323
- 'result': result
324
- })
325
-
326
- # Emit results to all players
327
- emit('round_result', {
328
- 'impostor': game['impostor'],
329
- 'most_voted': most_voted,
330
- 'votes': game['votes'],
331
- 'score': game['score'],
332
- 'result': result
333
- }, room=game_id)
334
-
335
- logger.info(f"Round completed for game {game_id}. Result: {result}")
336
 
337
- except Exception as e:
338
- error_msg = str(e)
339
- logger.error(f"Error processing vote: {error_msg}")
340
- emit('game_error', {'error': error_msg})
 
 
 
 
 
 
 
341
 
342
  if __name__ == '__main__':
343
- # Create temporary directory for recordings
344
  os.makedirs('temp', exist_ok=True)
345
-
346
- # Start the server
347
- logger.info("Starting server...")
348
- socketio.run(app, host='0.0.0.0', port=7860, debug=True)
 
 
 
 
1
  from flask import Flask, render_template, request, jsonify
2
+ from flask_socketio import SocketIO, emit, join_room
3
+ from flask_cors import CORS
4
  import os
5
  import requests
6
  import json
 
10
  import logging
11
  from werkzeug.utils import secure_filename
12
  import random
 
13
 
14
+ # Initialize Flask with CORS
15
  app = Flask(__name__)
16
+ CORS(app)
17
  app.config['SECRET_KEY'] = os.urandom(24)
18
  app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024
19
 
20
+ # Configure Socket.IO for Hugging Face Spaces
21
+ socketio = SocketIO(app,
22
+ cors_allowed_origins="*",
23
+ async_mode='eventlet',
24
+ logger=True,
25
+ engineio_logger=True
26
+ )
27
 
28
  # Load environment variables
29
  load_dotenv()
30
  MISTRAL_API_KEY = os.getenv('MISTRAL_API_KEY')
31
  ELEVENLABS_API_KEY = os.getenv('ELEVENLABS_API_KEY')
32
 
33
+ # Configure logging
34
+ logging.basicConfig(level=logging.INFO)
 
 
 
35
  logger = logging.getLogger(__name__)
36
 
37
  class GameState:
38
+ """Manages all active game sessions with WebSocket room support"""
39
 
40
  def __init__(self):
41
  self.games = {}
42
  self.cleanup_interval = 3600
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
 
44
+ def create_game(self):
45
+ game_id = str(uuid.uuid4())
46
+ self.games[game_id] = {
47
+ 'players': [],
48
+ 'current_phase': 'setup',
49
+ 'recordings': {},
50
+ 'impostor': None,
51
+ 'votes': {},
52
+ 'question': None,
53
+ 'impostor_answer': None,
54
+ 'modified_recording': None,
55
+ 'round_number': 1,
56
+ 'start_time': datetime.now().isoformat(),
57
+ 'completed_rounds': [],
58
+ 'score': {'impostor_wins': 0, 'player_wins': 0},
59
+ 'socket_room': f'game_{game_id}'
60
+ }
61
+ return game_id
62
 
63
  def cleanup_inactive_games(self):
 
64
  current_time = datetime.now()
65
  for game_id, game in list(self.games.items()):
66
+ if (current_time - datetime.fromisoformat(game['start_time'])).total_seconds() > 7200:
 
67
  del self.games[game_id]
 
68
 
 
69
  game_state = GameState()
70
 
71
+ # WebSocket Handlers
 
 
 
 
72
  @socketio.on('connect')
73
  def handle_connect():
74
+ logger.info('Client connected: %s', request.sid)
 
 
75
 
76
  @socketio.on('disconnect')
77
  def handle_disconnect():
78
+ logger.info('Client disconnected: %s', request.sid)
 
79
 
80
  @socketio.on('create_game')
81
  def handle_create_game():
 
82
  try:
83
  game_id = game_state.create_game()
 
 
84
  emit('game_created', {
85
+ 'status': 'success',
86
  'gameId': game_id,
87
+ 'message': 'Game created successfully'
88
  })
89
+ logger.info('Created game: %s', game_id)
90
  except Exception as e:
91
+ logger.error('Game creation failed: %s', str(e))
92
+ emit('game_created', {
93
+ 'status': 'error',
94
+ 'error': 'Game creation failed',
95
  'details': str(e)
96
  })
97
 
98
  @socketio.on('join_game')
99
  def handle_join_game(data):
 
100
  try:
101
+ game_id = data.get('game_id')
102
+ player_name = data.get('player_name')
103
+
104
  if not game_id or not player_name:
105
+ raise ValueError('Missing game ID or player name')
106
+
107
+ if game_id not in game_state.games:
108
+ raise KeyError('Game not found')
109
+
110
+ game = game_state.games[game_id]
111
 
 
112
  if len(game['players']) >= 5:
113
+ raise ValueError('Game is full')
114
 
 
115
  player_id = len(game['players']) + 1
116
+ new_player = {
117
  'id': player_id,
118
  'name': player_name,
119
  'socket_id': request.sid
120
  }
121
+ game['players'].append(new_player)
122
+ join_room(game['socket_room'])
123
 
 
 
 
 
 
124
  emit('player_joined', {
125
+ 'status': 'success',
126
+ 'player': new_player
127
+ }, room=game['socket_room'])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
128
 
129
+ logger.info('Player %s joined game %s', player_name, game_id)
130
 
 
 
 
 
 
 
 
 
 
 
131
  except Exception as e:
132
+ logger.error('Join game error: %s', str(e))
133
+ emit('join_failed', {
 
134
  'status': 'error',
135
+ 'error': str(e)
136
+ })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
 
138
+ # REST API Endpoints
139
+ @app.route('/')
140
+ def home():
141
+ return render_template('index.html')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
 
143
+ @app.route('/api/start_game', methods=['POST'])
144
+ def start_game():
 
145
  try:
146
+ data = request.get_json()
147
+ game_id = data.get('game_id')
 
148
 
149
+ if not game_id or game_id not in game_state.games:
150
+ return jsonify({'status': 'error', 'error': 'Invalid game ID'}), 400
151
 
152
+ game = game_state.games[game_id]
153
+ game['current_phase'] = 'recording'
 
 
 
 
 
 
 
154
 
155
+ socketio.emit('round_start', {
156
+ 'phase': 'recording',
157
+ 'duration': 30
158
+ }, room=game['socket_room'])
 
159
 
160
+ return jsonify({'status': 'success', 'message': 'Game started'})
161
 
162
  except Exception as e:
163
+ logger.error('Start game error: %s', str(e))
164
+ return jsonify({'status': 'error', 'error': str(e)}), 500
 
 
 
 
165
 
166
+ # Voice Processing Functions
167
+ async def generate_question():
168
+ # Implementation remains same as before
169
+ pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170
 
171
+ async def generate_impostor_answer(question):
172
+ # Implementation remains same as before
173
+ pass
174
+
175
+ async def clone_voice(audio_file):
176
+ # Implementation remains same as before
177
+ pass
178
+
179
+ async def generate_cloned_speech(voice_id, text):
180
+ # Implementation remains same as before
181
+ pass
182
 
183
  if __name__ == '__main__':
 
184
  os.makedirs('temp', exist_ok=True)
185
+ socketio.run(app,
186
+ host='0.0.0.0',
187
+ port=7860,
188
+ debug=True,
189
+ allow_unsafe_werkzeug=True,
190
+ use_reloader=False
191
+ )