Spaces:
Sleeping
Sleeping
| <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> |