File size: 11,512 Bytes
14f00fb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21974f1
 
 
d224495
 
 
 
 
 
 
21974f1
 
 
14f00fb
 
 
 
 
 
 
 
 
 
21974f1
14f00fb
 
 
 
 
 
21974f1
b7f3059
50cadbb
b7f3059
50cadbb
21974f1
 
 
 
 
 
b7f3059
 
 
 
 
21974f1
b7f3059
 
50cadbb
b7f3059
 
 
 
 
50cadbb
b7f3059
 
21974f1
b7f3059
 
 
 
 
 
 
 
 
 
 
 
 
d224495
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21974f1
d224495
 
 
 
 
 
 
 
 
 
 
 
14f00fb
7d5dfa0
14f00fb
 
 
21974f1
14f00fb
 
 
 
 
21974f1
786de4e
 
32c354e
83089c7
d224495
21974f1
d224495
 
789b8b7
 
d224495
 
 
 
ed04063
 
7183bac
ed04063
d224495
21974f1
d224495
 
 
 
 
 
 
 
50cadbb
32c354e
 
 
21974f1
 
 
c7ab2fb
21974f1
 
 
 
 
d224495
 
 
786de4e
14f00fb
21974f1
14f00fb
 
 
 
7d5dfa0
14f00fb
 
21974f1
14f00fb
 
 
 
 
 
21974f1
7d5dfa0
14f00fb
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
<!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>