Commit d34a923d authored by Mahmoud Aglan's avatar Mahmoud Aglan

fix: analysis system — progressive per-move analysis with progress bar

Problems fixed:
- Analysis no longer freezes (was sending all positions in one request)
- Now analyzes position-by-position with live progress updates
- Progress bar shows "تحليل النقلة 5 من 30" with fill animation
- Fallback: loads moves from moves[] array when PGN is empty
- Added 'cache' API action to save analysis results with service key
- Fixed curl handle leak (unset → proper handling)

Flow: click analyze → progress bar fills per move → results render → cached
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 4eddc5a6
......@@ -182,6 +182,38 @@ switch ($action) {
}
break;
case 'cache':
$gameId = $input['game_id'] ?? '';
$evaluations = $input['evaluations'] ?? [];
if (!$gameId || empty($evaluations)) {
http_response_code(400);
echo json_encode(['error' => 'missing data']);
exit;
}
$analysisData = [
'evaluations' => $evaluations,
'analyzed_at' => date('c')
];
$gameRes = supabase_rest('GET', "matches?id=eq.{$gameId}&select=game_state", [], SUPABASE_SERVICE_KEY);
$existingState = [];
if (!empty($gameRes['data'][0]['game_state'])) {
$existingState = $gameRes['data'][0]['game_state'];
if (is_string($existingState)) {
$existingState = json_decode($existingState, true) ?? [];
}
}
$existingState['analysis'] = $analysisData;
supabase_rest('PATCH', "matches?id=eq.{$gameId}", [
'game_state' => json_encode($existingState)
], SUPABASE_SERVICE_KEY);
echo json_encode(['ok' => true]);
break;
default:
http_response_code(400);
echo json_encode(['error' => 'invalid action']);
......
......@@ -144,9 +144,14 @@ const Analysis = {
this.gameData = data.game;
this.playerColor = data.game.player_color || 'w';
// Parse PGN or replay moves
if (data.game.pgn) {
// Parse PGN or replay moves from moves array
var movesLoaded = false;
if (data.game.pgn && data.game.pgn.trim().length > 0) {
try {
this.chess.load_pgn(data.game.pgn);
movesLoaded = true;
} catch(e) {}
}
// Build positions array
......@@ -154,16 +159,30 @@ const Analysis = {
this.positions = [tempChess.fen()];
this.moves = [];
if (data.game.pgn) {
tempChess.load_pgn(data.game.pgn);
const history = tempChess.history({ verbose: true });
if (movesLoaded) {
const replayChess = new Chess();
const history = this.chess.history({ verbose: true });
for (const move of history) {
replayChess.move(move);
this.positions.push(replayChess.fen());
this.moves.push(move);
}
} else if (data.game.moves && data.game.moves.length > 0) {
// Fallback: replay from moves array (SAN strings)
var moveArr = data.game.moves;
if (typeof moveArr === 'string') {
try { moveArr = JSON.parse(moveArr); } catch(e) { moveArr = []; }
}
const replayChess = new Chess();
for (var i = 0; i < moveArr.length; i++) {
var san = typeof moveArr[i] === 'string' ? moveArr[i] : (moveArr[i].san || moveArr[i]);
var result = replayChess.move(san);
if (result) {
this.positions.push(replayChess.fen());
this.moves.push(result);
}
}
this.chess.load(replayChess.fen());
}
// Load cached analysis if available
......@@ -645,22 +664,49 @@ const Analysis = {
const textEl = document.getElementById('analysis-progress-text');
const btnEl = document.getElementById('btn-analyze');
progressEl.style.display = 'block';
btnEl.disabled = true;
btnEl.textContent = 'جاري التحليل...';
if (progressEl) progressEl.style.display = 'block';
if (btnEl) { btnEl.disabled = true; btnEl.textContent = 'جاري التحليل...'; }
// Analyze position by position with progress
var evaluations = [];
var total = this.positions.length;
var failed = 0;
for (var i = 0; i < total; i++) {
var pct = Math.round(((i + 1) / total) * 100);
if (fillEl) fillEl.style.width = pct + '%';
if (textEl) textEl.textContent = 'تحليل النقلة ' + (i + 1) + ' من ' + total;
try {
const res = await App.fetch('/api/analysis', {
var res = await App.fetch('/api/analysis', {
method: 'POST',
body: JSON.stringify({
action: 'analyze',
game_id: this.gameId,
positions: this.positions
action: 'analyze_single',
fen: this.positions[i]
})
});
if (res && res.ok && res.analysis) {
this.evaluations = res.analysis.evaluations || [];
if (res && res.evaluation !== undefined) {
evaluations.push({
move_index: i,
fen: this.positions[i],
best_move: res.best_move || null,
evaluation: res.evaluation || 0,
depth: res.depth || 20
});
} else {
evaluations.push({ move_index: i, fen: this.positions[i], best_move: null, evaluation: 0, depth: 0 });
failed++;
}
} catch(e) {
evaluations.push({ move_index: i, fen: this.positions[i], best_move: null, evaluation: 0, depth: 0 });
failed++;
}
}
// Save analysis to server cache
if (failed < total / 2) {
this.evaluations = evaluations;
this.classifyMoves();
this.renderMoveList();
this.renderEvalGraph();
......@@ -668,19 +714,26 @@ const Analysis = {
this.renderCriticalMoments();
this.calculateAccuracy();
this.updateEvalBar(this.currentMoveIndex);
// Cache to server
App.fetch('/api/analysis', {
method: 'POST',
body: JSON.stringify({
action: 'cache',
game_id: this.gameId,
evaluations: evaluations
})
}).catch(function(){});
App.toast('تم التحليل بنجاح', 'success');
} else {
App.toast('خطأ في التحليل', 'error');
}
} catch (e) {
App.toast('خطأ في الاتصال', 'error');
App.toast('خطأ في التحليل — المحرك غير متاح', 'error');
}
fillEl.style.width = '100%';
textEl.textContent = 'اكتمل التحليل';
if (fillEl) fillEl.style.width = '100%';
if (textEl) textEl.textContent = 'اكتمل التحليل';
this.isAnalyzing = false;
btnEl.disabled = false;
btnEl.innerHTML = '<svg class="icon"><use href="/public/icons/sprite.svg#icon-star"></use></svg> اعادة التحليل';
if (btnEl) { btnEl.disabled = false; btnEl.innerHTML = '<svg class="icon"><use href="/public/icons/sprite.svg#icon-star"></use></svg> اعادة التحليل'; }
},
// Review mistakes mode
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment