365 lines
20 KiB
PHP
Executable File
365 lines
20 KiB
PHP
Executable File
<?php require_once __DIR__ . '/hamid-providers-config.php'; ?>
|
||
<!DOCTYPE html>
|
||
<html data-theme="dark" lang="fr">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>WEVAL MIND Widget - Assistant IA</title>
|
||
<style>
|
||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||
:root {
|
||
--primary: #22d3ee;
|
||
--primary-dark: #0891b2;
|
||
--secondary: #3b82f6;
|
||
--bg-dark: #0f172a;
|
||
--bg-medium: #1e293b;
|
||
--bg-light: #334155;
|
||
--text-primary: #f1f5f9;
|
||
--text-secondary: #94a3b8;
|
||
--text-muted: #64748b;
|
||
--success: #22c55e;
|
||
--error: #ef4444;
|
||
--warning: #f59e0b;
|
||
--border: #475569;
|
||
}
|
||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: linear-gradient(135deg, var(--bg-dark) 0%, #1a1a2e 100%); min-height: 100vh; display: flex; justify-content: center; align-items: center; padding: 20px; }
|
||
|
||
/* Widget Container */
|
||
.widget-wrapper { position: relative; }
|
||
|
||
/* Floating Button */
|
||
.widget-btn { width: 70px; height: 70px; border-radius: 50%; background: linear-gradient(135deg, var(--primary), var(--secondary)); border: none; cursor: pointer; box-shadow: 0 8px 32px rgba(34,211,238,0.4); display: flex; align-items: center; justify-content: center; transition: all 0.3s ease; position: fixed; bottom: 30px; right: 30px; z-index: 9998; }
|
||
.widget-btn:hover { transform: scale(1.1) rotate(10deg); box-shadow: 0 12px 40px rgba(34,211,238,0.6); }
|
||
.widget-btn img { width: 50px; height: 50px; border-radius: 50%; object-fit: cover; }
|
||
.widget-btn .pulse { position: absolute; width: 100%; height: 100%; border-radius: 50%; background: var(--primary); animation: pulse 2s infinite; opacity: 0.3; }
|
||
@keyframes pulse { 0%, 100% { transform: scale(1); opacity: 0.3; } 50% { transform: scale(1.2); opacity: 0; } }
|
||
|
||
/* Bubble */
|
||
.widget-bubble { position: fixed; bottom: 110px; right: 30px; background: white; padding: 12px 18px; border-radius: 20px; box-shadow: 0 4px 20px rgba(0,0,0,0.15); font-size: 14px; color: var(--bg-dark); animation: bubblePop 0.4s ease; z-index: 9997; }
|
||
.widget-bubble::after { content: ''; position: absolute; bottom: -8px; right: 30px; border: 8px solid transparent; border-top-color: white; }
|
||
@keyframes bubblePop { from { opacity: 0; transform: scale(0.8) translateY(10px); } to { opacity: 1; transform: scale(1) translateY(0); } }
|
||
|
||
/* Chat Window */
|
||
.widget-chat { position: fixed; bottom: 110px; right: 30px; width: 400px; height: 600px; background: var(--bg-dark); border-radius: 24px; box-shadow: 0 25px 80px rgba(0,0,0,0.5); display: none; flex-direction: column; overflow: hidden; z-index: 9999; animation: slideUp 0.3s ease; }
|
||
.widget-chat.open { display: flex; }
|
||
@keyframes slideUp { from { opacity: 0; transform: translateY(30px) scale(0.95); } to { opacity: 1; transform: translateY(0) scale(1); } }
|
||
|
||
/* Header */
|
||
.widget-header { background: linear-gradient(135deg, var(--primary), var(--secondary)); padding: 20px; display: flex; align-items: center; gap: 14px; }
|
||
.widget-header .avatar { width: 50px; height: 50px; border-radius: 14px; background: rgba(255,255,255,0.2); display: flex; align-items: center; justify-content: center; font-size: 28px; }
|
||
.widget-header .info { flex: 1; color: white; }
|
||
.widget-header .title { font-size: 18px; font-weight: 600; }
|
||
.widget-header .status { font-size: 13px; opacity: 0.9; display: flex; align-items: center; gap: 6px; }
|
||
.widget-header .status .dot { width: 8px; height: 8px; background: #4ade80; border-radius: 50%; animation: blink 2s infinite; }
|
||
@keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
|
||
.widget-header .close-btn { background: rgba(255,255,255,0.2); border: none; width: 36px; height: 36px; border-radius: 10px; color: white; font-size: 20px; cursor: pointer; transition: all 0.2s; }
|
||
.widget-header .close-btn:hover { background: rgba(255,255,255,0.3); }
|
||
|
||
/* Provider Select */
|
||
.provider-bar { background: var(--bg-medium); padding: 10px 16px; display: flex; align-items: center; gap: 10px; border-bottom: 1px solid var(--border); }
|
||
.provider-bar label { font-size: 12px; color: var(--text-muted); }
|
||
.provider-bar select { flex: 1; background: var(--bg-light); border: 1px solid var(--border); color: var(--text-primary); padding: 8px 12px; border-radius: 8px; font-size: 13px; }
|
||
|
||
/* Messages */
|
||
.widget-messages { flex: 1; overflow-y: auto; padding: 20px; display: flex; flex-direction: column; gap: 16px; }
|
||
.widget-messages::-webkit-scrollbar { width: 6px; }
|
||
.widget-messages::-webkit-scrollbar-track { background: transparent; }
|
||
.widget-messages::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
|
||
|
||
.message { display: flex; gap: 10px; animation: msgFade 0.3s ease; }
|
||
@keyframes msgFade { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
|
||
.message.user { flex-direction: row-reverse; }
|
||
.message .avatar { width: 36px; height: 36px; border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 18px; flex-shrink: 0; }
|
||
.message.bot .avatar { background: linear-gradient(135deg, var(--primary), var(--secondary)); }
|
||
.message.user .avatar { background: var(--bg-light); }
|
||
.message .bubble { max-width: 85%; padding: 14px 18px; border-radius: 18px; font-size: 14px; line-height: 1.5; }
|
||
.message.bot .bubble { background: var(--bg-medium); color: var(--text-primary); border-bottom-left-radius: 6px; }
|
||
.message.user .bubble { background: linear-gradient(135deg, var(--primary), var(--secondary)); color: white; border-bottom-right-radius: 6px; }
|
||
.message .time { font-size: 11px; color: var(--text-muted); margin-top: 6px; }
|
||
|
||
/* Thinking Box */
|
||
.thinking-box { background: linear-gradient(135deg, rgba(168,85,247,0.1), rgba(34,211,238,0.05)); border: 1px solid rgba(168,85,247,0.2); border-radius: 12px; margin: 8px 0; overflow: hidden; }
|
||
.thinking-header { padding: 10px 14px; cursor: pointer; display: flex; align-items: center; gap: 8px; font-size: 12px; color: #a855f7; }
|
||
.thinking-header .icon { animation: sparkle 2s linear infinite; }
|
||
@keyframes sparkle { 0%, 100% { opacity: 0.5; transform: rotate(0deg); } 50% { opacity: 1; transform: rotate(180deg); } }
|
||
.thinking-header .chevron { margin-left: auto; transition: transform 0.3s; font-size: 10px; }
|
||
.thinking-box.expanded .chevron { transform: rotate(180deg); }
|
||
.thinking-content { max-height: 0; overflow: hidden; transition: max-height 0.3s; }
|
||
.thinking-box.expanded .thinking-content { max-height: 150px; padding: 10px 14px; border-top: 1px solid rgba(168,85,247,0.15); font-size: 12px; color: var(--text-secondary); overflow-y: auto; }
|
||
|
||
/* Code Block */
|
||
.code-block { background: var(--bg-dark); border: 1px solid var(--border); border-radius: 10px; margin: 10px 0; overflow: hidden; }
|
||
.code-header { display: flex; justify-content: space-between; padding: 8px 12px; background: var(--bg-light); font-size: 11px; color: var(--text-muted); }
|
||
.code-header button { background: none; border: none; color: var(--primary); cursor: pointer; font-size: 11px; }
|
||
.code-content { padding: 12px; font-family: 'Fira Code', monospace; font-size: 12px; overflow-x: auto; color: var(--text-primary); }
|
||
|
||
/* Quick Actions */
|
||
.quick-actions { padding: 12px 16px; display: flex; gap: 8px; flex-wrap: wrap; border-bottom: 1px solid var(--border); }
|
||
.quick-btn { background: var(--bg-medium); border: 1px solid var(--border); color: var(--text-secondary); padding: 8px 14px; border-radius: 20px; font-size: 12px; cursor: pointer; transition: all 0.2s; }
|
||
.quick-btn:hover { border-color: var(--primary); color: var(--primary); }
|
||
|
||
/* Input Area */
|
||
.widget-input { padding: 16px; background: var(--bg-medium); border-top: 1px solid var(--border); }
|
||
.input-container { display: flex; gap: 10px; align-items: flex-end; }
|
||
.input-wrapper { flex: 1; background: var(--bg-light); border: 1px solid var(--border); border-radius: 16px; overflow: hidden; transition: border-color 0.3s; }
|
||
.input-wrapper:focus-within { border-color: var(--primary); }
|
||
.input-wrapper textarea { width: 100%; padding: 12px 16px; background: transparent; border: none; color: var(--text-primary); font-size: 14px; resize: none; outline: none; font-family: inherit; min-height: 44px; max-height: 100px; }
|
||
.input-wrapper textarea::placeholder { color: var(--text-muted); }
|
||
.input-toolbar { display: flex; padding: 8px 12px; gap: 8px; border-top: 1px solid var(--border); }
|
||
.input-toolbar button { background: none; border: none; color: var(--text-muted); cursor: pointer; padding: 4px; font-size: 16px; transition: color 0.2s; }
|
||
.input-toolbar button:hover { color: var(--primary); }
|
||
.send-btn { width: 48px; height: 48px; border-radius: 14px; background: linear-gradient(135deg, var(--primary), var(--secondary)); border: none; color: white; font-size: 20px; cursor: pointer; transition: all 0.2s; display: flex; align-items: center; justify-content: center; }
|
||
.send-btn:hover { transform: scale(1.05); box-shadow: 0 4px 20px rgba(34,211,238,0.4); }
|
||
.send-btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }
|
||
|
||
/* Typing Indicator */
|
||
.typing { display: flex; align-items: center; gap: 10px; padding: 14px 18px; }
|
||
.typing .dots { display: flex; gap: 4px; }
|
||
.typing .dots span { width: 8px; height: 8px; background: var(--primary); border-radius: 50%; animation: typing 1.4s infinite ease-in-out both; }
|
||
.typing .dots span:nth-child(1) { animation-delay: -0.32s; }
|
||
.typing .dots span:nth-child(2) { animation-delay: -0.16s; }
|
||
@keyframes typing { 0%, 80%, 100% { transform: scale(0.6); opacity: 0.5; } 40% { transform: scale(1); opacity: 1; } }
|
||
|
||
/* Responsive */
|
||
@media (max-width: 500px) {
|
||
.widget-chat { width: 100%; height: 100%; bottom: 0; right: 0; border-radius: 0; }
|
||
.widget-btn { bottom: 20px; right: 20px; }
|
||
}
|
||
|
||
/* Demo Mode Styles */
|
||
.demo-container { display: flex; flex-direction: column; align-items: center; gap: 30px; }
|
||
.demo-title { color: var(--text-primary); font-size: 24px; font-weight: 600; text-align: center; }
|
||
.demo-subtitle { color: var(--text-secondary); font-size: 14px; margin-top: -20px; }
|
||
.demo-widget { position: relative; }
|
||
</style>
|
||
|
||
</head>
|
||
<body>
|
||
<div class="demo-container">
|
||
<h1 class="demo-title">🔲 WEVAL MIND Widget</h1>
|
||
<p class="demo-subtitle">Widget IA intégrable sur n'importe quel site</p>
|
||
|
||
<div class="demo-widget">
|
||
<!-- Floating Button -->
|
||
<div class="widget-btn" onclick="toggleChat()" id="widgetBtn">
|
||
<div class="pulse"></div>
|
||
<span style="font-size:32px;">🤖</span>
|
||
</div>
|
||
|
||
<!-- Bubble -->
|
||
<div class="widget-bubble" id="bubble">Besoin d'aide? 💬</div>
|
||
|
||
<!-- Chat Window -->
|
||
<div class="widget-chat" id="chatWindow">
|
||
<div class="widget-header">
|
||
<div class="avatar">🤖</div>
|
||
<div class="info">
|
||
<div class="title">WEVAL MIND</div>
|
||
<div class="status"><span class="dot"></span> En ligne</div>
|
||
</div>
|
||
<button class="close-btn" onclick="toggleChat()">×</button>
|
||
</div>
|
||
|
||
<div class="provider-bar">
|
||
<label>Provider:</label>
|
||
<?php echo hamid_providers_dropdown("cerebras", "provider", ""); ?>
|
||
</div>
|
||
|
||
<div class="quick-actions">
|
||
<button class="quick-btn" onclick="quickMsg('Bonjour!')">👋 Saluer</button>
|
||
<button class="quick-btn" onclick="quickMsg('Aide moi avec mon code')">💻 Code</button>
|
||
<button class="quick-btn" onclick="quickMsg('Explique moi')">📖 Expliquer</button>
|
||
<button class="quick-btn" onclick="quickMsg('Résume')">📝 Résumer</button>
|
||
</div>
|
||
|
||
<div class="widget-messages" id="messages">
|
||
<div class="message bot">
|
||
<div class="avatar">🤖</div>
|
||
<div>
|
||
<div class="bubble">
|
||
Bonjour! 👋 Je suis <strong>WEVAL MIND</strong>, votre assistant IA.<br><br>
|
||
Comment puis-je vous aider aujourd'hui?
|
||
</div>
|
||
<div class="time">Maintenant</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="widget-input">
|
||
<div class="input-container">
|
||
<div class="input-wrapper">
|
||
<textarea id="input" placeholder="Écrivez votre message..." rows="1"></textarea>
|
||
<div class="input-toolbar">
|
||
<button title="Pièce jointe">📎</button>
|
||
<button title="Emoji">😊</button>
|
||
<button title="Microphone">🎤</button>
|
||
</div>
|
||
</div>
|
||
<button class="send-btn" onclick="sendMessage()" id="sendBtn">➤</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
const messagesEl = document.getElementById('messages');
|
||
const inputEl = document.getElementById('input');
|
||
const providerSelect = document.getElementById('provider');
|
||
const chatWindow = document.getElementById('chatWindow');
|
||
const bubble = document.getElementById('bubble');
|
||
const sendBtn = document.getElementById('sendBtn');
|
||
|
||
let sessionId = 'widget_' + Date.now();
|
||
let isOpen = false;
|
||
|
||
// Toggle chat
|
||
function toggleChat() {
|
||
isOpen = !isOpen;
|
||
chatWindow.classList.toggle('open', isOpen);
|
||
bubble.style.display = isOpen ? 'none' : 'block';
|
||
if (isOpen) inputEl.focus();
|
||
}
|
||
|
||
// Quick message
|
||
function quickMsg(text) {
|
||
inputEl.value = text;
|
||
sendMessage();
|
||
}
|
||
|
||
// Input handling
|
||
inputEl.addEventListener('keydown', e => {
|
||
if (e.key === 'Enter' && !e.shiftKey) {
|
||
e.preventDefault();
|
||
sendMessage();
|
||
}
|
||
});
|
||
|
||
inputEl.addEventListener('input', () => {
|
||
inputEl.style.height = 'auto';
|
||
inputEl.style.height = Math.min(inputEl.scrollHeight, 100) + 'px';
|
||
});
|
||
|
||
function addMessage(content, isUser = false) {
|
||
const div = document.createElement('div');
|
||
div.className = 'message ' + (isUser ? 'user' : 'bot');
|
||
const time = new Date().toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' });
|
||
|
||
div.innerHTML = `
|
||
<div class="avatar">${isUser ? '👤' : '🤖'}</div>
|
||
<div>
|
||
<div class="bubble">${formatContent(content)}</div>
|
||
<div class="time">${time}</div>
|
||
</div>
|
||
`;
|
||
|
||
messagesEl.appendChild(div);
|
||
messagesEl.scrollTop = messagesEl.scrollHeight;
|
||
}
|
||
|
||
function formatContent(text) {
|
||
// Code blocks
|
||
text = text.replace(/```(\w+)?\n?([\s\S]*?)```/g, (_, lang, code) => {
|
||
return `<div class="code-block">
|
||
<div class="code-header"><span>${lang || 'code'}</span><button onclick="copyCode(this)">📋 Copier</button></div>
|
||
<div class="code-content"><pre>${escapeHtml(code.trim())}</pre></div>
|
||
</div>`;
|
||
});
|
||
text = text.replace(/`([^`]+)`/g, '<code style="background:var(--bg-light);padding:2px 6px;border-radius:4px;">$1</code>');
|
||
text = text.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
|
||
text = text.replace(/\n/g, '<br>');
|
||
return text;
|
||
}
|
||
|
||
function escapeHtml(text) {
|
||
const div = document.createElement('div');
|
||
div.textContent = text;
|
||
return div.innerHTML;
|
||
}
|
||
|
||
function copyCode(btn) {
|
||
const code = btn.closest('.code-block').querySelector('pre').textContent;
|
||
navigator.clipboard.writeText(code);
|
||
btn.textContent = '✅ Copié!';
|
||
setTimeout(() => btn.textContent = '📋 Copier', 2000);
|
||
}
|
||
|
||
function addThinking(text) {
|
||
const div = document.createElement('div');
|
||
div.className = 'thinking-box';
|
||
div.innerHTML = `
|
||
<div class="thinking-header" onclick="this.parentElement.classList.toggle('expanded')">
|
||
<span class="icon">✨</span> Réflexion <span class="chevron">▼</span>
|
||
</div>
|
||
<div class="thinking-content">${text}</div>
|
||
`;
|
||
messagesEl.appendChild(div);
|
||
}
|
||
|
||
function addTyping() {
|
||
const div = document.createElement('div');
|
||
div.className = 'message bot';
|
||
div.id = 'typing';
|
||
div.innerHTML = `
|
||
<div class="avatar">🤖</div>
|
||
<div class="typing">
|
||
<div class="dots"><span></span><span></span><span></span></div>
|
||
</div>
|
||
`;
|
||
messagesEl.appendChild(div);
|
||
messagesEl.scrollTop = messagesEl.scrollHeight;
|
||
}
|
||
|
||
function removeTyping() {
|
||
const el = document.getElementById('typing');
|
||
if (el) el.remove();
|
||
}
|
||
|
||
async function sendMessage() {
|
||
const message = inputEl.value.trim();
|
||
if (!message) return;
|
||
|
||
addMessage(message, true);
|
||
inputEl.value = '';
|
||
inputEl.style.height = 'auto';
|
||
sendBtn.disabled = true;
|
||
|
||
addTyping();
|
||
|
||
try {
|
||
const res = await fetch('/hamid-api.php', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
message: message,
|
||
provider: providerSelect.value,
|
||
session_id: sessionId
|
||
})
|
||
});
|
||
|
||
const data = await res.json();
|
||
removeTyping();
|
||
|
||
if (data.error) {
|
||
addMessage('❌ ' + data.error);
|
||
} else {
|
||
if (data.thinking) addThinking(data.thinking);
|
||
addMessage(data.response);
|
||
}
|
||
} catch (e) {
|
||
removeTyping();
|
||
addMessage('❌ Erreur de connexion');
|
||
}
|
||
|
||
sendBtn.disabled = false;
|
||
}
|
||
|
||
// Auto-hide bubble after 5s
|
||
setTimeout(() => { bubble.style.display = 'none'; }, 5000);
|
||
</script>
|
||
|
||
|
||
|
||
</body>
|
||
</html>
|