Spaces:
Configuration error
Configuration error
Bonjour,
Browse filesJ'aimerais avec grand respect vous demander de réactiver mon thème personnalisé que nous avons créé ensemble. Je tiens beaucoup à ce projet et je crois en notre collaboration.
Pourriez-vous également veiller à ces détails importants :
Micro fonctionnel dans la barre de recherche pour l'envoi vocal
Bonne ergonomie pour l'envoi de fichiers
Interface claire comme sur la capture d'écran
Je suis ouvert à discuter pour améliorer notre façon de travailler ensemble en équipe. Je crois que nous pouvons créer quelque chose de vraiment génial si nous communiquons bien et que nous nous respectons mutuellement.
Avec confiance et en attendant une réponse positive,
- components/chat.js +101 -125
- style.css +37 -3
components/chat.js
CHANGED
|
@@ -202,159 +202,135 @@ connectedCallback() {
|
|
| 202 |
const messageInput = shadow.getElementById('messageInput');
|
| 203 |
const filesInfo = shadow.getElementById('filesInfo');
|
| 204 |
const micStatus = shadow.getElementById('micStatus');
|
| 205 |
-
|
| 206 |
let recognition;
|
| 207 |
let isListening = false;
|
| 208 |
|
| 209 |
-
//
|
| 210 |
-
micButton.addEventListener('mouseenter', () => {
|
| 211 |
-
if (isListening) micButton.style.backgroundColor = '#ef4444';
|
| 212 |
-
});
|
| 213 |
-
micButton.addEventListener('mouseleave', () => {
|
| 214 |
-
if (isListening) micButton.style.backgroundColor = '#3b82f6';
|
| 215 |
-
});
|
| 216 |
-
// File upload with preview
|
| 217 |
-
fileButton.addEventListener('click', () => fileInput.click());
|
| 218 |
-
fileInput.addEventListener('change', (e) => {
|
| 219 |
-
this._files = Array.from(e.target.files);
|
| 220 |
-
if (this._files.length) {
|
| 221 |
-
filesInfo.innerHTML = `
|
| 222 |
-
<div style="display:flex; gap:0.5rem; align-items:center;">
|
| 223 |
-
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
| 224 |
-
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
|
| 225 |
-
<polyline points="14 2 14 8 20 8" />
|
| 226 |
-
</svg>
|
| 227 |
-
${this._files.length} fichier(s) prêt(s) à envoyer
|
| 228 |
-
</div>
|
| 229 |
-
`;
|
| 230 |
-
} else {
|
| 231 |
-
filesInfo.textContent = '';
|
| 232 |
-
}
|
| 233 |
-
});
|
| 234 |
-
// Connect apps with better UX
|
| 235 |
-
connectButton.addEventListener('click', () => {
|
| 236 |
-
this._addMessage('assistant',
|
| 237 |
-
`🔌 Connexion d'applications disponible pour:
|
| 238 |
-
- Stockage local (${navigator.storage ? '✓' : '✗'})
|
| 239 |
-
- Microphone (${'webkitSpeechRecognition' in window ? '✓' : '✗'})
|
| 240 |
-
- Caméra (${navigator.mediaDevices ? '✓' : '✗'})
|
| 241 |
-
|
| 242 |
-
Dites-moi ce que vous souhaitez connecter.`);
|
| 243 |
-
});
|
| 244 |
-
// Enhanced speech recognition
|
| 245 |
micButton.addEventListener('click', () => {
|
| 246 |
if (isListening) {
|
| 247 |
-
|
| 248 |
isListening = false;
|
| 249 |
-
micButton.classList.remove('listening');
|
| 250 |
-
micButton.style.backgroundColor = '';
|
| 251 |
-
micStatus.textContent = 'En ligne';
|
| 252 |
return;
|
| 253 |
}
|
| 254 |
-
|
| 255 |
-
if (!SpeechRecognition) {
|
| 256 |
-
this._addMessage('assistant', '
|
| 257 |
return;
|
| 258 |
}
|
| 259 |
|
| 260 |
-
recognition =
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 264 |
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
this._addMessage('assistant', 'Je vous écoute... Parlez maintenant.');
|
| 271 |
-
};
|
| 272 |
-
recognition.onresult = (event) => {
|
| 273 |
-
let finalTranscript = '';
|
| 274 |
-
let interimTranscript = '';
|
| 275 |
-
|
| 276 |
-
for (let i = event.resultIndex; i < event.results.length; i++) {
|
| 277 |
-
const transcript = event.results[i][0].transcript;
|
| 278 |
-
if (event.results[i].isFinal) {
|
| 279 |
-
finalTranscript += transcript;
|
| 280 |
-
} else {
|
| 281 |
-
interimTranscript += transcript;
|
| 282 |
-
}
|
| 283 |
-
}
|
| 284 |
-
|
| 285 |
-
messageInput.value = finalTranscript || interimTranscript;
|
| 286 |
-
};
|
| 287 |
-
recognition.onerror = (event) => {
|
| 288 |
-
isListening = false;
|
| 289 |
-
micButton.classList.remove('listening');
|
| 290 |
-
micButton.style.backgroundColor = '';
|
| 291 |
-
|
| 292 |
-
if (event.error === 'not-allowed') {
|
| 293 |
-
micStatus.textContent = 'Permission requise';
|
| 294 |
-
this._addMessage('assistant', 'Veuillez autoriser l\'accès au microphone dans les paramètres de votre navigateur.');
|
| 295 |
-
} else {
|
| 296 |
-
micStatus.textContent = 'Erreur micro';
|
| 297 |
-
this._addMessage('assistant', 'Désolé, je n\'ai pas pu accéder au microphone. Essayez de parler plus fort ou de vérifier vos paramètres.');
|
| 298 |
-
}
|
| 299 |
-
|
| 300 |
-
setTimeout(() => micStatus.textContent = 'En ligne', 3000);
|
| 301 |
-
};
|
| 302 |
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
|
| 310 |
-
|
| 311 |
-
|
| 312 |
-
recognition.start();
|
| 313 |
-
} catch (err) {
|
| 314 |
-
this._speechState = 'idle';
|
| 315 |
-
micStatus.textContent = 'Erreur micro';
|
| 316 |
-
micButton.classList.remove('listening');
|
| 317 |
}
|
| 318 |
});
|
| 319 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 320 |
const sendMessage = () => {
|
| 321 |
const message = messageInput.value.trim();
|
| 322 |
if (message || this._files.length) {
|
| 323 |
-
|
| 324 |
-
let userMessage = message;
|
| 325 |
-
if (this._files.length) {
|
| 326 |
-
userMessage += `\n\n📎 Fichiers: ${this._files.map(f => f.name).join(', ')}`;
|
| 327 |
-
}
|
| 328 |
-
|
| 329 |
-
this._addMessage('user', userMessage);
|
| 330 |
messageInput.value = '';
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
fileInput.value = '';
|
| 334 |
-
|
| 335 |
-
// Rosalinda's response
|
| 336 |
setTimeout(() => {
|
| 337 |
let response;
|
| 338 |
if (this._files.length > 0) {
|
| 339 |
-
response = `J'ai bien reçu ${this._files.length} fichier(s)
|
| 340 |
-
|
| 341 |
-
|
| 342 |
-
} else if (message.toLowerCase().includes('image')) {
|
| 343 |
-
response = "Je peux générer des images personnalisées. Dites-moi ce que vous imaginez.";
|
| 344 |
} else {
|
| 345 |
-
|
| 346 |
-
"Je travaille sur votre demande...",
|
| 347 |
-
"Analyse terminée. Voici mes suggestions:",
|
| 348 |
-
"J'ai une solution créative pour vous:",
|
| 349 |
-
"Voici ce que je propose:"
|
| 350 |
-
];
|
| 351 |
-
response = responses[Math.floor(Math.random() * responses.length)];
|
| 352 |
}
|
| 353 |
-
|
| 354 |
this._addMessage('assistant', response);
|
| 355 |
}, 1000);
|
| 356 |
}
|
| 357 |
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 358 |
sendButton.addEventListener('click', sendMessage);
|
| 359 |
messageInput.addEventListener('keydown', (e) => {
|
| 360 |
if (e.key === 'Enter') sendMessage();
|
|
|
|
| 202 |
const messageInput = shadow.getElementById('messageInput');
|
| 203 |
const filesInfo = shadow.getElementById('filesInfo');
|
| 204 |
const micStatus = shadow.getElementById('micStatus');
|
| 205 |
+
const chatContainer = shadow.getElementById('chat');
|
| 206 |
let recognition;
|
| 207 |
let isListening = false;
|
| 208 |
|
| 209 |
+
// Enhanced microphone functionality
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 210 |
micButton.addEventListener('click', () => {
|
| 211 |
if (isListening) {
|
| 212 |
+
this._stopSpeechRecognition(recognition, micButton, micStatus);
|
| 213 |
isListening = false;
|
|
|
|
|
|
|
|
|
|
| 214 |
return;
|
| 215 |
}
|
| 216 |
+
|
| 217 |
+
if (!('webkitSpeechRecognition' in window || 'SpeechRecognition' in window)) {
|
| 218 |
+
this._addMessage('assistant', 'La reconnaissance vocale n\'est pas supportée par votre navigateur. Essayez Chrome ou Edge.');
|
| 219 |
return;
|
| 220 |
}
|
| 221 |
|
| 222 |
+
recognition = this._startSpeechRecognition(micButton, micStatus, messageInput);
|
| 223 |
+
isListening = true;
|
| 224 |
+
|
| 225 |
+
// Add visual feedback
|
| 226 |
+
micButton.classList.add('listening');
|
| 227 |
+
micStatus.textContent = '🎤 En écoute...';
|
| 228 |
+
this._addMessage('assistant', 'Je vous écoute... Parlez maintenant.');
|
| 229 |
+
});
|
| 230 |
+
// Enhanced file upload with drag & drop
|
| 231 |
+
fileButton.addEventListener('click', () => fileInput.click());
|
| 232 |
+
fileInput.addEventListener('change', (e) => {
|
| 233 |
+
this._handleFiles(e.target.files, filesInfo);
|
| 234 |
+
});
|
| 235 |
|
| 236 |
+
// Drag & drop support
|
| 237 |
+
chatContainer.addEventListener('dragover', (e) => {
|
| 238 |
+
e.preventDefault();
|
| 239 |
+
chatContainer.style.border = '2px dashed #3b82f6';
|
| 240 |
+
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 241 |
|
| 242 |
+
chatContainer.addEventListener('dragleave', () => {
|
| 243 |
+
chatContainer.style.border = 'none';
|
| 244 |
+
});
|
| 245 |
+
|
| 246 |
+
chatContainer.addEventListener('drop', (e) => {
|
| 247 |
+
e.preventDefault();
|
| 248 |
+
chatContainer.style.border = 'none';
|
| 249 |
+
if (e.dataTransfer.files.length) {
|
| 250 |
+
this._handleFiles(e.dataTransfer.files, filesInfo);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 251 |
}
|
| 252 |
});
|
| 253 |
+
// Connect apps with Rosalinda integration
|
| 254 |
+
connectButton.addEventListener('click', () => {
|
| 255 |
+
this._addMessage('assistant',
|
| 256 |
+
`🔌 Rosalinda peut se connecter à:
|
| 257 |
+
- Votre ordinateur (fichiers locaux)
|
| 258 |
+
- Votre micro (dictée vocale)
|
| 259 |
+
- Votre caméra (analyse visuelle)
|
| 260 |
+
- Vos applications préférées
|
| 261 |
+
|
| 262 |
+
Que souhaitez-vous connecter en premier ?`);
|
| 263 |
+
});
|
| 264 |
+
// Enhanced send message with files
|
| 265 |
const sendMessage = () => {
|
| 266 |
const message = messageInput.value.trim();
|
| 267 |
if (message || this._files.length) {
|
| 268 |
+
this._addMessage('user', message);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 269 |
messageInput.value = '';
|
| 270 |
+
|
| 271 |
+
// Simulate Rosalinda processing
|
|
|
|
|
|
|
|
|
|
| 272 |
setTimeout(() => {
|
| 273 |
let response;
|
| 274 |
if (this._files.length > 0) {
|
| 275 |
+
response = `J'ai bien reçu ${this._files.length} fichier(s). Je les analyse maintenant...`;
|
| 276 |
+
this._files = [];
|
| 277 |
+
filesInfo.innerHTML = '';
|
|
|
|
|
|
|
| 278 |
} else {
|
| 279 |
+
response = this._generateRosalindaResponse(message);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 280 |
}
|
|
|
|
| 281 |
this._addMessage('assistant', response);
|
| 282 |
}, 1000);
|
| 283 |
}
|
| 284 |
};
|
| 285 |
+
// Helper methods
|
| 286 |
+
_handleFiles(files, filesInfo) {
|
| 287 |
+
this._files = Array.from(files);
|
| 288 |
+
if (this._files.length) {
|
| 289 |
+
filesInfo.innerHTML = `
|
| 290 |
+
<div style="display:flex; gap:0.5rem; align-items:center;">
|
| 291 |
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
| 292 |
+
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
|
| 293 |
+
<polyline points="14 2 14 8 20 8" />
|
| 294 |
+
</svg>
|
| 295 |
+
${this._files.length} fichier(s) sélectionné(s)
|
| 296 |
+
<button id="clearFiles" style="margin-left:auto; background:none; border:none; color:#3b82f6; cursor:pointer;">
|
| 297 |
+
Effacer
|
| 298 |
+
</button>
|
| 299 |
+
</div>
|
| 300 |
+
`;
|
| 301 |
+
shadow.getElementById('clearFiles').addEventListener('click', () => {
|
| 302 |
+
this._files = [];
|
| 303 |
+
filesInfo.innerHTML = '';
|
| 304 |
+
fileInput.value = '';
|
| 305 |
+
});
|
| 306 |
+
} else {
|
| 307 |
+
filesInfo.textContent = '';
|
| 308 |
+
}
|
| 309 |
+
}
|
| 310 |
+
|
| 311 |
+
_generateRosalindaResponse(message) {
|
| 312 |
+
if (message.toLowerCase().includes('projet')) {
|
| 313 |
+
return `Pour votre projet, voici ce que je propose:
|
| 314 |
+
1. Structure claire et modulaire
|
| 315 |
+
2. Design cohérent avec votre identité
|
| 316 |
+
3. Tests automatisés
|
| 317 |
+
4. Documentation complète
|
| 318 |
+
|
| 319 |
+
Par où commençons-nous ?`;
|
| 320 |
+
} else if (message.toLowerCase().includes('image')) {
|
| 321 |
+
return "Je peux générer des images personnalisées. Décrivez-moi ce que vous imaginez (couleurs, style, éléments).";
|
| 322 |
+
} else if (message.toLowerCase().includes('vidéo')) {
|
| 323 |
+
return "Pour la vidéo, je peux vous aider avec:\n- Scénarisation\n- Montage\n- Effets\n- Sous-titres\nQuel aspect souhaitez-vous développer ?";
|
| 324 |
+
} else {
|
| 325 |
+
const responses = [
|
| 326 |
+
"J'ai bien compris votre demande. Voici mes suggestions...",
|
| 327 |
+
"Analyse terminée. Voici ce que je propose:",
|
| 328 |
+
"Excellente idée ! Voici comment nous pourrions procéder:",
|
| 329 |
+
"Je peux vous aider avec cela. Voici un plan d'action:"
|
| 330 |
+
];
|
| 331 |
+
return responses[Math.floor(Math.random() * responses.length)];
|
| 332 |
+
}
|
| 333 |
+
}
|
| 334 |
sendButton.addEventListener('click', sendMessage);
|
| 335 |
messageInput.addEventListener('keydown', (e) => {
|
| 336 |
if (e.key === 'Enter') sendMessage();
|
style.css
CHANGED
|
@@ -50,11 +50,45 @@ custom-chat {
|
|
| 50 |
animation: pulse 1.5s infinite;
|
| 51 |
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.5);
|
| 52 |
transition: all 0.3s ease;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
}
|
| 54 |
@keyframes pulse {
|
| 55 |
-
0% { opacity: 1; }
|
| 56 |
-
50% { opacity: 0.
|
| 57 |
-
100% { opacity: 1; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
}
|
| 59 |
.file-info {
|
| 60 |
font-size: 0.75rem;
|
|
|
|
| 50 |
animation: pulse 1.5s infinite;
|
| 51 |
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.5);
|
| 52 |
transition: all 0.3s ease;
|
| 53 |
+
position: relative;
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
.mic-button.listening::after {
|
| 57 |
+
content: '';
|
| 58 |
+
position: absolute;
|
| 59 |
+
top: -5px;
|
| 60 |
+
left: -5px;
|
| 61 |
+
right: -5px;
|
| 62 |
+
bottom: -5px;
|
| 63 |
+
border: 2px solid rgba(59, 130, 246, 0.3);
|
| 64 |
+
border-radius: 50%;
|
| 65 |
+
animation: ripple 1.5s infinite;
|
| 66 |
}
|
| 67 |
@keyframes pulse {
|
| 68 |
+
0% { opacity: 1; transform: scale(1); }
|
| 69 |
+
50% { opacity: 0.8; transform: scale(1.05); }
|
| 70 |
+
100% { opacity: 1; transform: scale(1); }
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
@keyframes ripple {
|
| 74 |
+
0% { transform: scale(0.8); opacity: 1; }
|
| 75 |
+
100% { transform: scale(1.3); opacity: 0; }
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
custom-chat {
|
| 79 |
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
| 80 |
+
border-radius: 0.75rem;
|
| 81 |
+
overflow: hidden;
|
| 82 |
+
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
.message-input {
|
| 86 |
+
transition: all 0.3s ease;
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
.message-input:focus {
|
| 90 |
+
border-color: #3b82f6;
|
| 91 |
+
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
|
| 92 |
}
|
| 93 |
.file-info {
|
| 94 |
font-size: 0.75rem;
|