Add mobile app support and opening training feature

This commit implements iOS/Android mobile app support using Capacitor
and adds a comprehensive opening training feature with LLM-powered
explanations.

## Mobile App Infrastructure

- Add Capacitor configuration for iOS/Android builds
- Create mobile build script that excludes API routes
- Update Next.js config for conditional static export
- Add layout components with generateStaticParams for static builds
- Generate 500+ static pages for offline mobile use

## Chess Engine Abstraction

- Create ChessEngine interface for pluggable implementations
- Add LocalEngine (GPL - uses stockfish.js in browser)
- Add RemoteEngine (proprietary - calls API server)
- Factory pattern selects engine based on environment
- Enables GPL compliance for web, proprietary for mobile

## Opening Training Feature

- Interactive opening repertoire training
- Move validation with engine-backed feedback
- LLM explanations using Gemini API
- Wikipedia integration for opening context
- Opening family grouping (e4, d4, c4, etc.)
- Session state management
- Real-time move feedback with evaluation

Components:
- OpeningSelector: Browse and select openings by family
- OpeningTrainer: Main training interface with chessboard
- MoveFeedback: Display move quality and LLM explanations
- WikipediaSummary: Show opening history and context
- ErrorBoundary: Graceful error handling

Services:
- openingLoader: Load and filter opening database
- engineService: Engine evaluation wrapper
- moveValidator: Validate moves against repertoire
- feedbackGenerator: Generate contextual feedback
- wikipediaService: Fetch and cache Wikipedia data
- sessionManager: Track training session state

## Wikipedia Integration

- Automatic Wikipedia article fetching for openings
- Client-side and server-side caching
- Sanitized summaries with proper formatting
- Link opening database to Wikipedia slugs
- API endpoints for on-demand fetching

## Docker Improvements

- Add entrypoint script for automatic data setup
- Fetch Wikipedia data on first container startup
- Generate opening move index automatically
- Remove generated data from git (public/openings/*.json, public/wikipedia/*.json)
- Add READMEs explaining data requirements
- Update .gitignore for generated files

## Dual Licensing Strategy

- Add LICENSING.md explaining dual licensing approach
- GPL-3.0 for web builds (includes Stockfish)
- Proprietary option for mobile builds (no GPL code)
- Single codebase, multiple licensing models
- Legal compliance documented

## API Endpoints

- POST /api/v1/llm/opening-explanation - Get LLM move explanations
- GET /api/v1/wikipedia/summary - Fetch Wikipedia summaries

## Type Updates

- Add openingTraining types
- Update Tutor component to use ChessEngine interface
- Add Gemini error handling types

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Stefan
2025-12-07 18:40:58 +01:00
parent 52dd32eb15
commit 002ed92bea
57 changed files with 7015 additions and 234280 deletions
+4 -4
View File
@@ -2,7 +2,7 @@
import { useState, useEffect, useRef } from "react";
import { useRouter, useParams } from "next/navigation";
import { Chess, Move } from "chess.js";
import { Chess, Move, Square } from "chess.js";
import { Chessboard } from "react-chessboard";
import { ArrowLeft, CheckCircle, XCircle, RefreshCw, SkipForward } from "lucide-react";
import Header from "@/components/Header";
@@ -153,7 +153,7 @@ export default function TacticalPracticePage() {
if (setupError) {
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-900">
<Header language={language} onLanguageChange={setLanguage} />
<Header language={language} />
<div className="container mx-auto px-4 py-8">
<div className="max-w-2xl mx-auto">
<div className="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-6">
@@ -218,7 +218,7 @@ export default function TacticalPracticePage() {
return t.learning.patterns[mapping[pattern]];
};
const onDrop = ({ sourceSquare, targetSquare }: { sourceSquare: string; targetSquare: string | null }) => {
const onDrop = ({ sourceSquare, targetSquare }: { sourceSquare: Square; targetSquare: Square | null }) => {
if (!targetSquare || feedback !== 'none') return false;
// Additional validation: Check if there's actually a piece on the source square
@@ -571,7 +571,7 @@ export default function TacticalPracticePage() {
position: fen,
onPieceDrop: ({ sourceSquare, targetSquare }) => {
console.log('🎲 onPieceDrop called with:', { sourceSquare, targetSquare });
return onDrop({ sourceSquare, targetSquare });
return onDrop({ sourceSquare: sourceSquare as Square, targetSquare: targetSquare as Square | null });
},
darkSquareStyle: { backgroundColor: '#779954' },
lightSquareStyle: { backgroundColor: '#e9edcc' },