MrSimple07 commited on
Commit
2816efd
Β·
1 Parent(s): 54fdac7

new version 2 with enhanced

Browse files
Files changed (1) hide show
  1. app.py +308 -82
app.py CHANGED
@@ -1,12 +1,14 @@
1
  import gradio as gr
2
  import chess
3
  import chess.pgn
 
4
  import io
5
  import requests
6
  import google.generativeai as genai
7
- from collections import Counter
8
  import os
9
  import time
 
10
 
11
  GEMINI_API_KEY = os.environ.get('GEMINI_API_KEY')
12
  genai.configure(api_key=GEMINI_API_KEY)
@@ -39,14 +41,14 @@ def get_user_games_from_chess_com(username):
39
  if response.status_code == 200:
40
  games = response.json()['games']
41
  all_games.extend(games)
42
- if len(all_games) >= 30:
43
  break
44
 
45
  rapid_games = [g for g in all_games if g.get('time_class') in ['rapid', 'blitz']]
46
  if not rapid_games:
47
- rapid_games = all_games[:30]
48
 
49
- pgn_list = [g['pgn'] for g in rapid_games[:30] if 'pgn' in g]
50
 
51
  if not pgn_list:
52
  return None, "❌ PGN formatdagi o'yinlar topilmadi."
@@ -78,7 +80,46 @@ def parse_pgn_content(pgn_content):
78
  break
79
  return games
80
 
81
- def analyze_game_simple(game, username):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  board = game.board()
83
  mistakes = []
84
  move_number = 0
@@ -93,6 +134,9 @@ def analyze_game_simple(game, username):
93
  elif username_lower in black_player:
94
  user_color = chess.BLACK
95
 
 
 
 
96
  material_values = {chess.PAWN: 1, chess.KNIGHT: 3, chess.BISHOP: 3, chess.ROOK: 5, chess.QUEEN: 9}
97
 
98
  def count_material(board):
@@ -138,7 +182,12 @@ def analyze_game_simple(game, username):
138
  if mistake_type:
139
  mistakes.append({'type': mistake_type, 'phase': phase, 'move_number': move_number})
140
 
141
- return mistakes
 
 
 
 
 
142
 
143
  def categorize_mistakes(all_mistakes):
144
  if not all_mistakes:
@@ -161,7 +210,7 @@ def categorize_mistakes(all_mistakes):
161
  }
162
 
163
  weaknesses = []
164
- for mistake_type, count in counts.most_common(5):
165
  if mistake_type in categories_map:
