|
|
<!DOCTYPE html> |
|
|
<html lang="fr"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>Client WebSocket Simple</title> |
|
|
<style> |
|
|
body { |
|
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; |
|
|
margin: 40px; |
|
|
background-color: #f0f2f5; |
|
|
} |
|
|
.container { |
|
|
max-width: 600px; |
|
|
margin: auto; |
|
|
padding: 20px; |
|
|
background-color: #fff; |
|
|
border-radius: 8px; |
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1); |
|
|
} |
|
|
#status { |
|
|
padding: 10px; |
|
|
border-radius: 5px; |
|
|
font-weight: bold; |
|
|
margin-bottom: 15px; |
|
|
} |
|
|
.connected { |
|
|
background-color: #e6ffed; |
|
|
color: #2f6f43; |
|
|
} |
|
|
.disconnected { |
|
|
background-color: #ffeef0; |
|
|
color: #c53030; |
|
|
} |
|
|
#logs { |
|
|
list-style-type: none; |
|
|
padding: 0; |
|
|
margin-top: 20px; |
|
|
background-color: #f7f7f7; |
|
|
border: 1px solid #ddd; |
|
|
border-radius: 5px; |
|
|
height: 200px; |
|
|
overflow-y: scroll; |
|
|
padding: 10px; |
|
|
} |
|
|
#logs li { |
|
|
padding: 5px; |
|
|
border-bottom: 1px solid #eee; |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body> |
|
|
|
|
|
<div class="container"> |
|
|
<h2>Client WebSocket pour API Mock</h2> |
|
|
<div id="status" class="disconnected">Déconnecté</div> |
|
|
|
|
|
<div style="margin-bottom: 20px;"> |
|
|
<label for="apiKey" style="display: block; margin-bottom: 5px; font-weight: bold;">Clé API :</label> |
|
|
<input type="password" id="apiKey" placeholder="Entrez votre clé API..." style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;"> |
|
|
<div style="margin-bottom: 20px;"> |
|
|
<label for="defaultModel" style="display: block; margin-bottom: 5px; font-weight: bold;">Modèle par défaut :</label> |
|
|
<select id="defaultModel" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;"> |
|
|
<option value="">Chargement des modèles...</option> |
|
|
</select> |
|
|
<small style="color: #666; font-size: 12px;">Modèle utilisé si non spécifié par Python.</small> |
|
|
</div> |
|
|
<small style="color: #666; font-size: 12px;">La clé est stockée uniquement dans votre navigateur pour cette session.</small> |
|
|
</div> |
|
|
|
|
|
<h3>Logs de Communication :</h3> |
|
|
<ul id="logs"> |
|
|
<li>En attente de connexion...</li> |
|
|
</ul> |
|
|
</div> |
|
|
|
|
|
<script> |
|
|
const statusDiv = document.getElementById('status'); |
|
|
const logsList = document.getElementById('logs'); |
|
|
let ws; |
|
|
|
|
|
function addLog(message) { |
|
|
const li = document.createElement('li'); |
|
|
li.textContent = `[${new Date().toLocaleTimeString()}] ${message}`; |
|
|
logsList.appendChild(li); |
|
|
logsList.scrollTop = logsList.scrollHeight; |
|
|
} |
|
|
|
|
|
|
|
|
async function callOpenAI(prompt, model = 'gemini-2.5-pro') { |
|
|
console.log('prompt:', prompt) |
|
|
console.log('model:', model) |
|
|
|
|
|
const apiKey = document.getElementById('apiKey').value.trim(); |
|
|
if (!apiKey) { |
|
|
throw new Error('Clé API manquante. Veuillez la saisir dans le champ prévu.'); |
|
|
} |
|
|
|
|
|
try { |
|
|
const response = await fetch('https://llm.synapse.thalescloud.io/v1/chat/completions', { |
|
|
method: 'POST', |
|
|
headers: { |
|
|
'Content-Type': 'application/json', |
|
|
'Authorization': `Bearer ${apiKey}` |
|
|
}, |
|
|
body: JSON.stringify({ |
|
|
model: model, |
|
|
messages: [ |
|
|
{ |
|
|
role: 'user', |
|
|
content: prompt |
|
|
} |
|
|
] |
|
|
}) |
|
|
}); |
|
|
|
|
|
console.log('response:', response) |
|
|
if (!response.ok) { |
|
|
throw new Error(`OpenAI API error: ${response.status}`); |
|
|
} |
|
|
|
|
|
const data = await response.json(); |
|
|
console.log('data:', data) |
|
|
return data.choices[0].message.content.trim(); |
|
|
} catch (error) { |
|
|
console.error('Error calling OpenAI:', error); |
|
|
throw error; |
|
|
} |
|
|
} |
|
|
|
|
|
async function loadAvailableModels() { |
|
|
const apiKey = document.getElementById('apiKey').value.trim(); |
|
|
const modelSelect = document.getElementById('defaultModel'); |
|
|
|
|
|
if (!apiKey) { |
|
|
modelSelect.innerHTML = '<option value="gemini-2.5-pro">gemini-2.5-pro (défaut)</option>'; |
|
|
addLog('Saisissez votre clé API pour charger les modèles disponibles'); |
|
|
return; |
|
|
} |
|
|
|
|
|
try { |
|
|
addLog('Chargement des modèles disponibles...'); |
|
|
const response = await fetch('https://llm.synapse.thalescloud.io/v1/models', { |
|
|
method: 'GET', |
|
|
headers: { |
|
|
'Authorization': `Bearer ${apiKey}` |
|
|
} |
|
|
}); |
|
|
|
|
|
if (!response.ok) { |
|
|
throw new Error(`Erreur API: ${response.status}`); |
|
|
} |
|
|
|
|
|
const data = await response.json(); |
|
|
|
|
|
|
|
|
modelSelect.innerHTML = ''; |
|
|
|
|
|
if (data.data && Array.isArray(data.data)) { |
|
|
data.data.forEach(model => { |
|
|
const option = document.createElement('option'); |
|
|
option.value = model.id; |
|
|
option.textContent = model.id; |
|
|
if (model.id === 'gemini-2.5-pro') { |
|
|
option.selected = true; |
|
|
} |
|
|
modelSelect.appendChild(option); |
|
|
}); |
|
|
addLog(`${data.data.length} modèles chargés`); |
|
|
} else { |
|
|
|
|
|
modelSelect.innerHTML = '<option value="gemini-2.5-pro" selected>gemini-2.5-pro (défaut)</option>'; |
|
|
} |
|
|
|
|
|
} catch (error) { |
|
|
console.error('Erreur lors du chargement des modèles:', error); |
|
|
addLog(`Erreur chargement modèles: ${error.message}`); |
|
|
|
|
|
modelSelect.innerHTML = '<option value="gemini-2.5-pro" selected>gemini-2.5-pro (défaut)</option>'; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
document.getElementById('apiKey').addEventListener('input', debounce(loadAvailableModels, 1000)); |
|
|
|
|
|
|
|
|
function debounce(func, wait) { |
|
|
let timeout; |
|
|
return function executedFunction(...args) { |
|
|
const later = () => { |
|
|
clearTimeout(timeout); |
|
|
func(...args); |
|
|
}; |
|
|
clearTimeout(timeout); |
|
|
timeout = setTimeout(later, wait); |
|
|
}; |
|
|
} |
|
|
function connect() { |
|
|
|
|
|
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; |
|
|
const wsUrl = `${protocol}//${window.location.host}/ws`; |
|
|
ws = new WebSocket(wsUrl); |
|
|
|
|
|
ws.onopen = function() { |
|
|
statusDiv.textContent = 'Connecté'; |
|
|
statusDiv.className = 'connected'; |
|
|
addLog('Connexion WebSocket établie.'); |
|
|
}; |
|
|
|
|
|
ws.onmessage = async function(event) { |
|
|
try { |
|
|
|
|
|
const startTime = performance.now(); |
|
|
let prompt, model; |
|
|
|
|
|
|
|
|
try { |
|
|
const messageData = JSON.parse(event.data); |
|
|
|
|
|
prompt = messageData.prompt; |
|
|
model = messageData.model; |
|
|
} catch (parseError) { |
|
|
|
|
|
|
|
|
|
|
|
console.error('parseError :', parseError); |
|
|
addLog(`Erreur : ${error.message}`); |
|
|
} |
|
|
|
|
|
|
|
|
if (!model) { |
|
|
model = document.getElementById('defaultModel').value || 'gemini-2.5-pro'; |
|
|
} |
|
|
|
|
|
console.log(`Message reçu - Prompt: "${prompt}", Modèle: "${model}"`); |
|
|
addLog(`Message reçu - Prompt: "${prompt}", Modèle: "${model}"`); |
|
|
|
|
|
const responsePhrase = await callOpenAI(prompt, model); |
|
|
|
|
|
const endTime = performance.now(); |
|
|
const duration = (endTime - startTime) / 1000 |
|
|
|
|
|
|
|
|
if (ws.readyState === WebSocket.OPEN) { |
|
|
addLog(`Envoi de la réponse automatique : "${responsePhrase}", durée : "${duration.toFixed(2)}"`); |
|
|
ws.send(responsePhrase); |
|
|
} else { |
|
|
addLog('Erreur: WebSocket fermé pendant l\'appel API'); |
|
|
} |
|
|
|
|
|
} catch (error) { |
|
|
console.error('Erreur traitement message:', error); |
|
|
addLog(`Erreur traitement message: ${error.message}`); |
|
|
} |
|
|
}; |
|
|
|
|
|
ws.onclose = function() { |
|
|
statusDiv.textContent = 'Déconnecté'; |
|
|
statusDiv.className = 'disconnected'; |
|
|
addLog('Connexion WebSocket fermée. Tentative de reconnexion dans 3 secondes...'); |
|
|
|
|
|
setTimeout(connect, 3000); |
|
|
}; |
|
|
|
|
|
ws.onerror = function(error) { |
|
|
addLog('Erreur WebSocket.'); |
|
|
console.error('WebSocket Error:', error); |
|
|
ws.close(); |
|
|
}; |
|
|
} |
|
|
|
|
|
|
|
|
connect(); |
|
|
</script> |
|
|
|
|
|
</body> |
|
|
</html> |