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 (
)
}
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 (
)
}
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
)}
)
}