import { useState, useCallback } from 'react' import axios from 'axios' import { Play, Square, RefreshCw, Monitor, Swords, Plus, X, ThumbsUp, ThumbsDown, Minus } from 'lucide-react' const ACTION_MAP = { buy: { label: 'Acheter', color: 'text-green-400' }, sell: { label: 'Vendre', color: 'text-red-400' }, freeze: { label: 'Geler', color: 'text-blue-400' }, upgrade: { label: 'Monter tier', color: 'text-yellow-400' }, reposition: { label: 'Repositionner', color: 'text-purple-400' }, hero_power: { label: 'Pouvoir héros', color: 'text-orange-400' }, wait: { label: 'Attendre', color: 'text-gray-400' }, } function ConfBar({ value }) { const pct = Math.round((value || 0) * 100) const col = pct >= 70 ? 'bg-green-500' : pct >= 40 ? 'bg-yellow-500' : 'bg-red-500' return (
Confiance{pct}%
) } function MinionChip({ minion, onRemove }) { return (
{onRemove && ( )}

{minion.name || '?'}

{minion.attack || 0}/{minion.health || 0}

{minion.race && (

{Array.isArray(minion.race) ? minion.race.join(', ') : minion.race}

)}
) } function AddMinionButton({ onClick }) { return ( ) } function FeedbackBar({ decisionId, onDone }) { const [sent, setSent] = useState(false) const [better, setBetter] = useState('') const [comment, setComment] = useState('') const send = async (rating) => { try { await axios.post('/api/learning/feedback', { decision_id: decisionId, rating, better_action: better ? { action: better } : null, comment: comment || null, }) setSent(true) onDone && onDone() } catch { alert('Erreur envoi feedback') } } if (sent) return (
✅ Feedback envoyé — merci!
) return (

Ce conseil était-il bon?

setBetter(e.target.value)} placeholder="Meilleure action? (buy/sell/freeze/upgrade/wait...)" className="w-full bg-gray-800 border border-gray-600 rounded-lg px-3 py-1.5 text-xs text-white mb-2 focus:outline-none focus:border-yellow-600" /> setComment(e.target.value)} placeholder="Commentaire (optionnel)" className="w-full bg-gray-800 border border-gray-600 rounded-lg px-3 py-1.5 text-xs text-white focus:outline-none focus:border-yellow-600" />
) } export default function GamePage() { const [session, setSession] = useState(null) const [advice, setAdvice] = useState(null) const [loading, setLoading] = useState(false) const [screenshot, setScreenshot] = useState(null) const [state, setState] = useState({ turn: 1, tavern_tier: 1, gold: 3, hero_hp: 40, board_minions: [], tavern_minions: [], freeze: false, can_upgrade: true, upgrade_cost: 5, current_placement: 5, player_count: 8, phase: 'recruit', }) const startSession = async () => { try { const r = await axios.post('/api/game/start') setSession(r.data) setAdvice(null) } catch { alert('Erreur démarrage session') } } const endSession = async () => { const place = parseInt(prompt('Place finale (1-8)?', '4') || '4') if (!place) return try { await axios.post(`/api/game/${session.session_id}/end?final_place=${place}`) setSession(null); setAdvice(null) } catch { alert('Erreur fin de session') } } const getAdvice = useCallback(async () => { setLoading(true) try { const r = await axios.post('/api/advice/', { ...state, session_id: session?.session_id }) setAdvice(r.data) } catch { alert('Service IA non disponible — vérifiez que le backend tourne sur :8000') } finally { setLoading(false) } }, [state, session]) const captureScreen = async () => { setLoading(true) try { const r = await axios.get('/api/advice/from-screen') setAdvice(r.data.advice) if (r.data.screenshot) setScreenshot(r.data.screenshot) if (r.data.extracted_state?.gold) setState(s => ({ ...s, ...r.data.extracted_state })) } catch { alert('Capture non disponible — activez la vision dans les paramètres') } finally { setLoading(false) } } const addMinion = (field) => { const name = prompt('Nom du serviteur?') if (!name) return const attack = parseInt(prompt('Attaque?', '2') || '2') const health = parseInt(prompt('Points de vie?', '2') || '2') const race = prompt('Race? (mech/beast/murloc/demon/dragon/none)', 'none') || 'none' setState(s => ({ ...s, [field]: [...s[field], { name, attack, health, race: [race] }] })) } const removeMinion = (field, idx) => { setState(s => ({ ...s, [field]: s[field].filter((_, i) => i !== idx) })) } const setField = (key, val) => setState(s => ({ ...s, [key]: val })) const mainAction = advice?.main_decision const actionInfo = ACTION_MAP[mainAction?.action] || { label: mainAction?.action, color: 'text-white' } return (
{/* Header */}

Partie en cours

{session ? `Session #${session.session_id}` : 'Aucune session active'}

{!session ? ( ) : ( )}
{/* Colonne gauche: état du jeu */}
{/* Stats numériques */}

État du tour

{[ ['Tour', 'turn', 1, 50], ['Tier', 'tavern_tier', 1, 6], ['Or (g)', 'gold', 0, 20], ['HP Héros', 'hero_hp', 1, 40], ['Position', 'current_placement', 1, 8], ['Coût up.', 'upgrade_cost', 0, 10], ].map(([label, key, min, max]) => (
setField(key, parseInt(e.target.value) || 0)} className="w-full bg-gray-800 border border-gray-700 rounded-lg px-2 py-1.5 text-sm text-white text-center focus:outline-none focus:border-yellow-600" />
))}
{[['freeze', '❄️ Gel actif'], ['can_upgrade', '⬆️ Peut monter']].map(([key, label]) => ( ))}
{/* Board */}

Board ({state.board_minions.length}/7)

{state.board_minions.map((m, i) => ( removeMinion('board_minions', i)} /> ))} {state.board_minions.length < 7 && ( addMinion('board_minions')} /> )}
{/* Taverne */}

