Abmacode12 commited on
Commit
c00a577
·
verified ·
1 Parent(s): 3c0491c

Voici une version améliorée de ton code avec plus de fonctionnalités et un meilleur design :

Browse files

html
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Reconnaissance Vocale Française - Version Avancée</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
}

.container {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
border-radius: 24px;
padding: 40px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
max-width: 800px;
width: 100%;
}

.header {
text-align: center;
margin-bottom: 40px;
}

.header h1 {
color: #2d3436;
font-size: 2.8rem;
margin-bottom: 10px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}

.header p {
color: #636e72;
font-size: 1.2rem;
}

.micro-controls {
display: flex;
flex-direction: column;
align-items: center;
gap: 30px;
margin-bottom: 40px;
}

.micro-btn {
width: 120px;
height: 120px;
border-radius: 50%;
border: none;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
font-size: 3.5rem;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
box-shadow: 0 10px 30px rgba(102, 126, 234, 0.4);
}

.micro-btn:hover {
transform: scale(1.05);
box-shadow: 0 15px 40px rgba(102, 126, 234, 0.6);
}

.micro-btn.listening {
animation: pulse 1.5s infinite;
background: linear-gradient(135deg, #ff6b6b 0%, #ee5a24 100%);
box-shadow: 0 10px 30px rgba(255, 107, 107, 0.4);
}

@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.1); }
100% { transform: scale(1); }
}

.status-indicator {
display: flex;
align-items: center;
gap: 15px;
font-size: 1.2rem;
color: #636e72;
}

.status-dot {
width: 12px;
height: 12px;
border-radius: 50%;
background: #4cd137;
}

.status-dot.inactive {
background: #e84118;
}

.status-dot.listening {
background: #fbc531;
animation: blink 1s infinite;
}

@keyframes blink {
0%, 100% { opacity: 1; }
50% { opacity: 0.3; }
}

.controls {
display: flex;
justify-content: center;
gap: 20px;
margin-bottom: 40px;
}

.control-btn {
padding: 12px 28px;
border: none;
border-radius: 12px;
background: #f5f6fa;
color: #2d3436;
font-size: 1rem;
cursor: pointer;
display: flex;
align-items: center;
gap: 10px;
transition: all 0.3s ease;
}

.control-btn:hover {
background: #dfe6e9;
transform: translateY(-2px);
}

.control-btn i {
font-size: 1.2rem;
}

.result-container {
background: #f8f9fa;
border-radius: 16px;
padding: 30px;
margin-bottom: 30px;
border: 2px solid #e9ecef;
min-height: 200px;
}

.result-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}

.result-header h2 {
color: #2d3436;
font-size: 1.5rem;
}

.result-content {
font-size: 1.3rem;
line-height: 1.6;
color: #2d3436;
min-height: 100px;
white-space: pre-wrap;
word-wrap: break-word;
padding: 15px;
background: white;
border-radius: 10px;
border: 1px solid #dfe6e9;
}

.stats {
display: flex;
justify-content: space-between;
background: #f8f9fa;
padding: 20px;
border-radius: 16px;
margin-top: 20px;
}

.stat-item {
text-align: center;
}

.stat-value {
font-size: 2rem;
font-weight: bold;
color: #667eea;
display: block;
}

.stat-label {
color: #636e72;
font-size: 0.9rem;
}

.notification {
position: fixed;
bottom: 20px;
right: 20px;
background: #00b894;
color: white;
padding: 15px 25px;
border-radius: 10px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
display: none;
z-index: 1000;
}

@media (max-width: 768px) {
.container {
padding: 20px;
}

.header h1 {
font-size: 2rem;
}

.controls {
flex-direction: column;
align-items: center;
}

.micro-btn {
width: 100px;
height: 100px;
font-size: 2.8rem;
}

.stats {
flex-direction: column;
gap: 15px;
}
}
</style>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
</head>
<body>
<div class="container">
<div class="header">
<h1><i class="fas fa-microphone-alt"></i> Reconnaissance Vocale Française</h1>
<p>Parlez, le système transcrit automatiquement en texte</p>
</div>

<div class="micro-controls">
<button id="micro-btn" class="micro-btn" aria-label="Activer/désactiver le microphone">
<i class="fas fa-microphone"></i>
</button>

