BryanBradfo commited on
Commit
19145af
·
verified ·
1 Parent(s): e851eb2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +160 -98
app.py CHANGED
@@ -5,18 +5,16 @@ import chess.engine
5
  import pandas as pd
6
  import os
7
  import uuid
 
8
  from openai import OpenAI
9
 
10
  # --- CONFIGURATION ---
11
  OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
12
- # On n'utilise plus ElevenLabs cause blocage IP HuggingFace
13
- # ELEVEN_API_KEY = os.getenv("ELEVEN_API_KEY")
14
-
15
  openai_client = OpenAI(api_key=OPENAI_API_KEY) if OPENAI_API_KEY else None
16
 
17
  STOCKFISH_PATH = "/usr/games/stockfish"
18
 
19
- # --- CHARGEMENT OUVERTURES ---
20
  def _load_lichess_openings(path_prefix="/app/data/lichess_openings/dist/"):
21
  try:
22
  files = [f"{path_prefix}{vol}.tsv" for vol in ("a", "b", "c", "d", "e")]
@@ -32,29 +30,45 @@ def _load_lichess_openings(path_prefix="/app/data/lichess_openings/dist/"):
32
 
33
  OPENINGS_DB = _load_lichess_openings()
34
 
35
- # --- MOTEUR STOCKFISH ---
36
- def get_stockfish_analysis(board, time_limit=0.1):
37
- if not os.path.exists(STOCKFISH_PATH): return 0, "Inconnu"
 
 
 
38
  try:
39
  with chess.engine.SimpleEngine.popen_uci(STOCKFISH_PATH) as engine:
40
  info = engine.analyse(board, chess.engine.Limit(time=time_limit))
41
  score = info["score"].white()
 
 
42
  if score.is_mate():
43
  score_val = f"Mat en {score.mate()}"
44
  else:
45
  score_val = score.score()
 
 
46
  best_move = info.get("pv", [None])[0]
47
- best_move_san = board.san(best_move) if best_move else "Aucun"
48
- return score_val, best_move_san
 
 
 
 
 
 
 
49
  except Exception as e:
50
- return 0, "Erreur Engine"
 
51
 
52
  def get_ai_move(board, level):
 
53
  levels = {
54
- "Débutant": {"time": 0.01, "skill": 0, "depth": 1},
55
- "Intermédiaire": {"time": 0.1, "skill": 5, "depth": 5},
56
- "Avancé": {"time": 0.5, "skill": 10, "depth": 10},
57
- "Grand Maître": {"time": 1.0, "skill": 20, "depth": 15}
58
  }
59
  config = levels.get(level, levels["Débutant"])
60
  try:
@@ -65,118 +79,166 @@ def get_ai_move(board, level):
65
  except:
66
  return None
67
 
68
- # --- AUDIO OPENAI (LA SOLUTION) ---
69
- def generate_voice_openai(text):
70
- """Génère l'audio via OpenAI TTS (Stable et non bloqué)."""
71
- if not openai_client:
72
- return None, "ERREUR: Clé OpenAI manquante."
73
- if not text: return None, "Pas de texte."
 
 
 
 
 
 
 
 
 
74
 
 
 
 
75
  try:
76
- # Génération via OpenAI
77
  response = openai_client.audio.speech.create(
78
  model="tts-1",
79
- voice="onyx", # Voix masculine grave type 'Coach'
80
  input=text
81
  )
82
-
83
- # Sauvegarde fichier temporaire unique
84
- unique_filename = f"coach_{uuid.uuid4()}.mp3"
85
  path = os.path.join("/tmp", unique_filename)
86
-
87
  response.stream_to_file(path)
88
- return path, None
89
-
90
- except Exception as e:
91
- return None, f"Erreur OpenAI TTS: {str(e)}"
 
92
 
93
- # --- PROMPT COACH ---
94
  SYSTEM_PROMPT = """
95
- Tu es Garry, un coach d'échecs légendaire et mentor.
96
- Tu analyses le dernier coup du joueur (Blancs).
97
- 1. Dis si le coup est bon ou mauvais.
98
- 2. Donne un conseil tactique précis pour la suite (ex: "Développe ton cavalier", "Contrôle d4", "Attention à la fourchette").
99
- 3. Adopte un ton encourageant mais ferme.
100
- 4. Fais court (max 25 mots) pour que l'audio soit fluide.
 
 
 
 
 
101
  """
102
 
