| <!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> |