(function (){
'use strict';
const state={
open: false,
loading: false,
category: 'dashboard',
history: [],
categories: null,
conversationId: null,
};
const isAdmin=typeof claudeChatbot!=='undefined'&&claudeChatbot.isAdmin;
let $trigger, $container, $messages, $input, $sendBtn, $categoriesBar;
const icons={
chat: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>',
close: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>',
send: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>',
bot: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 8V4H8"/><rect width="16" height="12" x="4" y="8" rx="2"/><path d="M2 14h2"/><path d="M20 14h2"/><path d="M15 13v2"/><path d="M9 13v2"/></svg>',
user: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>',
spark: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z"/></svg>',
'chart-bar': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="20" x2="12" y2="10"/><line x1="18" y1="20" x2="18" y2="4"/><line x1="6" y1="20" x2="6" y2="16"/></svg>',
'share': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"/><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"/></svg>',
'box': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z"/><path d="m3.3 7 8.7 5 8.7-5"/><path d="M12 22V12"/></svg>',
'users': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>',
'layout-dashboard': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="7" height="9" x="3" y="3" rx="1"/><rect width="7" height="5" x="14" y="3" rx="1"/><rect width="7" height="9" x="14" y="12" rx="1"/><rect width="7" height="5" x="3" y="16" rx="1"/></svg>',
};
document.addEventListener('DOMContentLoaded', ()=> {
$trigger=document.getElementById('ccb-trigger');
$container=document.getElementById('ccb-container');
$messages=document.getElementById('ccb-messages');
$input=document.getElementById('ccb-input');
$sendBtn=document.getElementById('ccb-send');
$categoriesBar=document.getElementById('ccb-categories');
if(!$trigger||!$container) return;
$trigger.addEventListener('click', toggleChat);
document.getElementById('ccb-close')?.addEventListener('click', toggleChat);
$sendBtn.addEventListener('click', sendMessage);
$input.addEventListener('keydown', (e)=> {
if(e.key==='Enter'&&!e.shiftKey){
e.preventDefault();
sendMessage();
}});
$input.addEventListener('input', autoResize);
loadCategories();
if(isAdmin){
loadConversations();
}});
function toggleChat(){
state.open = !state.open;
$container.classList.toggle('ccb-closed', !state.open);
$trigger.classList.toggle('ccb-hidden', state.open);
if(state.open){
setTimeout(()=> $input.focus(), 300);
}}
async function loadCategories(){
try {
const resp=await fetch(claudeChatbot.restUrl.replace('/ask', '/categories'));
state.categories=await resp.json();
renderCategories();
renderWelcome();
} catch {
renderWelcome();
}}
function renderCategories(){
if(!state.categories||!$categoriesBar) return;
$categoriesBar.innerHTML='';
for (const [key, cat] of Object.entries(state.categories)){
const btn=document.createElement('button');
btn.className='ccb-category-btn' + (key===state.category ? ' ccb-active':'');
btn.dataset.category=key;
btn.innerHTML=(icons[cat.icon]||'') + '<span>' + cat.label + '</span>';
btn.addEventListener('click', ()=> switchCategory(key));
$categoriesBar.appendChild(btn);
}}
function switchCategory(key){
state.category=key;
state.history=[];
state.conversationId=null;
document.querySelectorAll('.ccb-category-btn').forEach((btn)=> {
btn.classList.toggle('ccb-active', btn.dataset.category===key);
});
$messages.innerHTML='';
renderWelcome();
}
function renderWelcome(){
const cat=state.categories?.[state.category];
const suggestions=cat?.suggestions||[
'Fais-moi un résumé de la semaine',
'Quels sont mes meilleurs produits ?',
'Analyse mes ventes récentes',
];
const userName=(typeof claudeChatbot!=='undefined'&&claudeChatbot.userName) ? claudeChatbot.userName:'';
const welcomeEl=document.createElement('div');
welcomeEl.className='ccb-welcome';
welcomeEl.innerHTML=`
<div class="ccb-welcome-icon">${icons.spark}</div>
<h4>Bonjour${userName ? ' ' + escapeHtml(userName):''} ! Comment puis-je vous aider ?</h4>
<p>Posez-moi n'importe quelle question sur vos données.</p>
<div class="ccb-suggestions">
${suggestions.map((s)=> `<button class="ccb-suggestion">${escapeHtml(s)}</button>`).join('')}
</div>
`;
$messages.appendChild(welcomeEl);
welcomeEl.querySelectorAll('.ccb-suggestion').forEach((btn)=> {
btn.addEventListener('click', ()=> {
$input.value=btn.textContent;
sendMessage();
});
});
}
async function sendMessage(){
const text=$input.value.trim();
if(!text||state.loading) return;
const welcome=$messages.querySelector('.ccb-welcome');
if(welcome) welcome.remove();
appendMessage('user', text);
state.history.push({ role: 'user', content: text });
$input.value='';
$input.style.height='auto';
state.loading=true;
$sendBtn.disabled=true;
if(isAdmin){
if(!state.conversationId){
state.conversationId=await createConversation(state.category);
}
saveMessage(state.conversationId, 'user', text);
}
const typingEl=showTyping();
try {
const resp=await fetch(claudeChatbot.restUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-WP-Nonce': claudeChatbot.nonce,
},
body: JSON.stringify({
message: text,
category: state.category,
history: state.history.slice(-20),
}),
});
const data=await resp.json();
typingEl.remove();
if(data.error){
appendMessage('assistant', 'Désolé, une erreur est survenue:' + data.error);
}else{
const response=data.response;
appendMessage('assistant', response);
state.history.push({ role: 'assistant', content: response });
if(data.cached){
appendSystemNote('Réponse depuis le cache');
}
if(isAdmin&&state.conversationId){
saveMessage(state.conversationId, 'assistant', response);
refreshConversations();
}}
} catch (err){
typingEl.remove();
appendMessage('assistant', 'Erreur de connexion. Veuillez réessayer.');
}
state.loading=false;
$sendBtn.disabled=false;
$input.focus();
}
async function createConversation(category){
try {
const r=await fetch(claudeChatbot.restUrl.replace('/ask', '/conversations'), {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': claudeChatbot.nonce },
body: JSON.stringify({ category }),
});
const data=await r.json();
return data.id||null;
} catch { return null; }}
async function saveMessage(convId, role, content){
try {
await fetch(claudeChatbot.restUrl.replace('/ask', '/conversations/' + convId + '/messages'), {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': claudeChatbot.nonce },
body: JSON.stringify({ role, content }),
});
} catch {}}
async function loadConversations(){
const list=document.getElementById('cdf-conv-list');
if(!list) return;
try {
const r=await fetch(claudeChatbot.restUrl.replace('/ask', '/conversations'), {
headers: { 'X-WP-Nonce': claudeChatbot.nonce },
});
const convs=await r.json();
renderConversationList(list, convs);
} catch {}}
function refreshConversations(){
loadConversations();
}
function renderConversationList(el, convs){
if(!convs.length){
el.innerHTML='<div class="cdf-empty" style="padding:20px;font-size:12px">Aucune conversation</div>';
return;
}
el.innerHTML=convs.map(c=> `
<div class="cdf-conv-item ${c.id==state.conversationId ? 'active':''}" data-id="${c.id}">
<div class="cdf-conv-item-title">${escapeHtml(c.title||'Sans titre')}</div>
<div class="cdf-conv-item-meta">
<span>${c.message_count||0} msg</span>
<span>${formatDate(c.updated_at)}</span>
<button class="cdf-conv-delete" data-id="${c.id}" title="Supprimer">✕</button>
</div>
</div>
`).join('');
el.querySelectorAll('.cdf-conv-item').forEach(item=> {
item.addEventListener('click', (e)=> {
if(e.target.classList.contains('cdf-conv-delete')) return;
loadConversation(parseInt(item.dataset.id));
});
});
el.querySelectorAll('.cdf-conv-delete').forEach(btn=> {
btn.addEventListener('click', async (e)=> {
e.stopPropagation();
const id=parseInt(btn.dataset.id);
await fetch(claudeChatbot.restUrl.replace('/ask', '/conversations/' + id), {
method: 'DELETE',
headers: { 'X-WP-Nonce': claudeChatbot.nonce },
});
if(state.conversationId===id) startNewConversation();
refreshConversations();
});
});
}
async function loadConversation(id){
try {
const r=await fetch(claudeChatbot.restUrl.replace('/ask', '/conversations/' + id), {
headers: { 'X-WP-Nonce': claudeChatbot.nonce },
});
const conv=await r.json();
if(!conv||conv.error) return;
state.conversationId=id;
state.category=conv.category||'dashboard';
state.history=[];
document.querySelectorAll('.ccb-category-btn').forEach(btn=> {
btn.classList.toggle('ccb-active', btn.dataset.category===state.category);
});
$messages.innerHTML='';
if(conv.messages&&conv.messages.length){
conv.messages.forEach(m=> {
appendMessage(m.role, m.content);
state.history.push({ role: m.role, content: m.content });
});
}else{
renderWelcome();
}
document.querySelectorAll('.cdf-conv-item').forEach(el=> {
el.classList.toggle('active', el.dataset.id==id);
});
} catch {}}
window.cdfNewConversation=function (){
startNewConversation();
};
function startNewConversation(){
state.conversationId=null;
state.history=[];
$messages.innerHTML='';
renderWelcome();
document.querySelectorAll('.cdf-conv-item').forEach(el=> el.classList.remove('active'));
}
function appendMessage(role, content){
const msgEl=document.createElement('div');
msgEl.className='ccb-message ccb-' + role;
const avatarIcon=role==='assistant' ? icons.bot:icons.user;
const bubbleContent=role==='assistant' ? renderMarkdown(content):escapeHtml(content);
msgEl.innerHTML=`
<div class="ccb-msg-avatar">${avatarIcon}</div>
<div class="ccb-msg-bubble">${bubbleContent}</div>
`;
$messages.appendChild(msgEl);
scrollToBottom();
}
function appendSystemNote(text){
const el=document.createElement('div');
el.style.cssText='text-align:center;font-size:10px;color:#94a3b8;padding:4px;';
el.textContent=text;
$messages.appendChild(el);
}
function showTyping(){
const el=document.createElement('div');
el.className='ccb-typing';
el.innerHTML=`
<div class="ccb-msg-avatar">${icons.bot}</div>
<div class="ccb-typing-dots">
<span></span><span></span><span></span>
</div>
`;
$messages.appendChild(el);
scrollToBottom();
return el;
}
function renderMarkdown(text){
if(!text) return '';
const codeBlocks=[];
text=text.replace(/```(\w*)\n([\s\S]*?)```/g, (_, lang, code)=> {
codeBlocks.push('<pre><code>' + escapeHtml(code.trim()) + '</code></pre>');
return '\n%%CODE_BLOCK_' + (codeBlocks.length - 1) + '%%\n';
});
const lines=text.split('\n');
const blocks=[];
let i=0;
while (i < lines.length){
if(lines[i].trim().startsWith('|')&&lines[i].trim().endsWith('|')){
const tableLines=[];
while (i < lines.length&&lines[i].trim().startsWith('|')&&lines[i].trim().endsWith('|')){
tableLines.push(lines[i].trim());
i++;
}
blocks.push(renderTable(tableLines));
}else{
blocks.push(lines[i]);
i++;
}}
let html=blocks.join('\n');
html=html.replace(/`([^`]+)`/g, (_, code)=> '<code>' + escapeHtml(code) + '</code>');
html=html.replace(/\*\*\*(.+?)\*\*\*/g, '<strong><em>$1</em></strong>');
html=html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
html=html.replace(/\*(.+?)\*/g, '<em>$1</em>');
html=html.replace(/^#{3}\s+(.+)$/gm, '<h3>$1</h3>');
html=html.replace(/^#{2}\s+(.+)$/gm, '<h2>$1</h2>');
html=html.replace(/^#{1}\s+(.+)$/gm, '<h1>$1</h1>');
html=html.replace(/^---+$/gm, '<hr>');
html=html.replace(/^[\-\*]\s+(.+)$/gm, '%%LI%%$1%%/LI%%');
html=html.replace(/(%%LI%%[\s\S]*?%%\/LI%%\n?)+/g, (match)=> {
const items=match.replace(/%%LI%%/g, '<li>').replace(/%%\/LI%%/g, '</li>');
return '<ul>' + items.trim() + '</ul>';
});
html=html.replace(/^\d+\.\s+(.+)$/gm, '%%OLI%%$1%%/OLI%%');
html=html.replace(/(%%OLI%%[\s\S]*?%%\/OLI%%\n?)+/g, (match)=> {
const items=match.replace(/%%OLI%%/g, '<li>').replace(/%%\/OLI%%/g, '</li>');
return '<ol>' + items.trim() + '</ol>';
});
html=html.replace(/%%CODE_BLOCK_(\d+)%%/g, (_, idx)=> codeBlocks[parseInt(idx)]);
html=html.replace(/\n\n+/g, '</p><p>');
html=html.replace(/\n/g, '<br>');
html='<p>' + html + '</p>';
const blockTags=['h1', 'h2', 'h3', 'ul', 'ol', 'table', 'pre', 'hr', 'div'];
blockTags.forEach(tag=> {
html=html.replace(new RegExp('<p>\\s*(<' + tag + '[> ])', 'g'), '$1');
html=html.replace(new RegExp('(</' + tag + '>)\\s*</p>', 'g'), '$1');
});
html=html.replace(/<p>\s*<\/p>/g, '');
html=html.replace(/<br>\s*(<\/?(?:h[1-3]|ul|ol|li|table|thead|tbody|tr|pre|hr|div))/g, '$1');
return html;
}
function renderTable(tableLines){
if(tableLines.length < 2) return tableLines.join('<br>');
const parseRow=(line)=> {
return line.replace(/^\|/, '').replace(/\|$/, '').split('|').map(c=> c.trim());
};
const headerCells=parseRow(tableLines[0]);
const isSeparator=/^\|[\s\-:|]+\|$/.test(tableLines[1]);
const bodyStart=isSeparator ? 2:1;
let html='<div class="ccb-table-wrap"><table>';
html +='<thead><tr>';
headerCells.forEach(c=> { html +='<th>' + c + '</th>'; });
html +='</tr></thead>';
if(bodyStart < tableLines.length){
html +='<tbody>';
for (let r=bodyStart; r < tableLines.length; r++){
const cells=parseRow(tableLines[r]);
html +='<tr>';
cells.forEach(c=> { html +='<td>' + c + '</td>'; });
html +='</tr>';
}
html +='</tbody>';
}
html +='</table></div>';
return html;
}
function escapeHtml(text){
const div=document.createElement('div');
div.textContent=text;
return div.innerHTML;
}
function scrollToBottom(){
requestAnimationFrame(()=> {
$messages.scrollTop=$messages.scrollHeight;
});
}
function autoResize(){
$input.style.height='auto';
$input.style.height=Math.min($input.scrollHeight, 100) + 'px';
}
function formatDate(d){
if(!d) return '';
return new Date(d).toLocaleDateString('fr-FR', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' });
}})();