ReOpenAI / static /index.html
YchKhan's picture
Update static/index.html
c7ab2fb verified
<!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; // Auto-scroll
}
// OpenAI API Call for private mode
async function callOpenAI(prompt, model = 'gemini-2.5-pro') {
console.log('prompt:', prompt)
console.log('model:', model)
// Récupérer la clé API depuis l'input
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;
}
}
// Fonction pour récupérer la liste des modèles
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();
// Vider le select et ajouter les options
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; // Sélectionner par défaut
}
modelSelect.appendChild(option);
});
addLog(`${data.data.length} modèles chargés`);
} else {
// Fallback si la structure de réponse est différente
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}`);
// Ajouter une option par défaut en cas d'erreur
modelSelect.innerHTML = '<option value="gemini-2.5-pro" selected>gemini-2.5-pro (défaut)</option>';
}
}
// Écouter les changements sur le champ API Key pour recharger les modèles
document.getElementById('apiKey').addEventListener('input', debounce(loadAvailableModels, 1000));
// Fonction debounce pour éviter trop d'appels
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
function connect() {
// Adapte le protocole (ws ou wss pour le sécurisé)
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;
// Essayer de parser comme JSON (nouveau format)
try {
const messageData = JSON.parse(event.data);
// const messageData = event.data;
prompt = messageData.prompt;
model = messageData.model;
} catch (parseError) {
// Si ce n'est pas du JSON, traiter comme ancien format (string simple)
// prompt = event.data;
// model = null;
console.error('parseError :', parseError);
addLog(`Erreur : ${error.message}`);
}
// Si aucun modèle spécifié, utiliser celui sélectionné dans l'UI
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
// Vérifier à nouveau que la connexion est ouverte
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...');
// Tente de se reconnecter après 3 secondes
setTimeout(connect, 3000);
};
ws.onerror = function(error) {
addLog('Erreur WebSocket.');
console.error('WebSocket Error:', error);
ws.close();
};
}
// Lance la connexion au chargement de la page
connect();
</script>
</body>
</html>