Files
wevads-platform/backups-removed/hamid-fullscreen.php.backup

1141 lines
48 KiB
Plaintext
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
session_start();
$pdo = new PDO("pgsql:host=localhost;dbname=adx_system", "admin", "admin123");
$config = [];
try { $stmt = $pdo->query("SELECT config_key, config_value FROM admin.commonia_config"); while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) $config[$row['config_key']] = $row['config_value']; } catch (Exception $e) {}
$defaultProvider = $config['default_provider'] ?? 'cerebras';
?>
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>HAMID - Assistant WEVADS</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
<style>
:root{--bg:#f8f8f8;--sidebar:#fff;--chat:#fff;--text:#1a1a1a;--muted:#6b7280;--border:#e5e7eb;--primary:#06b6d4}
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:-apple-system,sans-serif;background:var(--bg);color:var(--text);height:100vh;overflow:hidden}
.app{display:flex;height:100vh}
.sidebar{width:260px;background:var(--sidebar);border-right:1px solid var(--border);display:flex;flex-direction:column}
.sidebar-header{padding:1rem}
.new-chat{width:100%;padding:.75rem 1rem;background:linear-gradient(135deg,#06b6d4,#3b82f6);color:#fff;border:none;border-radius:8px;cursor:pointer;display:flex;align-items:center;gap:.5rem}
.sidebar-section{padding:.5rem 1rem}
.sidebar-title{font-size:.7rem;font-weight:600;color:var(--muted);text-transform:uppercase;margin-bottom:.5rem}
.sidebar-item{display:flex;align-items:center;gap:.75rem;padding:.6rem .75rem;border-radius:6px;color:var(--text);text-decoration:none;font-size:.9rem}
.sidebar-item:hover{background:#f3f4f6}
.sidebar-footer{margin-top:auto;padding:1rem;border-top:1px solid var(--border)}
.sidebar-footer a{display:flex;align-items:center;gap:.5rem;padding:.5rem;color:var(--muted);text-decoration:none;font-size:.85rem}
.main{flex:1;display:flex;flex-direction:column}
.header{padding:.75rem 1.5rem;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between}
.header-left{display:flex;align-items:center;gap:.75rem}
.logo{width:36px;height:36px;border-radius:8px;background:linear-gradient(135deg,#06b6d4,#3b82f6);display:flex;align-items:center;justify-content:center;font-size:1.2rem}
.header-right{display:flex;align-items:center;gap:.5rem}
.help-btn{padding:.5rem .75rem;border:1px solid var(--border);background:#fff;border-radius:8px;cursor:pointer}
.provider-select{position:relative}
.provider-btn{padding:.5rem 1rem;border:1px solid var(--border);background:#fff;border-radius:8px;cursor:pointer;display:flex;align-items:center;gap:.5rem}
.provider-btn .dot{width:8px;height:8px;border-radius:50%;background:#10b981}
.provider-menu{position:absolute;top:100%;right:0;margin-top:.5rem;background:#fff;border:1px solid var(--border);border-radius:12px;box-shadow:0 10px 40px rgba(0,0,0,.15);min-width:280px;z-index:100;display:none}
.provider-menu.show{display:block}
.provider-group{padding:.5rem}
.provider-group-title{padding:.5rem .75rem;font-size:.7rem;font-weight:600;text-transform:uppercase}
.provider-group-title.free{color:#10b981}
.provider-group-title.limited{color:#f59e0b}
.provider-group-title.paid{color:#ef4444}
.provider-option{display:flex;align-items:center;gap:.75rem;padding:.6rem .75rem;border-radius:8px;cursor:pointer}
.provider-option:hover{background:#f3f4f6}
.provider-option.active{background:#e0f2fe}
.provider-option .icon{font-size:1.25rem}
.provider-option .info{flex:1}
.provider-option .name{font-size:.9rem;font-weight:500}
.provider-option .desc{font-size:.75rem;color:var(--muted)}
.messages{flex:1;overflow-y:auto;padding:1.5rem}
.welcome{text-align:center;padding:3rem 1rem}
.welcome-icon{width:80px;height:80px;background:linear-gradient(135deg,#06b6d4,#3b82f6);border-radius:20px;display:flex;align-items:center;justify-content:center;margin:0 auto 1.5rem;font-size:2.5rem}
.welcome h2{margin-bottom:.75rem}
.welcome p{color:var(--muted);margin-bottom:2rem}
.quick-actions{display:flex;gap:.75rem;justify-content:center;flex-wrap:wrap}
.quick-btn{padding:.75rem 1.25rem;border:2px solid var(--border);background:#fff;border-radius:10px;cursor:pointer;display:flex;align-items:center;gap:.5rem}
.quick-btn:hover{border-color:var(--primary);background:#f0fdfa}
.msg{display:flex;gap:1rem;margin-bottom:1.5rem;max-width:900px;margin-left:auto;margin-right:auto}
.msg-user{justify-content:flex-end}
.msg-user .bubble{background:#f3f4f6;padding:.875rem 1rem;border-radius:16px 16px 4px 16px;max-width:70%}
.msg-assistant{align-items:flex-start}
.msg-assistant .avatar{width:32px;height:32px;background:linear-gradient(135deg,#06b6d4,#3b82f6);border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:.9rem}
.msg-assistant .msg-wrap{flex:1;max-width:80%}
.msg-assistant .msg-content{line-height:1.6}
.msg-footer{margin-top:.5rem;padding-top:.5rem;border-top:1px solid var(--border);font-size:.7rem;color:var(--muted);display:flex;gap:.4rem;flex-wrap:wrap}
.msg-footer .tag{padding:.2rem .5rem;border-radius:4px;background:#f3f4f6}
.msg-footer .tag.prov{background:#dbeafe;color:#1e40af}
.msg-footer .tag.kb{background:#d1fae5;color:#065f46}
.typing{display:none;padding:1rem 1.5rem;max-width:900px;margin:0 auto}
.typing.show{display:flex !important;align-items:center;gap:.75rem}
.typing-avatar{width:32px;height:32px;background:linear-gradient(135deg,#06b6d4,#3b82f6);border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:.9rem}
.typing-dots{display:flex;gap:4px;padding:.75rem 1rem;background:#f3f4f6;border-radius:16px}
.typing-dots span{width:8px;height:8px;background:#94a3b8;border-radius:50%;animation:bounce 1.4s infinite}
.typing-dots span:nth-child(2){animation-delay:.2s}
.typing-dots span:nth-child(3){animation-delay:.4s}
@keyframes bounce{0%,80%,100%{transform:translateY(0)}40%{transform:translateY(-6px)}}
.input-area{padding:1rem 1.5rem 1.5rem;border-top:1px solid var(--border)}
.input-wrap{display:flex;gap:.5rem;max-width:900px;margin:0 auto;background:#fff;border:1px solid var(--border);border-radius:12px;padding:.5rem;align-items:flex-end}
.input-wrap:focus-within{border-color:var(--primary)}
.input-wrap textarea{flex:1;border:none;outline:none;resize:none;font-size:.95rem;padding:.5rem;max-height:120px}
.input-btn{width:40px;height:40px;background:none;border:none;color:var(--muted);cursor:pointer;border-radius:8px;font-size:1.1rem}
.input-btn:hover{background:#f3f4f6;color:var(--primary)}
.send-btn{width:40px;height:40px;background:linear-gradient(135deg,#06b6d4,#3b82f6);color:#fff;border:none;border-radius:8px;cursor:pointer;display:flex;align-items:center;justify-content:center}
#artPanel{position:fixed;right:-500px;top:0;width:500px;height:100vh;background:#fff;border-left:1px solid var(--border);display:flex;flex-direction:column;transition:right .3s;z-index:200}
#artPanel.open{right:0;box-shadow:-5px 0 30px rgba(0,0,0,.1)}
.art-header{padding:1rem;border-bottom:1px solid var(--border);display:flex;justify-content:space-between}
.art-close{background:none;border:none;font-size:1.5rem;cursor:pointer;color:var(--muted)}
#artContent{flex:1;overflow:auto;background:#f8fafc}
#artContent iframe{width:100%;height:100%;border:none}
.art-footer{padding:1rem;border-top:1px solid var(--border)}
.art-footer .btn{padding:.5rem 1rem;border:1px solid var(--border);background:#fff;border-radius:6px;text-decoration:none;color:var(--text);display:inline-flex;align-items:center;gap:.4rem}
.modal{display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.5);z-index:300;align-items:center;justify-content:center}
.modal.show{display:flex}
.modal-content{background:#fff;border-radius:16px;padding:2rem;max-width:500px;width:90%}
.modal-content h3{margin-bottom:1rem}
.modal-content ul{margin:1rem 0;padding-left:1.5rem}
.modal-content li{margin-bottom:.5rem;color:var(--muted)}
.modal-content button{margin-top:1rem;padding:.75rem 1.5rem;background:var(--primary);color:#fff;border:none;border-radius:8px;cursor:pointer}
@media(max-width:768px){.sidebar{display:none}}
.msg-tag{display:inline-block;font-size:10px;padding:2px 6px;border-radius:4px;margin-left:4px}
.msg-tag.kb{background:#10b981;color:white}
.msg-tag.ia{background:#8b5cf6;color:white}
/* Tools Panel */
.tools-panel {
background: rgba(15, 23, 42, 0.95);
border: 1px solid #334155;
border-radius: 12px;
padding: 1rem;
margin: 0.5rem;
}
.tools-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 0.5rem;
}
.tool-btn {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.25rem;
padding: 0.75rem 0.5rem;
background: linear-gradient(135deg, #1e293b, #0f172a);
border: 1px solid #334155;
border-radius: 8px;
color: #e2e8f0;
cursor: pointer;
transition: all 0.2s;
font-size: 0.75rem;
}
.tool-btn:hover {
border-color: #06b6d4;
background: linear-gradient(135deg, #1e3a5f, #0f172a);
transform: translateY(-2px);
}
.tool-btn .icon { font-size: 1.25rem; }
.tool-btn .label { color: #94a3b8; }
.tool-result {
background: #0f172a;
border: 1px solid #334155;
border-radius: 8px;
padding: 0.75rem;
margin: 0.5rem 0;
font-family: monospace;
font-size: 0.8rem;
max-height: 200px;
overflow-y: auto;
}
.tool-result pre {
margin: 0;
white-space: pre-wrap;
color: #10b981;
}
.tool-header {
color: #06b6d4;
font-weight: bold;
margin-bottom: 0.5rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.quick-actions {
display: flex;
gap: 0.5rem;
padding: 0.5rem;
overflow-x: auto;
border-top: 1px solid #334155;
}
.quick-action {
padding: 0.4rem 0.8rem;
background: rgba(6, 182, 212, 0.1);
border: 1px solid rgba(6, 182, 212, 0.3);
border-radius: 20px;
color: #06b6d4;
font-size: 0.7rem;
cursor: pointer;
white-space: nowrap;
transition: all 0.2s;
}
.quick-action:hover {
background: rgba(6, 182, 212, 0.2);
border-color: #06b6d4;
}
/* Thinking Indicator - Style Claude */
.thinking-container {
display: none;
align-items: center;
gap: 0.75rem;
padding: 1rem;
margin: 0.5rem 0;
}
.thinking-container.active {
display: flex;
}
.thinking-spinner {
width: 24px;
height: 24px;
position: relative;
}
.thinking-spinner::before {
content: "✦";
position: absolute;
font-size: 24px;
color: #f97316;
animation: sparkle 1s ease-in-out infinite;
}
@keyframes sparkle {
0%, 100% { transform: rotate(0deg) scale(1); opacity: 1; }
50% { transform: rotate(180deg) scale(1.2); opacity: 0.7; }
}
.thinking-text {
color: #94a3b8;
font-size: 0.9rem;
font-style: italic;
}
.thinking-steps {
margin-top: 0.5rem;
padding-left: 2rem;
color: #64748b;
font-size: 0.8rem;
}
.thinking-step {
padding: 0.25rem 0;
opacity: 0;
animation: fadeIn 0.3s forwards;
}
@keyframes fadeIn {
to { opacity: 1; }
}
/* Artefacts Panel - Style Claude */
.artifacts-panel {
width: 320px;
background: #fafafa;
border-left: 1px solid #e5e7eb;
display: flex;
flex-direction: column;
height: 100vh;
position: fixed;
right: 0;
top: 0;
z-index: 100;
transform: translateX(100%);
transition: transform 0.3s ease;
}
.artifacts-panel.open {
transform: translateX(0);
}
.artifacts-header {
padding: 1rem;
border-bottom: 1px solid #e5e7eb;
display: flex;
justify-content: space-between;
align-items: center;
background: white;
}
.artifacts-title {
font-weight: 600;
color: #1f2937;
display: flex;
align-items: center;
gap: 0.5rem;
}
.artifacts-close {
background: none;
border: none;
color: #6b7280;
cursor: pointer;
font-size: 1.25rem;
}
.artifacts-list {
flex: 1;
overflow-y: auto;
padding: 0.5rem;
}
.artifact-item {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.75rem;
border-radius: 8px;
cursor: pointer;
transition: background 0.2s;
margin-bottom: 0.5rem;
background: white;
border: 1px solid #e5e7eb;
}
.artifact-item:hover {
background: #f3f4f6;
border-color: #d1d5db;
}
.artifact-icon {
width: 40px;
height: 40px;
background: #f3f4f6;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.25rem;
}
.artifact-info {
flex: 1;
min-width: 0;
}
.artifact-name {
font-weight: 500;
color: #1f2937;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.artifact-meta {
font-size: 0.75rem;
color: #6b7280;
}
.artifact-download {
color: #6b7280;
cursor: pointer;
}
.artifact-download:hover {
color: #1f2937;
}
/* Section Contenu du projet */
.project-section {
padding: 1rem;
border-top: 1px solid #e5e7eb;
}
.project-title {
font-size: 0.75rem;
font-weight: 600;
color: #6b7280;
text-transform: uppercase;
margin-bottom: 0.75rem;
}
.project-card {
background: white;
border: 1px solid #e5e7eb;
border-radius: 8px;
padding: 1rem;
text-align: center;
}
.project-name {
font-weight: 500;
color: #1f2937;
margin-bottom: 0.5rem;
display: flex;
align-items: center;
gap: 0.5rem;
justify-content: center;
}
.project-desc {
font-size: 0.75rem;
color: #6b7280;
}
/* Bouton toggle artefacts */
.artifacts-toggle {
position: fixed;
right: 1rem;
top: 50%;
transform: translateY(-50%);
background: white;
border: 1px solid #e5e7eb;
border-radius: 8px;
padding: 0.5rem;
cursor: pointer;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
z-index: 99;
transition: all 0.2s;
}
.artifacts-toggle:hover {
background: #f3f4f6;
}
.artifacts-toggle.hidden {
display: none;
}
/* Ajuster le main content quand artefacts ouvert */
.main-content.with-artifacts {
margin-right: 320px;
}
</style>
</head>
<body>
<div class="app">
<div class="sidebar">
<div class="sidebar-header"><button class="new-chat" onclick="newChat()"><i class="fas fa-plus"></i> Nouvelle conversation</button></div>
<div class="sidebar-section"><div class="sidebar-title">Outils</div><a href="/ia-tools-test.php" class="sidebar-item"><i class="fas fa-wrench"></i> Test Tools</a><a href="javascript:void(0)" onclick="toggleToolsPanel()" class="sidebar-item" style="background:linear-gradient(135deg,#06b6d4,#3b82f6);color:white"><i class="fas fa-cogs"></i> 🔧 Tools Système</a><a href="/ia-documents.php" class="sidebar-item"><i class="fas fa-folder"></i> Documents</a><a href="/ia-knowledge.php" class="sidebar-item"><i class="fas fa-brain"></i> Knowledge Base</a></div>
<div class="sidebar-footer"><a href="/ia-index.php"><i class="fas fa-th-large"></i> Index IA</a><a href="/hamid-brain-config.php"><i class="fas fa-cog"></i> Configuration</a></div>
</div>
<div class="main">
<div class="header">
<div class="header-left"><div class="logo">🧞</div><span style="font-weight:600">HAMID - Assistant WEVADS</span></div>
<div class="header-right">
<button class="help-btn" onclick="showHelp()"><i class="fas fa-question-circle"></i> Aide</button>
<div class="provider-select">
<button class="provider-btn" onclick="document.getElementById('providerMenu').classList.toggle('show')"><span class="dot"></span><span id="currentProvider"><?= ucfirst($defaultProvider) ?></span><i class="fas fa-chevron-down"></i></button>
<div class="provider-menu" id="providerMenu">
<div class="provider-group"><div class="provider-group-title free">✅ Gratuits Illimités</div>
<div class="provider-option" data-prov="ollama_mini" onclick="selProv('ollama_mini','Ollama Mini')"><span class="icon">⚡</span><div class="info"><div class="name">Ollama Mini</div><div class="desc">Local • Rapide</div></div></div>
<div class="provider-option" data-prov="ollama" onclick="selProv('ollama','Ollama Mistral')"><span class="icon">🦙</span><div class="info"><div class="name">Ollama Mistral</div><div class="desc">Local • 7B</div></div></div>
<div class="provider-option" data-prov="deepseek" onclick="selProv('deepseek','DeepSeek')"><span class="icon">🔮</span><div class="info"><div class="name">DeepSeek</div><div class="desc">Cloud • Intelligent</div></div></div>
<div class="provider-option" data-prov="cerebras" onclick="selProv('cerebras','Cerebras')"><span class="icon">🧠</span><div class="info"><div class="name">Cerebras</div><div class="desc">Ultra rapide</div></div></div>
<div class="provider-option" data-prov="cerebras" onclick="selProv('cerebras','HAMID Engine')"><span class="icon">⭐</span><div class="info"><div class="name">HAMID Engine</div><div class="desc">Claude-Like • Local</div></div></div>
<div class="provider-option" data-prov="cerebras" onclick="selProv('cerebras','HAMID Engine')"><span class="icon">⭐</span><div class="info"><div class="name">HAMID Engine</div><div class="desc">Claude-Like • Local</div></div></div>
<div class="provider-option" data-prov="cerebras" onclick="selProv('cerebras','HAMID Engine')"><span class="icon">⭐</span><div class="info"><div class="name">HAMID Engine</div><div class="desc">Claude-Like • Local</div></div></div>
<div class="provider-option" data-prov="cerebras" onclick="selProv('cerebras','HAMID Engine')"><span class="icon">⭐</span><div class="info"><div class="name">HAMID Engine</div><div class="desc">Claude-Like • Local</div></div></div>
<div class="provider-option" data-prov="hyperbolic" onclick="selProv('hyperbolic','Hyperbolic')"><span class="icon">🌀</span><div class="info"><div class="name">Hyperbolic</div><div class="desc">Llama 405B</div></div></div>
<div class="provider-option" data-prov="mistral" onclick="selProv('mistral','Mistral')"><span class="icon">🇫🇷</span><div class="info"><div class="name">Mistral</div><div class="desc">Français</div></div></div>
<div class="provider-option" data-prov="cohere" onclick="selProv('cohere','Cohere')"><span class="icon">🔶</span><div class="info"><div class="name">Cohere</div><div class="desc">Command-R+</div></div></div>
</div>
<div class="provider-group"><div class="provider-group-title limited">⚠️ Gratuits Limités</div>
<div class="provider: 'cerebras','Groq')"><span class="icon">🚀</span><div class="info"><div class="name">Groq</div><div class="desc">Ultra rapide</div></div></div>
<div class="provider-option" data-prov="gemini" onclick="selProv('gemini','Gemini')"><span class="icon">💎</span><div class="info"><div class="name">Gemini</div><div class="desc">Google</div></div></div>
</div>
<div class="provider-group"><div class="provider-group-title paid">💰 Payants</div>
<div class="provider-option" data-prov="claude" onclick="selProv('claude','Claude')"><span class="icon">🧠</span><div class="info"><div class="name">Claude</div><div class="desc">Anthropic</div></div></div>
<div class="provider-option" data-prov="chatgpt" onclick="selProv('chatgpt','ChatGPT')"><span class="icon">🤖</span><div class="info"><div class="name">ChatGPT</div><div class="desc">OpenAI GPT-4</div></div></div>
<div class="provider-option" data-prov="copilot" onclick="selProv('copilot','Copilot')"><span class="icon">🐙</span><div class="info"><div class="name">GitHub Copilot</div><div class="desc">Code Assistant</div></div></div>
</div></div></div></div></div>
<div class="messages" id="msgs">
<div class="claude-thinking" id="claudeThinking" style="display:none;background:linear-gradient(135deg,#1a1a2e,#16213e);border:1px solid #4f46e5;border-left:4px solid #f97316;border-radius:12px;padding:16px 20px;margin:16px 0;">
<div style="display:flex;align-items:center;gap:10px;margin-bottom:12px;">
<span style="font-size:24px;animation:spin 2s linear infinite;">✦</span>
<span style="color:#f97316;font-weight:600;">Réflexion en cours...</span>
</div>
<div style="color:#a0aec0;font-size:13px;padding-left:34px;">
<div>→ Analyse de la demande...</div>
<div>→ Consultation de la Knowledge Base...</div>
<div>→ Structuration du contenu...</div>
<div>→ Génération de la réponse...</div>
</div>
</div>
<style>@keyframes spin{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}</style>
<script>
function showClaudeThinking(){document.getElementById('claudeThinking').style.display='block';document.getElementById('claudeThinking').scrollIntoView({behavior:'smooth',block:'center'});}
function hideClaudeThinking(){document.getElementById('claudeThinking').style.display='none';}
</script>
<!-- Thinking Indicator -->
<div class="thinking-container" id="thinkingIndicator">
<div class="thinking-spinner"></div>
<div>
<div class="thinking-text" id="thinkingText">Réflexion en cours...</div>
<div class="thinking-steps" id="thinkingSteps"></div>
</div>
</div>
<div class="welcome" id="welcome"><div class="welcome-icon">🧞</div><h2>Bonjour, je suis HAMID</h2><p>Assistant IA WEVADS avec génération de documents</p>
<!-- ========== THINKING BUBBLE - STYLE CLAUDE ========== -->
<style>
.claude-thinking {
display: none;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
border: 1px solid #4f46e5;
border-left: 4px solid #f97316;
border-radius: 12px;
padding: 16px 20px;
margin: 16px 0;
max-width: 700px;
}
.claude-thinking.visible {
display: block !important;
animation: thinkFadeIn 0.3s ease;
}
@keyframes thinkFadeIn {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
.claude-thinking-header {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 12px;
}
.claude-thinking-icon {
font-size: 24px;
animation: sparkleRotate 2s linear infinite;
}
@keyframes sparkleRotate {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.claude-thinking-title {
color: #f97316;
font-weight: 600;
font-size: 14px;
}
.claude-thinking-content {
color: #a0aec0;
font-size: 13px;
line-height: 1.6;
padding-left: 34px;
}
.claude-thinking-step {
padding: 4px 0;
opacity: 0;
animation: stepAppear 0.5s ease forwards;
}
.claude-thinking-step:nth-child(1) { animation-delay: 0.2s; }
.claude-thinking-step:nth-child(2) { animation-delay: 0.6s; }
.claude-thinking-step:nth-child(3) { animation-delay: 1.0s; }
.claude-thinking-step:nth-child(4) { animation-delay: 1.4s; }
@keyframes stepAppear {
to { opacity: 1; }
}
.claude-thinking-step::before {
content: "→ ";
color: #f97316;
}
</style>
<div class="quick-actions">
<button class="quick-btn" onclick="sendQuick('Génère un PDF')"><i class="fas fa-file-pdf" style="color:#ef4444"></i> PDF</button>
<button class="quick-btn" onclick="sendQuick('Génère un document Word')"><i class="fas fa-file-word" style="color:#2563eb"></i> Word</button>
<button class="quick-btn" onclick="sendQuick('Génère un fichier Excel')"><i class="fas fa-file-excel" style="color:#16a34a"></i> Excel</button>
<button class="quick-btn" onclick="sendQuick('Génère une présentation')"><i class="fas fa-file-powerpoint" style="color:#ea580c"></i> PPT</button>
<button class="quick-btn" onclick="sendQuick('Génère un QR code')"><i class="fas fa-qrcode" style="color:#7c3aed"></i> QR</button>
</div></div></div>
<div class="typing" id="typing">
<div class="typing-avatar">🧞</div>
<div class="typing-dots"><span></span><span></span><span></span></div>
<span style="color:var(--muted);font-size:.9rem">Réflexion...</span>
</div>
<div class="input-area"><div class="input-wrap">
<button class="input-btn" onclick="document.getElementById('fileInput').click()" title="Fichier"><i class="fas fa-paperclip"></i></button>
<button class="input-btn" onclick="document.getElementById('imageInput').click()" title="Image"><i class="fas fa-image"></i></button>
<input type="file" id="fileInput" style="display:none" multiple accept=".pdf,.doc,.docx,.xls,.xlsx,.txt" onchange="handleFiles(this.files)">
<input type="file" id="imageInput" style="display:none" multiple accept="image/*,video/*" onchange="handleFiles(this.files)">
<textarea id="input" placeholder="Écrivez votre message..." rows="1" onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();send()}"></textarea>
<button class="send-btn" onclick="send()"><i class="fas fa-arrow-up"></i></button>
</div></div></div>
<div id="artPanel"><div class="art-header"><h3><i class="fas fa-file-alt"></i> <span id="artTitle">Document</span></h3><button class="art-close" onclick="closeArt()">×</button></div><div id="artContent"></div><div class="art-footer"><a id="artDL" href="#" class="btn" download><i class="fas fa-download"></i> Télécharger</a></div></div>
</div>
<div class="modal" id="helpModal"><div class="modal-content"><h3>🧞 Aide HAMID</h3><p><strong>Génération :</strong></p><ul><li>PDF - "Génère un PDF"</li><li>Word - "Génère un document Word"</li><li>Excel - "Génère un fichier Excel"</li><li>PPT - "Génère une présentation"</li><li>QR - "Génère un QR code"</li></ul><p>📎 Fichiers | 🖼️ Images</p><button onclick="document.getElementById('helpModal').classList.remove('show')">OK</button></div></div>
<script>
let prov='<?= $defaultProvider ?>';let hist=[];let files=[];
document.addEventListener('DOMContentLoaded',()=>{const o=document.querySelector('[data-prov="'+prov+'"]');if(o)o.classList.add('active')});
function esc(s){const d=document.createElement('div');d.textContent=s;return d.innerHTML}
function fmt(s){return s.replace(/\*\*([^*]+)\*\*/g,'<strong>$1</strong>').replace(/\[([^\]]+)\]\(([^)]+)\)/g,'<a href="$2" target="_blank">$1</a>').replace(/\n/g,'<br>')}
function selProv(p,n){prov=p;document.getElementById('currentProvider').textContent=n;document.querySelectorAll('.provider-option').forEach(e=>e.classList.remove('active'));const o=document.querySelector('[data-prov="'+p+'"]');if(o)o.classList.add('active');document.getElementById('providerMenu').classList.remove('show')}
function showHelp(){document.getElementById('helpModal').classList.add('show')}
function newChat(){hist=[];files=[];document.getElementById('msgs').innerHTML='<div class="welcome" id="welcome"><div class="welcome-icon">🧞</div><h2>Bonjour</h2><p>Assistant IA WEVADS</p><div class="quick-actions"><button class="quick-btn" onclick="sendQuick(\'Génère un PDF\')"><i class="fas fa-file-pdf" style="color:#ef4444"></i> PDF</button><button class="quick-btn" onclick="sendQuick(\'Génère un Word\')"><i class="fas fa-file-word" style="color:#2563eb"></i> Word</button><button class="quick-btn" onclick="sendQuick(\'Génère un Excel\')"><i class="fas fa-file-excel" style="color:#16a34a"></i> Excel</button></div></div>';closeArt()}
function sendQuick(m){document.getElementById('input').value=m;send()}
function handleFiles(f){for(let x of f)files.push(x)}
function addMsg(role,txt,meta={}){const w=document.getElementById('welcome');if(w)w.style.display='none';const d=document.createElement('div');d.className='msg msg-'+role;if(role==='user')d.innerHTML='<div class="bubble">'+esc(txt)+'</div>';else{let tags='<span class="tag prov">'+(meta.provider||prov.toUpperCase())+'</span>';if(meta.kb_used)tags+='<span class="tag kb">📚 KB</span>';if(meta.duration)tags+='<span class="tag">'+meta.duration+'ms</span>';d.innerHTML='<div class="avatar">🧞</div><div class="msg-wrap"><div class="msg-content">'+fmt(txt)+'</div><div class="msg-footer">'+tags+'</div></div>'}document.getElementById('msgs').appendChild(d);document.getElementById('msgs').scrollTop=9999}
async function send(){
showClaudeThinking(); // showThinking(["Analyse de la demande...", "Consultation de la Knowledge Base...", "Réflexion et structuration...", "Génération de la réponse..."]);
const inp=document.getElementById('input');const m=inp.value.trim();if(!m)return;inp.value='';
addMsg('user',m);hist.push({role:'user',content:m});
updateThinkingText('🧠 Analyse en cours...');
try{
const fd=new FormData();fd.append('message',m);fd.append('provider',prov);fd.append('history',JSON.stringify(hist.slice(-6)));files.forEach(f=>fd.append('files[]',f));
const r=await fetch('/api/widget-api.php',{method:'POST',body:fd});const d=await r.json();
hideClaudeThinking();
if(d.success){addMsg('assistant',d.response,{provider:d.provider,duration:d.duration_ms,kb_used:d.kb_used});hist.push({role:'assistant',content:d.response});if(d.artifacts&&d.artifacts.length>0)showArt(d.artifacts[0])}
else addMsg('assistant','❌ '+(d.error||'Erreur'))
}catch(e){hideClaudeThinking();addMsg('assistant','❌ '+e.message)}
files=[]
}
function showArt(a){document.getElementById('artTitle').textContent=a.title||'Document';document.getElementById('artDL').href=a.url;const c=document.getElementById('artContent');if(a.type==='pdf')c.innerHTML='<iframe src="'+a.url+'"></iframe>';else if(a.type==='image')c.innerHTML='<div style="display:flex;align-items:center;justify-content:center;height:100%;padding:1rem"><img src="'+a.url+'" style="max-width:100%;max-height:100%"></div>';else c.innerHTML='<div style="text-align:center;padding:3rem"><i class="fas fa-file-alt" style="font-size:4rem;color:#06b6d4"></i><h3 style="margin:1rem 0">'+a.file+'</h3><a href="'+a.url+'" class="btn" download>Télécharger</a></div>';document.getElementById('artPanel').classList.add('open')}
function closeArt(){document.getElementById('artPanel').classList.remove('open')}
document.addEventListener('click',e=>{if(!e.target.closest('.provider-select'))document.getElementById('providerMenu').classList.remove('show');if(e.target.classList.contains('modal'))e.target.classList.remove('show')});
// === TOOLS FUNCTIONS ===
function toggleTools() {
const panel = document.getElementById("toolsPanel");
panel.style.display = panel.style.display === "none" ? "block" : "none";
}
function sendQuick(text) {
document.getElementById("messageInput").value = text;
document.getElementById("messageInput").focus();
}
async function runTool(action, params = {}) {
const url = new URL("/commonia/hamid-tools.php", window.location.origin);
url.searchParams.set("action", action);
for (const [k, v] of Object.entries(params)) {
url.searchParams.set(k, v);
}
try {
addMessage("assistant", `🔧 Exécution de ${action}...`);
const res = await fetch(url);
const data = await res.json();
let resultHTML = `<div class="tool-result"><div class="tool-header">🔧 ${action}</div><pre>${JSON.stringify(data, null, 2)}</pre></div>`;
addMessage("assistant", resultHTML, true);
} catch (e) {
addMessage("assistant", `❌ Erreur: ${e.message}`);
}
}
function promptTool(action, label, param, defaultVal = "") {
const value = prompt(label, defaultVal);
if (value) {
runTool(action, { [param]: value });
}
}
function addMessage(role, content, isHTML = false) {
const container = document.getElementById("messagesContainer") || document.querySelector(".messages-container");
if (!container) return;
const msg = document.createElement("div");
msg.className = "message " + role;
if (isHTML) {
msg.innerHTML = content;
} else {
msg.textContent = content;
}
container.appendChild(msg);
container.scrollTop = container.scrollHeight;
}
</script>
</body></html>
<!-- Tools Panel Modal -->
<div id="toolsPanelModal" style="display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.8);z-index:9999;padding:2rem;overflow-y:auto">
<div style="max-width:800px;margin:0 auto;background:#1e293b;border-radius:16px;padding:1.5rem;border:1px solid #334155">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:1rem">
<h2 style="color:#06b6d4;margin:0">🔧 Tools Système HAMID</h2>
<button onclick="toggleToolsPanel()" style="background:none;border:none;color:#94a3b8;font-size:1.5rem;cursor:pointer">✕</button>
</div>
<div style="display:grid;grid-template-columns:repeat(4,1fr);gap:0.75rem;margin-bottom:1rem">
<button onclick="runTool('status')" class="tool-btn-modal">📊 Status</button>
<button onclick="runTool('tables')" class="tool-btn-modal">🗄️ Tables DB</button>
<button onclick="promptAndRun('list','Chemin:','/opt/wevads/public')" class="tool-btn-modal">📁 Fichiers</button>
<button onclick="promptAndRun('dns','Domaine:')" class="tool-btn-modal">📧 DNS</button>
<button onclick="promptAndRun('bash','Commande:')" class="tool-btn-modal">💻 Bash</button>
<button onclick="promptAndRun('sql','Requête SQL:')" class="tool-btn-modal">🔎 SQL</button>
<button onclick="promptAndRun('read','Chemin fichier:')" class="tool-btn-modal">📄 Lire</button>
<button onclick="promptAndRun('calc','Expression:')" class="tool-btn-modal">🧮 Calcul</button>
</div>
<div id="toolResultArea" style="background:#0f172a;border-radius:8px;padding:1rem;min-height:200px;max-height:400px;overflow-y:auto">
<div style="color:#64748b;text-align:center">Cliquez sur un tool pour l'exécuter</div>
</div>
</div>
</div>
<style>
.tool-btn-modal{display:flex;flex-direction:column;align-items:center;gap:0.25rem;padding:0.75rem;background:linear-gradient(135deg,#0f172a,#1e293b);border:1px solid #334155;border-radius:8px;color:#e2e8f0;cursor:pointer;font-size:0.85rem;transition:all 0.2s}
.tool-btn-modal:hover{border-color:#06b6d4;transform:translateY(-2px);background:linear-gradient(135deg,#1e3a5f,#0f172a)}
</style>
<script>
function toggleToolsPanel(){
const p=document.getElementById('toolsPanelModal');
p.style.display=p.style.display==='none'?'flex':'none';
}
async function runTool(action,params={}){
const area=document.getElementById('toolResultArea');
area.innerHTML='<div style="color:#06b6d4">⏳ Exécution de '+action+'...</div>';
const url=new URL('/commonia/hamid-tools.php',location.origin);
url.searchParams.set('action',action);
for(const[k,v]of Object.entries(params))url.searchParams.set(k,v);
try{
const res=await fetch(url);
const data=await res.json();
area.innerHTML='<div style="color:#06b6d4;font-weight:bold;margin-bottom:0.5rem">🔧 '+action+'</div><pre style="color:#10b981;white-space:pre-wrap;margin:0">'+JSON.stringify(data,null,2)+'</pre>';
}catch(e){
area.innerHTML='<div style="color:#ef4444">❌ Erreur: '+e.message+'</div>';
}
}
function promptAndRun(action,label,defaultVal=''){
const val=prompt(label,defaultVal);
if(val){
const paramMap={list:'path',dns:'domain',bash:'cmd',sql:'sql',read:'path',calc:'expr',search:'q',fetch:'url'};
runTool(action,{[paramMap[action]||'q']:val});
}
}
</script>
</div>
<!-- Bouton Toggle Artefacts -->
<button class="artifacts-toggle" id="artifactsToggle" onclick="toggleArtifacts()">
📎
</button>
<!-- Panneau Artefacts -->
<div class="artifacts-panel" id="artifactsPanel">
<div class="artifacts-header">
<div class="artifacts-title">📎 Artefacts</div>
<button class="artifacts-close" onclick="toggleArtifacts()">✕</button>
</div>
<div class="artifacts-list" id="artifactsList">
<!-- Les artefacts seront ajoutés dynamiquement -->
</div>
<div class="project-section">
<div class="project-title">Contenu du projet</div>
<div class="project-card">
<div class="project-name">🧞 HAMID IA System</div>
<div class="project-desc">Créé par vous</div>
<div style="margin-top:0.75rem;padding:0.5rem;background:#f9fafb;border-radius:6px">
<small style="color:#6b7280">Ajoutez des PDF, documents ou autres textes à référencer</small>
</div>
</div>
</div>
</div>
<script>
// Toggle Artefacts Panel
function toggleArtifacts() {
const panel = document.getElementById('artifactsPanel');
const toggle = document.getElementById('artifactsToggle');
const main = document.querySelector('.chat-container') || document.querySelector('.main-content');
panel.classList.toggle('open');
toggle.classList.toggle('hidden');
if (main) main.classList.toggle('with-artifacts');
}
// Thinking Indicator Functions
function showThinking(steps = []) {
const container = document.getElementById('thinkingIndicator');
const stepsDiv = document.getElementById('thinkingSteps');
container.classList.add('active');
container.scrollIntoView({ behavior: 'smooth', block: 'center' });
stepsDiv.innerHTML = '';
if (steps.length > 0) {
steps.forEach((step, i) => {
setTimeout(() => {
const stepEl = document.createElement('div');
stepEl.className = 'thinking-step';
stepEl.textContent = '• ' + step;
stepEl.style.animationDelay = (i * 0.2) + 's';
stepsDiv.appendChild(stepEl);
}, i * 500);
});
}
}
function hideThinking() {
document.getElementById('thinkingIndicator').classList.remove('active');
}
function updateThinkingText(text) {
document.getElementById('thinkingText').textContent = text;
}
// Add Artifact to Panel
function addArtifact(artifact) {
const list = document.getElementById('artifactsList');
const icons = {
'pdf': '📄',
'doc': '📝',
'docx': '📝',
'xlsx': '📊',
'xls': '📊',
'pptx': '📽️',
'ppt': '📽️',
'html': '🌐',
'php': '💻',
'js': '⚡',
'json': '📋',
'txt': '📃',
'code': '💻',
'image': '🖼️',
'default': '📎'
};
const ext = artifact.type || artifact.name?.split('.').pop() || 'default';
const icon = icons[ext] || icons['default'];
const item = document.createElement('div');
item.className = 'artifact-item';
item.innerHTML = `
<div class="artifact-icon">${icon}</div>
<div class="artifact-info">
<div class="artifact-name">${artifact.name || 'Document'}</div>
<div class="artifact-meta">${artifact.meta || artifact.type || 'Fichier'}</div>
</div>
<a href="${artifact.url || '#'}" download class="artifact-download">⬇️</a>
`;
if (artifact.url) {
item.onclick = () => window.open(artifact.url, '_blank');
}
list.insertBefore(item, list.firstChild);
// Ouvrir le panel si fermé
if (!document.getElementById('artifactsPanel').classList.contains('open')) {
toggleArtifacts();
}
}
// Override send function to show thinking
const originalSend = window.sendMessage;
if (typeof originalSend === 'function') {
window.sendMessage = async function(...args) {
showClaudeThinking(); // showThinking(['Analyse de la requête...', 'Consultation de la Knowledge Base...', 'Génération de la réponse...']);
try {
const result = await originalSend.apply(this, args);
hideClaudeThinking();
return result;
} catch(e) {
hideClaudeThinking();
throw e;
}
};
}
// Exemple: Ajouter quelques artefacts de démonstration
document.addEventListener('DOMContentLoaded', function() {
// Charger les documents existants
fetch('/commonia/hamid-tools.php?action=sql&sql=SELECT id,title,type,url FROM admin.hamid_documents ORDER BY id DESC LIMIT 10')
.then(r => r.json())
.then(data => {
if (data.success && data.data) {
data.data.forEach(doc => {
addArtifact({
name: doc.title,
type: doc.type,
url: doc.url,
meta: doc.type
});
});
}
})
.catch(() => {});
});
</script>
<!-- Relevant Chats Button & Panel -->
<button class="relevant-chats-btn" onclick="toggleRelevantPanel()" id="relevantBtn">
<span>🔍 Relevant chats</span>
<span class="count" id="relevantCount">0</span>
</button>
<div class="relevant-panel" id="relevantPanel">
<div class="relevant-header">
<h3>🔍 Conversations pertinentes</h3>
<button class="relevant-close" onclick="toggleRelevantPanel()">×</button>
</div>
<div class="relevant-list" id="relevantList">
<div class="relevant-empty">
<p>💬 Les conversations similaires apparaîtront ici</p>
<small>Commencez à taper pour rechercher</small>
</div>
</div>
</div>
<script>
// Relevant Chats System
let relevantSearchTimeout = null;
let lastSearchQuery = '';
function toggleRelevantPanel() {
const panel = document.getElementById('relevantPanel');
panel.classList.toggle('open');
}
function searchRelevantChats(query) {
if (query.length < 3 || query === lastSearchQuery) return;
lastSearchQuery = query;
clearTimeout(relevantSearchTimeout);
relevantSearchTimeout = setTimeout(() => {
const list = document.getElementById('relevantList');
list.innerHTML = '<div class="relevant-searching">🔄 Recherche en cours...</div>';
fetch('/commonia/hamid-search.php?q=' + encodeURIComponent(query) + '&limit=10')
.then(r => r.json())
.then(data => {
if (data.success && data.count > 0) {
document.getElementById('relevantCount').textContent = data.count;
renderRelevantResults(data.results);
} else {
document.getElementById('relevantCount').textContent = '0';
list.innerHTML = '<div class="relevant-empty"><p>Aucune conversation similaire</p></div>';
}
})
.catch(err => {
list.innerHTML = '<div class="relevant-empty"><p>❌ Erreur de recherche</p></div>';
});
}, 500);
}
function renderRelevantResults(results) {
const list = document.getElementById('relevantList');
list.innerHTML = '';
results.forEach(item => {
const div = document.createElement('div');
div.className = 'relevant-item';
div.innerHTML = `
<div class="question">💬 ${escapeHtml(item.question)}</div>
<div class="response">${escapeHtml(item.response || 'Pas de réponse')}</div>
<div class="meta">
<span class="provider">${item.provider || 'IA'}</span>
<span>${item.date}</span>
</div>
`;
div.onclick = () => useRelevantChat(item);
list.appendChild(div);
});
}
function escapeHtml(text) {
if (!text) return '';
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
function useRelevantChat(item) {
// Insérer la question dans l'input
const input = document.getElementById('messageInput') || document.querySelector('textarea');
if (input) {
input.value = item.question;
input.focus();
}
toggleRelevantPanel();
}
// Hook sur l'input pour rechercher automatiquement
document.addEventListener('DOMContentLoaded', function() {
const input = document.getElementById('messageInput') || document.querySelector('textarea');
if (input) {
input.addEventListener('input', function() {
const query = this.value.trim();
if (query.length >= 3) {
searchRelevantChats(query);
}
});
}
});
</script>
<script>
// Auto-save conversations to database
function saveConversation(userMessage, aiResponse, provider) {
const sessionId = window.hamidSessionId || 'session_' + Date.now();
window.hamidSessionId = sessionId;
// Save user message
fetch('/commonia/hamid-tools.php?action=sql', {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'sql=' + encodeURIComponent(`INSERT INTO admin.hamid_conversations (session_id, role, content, provider) VALUES ('${sessionId}', 'user', '${userMessage.replace(/'/g, "''")}', '${provider}')`)
});
// Save AI response
fetch('/commonia/hamid-tools.php?action=sql', {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'sql=' + encodeURIComponent(`INSERT INTO admin.hamid_conversations (session_id, role, content, provider) VALUES ('${sessionId}', 'assistant', '${aiResponse.replace(/'/g, "''")}', '${provider}')`)
});
}
// Override the original send to capture conversations
(function() {
const originalFetch = window.fetch;
window.fetch = function(...args) {
return originalFetch.apply(this, args).then(response => {
// Clone response to read it
const clone = response.clone();
// Check if it's a chat API call
if (args[0] && args[0].includes && args[0].includes('commonia-brain.php')) {
clone.json().then(data => {
if (data.success && data.response) {
const input = document.getElementById('messageInput') || document.querySelector('textarea');
const userMsg = input ? input.dataset.lastMessage : '';
const provider: 'cerebras';
if (userMsg) {
saveConversation(userMsg, data.response, provider);
}
}
}).catch(() => {});
}
return response;
});
};
// Capture message before send
const input = document.getElementById('messageInput') || document.querySelector('textarea');
if (input) {
const form = input.closest('form') || input.parentElement;
form?.addEventListener('submit', () => {
input.dataset.lastMessage = input.value;
});
document.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
input.dataset.lastMessage = input.value;
}
});
}
})();
</script>
<style>
/* Thinking Bubble - Style Claude */
.thinking-bubble {
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
border: 1px solid #333;
border-radius: 16px;
padding: 16px 20px;
margin: 10px 0;
max-width: 85%;
animation: fadeIn 0.3s ease;
}
.thinking-bubble .thinking-header {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 12px;
color: #8b5cf6;
font-weight: 600;
font-size: 14px;
}
.thinking-bubble .thinking-header .icon {
animation: pulse 1.5s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.thinking-bubble .thinking-content {
color: #a0a0a0;
font-size: 13px;
line-height: 1.6;
font-style: italic;
border-left: 3px solid #8b5cf6;
padding-left: 12px;
margin-left: 5px;
}
.thinking-bubble .thinking-step {
display: flex;
align-items: flex-start;
gap: 8px;
margin: 8px 0;
color: #888;
font-size: 13px;
}
.thinking-bubble .thinking-step.done {
color: #10b981;
}
.thinking-bubble .thinking-step.active {
color: #8b5cf6;
}
.thinking-bubble .step-icon {
min-width: 20px;
}
.thinking-bubble .thinking-steps {
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid #333;
}
.thinking-collapsed {
cursor: pointer;
user-select: none;
}
.thinking-collapsed:hover {
background: #1e1e3e;
}
.thinking-toggle {
color: #666;
font-size: 12px;
margin-left: auto;
}
</style>
<style>
/* OVERRIDE - Thinking Bubble Style Claude */
.thinking-container {
display: none;
background: linear-gradient(135deg, #1e1b4b 0%, #312e81 100%);
border: 1px solid #4f46e5;
border-radius: 16px;
padding: 16px 20px;
margin: 16px auto;
max-width: 600px;
box-shadow: 0 4px 20px rgba(79, 70, 229, 0.3);
}
.thinking-container.active {
display: flex !important;
align-items: flex-start;
gap: 12px;
animation: fadeInUp 0.3s ease;
}
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.thinking-spinner::before {
content: "✦";
font-size: 28px;
color: #f97316;
animation: sparkle 1s ease-in-out infinite;
}
.thinking-text {
color: #e0e7ff;
font-size: 15px;
font-weight: 500;
}
.thinking-steps {
margin-top: 8px;
}
.thinking-step {
color: #a5b4fc;
font-size: 13px;
padding: 4px 0;
opacity: 0;
animation: stepFade 0.5s ease forwards;
}
@keyframes stepFade {
to { opacity: 1; }
}
</style>