MrSimple07 commited on
Commit
54fdac7
Β·
1 Parent(s): 6298ce7

new version 2"

Browse files
Files changed (1) hide show
  1. app.py +298 -151
app.py CHANGED
@@ -1,170 +1,317 @@
1
  import gradio as gr
2
  import chess
3
- import chess.svg
4
- import logging
5
- from handlers import analyze_user_games
6
- import config
 
 
 
7
 
8
- logger = logging.getLogger(__name__)
 
 
9
 
10
- def create_chess_board(fen):
11
- """FEN dan shaxmat taxtasi SVG yaratish"""
12
  try:
13
- board = chess.Board(fen)
14
- svg = chess.svg.board(board, size=300)
15
- return svg
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  except Exception as e:
17
- logger.error(f"Taxta chizish xatosi: {e}")
18
- return "<svg></svg>"
19
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
 
21
- def format_puzzles_html(puzzles):
22
- """Masalalarni HTML formatda tayyorlash"""
23
- if not puzzles:
24
- return "<p>Masalalar topilmadi</p>"
25
 
26
- html = "<div style='display: grid; gap: 20px;'>"
 
 
27
 
28
- for i, puzzle in enumerate(puzzles, 1):
29
- board_svg = create_chess_board(puzzle['fen'])
30
-
31
- html += f"""
32
- <div style='border: 2px solid #e0e0e0; border-radius: 10px; padding: 15px; background: #f9f9f9;'>
33
- <h3 style='margin-top: 0;'>🧩 Masala {i}: {puzzle['theme']}</h3>
34
- <div style='display: flex; gap: 20px; align-items: start;'>
35
- <div style='flex-shrink: 0;'>
36
- {board_svg}
37
- </div>
38
- <div style='flex-grow: 1;'>
39
- <p><strong>Qiyinlik reytingi:</strong> {puzzle['rating']}</p>
40
- <p><strong>Vazifa:</strong> Eng yaxshi harakatni toping</p>
41
- <p><strong>Pozitsiya:</strong> <code style='background: #fff; padding: 5px; border-radius: 3px; font-size: 11px;'>{puzzle['fen'][:50]}...</code></p>
42
- <a href='{puzzle['url']}' target='_blank'
43
- style='display: inline-block; background: #7fa650; color: white; padding: 10px 20px;
44
- text-decoration: none; border-radius: 5px; margin-top: 10px;'>
45
- Lichess da yechish β†’
46
- </a>
47
- </div>
48
- </div>
49
- </div>
50
- """
51
-
52
- html += "</div>"
53
- return html
54
-
55
- # ============= ASOSIY TAHLIL FUNKSIYASI =============
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
 
57
- def process_analysis(username):
58
- """Tahlilni boshlash va natijalarni qaytarish"""
59
- if not username or username.strip() == "":
60
- return (
61
- "❌ Iltimos, foydalanuvchi nomini kiriting",
62
- "",
63
- "<p>Masalalar ko'rsatilmaydi</p>"
64
- )
65
 
66
- username = username.strip()
67
- logger.info(f"Tahlil boshlandi: {username}")
 
 
68
 
69
- # Tahlil qilish
70
- report, explanation, _, puzzles = analyze_user_games(username)
71
 
72
- # Masalalarni HTML ga o'tkazish
73
- puzzles_html = format_puzzles_html(puzzles)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
 
75
- return report, explanation, puzzles_html
76
 
77
- # ============= GRADIO INTERFEYS =============
 
 
 
78
 
79
- def create_interface():
80
- """Gradio interfeys yaratish"""
81
-
82
- # CSS stillari
83
- custom_css = """
84
- .main-container {
85
- max-width: 1200px;
86
- margin: 0 auto;
87
- }
88
- .puzzle-container {
89
- margin-top: 20px;
90
- }
91
- """
92
-
93
- with gr.Blocks(
94
- title="Shaxmat Tahlil",
95
- theme=gr.themes.Soft(primary_hue="green"),
96
- css=custom_css
97
- ) as app:
98
-
99
- gr.Markdown(f"# {config.UI_TEXTS['title']}")
100
- gr.Markdown(config.UI_TEXTS['description'])
101
-
102
- with gr.Row():
103
- with gr.Column(scale=1):
104
- username_input = gr.Textbox(
105
- label=config.UI_TEXTS['username_label'],
106
- placeholder=config.UI_TEXTS['username_placeholder'],
107
- lines=1
108
- )
109
 
110
- analyze_btn = gr.Button(
111
- config.UI_TEXTS['analyze_button'],
112
- variant="primary",
113
- size="lg"
114
- )
115
 
116
- gr.Markdown(config.UI_TEXTS['instructions'])
117
-
118
- with gr.Row():
119
- with gr.Column():
120
- weakness_output = gr.Markdown(
121
- label=config.UI_TEXTS['weakness_title']
122
- )
123
-
124
- with gr.Row():
125
- with gr.Column():
126
- explanation_output = gr.Markdown(
127
- label=config.UI_TEXTS['explanation_title']
128
- )
129
-
130
- gr.Markdown(f"## {config.UI_TEXTS['puzzles_title']}")
131
-
132
- with gr.Row():
133
- with gr.Column():
134
- puzzles_output = gr.HTML(
135
- label="Masalalar",
136
- elem_classes="puzzle-container"
137
- )
138
-
139
- # Tugmani bog'lash
140
- analyze_btn.click(
141
- fn=process_analysis,
142
- inputs=[username_input],
143
- outputs=[weakness_output, explanation_output, puzzles_output]
144
- )
145
-
146
- gr.Markdown("""
147
- ---
148
- ### ℹ️ Ma'lumot:
149
- - Dastur so'nggi 50 ta blitz o'yiningizni tahlil qiladi
150
- - Agar blitz o'yinlar bo'lmasa, bullet o'yinlar tahlil qilinadi
151
- - Tahlil 2-3 daqiqa davom etishi mumkin
152
- """)
153
-
154
- return app
155
- if __name__ == "__main__":
156
- logger.info("Dastur ishga tushmoqda...")
157
-
158
- # Konfiguratsiyani tekshirish
159
- if not config.GEMINI_API_KEY:
160
- logger.warning("GEMINI_API_KEY sozlanmagan!")
161
-
162
- logger.info(f"Stockfish yo'li: {config.STOCKFISH_PATH}")
163
-
164
- # Interfeys yaratish va ishga tushirish
165
- app = create_interface()
166
- app.launch(
167
- share=False,
168
- server_name="0.0.0.0",
169
- server_port=7860
170
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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)
13
+ model = genai.GenerativeModel('gemini-2.0-flash-exp')
14
 
