Files
chess-project/src/components/GeminiErrorModal.tsx
T
Stefan 002ed92bea 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>
2025-12-07 18:40:58 +01:00

151 lines
6.4 KiB
TypeScript

'use client';
import { AlertCircle, ExternalLink, X, Settings, Key } from 'lucide-react';
import { GeminiErrorInfo } from '@/lib/geminiErrorHandler';
import { ApiKeyInfo, getApiKeySourceDescription } from '@/lib/apiKeyHelper';
import { useRouter } from 'next/navigation';
interface GeminiErrorModalProps {
error: GeminiErrorInfo;
apiKeyInfo: ApiKeyInfo;
onClose: () => void;
}
export function GeminiErrorModal({ error, apiKeyInfo, onClose }: GeminiErrorModalProps) {
const router = useRouter();
const handleGoToSettings = () => {
onClose();
router.push('/settings');
};
return (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-md w-full">
{/* Header */}
<div className="flex items-center justify-between p-6 border-b border-gray-200 dark:border-gray-700">
<div className="flex items-center gap-3">
<div className="w-10 h-10 bg-red-100 dark:bg-red-900/30 rounded-full flex items-center justify-center">
<AlertCircle className="text-red-600 dark:text-red-400" size={24} />
</div>
<h2 className="text-xl font-bold text-gray-900 dark:text-white">
{error.isQuotaError ? 'API Quota Exceeded' : 'API Error'}
</h2>
</div>
<button
onClick={onClose}
className="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors"
aria-label="Close"
>
<X size={24} />
</button>
</div>
{/* Content */}
<div className="p-6 space-y-4">
{/* API Key Info */}
<div className="bg-gray-50 dark:bg-gray-900/50 border border-gray-200 dark:border-gray-700 rounded-lg p-4">
<div className="flex items-start gap-3">
<Key className="text-gray-600 dark:text-gray-400 flex-shrink-0 mt-0.5" size={18} />
<div className="flex-1 space-y-1">
<p className="text-sm font-medium text-gray-900 dark:text-white">
Current API Key:
</p>
<p className="text-sm font-mono text-gray-700 dark:text-gray-300">
{apiKeyInfo.anonymized}
</p>
<p className="text-xs text-gray-600 dark:text-gray-400">
Source: {getApiKeySourceDescription(apiKeyInfo.source)}
</p>
</div>
</div>
</div>
<p className="text-gray-700 dark:text-gray-300">
{error.userMessage}
</p>
{error.retryAfterSeconds && (
<div className="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg p-4">
<p className="text-sm text-blue-800 dark:text-blue-300">
You can try again in <strong>{error.retryAfterSeconds} seconds</strong>.
</p>
</div>
)}
{error.isQuotaError && (
<div className="space-y-3">
<div className="bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg p-4">
<p className="text-sm text-yellow-800 dark:text-yellow-300 mb-2">
<strong>Solutions:</strong>
</p>
<ul className="text-sm text-yellow-800 dark:text-yellow-300 list-disc list-inside space-y-1">
{apiKeyInfo.source === 'localStorage' && (
<li>Change your API key to a different one with available quota</li>
)}
<li>Upgrade to a paid Gemini API plan for higher quotas</li>
<li>Wait until tomorrow for your free tier quota to reset</li>
<li>Use the chess tutor less frequently throughout the day</li>
{apiKeyInfo.source === 'env' && (
<li>Update the NEXT_PUBLIC_GEMINI_API_KEY environment variable</li>
)}
</ul>
</div>
{/* Change API Key button (if from localStorage) */}
{apiKeyInfo.source === 'localStorage' && (
<button
onClick={handleGoToSettings}
className="flex items-center justify-center gap-2 w-full px-4 py-3 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors font-medium"
>
<Settings size={16} />
Change API Key in Settings
</button>
)}
<a
href="https://ai.google.dev/pricing"
target="_blank"
rel="noopener noreferrer"
className="flex items-center justify-center gap-2 w-full px-4 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors font-medium"
>
View Gemini API Pricing
<ExternalLink size={16} />
</a>
<a
href="https://ai.dev/usage"
target="_blank"
rel="noopener noreferrer"
className="flex items-center justify-center gap-2 w-full px-4 py-3 bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors font-medium"
>
Check Your API Usage
<ExternalLink size={16} />
</a>
</div>
)}
{/* Technical details (collapsible) */}
<details className="mt-4">
<summary className="text-sm text-gray-500 dark:text-gray-400 cursor-pointer hover:text-gray-700 dark:hover:text-gray-300">
Technical details
</summary>
<pre className="mt-2 p-3 bg-gray-100 dark:bg-gray-900 rounded text-xs text-gray-600 dark:text-gray-400 overflow-x-auto">
{error.technicalMessage}
</pre>
</details>
</div>
{/* Footer */}
<div className="p-6 border-t border-gray-200 dark:border-gray-700 flex justify-end">
<button
onClick={onClose}
className="px-6 py-2 bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors font-medium"
>
Close
</button>
</div>
</div>
</div>
);
}