166
  weaknesses.append({
167
  'category': categories_map[mistake_type],
@@ -171,147 +220,324 @@ def categorize_mistakes(all_mistakes):
171
 
172
  return weaknesses[:3]
173
 
174
- def get_ai_explanation(weaknesses):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
175
  weakness_text = "\n".join([f"- {w['category']}: {w['count']} marta ({w['percentage']:.1f}%)" for w in weaknesses])
176
 
177
- prompt = f"""Siz ΡˆΠ°Ρ…ΠΌΠ°Ρ‚ murabbiysiz. O'yinchi o'zining so'nggi o'yinlarida quyidagi zaif tomonlarni ko'rsatdi:
 
 
 
 
 
 
 
 
178
 
 
179
  {weakness_text}
180
 
181
- Har bir zaif tomonni oddiy va rag'batlantiruvchi tilda tushuntiring (har biri uchun 2-3 jumla). Yaxshilash uchun amaliy maslahatlar bering. Do'stona va motivatsion bo'ling.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
182
 
183
- MUHIM: Javobni FAQAT O'ZBEK TILIDA yozing!"""
184
 
185
  try:
186
  response = model.generate_content(prompt)
187
  return response.text
188
  except Exception as e:
189
- return f"AI tushuntirish hozircha mavjud emas: {str(e)}"
190
-
191
- def fetch_puzzles(weaknesses, count=5):
192
- puzzles = []
193
-
194
- for _ in range(count):
195
- try:
196
- url = "https://lichess.org/api/puzzle/daily"
197
- response = requests.get(url, timeout=10)
198
-
199
- if response.status_code == 200:
200
- data = response.json()
201
- puzzle = data['puzzle']
202
-
203
- theme = weaknesses[len(puzzles) % len(weaknesses)]['category'] if weaknesses else 'Amaliyot'
204
-
205
- puzzles.append({
206
- 'id': puzzle['id'],
207
- 'rating': puzzle['rating'],
208
- 'theme': theme,
209
- 'url': f"https://lichess.org/training/{puzzle['id']}"
210
- })
211
- except:
212
- pass
213
-
214
- time.sleep(0.5)
215
-
216
- while len(puzzles) < count:
217
- puzzles.append({
218
- 'id': f'default_{len(puzzles)}',
219
- 'rating': 1500,
220
- 'theme': 'Amaliyot',
221
- 'url': 'https://lichess.org/training'
222
- })
223
-
224
- return puzzles
225
 
226
  def analyze_games(username, pgn_file):
227
  if username:
228
  pgn_content, error = get_user_games_from_chess_com(username)
229
  if error:
230
- return error, "", ""
231
  elif pgn_file:
232
  username = "Player"
233
  pgn_content = pgn_file.decode('utf-8') if isinstance(pgn_file, bytes) else pgn_file
234
  else:
235
- return "❌ Foydalanuvchi nomini kiriting yoki PGN faylni yuklang", "", ""
236
 
237
  games = parse_pgn_content(pgn_content)
238
 
239
  if not games:
240
- return "❌ O'yinlar topilmadi yoki tahlil qilinmadi", "", ""
241
 
242
  all_mistakes = []
243
- for game in games[:20]:
244
- mistakes = analyze_game_simple(game, username)
245
- all_mistakes.extend(mistakes)
 
 
246
 
247
- if not all_mistakes:
248
- return f"βœ… Ajoyib! {len(games)} ta o'yinda katta xatolar topilmadi!", "", ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
249
 
250
  weaknesses = categorize_mistakes(all_mistakes)
251
 
252
- report = f"## πŸ“Š {len(games)} ta o'yin tahlili\n\n"
253
- report += f"**Topilgan xatolar:** {len(all_mistakes)} ta\n\n"
254
- report += "### 🎯 Eng zaif 3 tomoningiz:\n\n"
255
 
 
256
  for i, w in enumerate(weaknesses, 1):
257
- report += f"**{i}. {w['category']}**\n"
258
- report += f" - {w['count']} marta ({w['percentage']:.1f}%)\n\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
259
 
260
- explanation = get_ai_explanation(weaknesses)
261
- explanation_text = f"## πŸ€– AI Murabbiy Tahlili\n\n{explanation}"
262
 
263
- puzzles = fetch_puzzles(weaknesses, count=5)
 
264
 
265
- puzzle_text = "## 🧩 Shaxsiy O'quv Rejangiz (5 ta masala)\n\n"
266
- for i, puzzle in enumerate(puzzles, 1):
267
- puzzle_text += f"**Masala {i}: {puzzle['theme']}** (Reyting: {puzzle['rating']})\n"
268
- puzzle_text += f"- [Lichess'da yechish]({puzzle['url']})\n\n"
 
 
269
 
270
- return report, explanation_text, puzzle_text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
271
 
272
- with gr.Blocks(title="Chess Study Plan", theme=gr.themes.Soft()) as demo:
273
  gr.Markdown("""
274
- # β™ŸοΈ Shaxsiy Π¨Π°Ρ…ΠΌΠ°Ρ‚ O'quv Rejasi
275
-
276
- Chess.com foydalanuvchi nomingizni kiriting yoki PGN faylni yuklang:
277
- - πŸ“Š Eng zaif 3 tomoningizni tahlil
278
- - πŸ€– AI murabbiy tushuntirishlari
279
- - 🧩 5 ta shaxsiy masala
 
 
280
  """)
281
 
282
  with gr.Row():
283
  with gr.Column():
284
  username_input = gr.Textbox(
285
  label="Chess.com foydalanuvchi nomi",
286
- placeholder="muslimbek_01"
287
  )
288
  pgn_upload = gr.File(
289
  label="πŸ“ Yoki PGN faylni yuklang",
290
  file_types=[".pgn"],
291
  type="binary"
292
  )
293
- analyze_btn = gr.Button("πŸ” O'yinlarni tahlil qilish", variant="primary", size="lg")
294
 
295
  with gr.Row():
296
- weakness_output = gr.Markdown()
297
 
298
  with gr.Row():
299
- explanation_output = gr.Markdown()
 
 
 
 
300
 
301
  with gr.Row():
302
- puzzle_output = gr.Markdown()
 
 
 
 
 
 
 
 
 
 
303
 
304
  analyze_btn.click(
305
  fn=analyze_games,
306
  inputs=[username_input, pgn_upload],
307
- outputs=[weakness_output, explanation_output, puzzle_output]
 
 
 
 
 
 
 
 
 
 
308
  )
309
 
310
  gr.Markdown("""
311
  ---
312
  ### πŸ“ Qanday foydalanish:
313
- - **Chess.com:** Foydalanuvchi nomingizni kiriting (masalan: muslimbek_01)
314
  - **Lichess:** Profile β†’ Games β†’ Export orqali PGN faylni yuklang
 
 
 
 
 
 
 
 
315
  """)
316
 
317
  demo.launch()
 
1
  import gradio as gr
2
  import chess
3
  import chess.pgn
4
+ import chess.svg
5
  import io
6
  import requests
7
  import google.generativeai as genai
8
+ from collections import Counter, defaultdict
9
  import os
10
  import time
11
+ import re
12
 
13
  GEMINI_API_KEY = os.environ.get('GEMINI_API_KEY')
14
  genai.configure(api_key=GEMINI_API_KEY)
 
41
  if response.status_code == 200:
42
  games = response.json()['games']
43
  all_games.extend(games)
44
+ if len(all_games) >= 50:
45
  break
46
 
47
  rapid_games = [g for g in all_games if g.get('time_class') in ['rapid', 'blitz']]
48
  if not rapid_games:
49
+ rapid_games = all_games[:50]
50
 
51
+ pgn_list = [g['pgn'] for g in rapid_games[:50] if 'pgn' in g]
52
 
53
  if not pgn_list:
54
  return None, "❌ PGN formatdagi o'yinlar topilmadi."
 
80
  break
81
  return games
82
 
83
+ def detect_opening(game):
84
+ """Detect opening from ECO code or first moves"""
85
+ eco = game.headers.get("ECO", "")
86
+ opening = game.headers.get("Opening", "")
87
+
88
+ if opening:
89
+ return opening
90
+
91
+ board = game.board()
92
+ moves = []
93
+ for move in list(game.mainline_moves())[:6]:
94
+ moves.append(move)
95
+ board.push(move)
96
+
97
+ # Basic opening detection
98
+ if len(moves) >= 2:
99
+ first_moves = " ".join([board.san(m) for m in moves[:4]])
100
+
101
+ if "e4 e5" in first_moves:
102
+ return "Open Game (e4 e5)"
103
+ elif "e4 c5" in first_moves:
104
+ return "Sicilian Defense"
105
+ elif "e4 e6" in first_moves:
106
+ return "French Defense"
107
+ elif "e4 c6" in first_moves:
108
+ return "Caro-Kann Defense"
109
+ elif "d4 d5" in first_moves:
110
+ return "Queen's Pawn Game"
111
+ elif "d4 Nf6" in first_moves:
112
+ if "c4" in first_moves:
113
+ return "Indian Defense"
114
+ return "Indian Game"
115
+ elif "Nf3" in first_moves and "d4" not in first_moves:
116
+ return "Reti Opening"
117
+ elif "c4" in first_moves and "d4" not in first_moves:
118
+ return "English Opening"
119
+
120
+ return "Other Opening"
121
+
122
+ def analyze_game_detailed(game, username):
123
  board = game.board()
124
  mistakes = []
125
  move_number = 0
 
134
  elif username_lower in black_player:
135
  user_color = chess.BLACK
136
 
137
+ result = game.headers.get("Result", "*")
138
+ opening = detect_opening(game)
139
+
140
  material_values = {chess.PAWN: 1, chess.KNIGHT: 3, chess.BISHOP: 3, chess.ROOK: 5, chess.QUEEN: 9}
141
 
142
  def count_material(board):
 
182
  if mistake_type:
183
  mistakes.append({'type': mistake_type, 'phase': phase, 'move_number': move_number})
184
 
185
+ return {
186
+ 'mistakes': mistakes,
187
+ 'opening': opening,
188
+ 'result': result,
189
+ 'user_color': user_color
190
+ }
191
 
192
  def categorize_mistakes(all_mistakes):
193
  if not all_mistakes:
 
210
  }
211
 
212
  weaknesses = []
213
+ for mistake_type, count in counts.most_common(6):
214
  if mistake_type in categories_map:
215
  weaknesses.append({
216
  'category': categories_map[mistake_type],
 
220
 
221
  return weaknesses[:3]
222
 
223
+ def fetch_lichess_puzzles(count=5):
224
+ """Fetch real puzzles from Lichess API"""
225
+ puzzles = []
226
+
227
+ try:
228
+ url = "https://lichess.org/api/puzzle/daily"
229
+ response = requests.get(url, timeout=10)
230
+
231
+ if response.status_code == 200:
232
+ data = response.json()
233
+ puzzle = data['puzzle']
234
+ game = data['game']
235
+
236
+ puzzles.append({
237
+ 'id': puzzle['id'],
238
+ 'fen': puzzle['initialPly']['fen'] if 'initialPly' in puzzle else game['fen'],
239
+ 'moves': puzzle['solution'],
240
+ 'rating': puzzle['rating'],
241
+ 'themes': puzzle.get('themes', ['tactics'])
242
+ })
243
+ except:
244
+ pass
245
+
246
+ # Add some default tactical puzzles if we can't fetch from API
247
+ default_puzzles = [
248
+ {
249
+ 'id': 'default_1',
250
+ 'fen': 'r1bqkb1r/pppp1ppp/2n2n2/4p2Q/2B1P3/8/PPPP1PPP/RNB1K1NR w KQkq - 0 1',
251
+ 'moves': ['h5f7'],
252
+ 'rating': 1200,
253
+ 'themes': ['fork', 'tactics']
254
+ },
255
+ {
256
+ 'id': 'default_2',
257
+ 'fen': 'r1bqk2r/ppp2ppp/2n5/3np1N1/1b1P4/2N5/PPP1QPPP/R1B1KB1R w KQkq - 0 1',
258
+ 'moves': ['g5f7'],
259
+ 'rating': 1300,
260
+ 'themes': ['fork', 'knight']
261
+ },
262
+ {
263
+ 'id': 'default_3',
264
+ 'fen': '6k1/5ppp/8/8/8/8/2R2PPP/6K1 w - - 0 1',
265
+ 'moves': ['c2c8'],
266
+ 'rating': 1000,
267
+ 'themes': ['endgame', 'rook']
268
+ }
269
+ ]
270
+
271
+ while len(puzzles) < count:
272
+ puzzles.append(default_puzzles[len(puzzles) % len(default_puzzles)])
273
+
274
+ return puzzles[:count]
275
+
276
+ def create_board_svg(fen, size=400):
277
+ """Create SVG representation of chess position"""
278
+ board = chess.Board(fen)
279
+ svg = chess.svg.board(board, size=size)
280
+ return svg
281
+
282
+ def get_comprehensive_analysis(weaknesses, opening_stats, color_stats, total_games):
283
+ """Get AI-powered comprehensive analysis and learning plan"""
284
+
285
  weakness_text = "\n".join([f"- {w['category']}: {w['count']} marta ({w['percentage']:.1f}%)" for w in weaknesses])
286
 
287
+ opening_text = "\n".join([f"- {opening}: {stats['total']} o'yin (G'alabalar: {stats['wins']}, Yutqazishlar: {stats['losses']}, Duranglar: {stats['draws']})"
288
+ for opening, stats in list(opening_stats.items())[:5]])
289
+
290
+ color_text = f"Oq rangda: {color_stats['white']['wins']}G-{color_stats['white']['losses']}Y-{color_stats['white']['draws']}D\n"
291
+ color_text += f"Qora rangda: {color_stats['black']['wins']}G-{color_stats['black']['losses']}Y-{color_stats['black']['draws']}D"
292
+
293
+ prompt = f"""Siz professional ΡˆΠ°Ρ…ΠΌΠ°Ρ‚ murabbiy va tahlilchisiz. O'yinchining {total_games} ta o'yinini tahlil qildingiz.
294
+
295
+ STATISTIKA:
296
 
297
+ Zaif tomonlar:
298
  {weakness_text}
299
 
300
+ Eng ko'p o'ynaladigan debyutlar:
301
+ {opening_text}
302
+
303
+ Rang bo'yicha natijalar:
304
+ {color_text}
305
+
306
+ Quyidagilarni taqdim eting:
307
+
308
+ 1. **ZAIF TOMONLAR TAHLILI**: Har bir zaif tomonni chuqur tahlil qiling va nima uchun bu muammo kelib chiqayotganini tushuntiring.
309
+
310
+ 2. **SHAXSIY O'QUV REJASI**: Kundalik mashg'ulotlar rejasini tuzing:
311
+ - Har kuni nechta masala yechish kerak va qanday turdagi masalalar
312
+ - Qaysi debyutlarni o'rganish kerak
313
+ - Qaysi o'yin bosqichiga ko'proq e'tibor berish kerak
314
+ - Kompyuter yoki botlar bilan qanday mashq qilish kerak
315
+
316
+ 3. **TAVSIYA ETILGAN RESURSLAR**:
317
+ - Kitoblar (muallif va nom bilan)
318
+ - Onlayn kurslar (ChessBase, Chess.com, Lichess)
319
+ - YouTube kanallari
320
+ - Mashq uchun maxsus botlar yoki dasturlar
321
+
322
+ 4. **DEBYUT TAVSIYALARI**: Statistikaga asoslanib, qaysi debyutlarni davom ettirish va qaysilarini o'zgartirish kerak.
323
+
324
+ 5. **MOTIVATSION XULOSA**: Qisqa va rag'batlantiruvchi xulosa.
325
 
326
+ MUHIM: Javobni FAQAT O'ZBEK TILIDA yozing! Aniq va amaliy maslahatlar bering."""
327
 
328
  try:
329
  response = model.generate_content(prompt)
330
  return response.text
331
  except Exception as e:
332
+ return f"AI tahlil hozircha mavjud emas: {str(e)}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
333
 
334
  def analyze_games(username, pgn_file):
335
  if username:
336
  pgn_content, error = get_user_games_from_chess_com(username)
337
  if error:
338
+ return error, "", "", "", None, None, None, None, None
339
  elif pgn_file:
340
  username = "Player"
341
  pgn_content = pgn_file.decode('utf-8') if isinstance(pgn_file, bytes) else pgn_file
342
  else:
343
+ return "❌ Foydalanuvchi nomini kiriting yoki PGN faylni yuklang", "", "", "", None, None, None, None, None
344
 
345
  games = parse_pgn_content(pgn_content)
346
 
347
  if not games:
348
+ return "❌ O'yinlar topilmadi yoki tahlil qilinmadi", "", "", "", None, None, None, None, None
349
 
350
  all_mistakes = []
351
+ opening_stats = defaultdict(lambda: {'wins': 0, 'losses': 0, 'draws': 0, 'total': 0})
352
+ color_stats = {
353
+ 'white': {'wins': 0, 'losses': 0, 'draws': 0},
354
+ 'black': {'wins': 0, 'losses': 0, 'draws': 0}
355
+ }
356
 
357
+ for game in games[:30]:
358
+ analysis = analyze_game_detailed(game, username)
359
+ all_mistakes.extend(analysis['mistakes'])
360
+
361
+ opening = analysis['opening']
362
+ result = analysis['result']
363
+ user_color = analysis['user_color']
364
+
365
+ opening_stats[opening]['total'] += 1
366
+
367
+ if user_color is not None:
368
+ color_key = 'white' if user_color == chess.WHITE else 'black'
369
+
370
+ if result == "1-0":
371
+ if user_color == chess.WHITE:
372
+ opening_stats[opening]['wins'] += 1
373
+ color_stats[color_key]['wins'] += 1
374
+ else:
375
+ opening_stats[opening]['losses'] += 1
376
+ color_stats[color_key]['losses'] += 1
377
+ elif result == "0-1":
378
+ if user_color == chess.BLACK:
379
+ opening_stats[opening]['wins'] += 1
380
+ color_stats[color_key]['wins'] += 1
381
+ else:
382
+ opening_stats[opening]['losses'] += 1
383
+ color_stats[color_key]['losses'] += 1
384
+ elif result == "1/2-1/2":
385
+ opening_stats[opening]['draws'] += 1
386
+ color_stats[color_key]['draws'] += 1
387
 
388
  weaknesses = categorize_mistakes(all_mistakes)
389
 
390
+ # Statistics Report
391
+ stats_report = f"## πŸ“Š {len(games)} ta o'yin tahlili\n\n"
392
+ stats_report += f"**Jami xatolar:** {len(all_mistakes)} ta\n\n"
393
 
394
+ stats_report += "### 🎯 Eng zaif 3 tomoningiz:\n\n"
395
  for i, w in enumerate(weaknesses, 1):
396
+ stats_report += f"**{i}. {w['category']}** - {w['count']} marta ({w['percentage']:.1f}%)\n"
397
+
398
+ # Opening Statistics
399
+ opening_report = "\n\n## 🎭 Debyut Statistikasi\n\n"
400
+ sorted_openings = sorted(opening_stats.items(), key=lambda x: x[1]['total'], reverse=True)[:5]
401
+
402
+ for opening, stats in sorted_openings:
403
+ total = stats['total']
404
+ wins = stats['wins']
405
+ losses = stats['losses']
406
+ draws = stats['draws']
407
+ win_rate = (wins / total * 100) if total > 0 else 0
408
+
409
+ opening_report += f"**{opening}** ({total} o'yin)\n"
410
+ opening_report += f"- G'alabalar: {wins} ({win_rate:.1f}%) | Yutqazishlar: {losses} | Duranglar: {draws}\n\n"
411
 
412
+ # Color Statistics
413
+ color_report = "\n\n## βšͺ⚫ Rang bo'yicha natijalar\n\n"
414
 
415
+ white_total = sum(color_stats['white'].values())
416
+ black_total = sum(color_stats['black'].values())
417
 
418
+ if white_total > 0:
419
+ white_wr = color_stats['white']['wins'] / white_total * 100
420
+ color_report += f"**Oq figuralar bilan:**\n"
421
+ color_report += f"- G'alabalar: {color_stats['white']['wins']} ({white_wr:.1f}%)\n"
422
+ color_report += f"- Yutqazishlar: {color_stats['white']['losses']}\n"
423
+ color_report += f"- Duranglar: {color_stats['white']['draws']}\n\n"
424
 
425
+ if black_total > 0:
426
+ black_wr = color_stats['black']['wins'] / black_total * 100
427
+ color_report += f"**Qora figuralar bilan:**\n"
428
+ color_report += f"- G'alabalar: {color_stats['black']['wins']} ({black_wr:.1f}%)\n"
429
+ color_report += f"- Yutqazishlar: {color_stats['black']['losses']}\n"
430
+ color_report += f"- Duranglar: {color_stats['black']['draws']}\n"
431
+
432
+ full_report = stats_report + opening_report + color_report
433
+
434
+ # Get AI Analysis
435
+ ai_analysis = get_comprehensive_analysis(weaknesses, opening_stats, color_stats, len(games))
436
+ ai_report = f"## πŸ€– AI Murabbiy: To'liq Tahlil va O'quv Rejasi\n\n{ai_analysis}"
437
+
438
+ # Fetch puzzles
439
+ puzzles = fetch_lichess_puzzles(5)
440
+
441
+ puzzle_svgs = []
442
+ puzzle_info = []
443
+
444
+ for i, puzzle in enumerate(puzzles):
445
+ svg = create_board_svg(puzzle['fen'], size=300)
446
+ puzzle_svgs.append(svg)
447
+
448
+ themes = ", ".join(puzzle['themes'][:2]) if 'themes' in puzzle else "Taktika"
449
+ info = f"**Masala {i+1}**: {themes.title()} (Reyting: {puzzle['rating']})"
450
+ puzzle_info.append(info)
451
+
452
+ return (
453
+ full_report,
454
+ ai_report,
455
+ "## 🧩 Sizning shaxsiy masalalaringiz\n\nQuyidagi pozitsiyalarda eng yaxshi yurishni toping:",
456
+ puzzle_info[0] if len(puzzle_info) > 0 else "",
457
+ puzzle_svgs[0] if len(puzzle_svgs) > 0 else None,
458
+ puzzle_info[1] if len(puzzle_info) > 1 else "",
459
+ puzzle_svgs[1] if len(puzzle_svgs) > 1 else None,
460
+ puzzle_info[2] if len(puzzle_info) > 2 else "",
461
+ puzzle_svgs[2] if len(puzzle_svgs) > 2 else None
462
+ )
463
 
464
+ with gr.Blocks(title="Chess Study Plan Pro", theme=gr.themes.Soft()) as demo:
465
  gr.Markdown("""
466
+ # β™ŸοΈ Professional Π¨Π°Ρ…ΠΌΠ°Ρ‚ O'quv Rejasi
467
+
468
+ ### To'liq tahlil va shaxsiy o'quv rejasi:
469
+ - πŸ“Š Batafsil statistika (debyutlar, ranglar, natijalar)
470
+ - 🎯 Zaif tomonlar tahlili
471
+ - πŸ€– AI murabbiy tavsiyalari
472
+ - πŸ“š Kitoblar va kurslar tavsiyasi
473
+ - 🧩 Interaktiv masalalar (gradio interfeysi ichida)
474
  """)
475
 
476
  with gr.Row():
477
  with gr.Column():
478
  username_input = gr.Textbox(
479
  label="Chess.com foydalanuvchi nomi",
480
+ placeholder="Foydalanuvchi nomini kiriting",
481
  )
482
  pgn_upload = gr.File(
483
  label="πŸ“ Yoki PGN faylni yuklang",
484
  file_types=[".pgn"],
485
  type="binary"
486
  )
487
+ analyze_btn = gr.Button("πŸ” To'liq tahlil qilish", variant="primary", size="lg")
488
 
489
  with gr.Row():
490
+ stats_output = gr.Markdown(label="Statistika")
491
 
492
  with gr.Row():
493
+ ai_output = gr.Markdown(label="AI Tahlil")
494
+
495
+ gr.Markdown("---")
496
+
497
+ puzzle_header = gr.Markdown()
498
 
499
  with gr.Row():
500
+ with gr.Column():
501
+ puzzle1_info = gr.Markdown()
502
+ puzzle1_board = gr.HTML()
503
+
504
+ with gr.Column():
505
+ puzzle2_info = gr.Markdown()
506
+ puzzle2_board = gr.HTML()
507
+
508
+ with gr.Column():
509
+ puzzle3_info = gr.Markdown()
510
+ puzzle3_board = gr.HTML()
511
 
512
  analyze_btn.click(
513
  fn=analyze_games,
514
  inputs=[username_input, pgn_upload],
515
+ outputs=[
516
+ stats_output,
517
+ ai_output,
518
+ puzzle_header,
519
+ puzzle1_info,
520
+ puzzle1_board,
521
+ puzzle2_info,
522
+ puzzle2_board,
523
+ puzzle3_info,
524
+ puzzle3_board
525
+ ]
526
  )
527
 
528
  gr.Markdown("""
529
  ---
530
  ### πŸ“ Qanday foydalanish:
531
+ - **Chess.com:** Foydalanuvchi nomingizni kiriting (oxirgi 30-50 ta o'yin tahlil qilinadi)
532
  - **Lichess:** Profile β†’ Games β†’ Export orqali PGN faylni yuklang
533
+ - **Masalalar:** Har bir masalani tahlil qiling va eng yaxshi yurishni toping
534
+
535
+ ### 🎯 Yangi xususiyatlar:
536
+ - βœ… Debyut statistikasi (qaysi debyutlarda yaxshi/yomon o'ynaysiz)
537
+ - βœ… Oq/Qora rang bo'yicha natijalar
538
+ - βœ… Interaktiv masalalar (gradio interfeysi ichida)
539
+ - βœ… Shaxsiy o'quv rejasi (kundalik mashg'ulotlar)
540
+ - βœ… Kitoblar va kurslar tavsiyasi
541
  """)
542
 
543
  demo.launch()