15
+ def get_user_games_from_chess_com(username):
 
16
  try:
17
+ username = username.strip().lower()
18
+
19
+ user_url = f"https://api.chess.com/pub/player/{username}"
20
+ response = requests.get(user_url, timeout=10, headers={'User-Agent': 'Mozilla/5.0'})
21
+
22
+ if response.status_code != 200:
23
+ return None, f"❌ Foydalanuvchi topilmadi: {username}. Chess.com'da mavjudligini tekshiring."
24
+
25
+ archives_url = f"https://api.chess.com/pub/player/{username}/games/archives"
26
+ response = requests.get(archives_url, timeout=10, headers={'User-Agent': 'Mozilla/5.0'})
27
+
28
+ if response.status_code != 200:
29
+ return None, "❌ O'yinlar arxivi topilmadi."
30
+
31
+ archives = response.json()['archives']
32
+ if not archives:
33
+ return None, "❌ O'yinlar topilmadi."
34
+
35
+ all_games = []
36
+ for archive_url in reversed(archives[-3:]):
37
+ time.sleep(0.3)
38
+ response = requests.get(archive_url, timeout=10, headers={'User-Agent': 'Mozilla/5.0'})
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."
53
+
54
+ return pgn_list, None
55
+
56
  except Exception as e:
57
+ return None, f"❌ Xatolik: {str(e)}"
 
58
 
59
+ def parse_pgn_content(pgn_content):
60
+ games = []
61
+ if isinstance(pgn_content, list):
62
+ for pgn_text in pgn_content:
63
+ try:
64
+ game = chess.pgn.read_game(io.StringIO(pgn_text))
65
+ if game:
66
+ games.append(game)
67
+ except:
68
+ pass
69
+ else:
70
+ pgn_io = io.StringIO(pgn_content)
71
+ while True:
72
+ try:
73
+ game = chess.pgn.read_game(pgn_io)
74
+ if game is None:
75
+ break
76
+ games.append(game)
77
+ except:
78
+ break
79
+ return games
80
 
81
+ def analyze_game_simple(game, username):
82
+ board = game.board()
83
+ mistakes = []
84
+ move_number = 0
85
 
86
+ white_player = game.headers.get("White", "").lower()
87
+ black_player = game.headers.get("Black", "").lower()
88
+ username_lower = username.lower()
89
 
90
+ user_color = None
91
+ if username_lower in white_player:
92
+ user_color = chess.WHITE
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):
99
+ total = 0
100
+ for piece_type in material_values:
101
+ total += len(board.pieces(piece_type, chess.WHITE)) * material_values[piece_type]
102
+ total -= len(board.pieces(piece_type, chess.BLACK)) * material_values[piece_type]
103
+ return total
104
+
105
+ for move in game.mainline_moves():
106
+ move_number += 1
107
+
108
+ if user_color is not None and board.turn != user_color:
109
+ board.push(move)
110
+ continue
111
+
112
+ material_before = count_material(board)
113
+ board.push(move)
114
+ material_after = count_material(board)
115
+
116
+ material_loss = abs(material_after - material_before) if board.turn == chess.WHITE else abs(material_before - material_after)
117
+
118
+ moved_piece = board.piece_at(move.to_square)
119
+ mistake_type = None
120
+
121
+ if material_loss >= 3:
122
+ mistake_type = 'blunder'
123
+ elif material_loss >= 1:
124
+ mistake_type = 'mistake'
125
+ elif moved_piece and board.is_attacked_by(not board.turn, move.to_square):
126
+ attackers = len(board.attackers(not board.turn, move.to_square))
127
+ defenders = len(board.attackers(board.turn, move.to_square))
128
+ if attackers > defenders:
129
+ mistake_type = 'hanging_piece'
130
+
131
+ if move_number <= 10:
132
+ phase = 'opening_mistake'
133
+ elif len(board.piece_map()) <= 10:
134
+ phase = 'endgame_mistake'
135
+ else:
136
+ phase = 'middlegame_mistake'
137
+
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:
145
+ return []
 
 
 
 
 
146
 
147
+ types = []
148
+ for m in all_mistakes:
149
+ types.append(m['type'])
150
+ types.append(m['phase'])
151
 
152
+ counts = Counter(types)
 
153
 
154
+ categories_map = {
155
+ 'blunder': "Qo'pol xatolar",
156
+ 'mistake': 'Kichik xatolar',
157
+ 'hanging_piece': 'Himoyasiz qoldirish',
158
+ 'opening_mistake': 'Debyut xatolari',
159
+ 'middlegame_mistake': "O'rta o'yin xatolari",
160
+ 'endgame_mistake': 'Endshpil xatolari'
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],
168
+ 'count': count,
169
+ 'percentage': (count / len(all_mistakes) * 100)
170
+ })
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()