<div class="status-indicator">
<div id="status-dot" class="status-dot inactive"></div>
<span id="status-text">Microphone désactivé</span>
</div>
</div>

<div class="controls">
<button id="clear-btn" class="control-btn">
<i class="fas fa-trash-alt"></i> Effacer tout
</button>
<button id="copy-btn" class="control-btn">
<i class="fas fa-copy"></i> Copier le texte
</button>
<button id="save-btn" class="control-btn">
<i class="fas fa-save"></i> Sauvegarder
</button>
</div>

<div class="result-container">
<div class="result-header">
<h2><i class="fas fa-comment-alt"></i> Texte transcrit</h2>
<div class="language-selector">
<select id="language-select" style="padding: 8px; border-radius: 8px; border: 1px solid #ddd;">
<option value="fr-FR">Français (France)</option>
<option value="fr-CA">Français (Canada)</option>
<option value="fr-BE">Français (Belgique)</option>
<option value="fr-CH">Français (Suisse)</option>
<option value="en-US">Anglais (USA)</option>
<option value="es-ES">Espagnol (Espagne)</option>
</select>
</div>
</div>
<div id="result" class="result-content" aria-live="polite" aria-atomic="true">
Votre texte apparaîtra ici...
</div>
</div>

<div class="stats">
<div class="stat-item">
<span id="word-count" class="stat-value">0</span>
<span class="stat-label">Mots</span>
</div>
<div class="stat-item">
<span id="char-count" class="stat-value">0</span>
<span class="stat-label">Caractères</span>
</div>
<div class="stat-item">
<span id="line-count" class="stat-value">0</span>
<span class="stat-label">Lignes</span>
</div>
<div class="stat-item">
<span id="session-count" class="stat-value">0</span>
<span class="stat-label">Sessions</span>
</div>
</div>
</div>

<div id="notification" class="notification">
<i class="fas fa-check-circle"></i> Texte copié dans le presse-papier !
</div>

<script>
// Éléments DOM
const btn = document.getElementById('micro-btn');
const resultDiv = document.getElementById('result');
const statusDot = document.getElementById('status-dot');
const statusText = document.getElementById('status-text');
const clearBtn = document.getElementById('clear-btn');
const c

Files changed (3) hide show
  1. index.html +2 -2
  2. script.js +182 -2
  3. styles.css +14 -0
index.html CHANGED
@@ -94,10 +94,10 @@
94
  <i class="fas fa-plug"></i>
95
  </button>
96
  <input type="text" id="chatInput" placeholder="Écrire à Rosalinda..." />
97
- <button id="micBtn" title="Micro">
98
  <i class="fas fa-microphone"></i>
99
  </button>
100
- <button id="sendBtn" title="Envoyer">
101
  <i class="fas fa-paper-plane"></i>
102
  </button>
103
  </div>
 
94
  <i class="fas fa-plug"></i>
95
  </button>
96
  <input type="text" id="chatInput" placeholder="Écrire à Rosalinda..." />
97
+ <button id="micBtn" title="Activer/Désactiver le micro (Ctrl+Espace)">
98
  <i class="fas fa-microphone"></i>
99
  </button>
100
+ <button id="sendBtn" title="Envoyer">
101
  <i class="fas fa-paper-plane"></i>
102
  </button>
103
  </div>
script.js CHANGED
@@ -193,8 +193,188 @@ sendBtn.addEventListener('click', sendMessage);
193
  chatInput.addEventListener('keydown', (e) => {
194
  if (e.key === 'Enter') sendMessage();
195
  });
196
- micBtn.addEventListener('click', () => {
197
- alert('Fonction "Micro" à implémenter (Web Speech API, Whisper, etc.).');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
198
  });
