Abmacode12's picture
html
555bfe6 verified
class CustomChat extends HTMLElement {
constructor() {
super();
this._logs = [
"Rosalinda • Démarrage autonome...",
"Chargement des services locaux...",
"Initialisation sécurisée...",
"Prêt à aider ✅"
];
this._messages = [
{ role: "assistant", content: "Bonjour ! Je suis Rosalinda, votre IA de création. Je peux vous aider avec des projets, du code, des images et des vidéos. Que souhaitez-vous créer aujourd'hui ?" }
];
this._files = [];
this._speechState = "idle"; // idle, listening, unsupported, denied
}
connectedCallback() {
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
<style>
:host {
display: none;
flex-direction: column;
height: 100%;
background: #0b0f19;
color: white;
position: relative;
width: 100%;
}
:host(.active) {
display: flex;
}
.chat-container {
flex: 1;
overflow-y: auto;
padding: 1rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.message {
margin-bottom: 0.5rem;
padding: 0.75rem 1rem;
border-radius: 0.75rem;
max-width: 80%;
font-size: 0.875rem;
line-height: 1.4;
}
.user-message {
background: #3b82f6;
margin-left: auto;
border: 1px solid #2563eb;
}
.ai-message {
background: #1e293b;
margin-right: auto;
border: 1px solid #334155;
}
.computer-screen {
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 0.5rem;
margin: 0.5rem;
overflow: hidden;
}
.screen-header {
display: flex;
justify-content: space-between;
padding: 0.5rem 1rem;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
font-size: 0.75rem;
font-weight: 500;
}
.logs-container {
height: 7rem;
overflow-y: auto;
padding: 0.5rem 1rem;
font-family: monospace;
font-size: 0.7rem;
line-height: 1.5;
color: rgba(255, 255, 255, 0.7);
}
.log-line {
white-space: pre-wrap;
margin-bottom: 0.25rem;
}
.input-container {
padding: 1rem;
background: #0f172a;
border-top: 1px solid #1e293b;
position: sticky;
bottom: 0;
}
.input-area {
display: flex;
gap: 0.5rem;
align-items: center;
}
input {
flex: 1;
padding: 0.75rem;
border-radius: 0.5rem;
border: 1px solid #334155;
background: #1e293b;
color: white;
font-size: 0.875rem;
}
.action-button {
width: 2.25rem;
height: 2.25rem;
display: flex;
align-items: center;
justify-content: center;
border-radius: 0.5rem;
background: transparent;
border: 1px solid rgba(255, 255, 255, 0.1);
color: white;
cursor: pointer;
}
.action-button:hover {
background: rgba(255, 255, 255, 0.1);
}
.send-button {
background: #3b82f6;
border: none;
}
.send-button:hover {
background: #2563eb;
}
.mic-button.listening {
background: white;
color: black;
}
.file-input {
display: none;
}
.files-info {
font-size: 0.7rem;
color: rgba(255, 255, 255, 0.6);
padding: 0.25rem 0.5rem;
}
</style>
<div class="chat-container" id="chat">
${this._messages.map(msg => `
<div class="message ${msg.role === 'user' ? 'user-message' : 'ai-message'}">
${msg.content}
</div>
`).join('')}
</div>
<div class="computer-screen">
<div class="screen-header">
<span>Voir l'ordinateur de Espace Codage</span>
<span id="micStatus">En ligne</span>
</div>
<div class="logs-container" id="logsContainer">
${this._logs.map(log => `<div class="log-line">${log}</div>`).join('')}
</div>
</div>
<div class="input-container">
<div class="input-area">
<button class="action-button" title="Ajouter des fichiers" id="fileButton">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none">
<path d="M12 5v14M5 12h14" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
</svg>
</button>
<button class="action-button" title="Connecter des applications" id="connectButton">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none">
<path d="M9 7v5M15 7v5M7 12h10M10 12v4a4 4 0 0 0 4 4h1" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
<path d="M6 7h12" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
</svg>
</button>
<input type="text" class="message-input" placeholder="Envoyer un message à Rosalinda" id="messageInput">
<button class="action-button mic-button" title="Saisie vocale" id="micButton">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none">
<path d="M12 14a3 3 0 0 0 3-3V7a3 3 0 0 0-6 0v4a3 3 0 0 0 3 3Z" stroke="currentColor" stroke-width="2" />
<path d="M19 11a7 7 0 0 1-14 0" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
<path d="M12 18v3" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
<path d="M8 21h8" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
</svg>
</button>
<button class="action-button send-button" title="Envoyer" id="sendButton">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none">
<path d="M5 12h12" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
<path d="M13 6l6 6-6 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</button>
<input type="file" class="file-input" id="fileInput" multiple>
</div>
<div class="files-info" id="filesInfo"></div>
</div>
`;
this._setupEventListeners();
this._startLogsSimulation();
}
_setupEventListeners() {
const shadow = this.shadowRoot;
const fileButton = shadow.getElementById('fileButton');
const fileInput = shadow.getElementById('fileInput');
const connectButton = shadow.getElementById('connectButton');
const micButton = shadow.getElementById('micButton');
const sendButton = shadow.getElementById('sendButton');
const messageInput = shadow.getElementById('messageInput');
const filesInfo = shadow.getElementById('filesInfo');
const micStatus = shadow.getElementById('micStatus');
const chatContainer = shadow.getElementById('chat');
let recognition;
let isListening = false;
// Enhanced microphone functionality
micButton.addEventListener('click', () => {
if (isListening) {
this._stopSpeechRecognition(recognition, micButton, micStatus);
isListening = false;
return;
}
if (!('webkitSpeechRecognition' in window || 'SpeechRecognition' in window)) {
this._addMessage('assistant', 'La reconnaissance vocale n\'est pas supportée par votre navigateur. Essayez Chrome ou Edge.');
return;
}
recognition = this._startSpeechRecognition(micButton, micStatus, messageInput);
isListening = true;
// Add visual feedback
micButton.classList.add('listening');
micStatus.textContent = '🎤 En écoute...';
this._addMessage('assistant', 'Je vous écoute... Parlez maintenant.');
});
// Enhanced file upload with drag & drop
fileButton.addEventListener('click', () => fileInput.click());
fileInput.addEventListener('change', (e) => {
this._handleFiles(e.target.files, filesInfo);
});
// Drag & drop support
chatContainer.addEventListener('dragover', (e) => {
e.preventDefault();
chatContainer.style.border = '2px dashed #3b82f6';
});
chatContainer.addEventListener('dragleave', () => {
chatContainer.style.border = 'none';
});
chatContainer.addEventListener('drop', (e) => {
e.preventDefault();
chatContainer.style.border = 'none';
if (e.dataTransfer.files.length) {
this._handleFiles(e.dataTransfer.files, filesInfo);
}
});
// Connect apps with Rosalinda integration
connectButton.addEventListener('click', () => {
this._addMessage('assistant',
`🔌 Rosalinda peut se connecter à:
- Votre ordinateur (fichiers locaux)
- Votre micro (dictée vocale)
- Votre caméra (analyse visuelle)
- Vos applications préférées
Que souhaitez-vous connecter en premier ?`);
});
// Enhanced send message with files
const sendMessage = () => {
const message = messageInput.value.trim();
if (message || this._files.length) {
this._addMessage('user', message);
messageInput.value = '';
// Simulate Rosalinda processing
setTimeout(() => {
let response;
if (this._files.length > 0) {
response = `J'ai bien reçu ${this._files.length} fichier(s). Je les analyse maintenant...`;
this._files = [];
filesInfo.innerHTML = '';
} else {
response = this._generateRosalindaResponse(message);
}
this._addMessage('assistant', response);
}, 1000);
}
};
// Helper methods
_handleFiles(files, filesInfo) {
this._files = Array.from(files);
if (this._files.length) {
filesInfo.innerHTML = `
<div style="display:flex; gap:0.5rem; align-items:center;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
<polyline points="14 2 14 8 20 8" />
</svg>
${this._files.length} fichier(s) sélectionné(s)
<button id="clearFiles" style="margin-left:auto; background:none; border:none; color:#3b82f6; cursor:pointer;">
Effacer
</button>
</div>
`;
shadow.getElementById('clearFiles').addEventListener('click', () => {
this._files = [];
filesInfo.innerHTML = '';
fileInput.value = '';
});
} else {
filesInfo.textContent = '';
}
}
_generateRosalindaResponse(message) {
if (message.toLowerCase().includes('projet')) {
return `Pour votre projet, voici ce que je propose:
1. Structure claire et modulaire
2. Design cohérent avec votre identité
3. Tests automatisés
4. Documentation complète
Par où commençons-nous ?`;
} else if (message.toLowerCase().includes('image')) {
return "Je peux générer des images personnalisées. Décrivez-moi ce que vous imaginez (couleurs, style, éléments).";
} else if (message.toLowerCase().includes('vidéo')) {
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 ?";
} else {
const responses = [
"J'ai bien compris votre demande. Voici mes suggestions...",
"Analyse terminée. Voici ce que je propose:",
"Excellente idée ! Voici comment nous pourrions procéder:",
"Je peux vous aider avec cela. Voici un plan d'action:"
];
return responses[Math.floor(Math.random() * responses.length)];
}
}
sendButton.addEventListener('click', sendMessage);
messageInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter') sendMessage();
});
}
_addMessage(role, content) {
this._messages.push({ role, content });
this._renderMessages();
}
_renderMessages() {
const chatContainer = this.shadowRoot.getElementById('chat');
chatContainer.innerHTML = this._messages.map(msg => `
<div class="message ${msg.role === 'user' ? 'user-message' : 'ai-message'}">
${msg.content}
</div>
`).join('');
chatContainer.scrollTop = chatContainer.scrollHeight;
}
_startLogsSimulation() {
const logsContainer = this.shadowRoot.getElementById('logsContainer');
setInterval(() => {
const actions = [
'Analyse du projet',
'Optimisation des ressources',
'Génération de contenu',
'Connexion aux services',
'Préparation des résultats'
];
const randomAction = actions[Math.floor(Math.random() * actions.length)];
this._logs.push(`Rosalinda • ${new Date().toLocaleTimeString()}${randomAction}...`);
if (this._logs.length > 20) this._logs.shift();
logsContainer.innerHTML = this._logs.map(log =>
`<div class="log-line">${log}</div>`
).join('');
logsContainer.scrollTop = logsContainer.scrollHeight;
}, 1400);
}
_startSpeechRecognition(micButton, micStatus, messageInput) {
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
const recognition = new SpeechRecognition();
recognition.lang = 'fr-FR';
recognition.interimResults = true;
recognition.continuous = true;
micButton.classList.add('listening');
micStatus.textContent = 'Micro actif...';
recognition.onresult = (event) => {
let transcript = '';
for (let i = event.resultIndex; i < event.results.length; i++) {
transcript += event.results[i][0].transcript;
}
messageInput.value = transcript;
};
recognition.onerror = (event) => {
console.error('Speech recognition error', event.error);
micButton.classList.remove('listening');
micStatus.textContent = 'Erreur micro';
setTimeout(() => micStatus.textContent = 'En ligne', 2000);
};
recognition.onend = () => {
micButton.classList.remove('listening');
micStatus.textContent = 'En ligne';
};
recognition.start();
return recognition;
}
_stopSpeechRecognition(recognition, micButton, micStatus) {
if (recognition) {
recognition.stop();
}
micButton.classList.remove('listening');
micStatus.textContent = 'En ligne';
}
`;
}
}
customElements.define('custom-chat', CustomChat);