Taverne ({state.tavern_minions.length})

{state.tavern_minions.map((m, i) => ( removeMinion('tavern_minions', i)} /> ))} {state.tavern_minions.length < 7 && ( addMinion('tavern_minions')} /> )}
{/* Actions */}
{/* Colonne droite: conseils */}
{advice ? ( <> {/* Conseil principal */}
Conseil principal {advice.model_used}

{actionInfo.label}

{mainAction?.target && (

→ {mainAction.target?.name || JSON.stringify(mainAction.target)}

)}

{mainAction?.reasoning}

{mainAction?.warnings?.length > 0 && (
{mainAction.warnings.map((w, i) => (

⚠️ {w}

))}
)} {mainAction?.synergies?.length > 0 && (
{mainAction.synergies.map(s => ( {s} ))}
)}
{/* Analyse du board */} {advice.board_analysis && (

Analyse

{advice.board_analysis}

)} {/* Stratégie LLM */} {advice.strategy_long_term && (

Stratégie

{advice.strategy_long_term}

)} {/* Menaces */} {advice.threat_assessment && (

Menaces

{advice.threat_assessment}

)} {/* Alternatives */} {advice.secondary_decisions?.length > 0 && (

Alternatives

{advice.secondary_decisions.slice(0, 4).map((d, i) => { const info = ACTION_MAP[d.action] || { label: d.action, color: 'text-gray-400' } return (
{info.label}
{d.reasoning?.slice(0, 40)}... {Math.round(d.confidence * 100)}%
) })}
)} {/* Feedback */} {advice.decision_id && ( )} {/* Meta */}

{advice.processing_ms}ms de traitement

) : (

Prêt à analyser

Renseignez l'état du jeu puis cliquez sur "Obtenir conseil"

)}
{/* Screenshot preview */} {screenshot && (

Capture d'écran

capture
)}
) }