199
  connectBtn.addEventListener('click', () => {
200
  alert('Fonction "Connexion" à implémenter (OAuth, clés API, etc.).');
 
193
  chatInput.addEventListener('keydown', (e) => {
194
  if (e.key === 'Enter') sendMessage();
195
  });
196
+ const micBtn = document.getElementById('micBtn');
197
+ const languageSelect = document.createElement('select');
198
+ languageSelect.id = 'languageSelect';
199
+ languageSelect.style.marginLeft = '8px';
200
+ languageSelect.style.padding = '6px 8px';
201
+ languageSelect.style.borderRadius = '6px';
202
+ languageSelect.style.border = '1px solid rgb(226 232 240)';
203
+ languageSelect.style.background = 'rgb(248 250 252)';
204
+ languageSelect.style.color = 'inherit';
205
+ languageSelect.innerHTML = `
206
+ <option value="fr-FR">Français (France)</option>
207
+ <option value="fr-CA">Français (Canada)</option>
208
+ <option value="fr-BE">Français (Belgique)</option>
209
+ <option value="fr-CH">Français (Suisse)</option>
210
+ <option value="en-US">English (US)</option>
211
+ <option value="es-ES">Español (España)</option>
212
+ `;
213
+ const chatInputParent = chatInput.parentElement;
214
+ if (chatInputParent && !document.getElementById('languageSelect')) {
215
+ chatInputParent.insertBefore(languageSelect, chatInput);
216
+ }
217
+
218
+ let isListening = false;
219
+ let recognition = null;
220
+ let interimTranscript = '';
221
+ let lastFinalTranscript = '';
222
+ const SpeechRecognitionAPI = window.SpeechRecognition || window.webkitSpeechRecognition;
223
+
224
+ function initSpeechRecognition() {
225
+ if (!SpeechRecognitionAPI) {
226
+ aiActivityText.textContent = 'Reconnaissance vocale non supportée';
227
+ aiActivityDot.style.background = 'rgb(239,68,68)';
228
+ micBtn.disabled = true;
229
+ micBtn.title = 'Navigateur non compatible';
230
+ micBtn.style.opacity = '0.6';
231
+ micBtn.style.cursor = 'not-allowed';
232
+ return;
233
+ }
234
+ recognition = new SpeechRecognitionAPI();
235
+ recognition.lang = languageSelect.value;
236
+ recognition.continuous = true;
237
+ recognition.interimResults = true;
238
+ recognition.maxAlternatives = 1;
239
+
240
+ recognition.onstart = () => {
241
+ isListening = true;
242
+ micBtn.classList.add('listening');
243
+ micBtn.innerHTML = '<i class="fas fa-microphone-slash"></i>';
244
+ aiActivityText.textContent = 'Écoute en cours...';
245
+ aiActivityDot.style.background = 'rgb(234,179,8)';
246
+ setAIActivity('Écoute en cours...', true);
247
+ };
248
+
249
+ recognition.onend = () => {
250
+ isListening = false;
251
+ micBtn.classList.remove('listening');
252
+ micBtn.innerHTML = '<i class="fas fa-microphone"></i>';
253
+ aiActivityText.textContent = 'IA en veille';
254
+ aiActivityDot.style.background = 'rgb(100,116,139)';
255
+ setAIActivity('IA en veille', false);
256
+ interimTranscript = '';
257
+ };
258
+
259
+ recognition.onerror = (event) => {
260
+ console.error('SpeechRecognition error:', event.error);
261
+ let message = 'Erreur de reconnaissance vocale.';
262
+ switch (event.error) {
263
+ case 'no-speech':
264
+ message = 'Aucune parole détectée. Essayez de parler plus fort.';
265
+ break;
266
+ case 'audio-capture':
267
+ message = 'Aucun microphone détecté.';
268
+ break;
269
+ case 'not-allowed':
270
+ message = 'Accès au microphone refusé.';
271
+ break;
272
+ case 'network':
273
+ message = 'Erreur réseau.';
274
+ break;
275
+ }
276
+ addMessage('ai', message);
277
+ recognition.stop();
278
+ };
279
+
280
+ recognition.onresult = (event) => {
281
+ interimTranscript = '';
282
+ for (let i = event.resultIndex; i < event.results.length; i++) {
283
+ const transcript = event.results[i][0].transcript.trim();
284
+ if (event.results[i].isFinal) {
285
+ lastFinalTranscript = transcript;
286
+ addMessage('user', transcript);
287
+ setAIActivity('Rosalinda réfléchit...', true);
288
+ sendToAI(transcript);
289
+ } else {
290
+ interimTranscript += transcript + ' ';
291
+ }
292
+ }
293
+ if (interimTranscript) {
294
+ codeOutput.textContent = `// En écoute...\n${interimTranscript}`;
295
+ }
296
+ };
297
+ }
298
+
299
+ function sendToAI(text) {
300
+ fetch('https://api.openai.com/v1/chat/completions', {
301
+ method: 'POST',
302
+ headers: {
303
+ 'Content-Type': 'application/json',
304
+ 'Authorization': `Bearer ${OPENAI_API_KEY}`
305
+ },
306
+ body: JSON.stringify({
307
+ model: 'gpt-3.5-turbo',
308
+ messages: [
309
+ { role: 'system', content: 'Tu es Rosalinda, une assistante IA utile et concise.' },
310
+ ...getChatHistory(),
311
+ { role: 'user', content: text }
312
+ ],
313
+ temperature: 0.7
314
+ })
315
+ })
316
+ .then(response => {
317
+ if (!response.ok) throw new Error(`Erreur API: ${response.status}`);
318
+ return response.json();
319
+ })
320
+ .then(data => {
321
+ const assistantMessage = data.choices?.[0]?.message?.content ?? 'Désolé, aucune réponse.';
322
+ addMessage('ai', assistantMessage);
323
+ codeOutput.textContent = assistantMessage;
324
+ })
325
+ .catch(error => {
326
+ console.error(error);
327
+ addMessage('ai', 'Une erreur est survenue lors de la communication avec l\'IA.');
328
+ })
329
+ .finally(() => {
330
+ setAIActivity('IA en veille', false);
331
+ });
332
+ }
333
+
334
+ async function toggleListening() {
335
+ if (!recognition) initSpeechRecognition();
336
+ if (!recognition) return;
337
+
338
+ try {
339
+ if (isListening) {
340
+ recognition.stop();
341
+ } else {
342
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true }).catch(() => null);
343
+ if (!stream) {
344
+ addMessage('ai', 'Impossible d\'accéder au microphone. Vérifiez les permissions.');
345
+ return;
346
+ }
347
+ stream.getTracks().forEach(t => t.stop());
348
+ recognition.start();
349
+ }
350
+ } catch (err) {
351
+ console.error(err);
352
+ addMessage('ai', 'Erreur lors du démarrage de la reconnaissance vocale.');
353
+ }
354
+ }
355
+
356
+ function stopListening() {
357
+ if (recognition && isListening) recognition.stop();
358
+ }
359
+
360
+ micBtn.addEventListener('click', toggleListening);
361
+ languageSelect.addEventListener('change', () => {
362
+ if (recognition) recognition.lang = languageSelect.value;
363
+ });
364
+
365
+ document.addEventListener('keydown', (e) => {
366
+ if (e.code === 'Space' && (e.ctrlKey || e.metaKey)) {
367
+ e.preventDefault();
368
+ toggleListening();
369
+ }
370
+ if (e.key === 'Escape' && isListening) {
371
+ stopListening();
372
+ }
373
+ });
374
+
375
+ // Stop listening on tab change to avoid dangling permissions
376
+ document.addEventListener('visibilitychange', () => {
377
+ if (document.hidden) stopListening();
378
  });
