espace-codage-theme / script.js
Abmacode12's picture
Voici une version améliorée de ton code avec plus de fonctionnalités et un meilleur design :
c00a577 verified
console.log("Interface Espace Codage chargée.");
// ===== Configuration API OpenAI =====
const OPENAI_API_KEY = "sk-votre_cle_api_ici"; // Remplacez par votre clé API
// ===== Thème (light/dark) =====
const themeToggle = document.getElementById('themeToggle');
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)');
const storedTheme = localStorage.getItem('theme'); // 'dark' | 'light' | null
const setTheme = (mode) => {
const html = document.documentElement;
if (mode === 'dark') {
html.classList.add('dark');
localStorage.setItem('theme', 'dark');
themeToggle.innerHTML = '<i class="fa-solid fa-sun"></i>';
} else {
html.classList.remove('dark');
localStorage.setItem('theme', 'light');
themeToggle.innerHTML = '<i class="fa-solid fa-moon"></i>';
}
};
setTheme(storedTheme ? storedTheme : (prefersDark.matches ? 'dark' : 'light'));
themeToggle.addEventListener('click', () => {
const current = document.documentElement.classList.contains('dark') ? 'dark' : 'light';
setTheme(current === 'dark' ? 'light' : 'dark');
});
prefersDark.addEventListener('change', (e) => {
if (!localStorage.getItem('theme')) {
setTheme(e.matches ? 'dark' : 'light');
}
});
// ===== Éléments du DOM =====
const projectList = document.getElementById('projectList');
const chatMessages = document.getElementById('chatMessages');
const chatInput = document.getElementById('chatInput');
const sendBtn = document.getElementById('sendBtn');
const micBtn = document.getElementById('micBtn');
const connectBtn = document.getElementById('connectBtn');
const attachBtn = document.getElementById('attachBtn');
const codeOutput = document.getElementById('codeOutput').querySelector('code');
const aiActivityText = document.getElementById('aiActivityText');
const aiActivityDot = document.getElementById('aiActivityDot');
const tabButtons = document.querySelectorAll('.tab-btn');
// ===== Gestion des projets =====
let projects = JSON.parse(localStorage.getItem('projects') || '[]');
function saveProjects() {
localStorage.setItem('projects', JSON.stringify(projects));
}
function renderProjects() {
projectList.innerHTML = '';
if (projects.length === 0) {
const li = document.createElement('li');
li.className = 'text-slate-500';
li.textContent = 'Aucun projet. Cliquez sur "Nouvelle tâche" pour commencer.';
projectList.appendChild(li);
return;
}
projects.forEach((p, idx) => {
const li = document.createElement('li');
li.innerHTML = `
<span>${p}</span>
<span class="actions">
<button title="Renommer" data-action="rename" data-index="${idx}"><i class="fa-solid fa-pen"></i></button>
<button title="Supprimer" data-action="delete" data-index="${idx}"><i class="fa-solid fa-trash"></i></button>
</span>
`;
projectList.appendChild(li);
});
}
function addProject(name) {
if (!name) return;
projects.push(name);
saveProjects();
renderProjects();
}
function renameProject(index) {
const newName = prompt('Nouveau nom du projet:', projects[index]);
if (newName && newName.trim()) {
projects[index] = newName.trim();
saveProjects();
renderProjects();
}
}
function deleteProject(index) {
if (confirm('Supprimer ce projet ?')) {
projects.splice(index, 1);
saveProjects();
renderProjects();
}
}
projectList.addEventListener('click', (e) => {
const btn = e.target.closest('button[data-action]');
if (!btn) return;
const idx = Number(btn.dataset.index);
if (btn.dataset.action === 'rename') renameProject(idx);
if (btn.dataset.action === 'delete') deleteProject(idx);
});
document.getElementById('newTaskBtn').addEventListener('click', () => {
const name = prompt('Nom du nouveau projet:');
if (name) addProject(name.trim());
});
document.getElementById('searchBtn').addEventListener('click', () => {
alert('Fonction "Rechercher" à implémenter.');
});
document.getElementById('libraryBtn').addEventListener('click', () => {
alert('Fonction "Bibliothèque" à implémenter.');
});
renderProjects();
// ===== Chat (Rosalinda) =====
function setAIActivity(text, active = true) {
aiActivityText.textContent = text;
aiActivityDot.style.background = active ? 'rgb(34,197,94)' : 'rgb(100,116,139)'; // emerald vs slate
}
function getChatHistory() {
const messages = [];
const nodes = chatMessages.querySelectorAll('.msg');
nodes.forEach(node => {
const role = node.classList.contains('msg-user') ? 'user' : 'assistant';
const text = node.querySelector('.bubble').textContent.trim();
messages.push({ role, content: text });
});
return messages;
}
function addMessage(role, content) {
const msg = document.createElement('div');
msg.className = 'msg ' + (role === 'user' ? 'msg-user' : 'msg-ai');
if (role === 'user') {
msg.innerHTML = `
<div class="bubble">${escapeHTML(content)}</div>
<div class="avatar">
<i class="fa-solid fa-user"></i>
</div>
`;
} else {
msg.innerHTML = `
<div class="avatar">
<i class="fa-solid fa-robot"></i>
</div>
<div class="bubble">${content}</div>
`;
}
chatMessages.appendChild(msg);
chatMessages.scrollTop = chatMessages.scrollHeight;
}
function escapeHTML(str) {
return str.replace(/[&<>"']/g, (m) => ({
'&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;'
}[m]));
}
async function sendMessage() {
const text = chatInput.value.trim();
if (!text) return;
addMessage('user', text);
chatInput.value = '';
setAIActivity('Rosalinda réfléchit...', true);
try {
const response = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${OPENAI_API_KEY}`
},
body: JSON.stringify({
model: 'gpt-3.5-turbo',
messages: [
{ role: 'system', content: 'Tu es Rosalinda, une assistante IA utiles et concise.' },
...getChatHistory(),
{ role: 'user', content: text }
],
temperature: 0.7
})
});
if (!response.ok) {
throw new Error(`Erreur API: ${response.status} ${response.statusText}`);
}
const data = await response.json();
const assistantMessage = data.choices?.[0]?.message?.content ?? 'Désolé, aucune réponse.';
setAIActivity('IA en veille', false);
addMessage('ai', assistantMessage);
codeOutput.textContent = assistantMessage;
} catch (error) {
console.error(error);
setAIActivity('IA en veille', false);
addMessage('ai', 'Une erreur est survenue lors de la communication avec l\'IA.');
}
}
sendBtn.addEventListener('click', sendMessage);
chatInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter') sendMessage();
});
const micBtn = document.getElementById('micBtn');
const languageSelect = document.createElement('select');
languageSelect.id = 'languageSelect';
languageSelect.style.marginLeft = '8px';
languageSelect.style.padding = '6px 8px';
languageSelect.style.borderRadius = '6px';
languageSelect.style.border = '1px solid rgb(226 232 240)';
languageSelect.style.background = 'rgb(248 250 252)';
languageSelect.style.color = 'inherit';
languageSelect.innerHTML = `
<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">English (US)</option>
<option value="es-ES">Español (España)</option>
`;
const chatInputParent = chatInput.parentElement;
if (chatInputParent && !document.getElementById('languageSelect')) {
chatInputParent.insertBefore(languageSelect, chatInput);
}
let isListening = false;
let recognition = null;
let interimTranscript = '';
let lastFinalTranscript = '';
const SpeechRecognitionAPI = window.SpeechRecognition || window.webkitSpeechRecognition;
function initSpeechRecognition() {
if (!SpeechRecognitionAPI) {
aiActivityText.textContent = 'Reconnaissance vocale non supportée';
aiActivityDot.style.background = 'rgb(239,68,68)';
micBtn.disabled = true;
micBtn.title = 'Navigateur non compatible';
micBtn.style.opacity = '0.6';
micBtn.style.cursor = 'not-allowed';
return;
}
recognition = new SpeechRecognitionAPI();
recognition.lang = languageSelect.value;
recognition.continuous = true;
recognition.interimResults = true;
recognition.maxAlternatives = 1;
recognition.onstart = () => {
isListening = true;
micBtn.classList.add('listening');
micBtn.innerHTML = '<i class="fas fa-microphone-slash"></i>';
aiActivityText.textContent = 'Écoute en cours...';
aiActivityDot.style.background = 'rgb(234,179,8)';
setAIActivity('Écoute en cours...', true);
};
recognition.onend = () => {
isListening = false;
micBtn.classList.remove('listening');
micBtn.innerHTML = '<i class="fas fa-microphone"></i>';
aiActivityText.textContent = 'IA en veille';
aiActivityDot.style.background = 'rgb(100,116,139)';
setAIActivity('IA en veille', false);
interimTranscript = '';
};
recognition.onerror = (event) => {
console.error('SpeechRecognition error:', event.error);
let message = 'Erreur de reconnaissance vocale.';
switch (event.error) {
case 'no-speech':
message = 'Aucune parole détectée. Essayez de parler plus fort.';
break;
case 'audio-capture':
message = 'Aucun microphone détecté.';
break;
case 'not-allowed':
message = 'Accès au microphone refusé.';
break;
case 'network':
message = 'Erreur réseau.';
break;
}
addMessage('ai', message);
recognition.stop();
};
recognition.onresult = (event) => {
interimTranscript = '';
for (let i = event.resultIndex; i < event.results.length; i++) {
const transcript = event.results[i][0].transcript.trim();
if (event.results[i].isFinal) {
lastFinalTranscript = transcript;
addMessage('user', transcript);
setAIActivity('Rosalinda réfléchit...', true);
sendToAI(transcript);
} else {
interimTranscript += transcript + ' ';
}
}
if (interimTranscript) {
codeOutput.textContent = `// En écoute...\n${interimTranscript}`;
}
};
}
function sendToAI(text) {
fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${OPENAI_API_KEY}`
},
body: JSON.stringify({
model: 'gpt-3.5-turbo',
messages: [
{ role: 'system', content: 'Tu es Rosalinda, une assistante IA utile et concise.' },
...getChatHistory(),
{ role: 'user', content: text }
],
temperature: 0.7
})
})
.then(response => {
if (!response.ok) throw new Error(`Erreur API: ${response.status}`);
return response.json();
})
.then(data => {
const assistantMessage = data.choices?.[0]?.message?.content ?? 'Désolé, aucune réponse.';
addMessage('ai', assistantMessage);
codeOutput.textContent = assistantMessage;
})
.catch(error => {
console.error(error);
addMessage('ai', 'Une erreur est survenue lors de la communication avec l\'IA.');
})
.finally(() => {
setAIActivity('IA en veille', false);
});
}
async function toggleListening() {
if (!recognition) initSpeechRecognition();
if (!recognition) return;
try {
if (isListening) {
recognition.stop();
} else {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true }).catch(() => null);
if (!stream) {
addMessage('ai', 'Impossible d\'accéder au microphone. Vérifiez les permissions.');
return;
}
stream.getTracks().forEach(t => t.stop());
recognition.start();
}
} catch (err) {
console.error(err);
addMessage('ai', 'Erreur lors du démarrage de la reconnaissance vocale.');
}
}
function stopListening() {
if (recognition && isListening) recognition.stop();
}
micBtn.addEventListener('click', toggleListening);
languageSelect.addEventListener('change', () => {
if (recognition) recognition.lang = languageSelect.value;
});
document.addEventListener('keydown', (e) => {
if (e.code === 'Space' && (e.ctrlKey || e.metaKey)) {
e.preventDefault();
toggleListening();
}
if (e.key === 'Escape' && isListening) {
stopListening();
}
});
// Stop listening on tab change to avoid dangling permissions
document.addEventListener('visibilitychange', () => {
if (document.hidden) stopListening();
});
connectBtn.addEventListener('click', () => {
alert('Fonction "Connexion" à implémenter (OAuth, clés API, etc.).');
});
attachBtn.addEventListener('click', () => {
alert('Fonction "Ajouter un fichier" à implémenter (FileReader, uploads, etc.).');
});
// ===== Onglets =====
tabButtons.forEach(btn => {
btn.addEventListener('click', () => {
tabButtons.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
const tab = btn.dataset.tab;
const demo = {
code: '// Code généré s’affichera ici...',
dashboard: 'Tableau de bord (graphiques, KPIs)...',
db: 'Visualisation base de données (tables, requêtes)...',
storage: 'Gestion du stockage (fichiers, objets)...',
settings: 'Paramètres (thème, clés API, intégrations)...'
};
codeOutput.textContent = demo[tab] || '// ...';
});
});
// Surveillance anti-modification non autorisée du résultat
setInterval(() => {
const codeActuel = document.getElementById('resultat').innerText;
if (!codeActuel.includes('Résultat final')) {
alert('⚠️ Alerte : Modification non autorisée détectée. Restauration...');
location.reload();
}
}, 5000);