rts-commander / nl_interface.html
Luigi's picture
Initial commit: Complete RTS project with MCP evaluation
551ad28
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Contrôle Vocal RTS - Interface NL</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
color: white;
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 800px;
margin: 0 auto;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 15px;
padding: 30px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
}
.header {
text-align: center;
margin-bottom: 30px;
}
.header h1 {
font-size: 2.5em;
margin-bottom: 10px;
background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.header p {
font-size: 1.1em;
opacity: 0.9;
}
.command-input {
margin-bottom: 30px;
}
.command-input textarea {
width: 100%;
height: 100px;
padding: 15px;
border: 2px solid rgba(255, 255, 255, 0.2);
border-radius: 10px;
background: rgba(255, 255, 255, 0.1);
color: white;
font-size: 1.1em;
resize: vertical;
transition: all 0.3s ease;
}
.command-input textarea:focus {
outline: none;
border-color: #4ecdc4;
box-shadow: 0 0 20px rgba(78, 205, 196, 0.3);
}
.command-input textarea::placeholder {
color: rgba(255, 255, 255, 0.6);
}
.controls {
display: flex;
gap: 15px;
margin-bottom: 20px;
}
.btn {
flex: 1;
padding: 12px 20px;
border: none;
border-radius: 8px;
font-size: 1em;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
text-transform: uppercase;
letter-spacing: 1px;
}
.btn-primary {
background: linear-gradient(45deg, #4ecdc4, #44a08d);
color: white;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(78, 205, 196, 0.4);
}
.btn-secondary {
background: rgba(255, 255, 255, 0.2);
color: white;
border: 2px solid rgba(255, 255, 255, 0.3);
}
.btn-secondary:hover {
background: rgba(255, 255, 255, 0.3);
transform: translateY(-2px);
}
.response-area {
background: rgba(0, 0, 0, 0.3);
border-radius: 10px;
padding: 20px;
margin-bottom: 20px;
}
.response-header {
display: flex;
justify-content: between;
align-items: center;
margin-bottom: 15px;
}
.response-title {
font-size: 1.3em;
font-weight: 600;
}
.status-indicator {
padding: 5px 12px;
border-radius: 20px;
font-size: 0.9em;
font-weight: 600;
}
.status-success {
background: rgba(76, 175, 80, 0.3);
color: #4caf50;
}
.status-error {
background: rgba(244, 67, 54, 0.3);
color: #f44336;
}
.status-processing {
background: rgba(255, 193, 7, 0.3);
color: #ffc107;
}
.response-content {
line-height: 1.6;
}
.examples {
margin-top: 30px;
}
.examples h3 {
margin-bottom: 15px;
color: #4ecdc4;
}
.example-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 15px;
}
.example-card {
background: rgba(255, 255, 255, 0.1);
padding: 15px;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
border: 1px solid rgba(255, 255, 255, 0.2);
}
.example-card:hover {
background: rgba(255, 255, 255, 0.2);
transform: translateY(-2px);
border-color: #4ecdc4;
}
.example-card h4 {
margin-bottom: 8px;
color: #4ecdc4;
}
.example-card p {
font-size: 0.9em;
opacity: 0.9;
}
.connection-status {
position: fixed;
top: 20px;
right: 20px;
padding: 10px 15px;
border-radius: 20px;
font-size: 0.9em;
font-weight: 600;
z-index: 1000;
}
.connected {
background: rgba(76, 175, 80, 0.3);
color: #4caf50;
border: 2px solid #4caf50;
}
.disconnected {
background: rgba(244, 67, 54, 0.3);
color: #f44336;
border: 2px solid #f44336;
}
.loading {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
border-top-color: #4ecdc4;
animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.hidden {
display: none;
}
</style>
</head>
<body>
<div class="connection-status disconnected" id="connectionStatus">
🔴 Déconnecté
</div>
<div class="container">
<div class="header">
<h1>🎮 Contrôle Vocal RTS</h1>
<p>Contrôlez votre jeu RTS avec des commandes en langage naturel</p>
</div>
<div class="command-input">
<textarea
id="commandInput"
placeholder="Tapez votre commande... Exemple: 'Déplace mon infanterie vers la position 100,200' ou 'Attaque l'unité ennemie numéro 5'"
maxlength="500">
</textarea>
</div>
<div class="controls">
<button class="btn btn-primary" onclick="sendCommand()">
<span id="sendText">🚀 Exécuter la commande</span>
<span id="sendLoading" class="hidden"><div class="loading"></div> Traitement...</span>
</button>
<button class="btn btn-secondary" onclick="clearCommand()">🗑️ Effacer</button>
</div>
<div class="response-area" id="responseArea" style="display: none;">
<div class="response-header">
<div class="response-title">Réponse du système</div>
<div class="status-indicator" id="statusIndicator"></div>
</div>
<div class="response-content" id="responseContent"></div>
</div>
<div class="examples">
<h3>💡 Exemples de commandes</h3>
<div class="example-grid">
<div class="example-card" onclick="loadExample(this)">
<h4>📊 État du jeu</h4>
<p>"Montre l'état du jeu"</p>
</div>
<div class="example-card" onclick="loadExample(this)">
<h4>🚶‍♂️ Déplacement</h4>
<p>"Déplace l'infanterie vers 150,75"</p>
</div>
<div class="example-card" onclick="loadExample(this)">
<h4>⚔️ Attaque</h4>
<p>"Attaque l'unité ennemie numéro 3"</p>
</div>
<div class="example-card" onclick="loadExample(this)">
<h4>🏗️ Construction</h4>
<p>"Construis une caserne à 200,100"</p>
</div>
<div class="example-card" onclick="loadExample(this)">
<h4>🎯 Stratégie</h4>
<p>"Quelle est la meilleure stratégie ?"</p>
</div>
<div class="example-card" onclick="loadExample(this)">
<h4>🔍 Analyse</h4>
<p>"Analyse le champ de bataille"</p>
</div>
</div>
</div>
</div>
<script>
let websocket = null;
const wsUrl = 'ws://localhost:8000/ws';
// État de la connexion
let isConnected = false;
function connectWebSocket() {
try {
websocket = new WebSocket(wsUrl);
websocket.onopen = function(event) {
console.log('Connexion WebSocket établie');
isConnected = true;
updateConnectionStatus(true);
};
websocket.onmessage = function(event) {
const data = JSON.parse(event.data);
handleWebSocketMessage(data);
};
websocket.onclose = function(event) {
console.log('Connexion WebSocket fermée');
isConnected = false;
updateConnectionStatus(false);
// Tentative de reconnexion
setTimeout(connectWebSocket, 3000);
};
websocket.onerror = function(error) {
console.error('Erreur WebSocket:', error);
isConnected = false;
updateConnectionStatus(false);
};
} catch (error) {
console.error('Erreur de connexion:', error);
isConnected = false;
updateConnectionStatus(false);
}
}
function updateConnectionStatus(connected) {
const statusElement = document.getElementById('connectionStatus');
if (connected) {
statusElement.className = 'connection-status connected';
statusElement.innerHTML = '🟢 Connecté au jeu';
} else {
statusElement.className = 'connection-status disconnected';
statusElement.innerHTML = '🔴 Déconnecté - Reconnexion...';
}
}
function handleWebSocketMessage(data) {
if (data.type === 'nl_command_response') {
handleCommandResponse(data);
}
}
function handleCommandResponse(response) {
// Masquer l'indicateur de chargement
document.getElementById('sendText').classList.remove('hidden');
document.getElementById('sendLoading').classList.add('hidden');
const responseArea = document.getElementById('responseArea');
const statusIndicator = document.getElementById('statusIndicator');
const responseContent = document.getElementById('responseContent');
responseArea.style.display = 'block';
if (response.success) {
statusIndicator.className = 'status-indicator status-success';
statusIndicator.textContent = '✅ Succès';
let content = `<strong>Commande originale:</strong> "${response.original_command}"<br><br>`;
content += `<strong>Traduction MCP:</strong> ${JSON.stringify(response.translation, null, 2)}<br><br>`;
content += `<strong>Résultat:</strong> ${JSON.stringify(response.result, null, 2)}`;
responseContent.innerHTML = content;
} else {
statusIndicator.className = 'status-indicator status-error';
statusIndicator.textContent = '❌ Erreur';
let content = `<strong>Commande originale:</strong> "${response.original_command}"<br><br>`;
content += `<strong>Erreur:</strong> ${response.error}<br>`;
if (response.clarification) {
content += `<br><strong>Suggestion:</strong> ${response.clarification}`;
}
responseContent.innerHTML = content;
}
}
function sendCommand() {
if (!isConnected) {
alert('Veuillez d\'abord vous connecter au jeu.');
return;
}
const commandInput = document.getElementById('commandInput');
const command = commandInput.value.trim();
if (!command) {
alert('Veuillez entrer une commande.');
return;
}
// Afficher l'indicateur de chargement
document.getElementById('sendText').classList.add('hidden');
document.getElementById('sendLoading').classList.remove('hidden');
// Envoyer la commande via WebSocket
websocket.send(JSON.stringify({
type: 'nl_command',
command: command
}));
}
function clearCommand() {
document.getElementById('commandInput').value = '';
document.getElementById('responseArea').style.display = 'none';
}
function loadExample(exampleElement) {
const commandText = exampleElement.querySelector('p').textContent;
document.getElementById('commandInput').value = commandText;
}
// Connexion automatique au chargement de la page
window.addEventListener('load', function() {
connectWebSocket();
});
// Gestion de la touche Entrée
document.getElementById('commandInput').addEventListener('keypress', function(e) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendCommand();
}
});
</script>
</body>
</html>