379
  connectBtn.addEventListener('click', () => {
380
  alert('Fonction "Connexion" à implémenter (OAuth, clés API, etc.).');
styles.css CHANGED
@@ -181,6 +181,20 @@ body { margin: 0; font-family: Inter, ui-sans-serif, system-ui, -apple-system, S
181
  .chat-input button:hover { background: rgb(241 245 249); }
182
  .dark .chat-input button:hover { background: rgb(30 41 59); }
183
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
184
  /* Colonne 3 : Output */
185
  .output {
186
  width: 35%;
 
181
  .chat-input button:hover { background: rgb(241 245 249); }
182
  .dark .chat-input button:hover { background: rgb(30 41 59); }
183
 
184
+ /* Micro button listening state */
185
+ #micBtn.listening {
186
+ background: rgb(239, 68, 68); /* red-500 */
187
+ border-color: rgb(239, 68, 68);
188
+ animation: micPulse 1.2s infinite;
189
+ }
190
+ @keyframes micPulse {
191
+ 0% { transform: scale(1); box-shadow: 0 0 0 0 rgba(239,68,68,0.6); }
192
+ 70% { transform: scale(1.03); box-shadow: 0 0 0 10px rgba(239,68,68,0); }
193
+ 100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(239,68,68,0); }
194
+ }
195
+
196
+ /* Small status dot improvements (used for activity too) */
197
+ .dot.listening { background: rgb(234,179,8); } /* amber-500 */
198
  /* Colonne 3 : Output */
199
  .output {
200
  width: 35%;