103
- def process_turn(fen, level):
104
- if not fen: return fen, "", None, {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
  board = chess.Board(fen)
107
 
108
- # Tour du joueur terminé (c'est aux noirs)
109
  if board.turn == chess.BLACK:
110
- # 1. Analyse Technique
111
- opening_name = "Inconnue"
112
- if not OPENINGS_DB.empty:
113
- match = OPENINGS_DB[OPENINGS_DB["epd"] == board.epd()]
114
- if not match.empty: opening_name = match.iloc[0]['name']
115
-
116
- score, best_move = get_stockfish_analysis(board)
117
-
118
- # 2. LLM
119
- llm_context = f"Ouverture: {opening_name}. Score actuel (centipawns): {score}. Le meilleur coup théorique était: {best_move}."
120
- coach_text = "..."
121
-
122
- if openai_client:
123
- try:
124
- response = openai_client.chat.completions.create(
125
- model="gpt-4o-mini",
126
- messages=[
127
- {"role": "system", "content": SYSTEM_PROMPT},
128
- {"role": "user", "content": llm_context}
129
- ]
130
- )
131
- coach_text = response.choices[0].message.content
132
- except Exception as e:
133
- coach_text = f"Erreur OpenAI Text: {e}"
134
- else:
135
- coach_text = "Clé OpenAI manquante."
136
-
137
- # 3. Audio (Via OpenAI TTS)
138
- audio_path, error_audio = generate_voice_openai(coach_text)
139
-
140
- # Debug audio dans le texte si besoin
141
- if error_audio:
142
- coach_text += f"\n[AUDIO ERROR]: {error_audio}"
143
-
144
- # 4. Debug Data
145
- debug_info = {
146
- "Ouverture": opening_name,
147
- "Score": str(score),
148
- "Conseil Engine": best_move,
149
- "Source Audio": "OpenAI TTS-1"
150
- }
151
 
152
- # 5. IA joue
153
  if not board.is_game_over():
154
  ai_move = get_ai_move(board, level)
155
  if ai_move: board.push(ai_move)
 
 
156
 
157
- return board.fen(), coach_text, audio_path, debug_info
158
-
159
- return fen, "À vous les Blancs !", None, {}
160
 
161
  # --- INTERFACE ---
162
- with gr.Blocks(title="ChessCoach Pro") as demo:
163
- gr.Markdown("# ♟️ ChessCoach Pro - Entraînement Vocal")
164
- gr.Markdown("Jouez les blancs. Garry vous coache vocalement à chaque coup.")
165
-
 
166
  with gr.Row():
167
  with gr.Column(scale=2):
168
- level_radio = gr.Radio(
169
- ["Débutant", "Intermédiaire", "Avancé", "Grand Maître"],
170
- label="Niveau IA", value="Débutant"
171
- )
172
  board = Chessboard(label="Échiquier", value=chess.STARTING_FEN, game_mode=True, interactive=True)
173
-
 
 
 
 
174
  with gr.Column(scale=1):
175
- coach_box = gr.Textbox(label="Conseils de Garry", interactive=False, lines=3)
176
- audio_box = gr.Audio(label="Voix", autoplay=True, interactive=False, type="filepath")
177
- debug_box = gr.JSON(label="Données Techniques")
 
 
 
 
 
 
 
 
 
178
 
179
- board.move(fn=process_turn, inputs=[board, level_radio], outputs=[board, coach_box, audio_box, debug_box])
 
 
 
 
 
 
 
 
 
 
 
180
 
181
  if __name__ == "__main__":
182
  demo.launch(server_name="0.0.0.0", server_port=7860, ssr_mode=False, allowed_paths=["/tmp"])
 
5
  import pandas as pd
6
  import os
7
  import uuid
8
+ import time
9
  from openai import OpenAI
10
 
11
  # --- CONFIGURATION ---
12
  OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
 
 
 
13
  openai_client = OpenAI(api_key=OPENAI_API_KEY) if OPENAI_API_KEY else None
14
 
15
  STOCKFISH_PATH = "/usr/games/stockfish"
16
 
17
+ # --- DONNÉES OUVERTURES ---
18
  def _load_lichess_openings(path_prefix="/app/data/lichess_openings/dist/"):
19
  try:
20
  files = [f"{path_prefix}{vol}.tsv" for vol in ("a", "b", "c", "d", "e")]
 
30
 
31
  OPENINGS_DB = _load_lichess_openings()
32
 
33
+ # --- ANALYSE TECHNIQUE ---
34
+
35
+ def get_technical_data(board, time_limit=0.1):
36
+ """Récupère les données brutes pour l'IA."""
37
+ if not os.path.exists(STOCKFISH_PATH): return 0, "Inconnu", []
38
+
39
  try:
40
  with chess.engine.SimpleEngine.popen_uci(STOCKFISH_PATH) as engine:
41
  info = engine.analyse(board, chess.engine.Limit(time=time_limit))
42
  score = info["score"].white()
43
+
44
+ # Score lisible
45
  if score.is_mate():
46
  score_val = f"Mat en {score.mate()}"
47
  else:
48
  score_val = score.score()
49
+
50
+ # Meilleur coup (Format UCI: e2e4)
51
  best_move = info.get("pv", [None])[0]
52
+ best_move_uci = best_move.uci() if best_move else "Aucun"
53
+
54
+ # Nom de la pièce à bouger pour le meilleur coup (pour aider l'IA à expliquer)
55
+ piece_name = "Inconnue"
56
+ if best_move:
57
+ piece = board.piece_at(best_move.from_square)
58
+ if piece: piece_name = chess.piece_name(piece.piece_type)
59
+
60
+ return score_val, best_move_uci, piece_name
61
  except Exception as e:
62
+ print(f"Engine Error: {e}")
63
+ return 0, "Erreur", "Erreur"
64
 
65
  def get_ai_move(board, level):
66
+ """Stockfish joue les noirs."""
67
  levels = {
68
+ "Débutant": {"time": 0.01, "skill": 1, "depth": 1},
69
+ "Intermédiaire": {"time": 0.1, "skill": 8, "depth": 6},
70
+ "Avancé": {"time": 0.5, "skill": 15, "depth": 12},
71
+ "Grand Maître": {"time": 1.0, "skill": 20, "depth": 18}
72
  }
73
  config = levels.get(level, levels["Débutant"])
74
  try:
 
79
  except:
80
  return None
81
 
82
+ # --- ORCHESTRATION OPENAI (Audio & Chat) ---
83
+
84
+ def transcribe_audio(audio_path):
85
+ """Transforme la voix du joueur en texte (Whisper)."""
86
+ if not audio_path or not openai_client: return None
87
+ try:
88
+ with open(audio_path, "rb") as audio_file:
89
+ transcript = openai_client.audio.transcriptions.create(
90
+ model="whisper-1",
91
+ file=audio_file,
92
+ language="fr" # On force le français
93
+ )
94
+ return transcript.text
95
+ except Exception as e:
96
+ return f"Erreur transcription: {e}"
97
 
98
+ def generate_garry_voice(text, voice_model="onyx"):
99
+ """Génère la voix de Garry."""
100
+ if not openai_client or not text: return None
101
  try:
 
102
  response = openai_client.audio.speech.create(
103
  model="tts-1",
104
+ voice=voice_model,
105
  input=text
106
  )
107
+ unique_filename = f"garry_{uuid.uuid4()}.mp3"
 
 
108
  path = os.path.join("/tmp", unique_filename)
 
109
  response.stream_to_file(path)
110
+ return path
111
+ except:
112
+ return None
113
+
114
+ # --- LE CERVEAU DE KASPAROV (PROMPT AMÉLIORÉ) ---
115
 
 
116
  SYSTEM_PROMPT = """
117
+ Tu es Garry Kasparov, le plus grand joueur d'échecs de l'histoire.
118
+ Tu t'adresses à un DÉBUTANT COMPLET.
119
+ Ton objectif : Expliquer les échecs simplement, sans jargon.
120
+
121
+ RÈGLES D'OR :
122
+ 1. **INTERDIT** d'utiliser la notation algébrique (ex: NE DIS PAS "Cavalier f3", "e4", "Fxe5").
123
+ 2. **UTILISE** le langage naturel (ex: "Sors ton cavalier de droite", "Avance le pion devant ton Roi", "Mange sa Dame !").
124
+ 3. **EXPLIQUE POURQUOI** : "Il faut contrôler le centre", "Ton Roi est en danger", "C'est une pièce gratuite".
125
+ 4. Si le joueur te pose une question, réponds-y directement.
126
+ 5. Sois charismatique, encourageant, mais exigeant.
127
+ 6. Fais court (2 ou 3 phrases max) pour que la conversation soit fluide.
128
  """
129
 
130
+ def chat_with_garry(fen, user_audio, history_text, voice_choice="onyx"):
131
+ """Fonction appelée quand le joueur parle au micro."""
132
+ if not fen: return history_text, None
133
+ board = chess.Board(fen)
134
+
135
+ # 1. Transcription de la question du joueur
136
+ user_question = "Analyse la situation stp."
137
+ if user_audio:
138
+ user_question = transcribe_audio(user_audio)
139
+
140
+ # 2. Analyse technique pour donner des yeux à Garry
141
+ score, best_move_uci, piece_to_move = get_technical_data(board)
142
+ opening = "Non définie"
143
+ if not OPENINGS_DB.empty:
144
+ match = OPENINGS_DB[OPENINGS_DB["epd"] == board.epd()]
145
+ if not match.empty: opening = match.iloc[0]['name']
146
+
147
+ # 3. Construction du contexte pour l'IA
148
+ turn_color = "Blancs" if board.turn == chess.WHITE else "Noirs"
149
+ context = f"""
150
+ [SITUATION PLATEAU]
151
+ Trait aux : {turn_color}.
152
+ Ouverture : {opening}.
153
+ Avantage numérique (Score) : {score} (Positif = Blancs gagnent, Négatif = Noirs gagnent).
154
+ Meilleur coup suggéré par l'ordi : Bouger la pièce '{piece_to_move}' (coordonnées internes: {best_move_uci}).
155
 
156
+ [JOUEUR]
157
+ Le joueur te demande : "{user_question}"
158
+ """
159
+
160
+ # 4. Réponse de Garry
161
+ reply_text = "..."
162
+ if openai_client:
163
+ try:
164
+ response = openai_client.chat.completions.create(
165
+ model="gpt-4o-mini",
166
+ messages=[
167
+ {"role": "system", "content": SYSTEM_PROMPT},
168
+ {"role": "user", "content": context}
169
+ ]
170
+ )
171
+ reply_text = response.choices[0].message.content
172
+ except Exception as e:
173
+ reply_text = f"Erreur de connexion à mon cerveau : {e}"
174
+
175
+ # 5. Audio
176
+ reply_audio = generate_garry_voice(reply_text, voice_choice)
177
+
178
+ # Mise à jour historique chat
179
+ new_history = f"Vous : {user_question}\nGarry : {reply_text}\n\n" + history_text
180
+
181
+ return new_history, reply_audio
182
+
183
+
184
+ def process_game_move(fen, level, history, voice_choice):
185
+ """Appelé quand une pièce bouge."""
186
  board = chess.Board(fen)
187
 
188
+ # Si c'est aux noirs, le joueur vient de jouer -> Garry commente
189
  if board.turn == chess.BLACK:
190
+ # On simule une question "Comment j'ai joué ?"
191
+ hist, audio = chat_with_garry(fen, None, history, voice_choice)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
192
 
193
+ # L'IA joue
194
  if not board.is_game_over():
195
  ai_move = get_ai_move(board, level)
196
  if ai_move: board.push(ai_move)
197
+
198
+ return board.fen(), hist, audio
199
 
200
+ return fen, history, None
 
 
201
 
202
  # --- INTERFACE ---
203
+
204
+ with gr.Blocks(title="Talk with Kasparov") as demo:
205
+ gr.Markdown("# 🗣️ Parlez avec Kasparov (IA Coach)")
206
+ gr.Markdown("Jouez les Blancs. Posez des questions au micro. Garry vous répond en langage naturel.")
207
+
208
  with gr.Row():
209
  with gr.Column(scale=2):
 
 
 
 
210
  board = Chessboard(label="Échiquier", value=chess.STARTING_FEN, game_mode=True, interactive=True)
211
+
212
+ with gr.Row():
213
+ mic_btn = gr.Audio(sources=["microphone"], type="filepath", label="Posez une question à Garry")
214
+ submit_mic = gr.Button("Envoyer la question")
215
+
216
  with gr.Column(scale=1):
217
+ voice_selector = gr.Dropdown(["onyx", "fable", "echo", "shimmer"], value="fable", label="Voix du Coach")
218
+ level_selector = gr.Dropdown(["Débutant", "Intermédiaire", "Avancé", "Grand Maître"], value="Débutant", label="Niveau IA")
219
+
220
+ chat_display = gr.Textbox(label="Conversation", interactive=False, lines=10)
221
+ audio_output = gr.Audio(label="Réponse Vocale", autoplay=True, interactive=False)
222
+
223
+ # 1. Joueur bouge une pièce -> Garry commente + IA joue
224
+ board.move(
225
+ fn=process_game_move,
226
+ inputs=[board, level_selector, chat_display, voice_selector],
227
+ outputs=[board, chat_display, audio_output]
228
+ )
229
 
230
+ # 2. Joueur parle au micro -> Garry répond
231
+ submit_mic.click(
232
+ fn=chat_with_garry,
233
+ inputs=[board, mic_btn, chat_display, voice_selector],
234
+ outputs=[chat_display, audio_output]
235
+ )
236
+ # Déclenchement automatique quand on arrête d'enregistrer
237
+ mic_btn.stop_recording(
238
+ fn=chat_with_garry,
239
+ inputs=[board, mic_btn, chat_display, voice_selector],
240
+ outputs=[chat_display, audio_output]
241
+ )
242
 
243
  if __name__ == "__main__":
244
  demo.launch(server_name="0.0.0.0", server_port=7860, ssr_mode=False, allowed_paths=["/tmp"])