Files
html/wevia.html
2026-04-22 17:15:03 +02:00

3813 lines
239 KiB
HTML
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.
<!DOCTYPE html>
<html lang="fr">
<head>
<script src="/api/ambre-confidential-shield.js"></script>
<script>
(function(){
window.addEventListener("error", function(e){
if (e.message && (e.message.includes("regular expression") || e.message.includes("Invalid regular"))) {
e.preventDefault();
e.stopPropagation();
return true;
}
}, true);
window.addEventListener("unhandledrejection", function(e){
if (e.reason && e.reason.message && e.reason.message.includes("regular expression")) {
e.preventDefault();
}
});
})();
</script>
<meta charset="UTF-8">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<meta name="apple-mobile-web-app-capable" content="yes">
<title>WEVIA — Intelligence Artificielle Cognitive</title>
<link rel="icon" href="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20120%20120%22%3E%3Cdefs%3E%3ClinearGradient%20id%3D%22gl%22%20x1%3D%220%22%20y1%3D%220%22%20x2%3D%220%22%20y2%3D%221%22%3E%3Cstop%20offset%3D%220%25%22%20stop-color%3D%22%23a78bfa%22%2F%3E%3Cstop%20offset%3D%22100%25%22%20stop-color%3D%22%237c3aed%22%2F%3E%3C%2FlinearGradient%3E%3ClinearGradient%20id%3D%22gr%22%20x1%3D%220%22%20y1%3D%220%22%20x2%3D%220%22%20y2%3D%221%22%3E%3Cstop%20offset%3D%220%25%22%20stop-color%3D%22%2367e8f9%22%2F%3E%3Cstop%20offset%3D%22100%25%22%20stop-color%3D%22%2306b6d4%22%2F%3E%3C%2FlinearGradient%3E%3Cfilter%20id%3D%22glow%22%3E%3CfeGaussianBlur%20stdDeviation%3D%221.5%22%20result%3D%22b%22%2F%3E%3CfeMerge%3E%3CfeMergeNode%20in%3D%22b%22%2F%3E%3CfeMergeNode%20in%3D%22SourceGraphic%22%2F%3E%3C%2FfeMerge%3E%3C%2Ffilter%3E%3C%2Fdefs%3E%3Cpath%20d%3D%22M60%2C28%20C56%2C24%2048%2C22%2041%2C25%20C34%2C28%2030%2C35%2030%2C42%20C27%2C44%2025%2C49%2027%2C54%20C24%2C57%2023%2C63%2026%2C68%20C29%2C73%2035%2C75%2040%2C73%20C42%2C78%2046%2C82%2052%2C82%20C57%2C82%2060%2C79%2060%2C79%22%20fill%3D%22none%22%20stroke%3D%22url%28%23gl%29%22%20stroke-width%3D%223%22%20stroke-linecap%3D%22round%22%20filter%3D%22url%28%23glow%29%22%2F%3E%3Cpath%20d%3D%22M60%2C28%20C64%2C24%2072%2C22%2079%2C25%20C86%2C28%2090%2C35%2090%2C42%20C93%2C44%2095%2C49%2093%2C54%20C96%2C57%2097%2C63%2094%2C68%20C91%2C73%2085%2C75%2080%2C73%20C78%2C78%2074%2C82%2068%2C82%20C63%2C82%2060%2C79%2060%2C79%22%20fill%3D%22none%22%20stroke%3D%22url%28%23gr%29%22%20stroke-width%3D%223%22%20stroke-linecap%3D%22round%22%20filter%3D%22url%28%23glow%29%22%2F%3E%3Ccircle%20cx%3D%2260%22%20cy%3D%2253%22%20r%3D%226.5%22%20fill%3D%22url%28%23gl%29%22%20filter%3D%22url%28%23glow%29%22%2F%3E%3Ccircle%20cx%3D%2240%22%20cy%3D%2230%22%20r%3D%223%22%20fill%3D%22%23a78bfa%22%2F%3E%3Ccircle%20cx%3D%2280%22%20cy%3D%2230%22%20r%3D%223%22%20fill%3D%22%2367e8f9%22%2F%3E%3Ccircle%20cx%3D%2228%22%20cy%3D%2252%22%20r%3D%222.5%22%20fill%3D%22%237c3aed%22%2F%3E%3Ccircle%20cx%3D%2292%22%20cy%3D%2252%22%20r%3D%222.5%22%20fill%3D%22%2306b6d4%22%2F%3E%3Cline%20x1%3D%2242%22%20y1%3D%2232%22%20x2%3D%2257%22%20y2%3D%2250%22%20stroke%3D%22%23a78bfa%22%20stroke-width%3D%221.2%22%20opacity%3D%220.6%22%2F%3E%3Cline%20x1%3D%2230%22%20y1%3D%2252%22%20x2%3D%2254%22%20y2%3D%2253%22%20stroke%3D%22%237c3aed%22%20stroke-width%3D%221.2%22%20opacity%3D%220.6%22%2F%3E%3Cline%20x1%3D%2278%22%20y1%3D%2232%22%20x2%3D%2263%22%20y2%3D%2250%22%20stroke%3D%22%2367e8f9%22%20stroke-width%3D%221.2%22%20opacity%3D%220.6%22%2F%3E%3Cline%20x1%3D%2290%22%20y1%3D%2252%22%20x2%3D%2266%22%20y2%3D%2253%22%20stroke%3D%22%2306b6d4%22%20stroke-width%3D%221.2%22%20opacity%3D%220.6%22%2F%3E%3C%2Fsvg%3E">
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/mermaid/10.9.0/mermaid.min.js"></script>
<script defer src="/wevia-mermaid-fix.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css" media="print" onload="this.media='all'">
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/python.min.js"></script>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/php.min.js"></script>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/sql.min.js"></script>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/bash.min.js"></script>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/json.min.js"></script>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/yaml.min.js"></script>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/xml.min.js"></script>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/css.min.js"></script>
<script defer src="/js/wevia-artifact-renderer.js"></script>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/javascript.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.9/katex.min.css" media="print" onload="this.media='all'">
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.9/katex.min.js"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<style>
:root {
--bg: #FFFFFF; --bg2: #F7F7F8; --tx: #1a1a2e; --tx2: #6b7280;
--acc: #7c3aed; --acc2: #06b6d4; --border: #e5e7eb;
--msg-user: #7c6bf0; --msg-ai: #ffffff; --radius: 16px;
}
.dark {
--bg: #171717; --bg2: #1f1f1f; --tx: #e8e8f0; --tx2: #8888a0;
--acc: #9b8afb; --acc2: #06b6d4; --border: #2e2e2e;
--msg-user: #7c6bf0; --msg-ai: #1a1a30;
}
* { margin: 0; padding: 0; box-sizing: border-box; } html, body { background: #FFFFFF !important; }
body { font-family: 'Inter', 'DM Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; -webkit-font-smoothing: antialiased; background: var(--bg); color: var(--tx); height: 100dvh; overflow: hidden; margin: 0; transition: background .3s, color .3s; }
/* ─── Header ─── */
.header { display: flex; align-items: center; justify-content: space-between; padding: 8px 16px; background: #FFFFFF; border-bottom: 1px solid #eee; flex-shrink: 0; position: relative; }
.brand { display: flex; align-items: center; gap: 10px; }
.brand-icon { font-size: 28px; }
.brand-name { font-size: 17px; font-weight: 700; color: var(--tx); }
.nx-badges { display: none !important; }
.nx-badge { font-size: 10px; padding: 2px 7px; border-radius: 10px; background: rgba(255,255,255,0.06); color: var(--tx2); cursor: default; white-space: nowrap; }
.nx-prov { background: rgba(55,48,163,0.15); color: #a78bfa; }
.nx-kb { background: rgba(16,185,129,0.12); color: #6ee7b7; }
.nx-web { background: rgba(55,48,163,0.12); color: #93c5fd; }
.nx-graph { background: rgba(245,158,11,0.12); color: #fcd34d; }
.nx-mem { background: rgba(236,72,153,0.12); color: #f9a8d4; }
.nx-verified { background: rgba(16,185,129,0.2); color: #34d399; font-weight: 600; }
.nx-critic { background: rgba(251,146,60,0.15); color: #fdba74; }
.nx-src { background: rgba(139,92,246,0.12); color: #c4b5fd; }
.nx-engine { background: rgba(255,255,255,0.04); color: var(--tx3); font-size: 9px; }
.brand-tag { font-size: 9px; color: #94a3b8; font-weight: 500; letter-spacing: 1px; text-transform: uppercase; }
.hdr-btns { display: flex; gap: 6px; align-items: center; }
.hdr-btn { background: transparent; border: none; border-radius: 8px; padding: 7px 12px; font-size: 13px; cursor: pointer; color: #6b7280; transition: all .15s; font-family: inherit; font-weight: 500; }
.hdr-btn:hover { background: #f3f4f6; color: #374151; }
.hdr-btn.active { background: #f3f4f6; color: #374151; border-radius: 8px; }
#modeBtn { background: #FFFFFF; color: #374151; border: 1px solid #d1d5db; padding: 6px 14px; border-radius: 999px; font-weight: 500; font-size: 13px; letter-spacing: 0; box-shadow: 0 1px 2px rgba(0,0,0,0.05); transition: all 0.15s ease; }
#modeBtn:hover { background: #f9fafb; border-color: #9ca3af; }
#modeBtn .mode-dot { width: 6px; height: 6px; background: #10b981; box-shadow: 0 0 0 2px rgba(16,185,129,0.2); }
.mode-dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: var(--acc); margin-right: 5px; }
/* ─── Language Screen ─── */
.lang-screen { flex: 1; display: none; flex-direction: column; align-items: center; justify-content: center; gap: 24px; padding: 30px; }
.lang-screen h1 { font-size: 28px; font-weight: 700; }
.lang-screen p { color: var(--tx2); font-size: 15px; }
.lang-grid { display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; max-width: 500px; }
.lang-btn { padding: 10px 20px; border-radius: 12px; border: 1px solid #e5e7eb; background: #fff; cursor: pointer; font-size: 14px; font-family: inherit; color: #374151; transition: all .15s; min-width: 120px; }
.lang-btn:hover { border-color: #9ca3af; background: #f9fafb; }
/* ─── Chat Area ─── */
.chat-wrap { flex: 1; display: none; flex-direction: column; overflow: hidden; }
.chat-wrap.active { display: flex; }
.messages { flex: 1; overflow-y: auto; padding: 0; -webkit-overflow-scrolling: touch; scroll-behavior: smooth; scrollbar-width: thin; scrollbar-color: transparent transparent; } .messages:hover { scrollbar-color: rgba(0,0,0,0.12) transparent; }
.msg { padding: 12px 0; animation: fadeIn .3s ease; }
.msg.user { display: flex; justify-content: center; }
.msg.assistant { background: var(--bg); display: flex; justify-content: center; }
.msg-inner { max-width: 780px; width: 100%; padding: 0 24px; }
.msg.user .msg-inner { display: flex; justify-content: flex-end; }
.bubble { font-size: 15.5px; line-height: 1.6; word-wrap: break-word; overflow-wrap: break-word; }
.msg.user .bubble { background: #f0edff; color: #1a1a2e; padding: 10px 16px; border-radius: 18px 18px 4px 18px; font-size: 13.5px; line-height: 1.6; display: inline-block; max-width: 72%; border: 1px solid #e0d8ff; }
.msg.assistant .bubble { padding: 4px 0; background: none; border: none; box-shadow: none; }
.bubble-ai { padding: 4px 0; background: none; border: none; box-shadow: none; font-size: 15.5px; line-height: 1.6; word-wrap: break-word; overflow-wrap: break-word; }
.msg-meta { display: inline-flex; align-items: center; gap: 2px; margin-top: 8px; padding: 3px 4px; font-size: 11px; color: var(--tx2); border-radius: 8px; opacity: 0; transition: opacity .25s; }
.msg:hover .msg-meta { opacity: 0.7; } .msg:hover .msg-meta:hover { opacity: 1; }
.meta-time { font-weight: 500; }
.meta-sep { opacity: 0.3; font-size: 10px; }
.meta-btn { background: none; border: none; cursor: pointer; font-size: 13px; padding: 4px 6px; border-radius: 6px; transition: all .12s; line-height: 1; color: #9ca3af; }
.meta-btn:hover { background: #f3f4f6; color: #374151; }
/* ─── Markdown Rendering ─── */
.bubble pre { background: #1a1a2e; color: #cdd6f4; padding: 14px; border-radius: 10px; overflow-x: auto; margin: 10px 0; font-family: 'JetBrains Mono', monospace; font-size: 13px; line-height: 1.5; }
.bubble code { font-family: 'JetBrains Mono', monospace; background: rgba(55,48,163,.1); padding: 2px 6px; border-radius: 4px; font-size: 13px; }
.bubble pre code { background: none; padding: 0; }
/* ─── Rich Tables ─── */
.bubble table { width: 100%; border-collapse: separate; border-spacing: 0; margin: 14px 0; border-radius: 10px; overflow: hidden; border: 1px solid var(--border); font-size: 13.5px; }
.bubble thead th { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: #fff; padding: 10px 14px; text-align: left; font-weight: 600; font-size: 12.5px; text-transform: uppercase; letter-spacing: 0.5px; }
.bubble tbody td { padding: 9px 14px; border-bottom: 1px solid var(--border); }
.bubble tbody tr:nth-child(even) { background: rgba(55,48,163,.04); }
.bubble tbody tr:hover { background: rgba(55,48,163,.08); transition: background .15s; }
.bubble tbody tr:last-child td { border-bottom: none; }
.dark .bubble thead th { background: linear-gradient(135deg, #4c3d99 0%, #553788 100%); }
.dark .bubble tbody tr:nth-child(even) { background: rgba(55,48,163,.08); }
.dark .bubble tbody td { border-color: rgba(255,255,255,.08); }
.table-wrap { overflow-x: auto; margin: 12px 0; border-radius: 10px; }
/* ─── Rich Code Blocks ─── */
.code-block-wrap { position: relative; margin: 14px 0; border-radius: 12px; overflow: hidden; border: 1px solid rgba(55,48,163,.15); }
.code-block-header { display: flex; justify-content: space-between; align-items: center; padding: 6px 14px; background: #161b22; border-bottom: 1px solid rgba(255,255,255,.06); }
.code-lang-badge { font-size: 11px; font-weight: 600; color: #7c6bf0; text-transform: uppercase; letter-spacing: 0.5px; }
.code-copy-btn { background: transparent; border: 1px solid rgba(255,255,255,.15); color: #8b949e; padding: 4px 10px; border-radius: 6px; cursor: pointer; font-size: 11px; font-weight: 500; transition: all .12s; }
.code-copy-btn:hover { background: rgba(55,48,163,.2); color: #fff; border-color: rgba(55,48,163,.4); }
.code-block-wrap pre { margin: 0 !important; border-radius: 0 !important; border: none !important; }
.code-block-wrap pre code { display: block; padding: 14px !important; font-size: 13px; line-height: 1.6; }
/* ─── Rich Blockquotes ─── */
.bubble blockquote { border-left: 4px solid #7c6bf0; background: linear-gradient(90deg, rgba(55,48,163,.06), transparent); margin: 12px 0; padding: 12px 18px; border-radius: 0 10px 10px 0; font-style: italic; color: var(--tx2); }
.dark .bubble blockquote { background: linear-gradient(90deg, rgba(55,48,163,.12), transparent); }
.bubble blockquote strong { color: var(--tx); font-style: normal; }
/* ─── Rich Headings ─── */
.bubble h1 { font-size: 22px; font-weight: 700; margin: 20px 0 10px; padding-bottom: 8px; border-bottom: 2px solid rgba(55,48,163,.2); color: var(--tx); }
.bubble h2 { font-size: 18px; font-weight: 700; margin: 18px 0 8px; color: #7c3aed; }
.bubble h3 { font-size: 15px; font-weight: 600; margin: 14px 0 6px; color: #06b6d4; }
.dark .bubble h2 { color: #a78bfa; }
.dark .bubble h3 { color: #22d3ee; }
/* ─── Rich Lists ─── */
.md-list { margin: 8px 0; padding-left: 0; }
.md-list-item { display: flex; gap: 8px; padding: 4px 0 4px 8px; align-items: flex-start; }
.md-list-bullet { color: #7c6bf0; font-weight: 700; flex-shrink: 0; margin-top: 1px; }
.md-list-num { color: #7c6bf0; font-weight: 700; min-width: 20px; flex-shrink: 0; }
/* ─── Horizontal Rules ─── */
.bubble hr { border: none; height: 2px; background: linear-gradient(90deg, transparent, rgba(55,48,163,.3), transparent); margin: 20px 0; }
/* ─── Math (KaTeX) ─── */
.katex-display { margin: 16px 0; overflow-x: auto; padding: 12px; background: rgba(55,48,163,.04); border-radius: 10px; }
/* ─── Inline Badge for status ─── */
.status-badge { display: inline-block; padding: 2px 8px; border-radius: 10px; font-size: 11px; font-weight: 600; }
.status-ok { background: rgba(34,197,94,.12); color: #16a34a; }
.status-warn { background: rgba(245,158,11,.12); color: #d97706; }
.status-err { background: rgba(239,68,68,.12); color: #dc2626; }
.bubble strong { font-weight: 600; }
.bubble em { font-style: italic; }
.bubble a { color: var(--acc); text-decoration: underline; }
.bubble img { max-width: 100%; border-radius: 12px; margin: 8px 0; }
.bubble .table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; margin: 8px 0; border-radius: 8px; border: 1px solid var(--border); }
.bubble .table-wrap table { margin: 0; border: none; }
.bubble ul, .bubble ol { padding-left: 20px; margin: 6px 0; }
.bubble li { margin: 3px 0; }
.bubble li::marker { color: var(--acc); }
.bubble br + br { display: block; content: ""; margin-top: 4px; } .bubble br + br + br { display: none; }
.artifact-card { display: flex; align-items: center; gap: 14px; padding: 14px 18px; margin: 10px 0; background: linear-gradient(135deg, rgba(55,48,163,.08), rgba(34,211,238,.04)); border: 1px solid var(--border); border-radius: 14px; cursor: pointer; transition: all .25s; }
.artifact-card:hover { border-color: var(--acc); transform: translateY(-2px); box-shadow: 0 4px 20px rgba(55,48,163,.15); }
.artifact-card .art-actions { display: flex; gap: 6px; margin-left: auto; }
.artifact-card .art-btn { width: 32px; height: 32px; border-radius: 8px; border: none; background: transparent; color: #6b7280; cursor: pointer; display: flex; align-items: center; justify-content: center; font-size: 14px; transition: all .12s; }
.artifact-card .art-btn:hover { background: #f3f4f6; color: #374151; }
.artifact-preview { margin: 8px 0; border-radius: 12px; overflow: hidden; border: 1px solid var(--border); background: #fff; }
.artifact-preview iframe { width: 100%; height: 350px; border: 0; }
.artifact-preview.expanded iframe { height: 600px; }
.art-toolbar { display: flex; gap: 6px; padding: 6px 10px; background: var(--bg2); border-top: 1px solid var(--border); justify-content: flex-end; }
.art-toolbar button { padding: 4px 10px; border-radius: 6px; border: none; background: transparent; color: #6b7280; font-size: 11px; cursor: pointer; font-weight: 500; }
.art-toolbar button:hover { background: #f3f4f6; color: #374151; }
/* ─── Mermaid ─── */
.mermaid-container { margin: 12px 0; padding: 16px; background: #fff; border: 1px solid var(--border); border-radius: 12px; overflow-x: auto; }
.dark .mermaid-container { background: #1a1a30; }
.dark .artifact-card { background: linear-gradient(135deg, rgba(55,48,163,.12), rgba(34,211,238,.06)); border-color: #2e2e2e; }
.dark .artifact-card .art-btn:hover { background: rgba(255,255,255,.08); color: #e5e7eb; }
.dark .artifact-preview { background: #1a1a30; border-color: #2e2e2e; }
.mermaid { text-align: center; } .mermaid:not([data-processed]) { font-size:0 !important; line-height:0 !important; color:transparent !important; min-height:80px; background:linear-gradient(90deg,#f0f0ff 25%,#e8e8ff 50%,#f0f0ff 75%); background-size:200% 100%; animation:shimmer 1.5s infinite; } .dark .mermaid:not([data-processed]) { background:linear-gradient(90deg,#1a1a30 25%,#252550 50%,#1a1a30 75%); background-size:200% 100%; } @keyframes shimmer { 0%{background-position:200% 0} 100%{background-position:-200% 0} } .mermaid[data-processed] svg { max-width:100%; }
.mermaid svg { max-width: 100%; height: auto; } @keyframes spin { to { transform: rotate(360deg); } }
/* ─── Thinking Indicator ─── */
.thinking { display: flex; align-items: center; gap: 6px; padding: 16px 20px; }
.thinking-dots { display: flex; gap: 5px; }
.thinking-dots span { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--acc); opacity: 0.4; animation: pulse 1.4s infinite ease-in-out; }
.thinking-dots span:nth-child(2) { animation-delay: .2s; }
.thinking-dots span:nth-child(3) { animation-delay: .4s; }
@keyframes pulse { 0%,80%,100% { opacity: 0.3; transform: scale(0.8); } 40% { opacity: 1; transform: scale(1.1); } }
/* ─── Input Area ─── */
.input-area { padding: 12px 24px 16px; background: #FFFFFF; border-top: none; flex-shrink: 0; }
.input-row { display: flex; gap: 4px; align-items: flex-end; max-width: 780px; margin: 0 auto; background: #FFFFFF; border: 1px solid #d1d5db; border-radius: 22px; padding: 8px 10px 8px 6px; transition: border-color .2s, box-shadow .2s; box-shadow: 0 1px 3px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.04); }
.input-row textarea { flex: 1; resize: none; border: none; border-radius: 0; padding: 8px 4px; font-family: inherit; font-size: 16px; background: transparent; color: var(--tx); outline: none; max-height: 200px; min-height: 24px; line-height: 1.5; }
.input-row:focus-within { border-color: var(--acc); box-shadow: 0 0 0 2px rgba(0,0,0,0.08); }
.send-btn { width: 34px; height: 34px; border-radius: 50%; border: none; background: #1a1a2e; color: #fff; font-size: 15px; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all .12s; flex-shrink: 0; box-shadow: none; font-weight: 400; }
.send-btn:hover { background: #374151; }
.send-btn:disabled { opacity: .5; cursor: not-allowed; }
.input-tools { display: flex; gap: 4px; margin-top: 6px; }
.tool-btn { background: none; border: none; font-size: 16px; cursor: pointer; padding: 4px 8px; border-radius: 8px; transition: background .2s; }
.tool-btn:hover { background: var(--border); }
.tool-btn.active { background: rgba(55,48,163,.15); }
/* ─── Input Icon Buttons (Claude-style) ─── */
.input-icon { width: 34px; height: 34px; border-radius: 10px; border: none; background: transparent; color: #9ca3af; font-size: 18px; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all .12s ease; flex-shrink: 0; padding: 0; }
.input-icon:hover { background: #f3f4f6; color: #374151; }
.dark .input-icon:hover { background: rgba(255,255,255,0.08); } .dark .header { background: #171717; border-color: #2e2e2e; } .dark .input-area { background: #171717; } .dark .input-row { background: #1f1f1f; border-color: #374151; box-shadow: 0 1px 3px rgba(0,0,0,0.3); } .dark .send-btn { background: #e5e7eb; color: #1a1a2e; } .dark .send-btn:hover { background: #fff; } .dark .input-icon { color: #6b7280; } .dark .meta-btn { color: #6b7280; } .dark .meta-btn:hover { background: rgba(255,255,255,0.06); color: #e5e7eb; } .dark .lang-btn { background: #1f1f1f; border-color: #374151; color: #e5e7eb; } .dark .lang-btn:hover { background: #2a2a2a; border-color: #4b5563; } .dark .think-block { background: #1f1f1f; border-color: #2e2e2e; } .dark .hdr-btn { color: #9ca3af; } .dark .hdr-btn:hover { background: rgba(255,255,255,0.06); color: #e5e7eb; } .dark .msg.user .bubble { background: #2a2540; color: #e8e8f0; border-color: #3d3560; } .dark #modeBtn { background: #1f1f1f; color: #e5e7eb; border-color: #374151; } .dark #modeBtn:hover { background: #2a2a2a; border-color: #4b5563; } .dark #modeBtn .mode-dot { background: #10b981; } .dark .hdr-btn.active { background: rgba(255,255,255,0.06); color: #e5e7eb; } .dark .think-stream { color: #94a3b8; } .dark .think-body { color: #94a3b8; }
.input-icon.recording { background: #ef4444 !important; color: #fff !important; animation: rec-pulse 1.5s ease infinite; }
@keyframes rec-pulse { 0%,100% { box-shadow: 0 0 0 0 rgba(239,68,68,0.3); } 50% { box-shadow: 0 0 0 8px rgba(239,68,68,0); } }
/* ─── Drop Overlay ─── */
.drop-overlay { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.6); z-index: 999; align-items: center; justify-content: center; flex-direction: column; gap: 12px; backdrop-filter: blur(4px); }
.drop-overlay.visible { display: flex; }
.drop-overlay .drop-box { width: 320px; padding: 40px; background: var(--bg); border: 2px dashed var(--acc); border-radius: 20px; text-align: center; }
.drop-overlay .drop-icon { font-size: 48px; margin-bottom: 8px; }
.drop-overlay .drop-text { font-size: 16px; font-weight: 600; color: var(--tx); }
.drop-overlay .drop-sub { font-size: 13px; color: var(--tx2); margin-top: 4px; }
/* ─── File Preview Bar ─── */
.file-preview-bar { display: none; align-items: center; gap: 10px; padding: 8px 14px; margin: 0 auto 8px; max-width: 780px; background: rgba(124,107,240,0.06); border: 1px solid rgba(124,107,240,0.15); border-radius: 12px; font-size: 13px; color: var(--tx); }
.file-preview-bar.visible { display: flex; }
.file-preview-bar .fp-icon { font-size: 20px; }
.file-preview-bar .fp-name { flex: 1; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.file-preview-bar .fp-size { color: var(--tx2); font-size: 11px; }
.file-preview-bar .fp-close { width: 24px; height: 24px; border-radius: 50%; border: none; background: transparent; color: var(--tx2); cursor: pointer; font-size: 16px; display: flex; align-items: center; justify-content: center; }
.file-preview-bar .fp-close:hover { background: rgba(0,0,0,0.08); color: var(--tx); }
.file-preview-bar .fp-thumb { width: 40px; height: 40px; border-radius: 8px; object-fit: cover; }
/* ─── Dark mode refinements ─── */
.dark .input-row { border-color: var(--border); }
.dark .drop-overlay .drop-box { background: var(--bg2); }
/* Hide mermaid syntax error toasts */
#d[id^="mermaid-"] .error-icon, #d[id^="mermaid-"] .error-text,
[id^="dmermaid"] { display: none !important; }
.mermaid .error-icon, .mermaid .error-text { display: none !important; }
svg[id*="mermaid"] text.error-text { display: none !important; }
div[style*="position: fixed"][style*="bottom"] { display: none !important; }
/* Suppress mermaid error display globally */
.mermaid-error, .mermaid[data-processed="true"] + .error { display: none !important; }
.dark .file-preview-bar { background: rgba(124,107,240,0.08); border-color: rgba(124,107,240,0.2); }
/* ─── SSH Panel ─── */
.ssh-panel { display: none; padding: 12px 16px; background: var(--bg2); border-top: 1px solid var(--border); }
.ssh-panel.open { display: block; }
.ssh-row { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.ssh-input { padding: 8px 12px; border: 1px solid var(--border); border-radius: 8px; font-size: 13px; background: var(--bg); color: var(--tx); font-family: inherit; width: 140px; }
.ssh-btn { padding: 8px 14px; border-radius: 8px; border: none; font-size: 13px; cursor: pointer; font-family: inherit; font-weight: 600; }
.ssh-connect { background: var(--acc); color: #fff; }
.ssh-status { font-size: 12px; color: var(--tx2); margin-top: 6px; }
/* ─── Utilities ─── */
@keyframes fadeIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
@keyframes bounce { to { transform: translateY(-4px); } }
::selection { background: rgba(124,107,240,0.15); } .disclaimer { font-size: 11px; color: var(--tx2); text-align: center; padding: 4px 0 2px; opacity: 0.6; } /* ── Tablet ── */
/* Large screen logo */
@media (min-width: 1440px) {
.wevia-logo-img { width: 52px !important; height: 52px !important; }
.brand-name { font-size: 20px !important; }
.brand-tag { font-size: 10px !important; }
}
@media (min-width: 1920px) {
.wevia-logo-img { width: 58px !important; height: 58px !important; }
.brand-name { font-size: 22px !important; }
}
@media (max-width: 1024px) {
.msg-inner { padding: 0 20px; }
}
/* ── Mobile ── */
@media (max-width: 640px) {
.msg-inner { padding: 0 14px; }
.msg.user .bubble { max-width: 90%; }
.header { padding: 8px 12px; }
.brand-name { font-size: 16px; }
.brand-tag { font-size: 9px; }
.messages { padding: 10px; }
.lang-btn { min-width: 100px; padding: 10px 14px; font-size: 13px; }
.bubble { padding: 10px 14px; font-size: 13.5px; }
.bubble pre { font-size: 11.5px; padding: 10px; }
.bubble table { font-size: 11px; display: block; overflow-x: auto; white-space: nowrap; }
.input-row { padding: 8px 10px; gap: 6px; }
.input-row textarea { font-size: 14px; padding: 10px 14px; }
.hdr-btn { font-size: 11px; padding: 4px 8px; }
.artifact-card { padding: 10px; }
.msg-meta { gap: 4px; }
.meta-btn { font-size: 12px; padding: 3px 6px; }
}
/* ── Small mobile ── */
@media (max-width: 380px) {
.brand-icon { font-size: 22px; }
.brand-name { font-size: 14px; }
.brand-tag { display: none; }
.hdr-btn span { display: none; }
.bubble { padding: 8px 12px; font-size: 13px; }
.lang-grid { gap: 6px; }
.lang-btn { min-width: 90px; padding: 8px 12px; font-size: 12px; }
}
/* ——— Preview Panel (Claude-style) ——— */
.main-layout { display: flex; flex: 1; overflow: hidden; }
.main-layout .chat-side { flex: 1; display: flex; flex-direction: column; overflow: hidden; transition: all .4s cubic-bezier(.4,0,.2,1); min-width: 0; }
.main-layout .preview-panel { width: 0; overflow: hidden; transition: all .4s cubic-bezier(.4,0,.2,1); border-left: 0px solid var(--border); display: flex; flex-direction: column; background: var(--bg2); }
.main-layout.panel-open .chat-side { flex: 0 0 50%; }
.main-layout.panel-open .preview-panel { width: 50%; border-left-width: 1px; }
.preview-header { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; border-bottom: 1px solid var(--border); flex-shrink: 0; background: var(--bg2); }
.preview-header .prev-title { display: flex; align-items: center; gap: 8px; font-size: 14px; font-weight: 600; color: var(--tx); }
.preview-header .prev-icon { font-size: 18px; }
.preview-header .prev-tabs { display: flex; gap: 4px; }
.preview-header .prev-tab { padding: 4px 12px; border-radius: 8px; border: 1px solid var(--border); background: none; font-size: 12px; cursor: pointer; color: var(--tx2); font-family: inherit; transition: all .2s; }
.preview-header .prev-tab.active { background: var(--acc); color: #fff; border-color: var(--acc); }
.preview-actions { display: flex; gap: 6px; }
.preview-actions button { width: 32px; height: 32px; border-radius: 8px; border: none; background: transparent; color: #6b7280; cursor: pointer; display: flex; align-items: center; justify-content: center; font-size: 14px; transition: all .12s; }
.preview-actions button:hover { background: #f3f4f6; color: #374151; }
.preview-body { flex: 1; overflow: auto; }
.preview-body iframe { width: 100%; height: 100%; border: 0; }
.preview-body .prev-placeholder { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%; color: var(--tx2); gap: 12px; }
.preview-body .prev-placeholder .prev-ph-icon { font-size: 48px; opacity: .3; }
@media (max-width: 900px) {
.main-layout.panel-open .chat-side { flex: 0 0 0%; overflow: hidden; }
.main-layout.panel-open .preview-panel { width: 100%; }
.main-layout.panel-open .preview-header .prev-back { display: flex !important; }
}
.hidden { display: none !important; }
@keyframes pulse-rec { 0%,100% { box-shadow: 0 0 0 0 rgba(239,68,68,.4); } 50% { box-shadow: 0 0 0 8px rgba(239,68,68,0); } }
#micBtn[style*="ef4444"] { animation: pulse-rec 1s infinite; }
/* === CLAUDE-STYLE THINKING BLOCK === */
.think-block { background: #f8fafc; border: 1px solid #e8ecf0; border-radius: 12px; overflow: hidden; transition: all 0.3s ease; margin: 4px auto; max-width: 780px; }
.think-block:hover { border-color: rgba(55,48,163,0.25); }
.think-header { display: flex; align-items: center; gap: 8px; padding: 10px 16px; cursor: pointer; user-select: none; }
.think-header:hover { background: rgba(55,48,163,0.04); }
.think-chevron { font-size: 10px; color: #7C6BF0; transition: transform 0.2s ease; width: 16px; text-align: center; }
.think-chevron.open { transform: rotate(90deg); }
.think-sparkle { width: 26px; height: 26px; border-radius: 50%; background: transparent; display: flex; align-items: center; justify-content: center; font-size: 20px; flex-shrink: 0; color: #fff; will-change: transform, box-shadow; }
.think-sparkle.active { animation: sparkle-pulse 1.2s ease-in-out infinite; }
@keyframes sparkle-pulse { 0%,100% { transform: scale(1); } 50% { transform: scale(1.3); } }
.think-label { font-size: 13px; color: #7C6BF0; font-weight: 500; }
.think-timer { font-size: 12px; color: #94A3B8; margin-left: auto; font-variant-numeric: tabular-nums; }
.think-body { max-height: 500px; overflow-y: auto; transition: max-height 0.35s ease, opacity 0.25s ease, padding 0.3s ease; opacity: 1; padding: 0 16px 12px 16px; font-size: 13px; color: #64748b; }
.think-body.collapsed { max-height: 0 !important; opacity: 0; padding-top: 0 !important; padding-bottom: 0 !important; }
.think-stream { font-size: 13px; color: #64748B; line-height: 1.75; font-style: italic; white-space: pre-wrap; word-wrap: break-word; }
.think-stream .think-word { opacity: 0; animation: wordIn 0.15s ease forwards; }
@keyframes wordIn { to { opacity: 1; } }
.think-cursor { display: inline-block; width: 2px; height: 14px; background: #7C6BF0; animation: cursor-blink 0.8s step-end infinite; vertical-align: middle; margin-left: 2px; }
@keyframes cursor-blink { 0%,100% { opacity: 1; } 50% { opacity: 0; } }
.think-block.done .think-sparkle { background: transparent; } .think-block.done .think-body { max-height: 500px; opacity: 1; transition: max-height .3s, opacity .3s; } .think-block.done .think-timer { display: none; }
.think-block.done .think-label { color: #64748B; }
/* ═══ SIDEBAR Claude-style ═══ */
.app-layout { display: flex; height: 100dvh; overflow: hidden; }
.sidebar { width: 260px; background: #f9fafb; border-right: 1px solid #e5e7eb; display: flex; flex-direction: column; flex-shrink: 0; transition: width .2s, margin .2s; overflow: hidden; }
.sidebar.collapsed { width: 0; margin-left: -1px; }
.sb-header { padding: 12px 14px; display: flex; align-items: center; gap: 8px; }
.sb-toggle { width: 32px; height: 32px; border-radius: 8px; border: none; background: transparent; color: #6b7280; cursor: pointer; display: flex; align-items: center; justify-content: center; font-size: 18px; transition: all .12s; flex-shrink: 0; }
.sb-toggle:hover { background: #e5e7eb; color: #374151; }
.sb-new { flex: 1; padding: 7px 14px; border-radius: 8px; border: none; background: transparent; color: #374151; font-size: 13px; font-weight: 500; cursor: pointer; text-align: left; font-family: inherit; transition: all .12s; display: flex; align-items: center; gap: 6px; }
.sb-new:hover { background: #e5e7eb; }
.sb-search { margin: 0 12px 8px; padding: 7px 10px; border-radius: 8px; border: 1px solid #e5e7eb; background: #fff; font-size: 12px; color: #374151; font-family: inherit; outline: none; width: calc(100% - 24px); }
.sb-search:focus { border-color: #9ca3af; }
.sb-section { padding: 4px 14px; font-size: 11px; font-weight: 600; color: #9ca3af; text-transform: uppercase; letter-spacing: 0.5px; }
.sb-list { flex: 1; overflow-y: auto; padding: 0 6px; }
.sb-list::-webkit-scrollbar { width: 4px; } .sb-list::-webkit-scrollbar-thumb { background: transparent; border-radius: 4px; } .sb-list:hover::-webkit-scrollbar-thumb { background: #d1d5db; }
.sb-item { padding: 8px 10px; border-radius: 8px; cursor: pointer; font-size: 13px; color: #374151; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; transition: all .1s; margin-bottom: 1px; }
.sb-item:hover { background: #e5e7eb; }
.sb-item.active { background: #e5e7eb; font-weight: 500; }
.sb-item .sb-time { font-size: 10px; color: #9ca3af; display: block; margin-top: 1px; }
.sb-footer { padding: 10px 14px; border-top: 1px solid #e5e7eb; font-size: 11px; color: #9ca3af; }
.sb-empty { padding: 20px; text-align: center; color: #9ca3af; font-size: 12px; }
.main-content { flex: 1; display: flex; flex-direction: column; overflow: hidden; min-width: 0; }
.float-toggle { position: fixed; top: 12px; left: 12px; z-index: 50; width: 32px; height: 32px; border-radius: 8px; border: none; background: #fff; color: #6b7280; cursor: pointer; display: none; align-items: center; justify-content: center; font-size: 18px; transition: all .12s; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
.float-toggle:hover { background: #f3f4f6; color: #374151; }
.sidebar.collapsed ~ .main-content .float-toggle { display: flex; }
.dark .sidebar { background: #171717; border-color: #2e2e2e; }
.dark .sb-toggle:hover { background: #2a2a2a; color: #e5e7eb; }
.dark .sb-new { color: #e5e7eb; } .dark .sb-new:hover { background: #2a2a2a; }
.dark .sb-search { background: #1f1f1f; border-color: #374151; color: #e5e7eb; }
.dark .sb-item { color: #d1d5db; } .dark .sb-item:hover, .dark .sb-item.active { background: #2a2a2a; }
.dark .sb-footer { border-color: #2e2e2e; }
.dark .float-toggle { background: #1f1f1f; box-shadow: 0 1px 3px rgba(0,0,0,0.3); }
.dark .float-toggle:hover { background: #2a2a2a; color: #e5e7eb; }
.dark .wevia-mindmap { background: #1a1a30; border-color: #2e2e2e; }
.dark .preview-actions button:hover { background: rgba(255,255,255,.08); color: #e5e7eb; }
.dark .art-toolbar button:hover { background: rgba(255,255,255,.08); color: #e5e7eb; }
@media (max-width: 768px) { .sidebar { position: fixed; left: 0; top: 0; z-index: 100; height: 100vh; box-shadow: 4px 0 20px rgba(0,0,0,0.15); } .sidebar.collapsed { width: 0; left: -5px; } }
/* ═══ RLHF ═══ */
.fb-btn { width: 28px; height: 28px; border-radius: 8px; border: none; background: transparent; color: #9ca3af; cursor: pointer; font-size: 14px; display: inline-flex; align-items: center; justify-content: center; transition: all .12s; }
.fb-btn:hover { background: #f3f4f6; color: #374151; }
.fb-btn.voted { pointer-events: none; }
.fb-btn.up { color: #10b981; background: #ecfdf5; }
.fb-btn.down { color: #ef4444; background: #fef2f2; }
.dark .fb-btn:hover { background: rgba(255,255,255,0.06); color: #e5e7eb; }
.dark .fb-btn.up { color: #10b981; background: rgba(16,185,129,0.1); }
.dark .fb-btn.down { color: #ef4444; background: rgba(239,68,68,0.1); }
/* ===== MOBILE RESPONSIVE ===== */
@media(max-width:768px) {
.sidebar { position:fixed !important; left:-280px !important; width:260px !important; z-index:1000 !important; transition:left 0.3s !important; }
.sidebar.open { left:0 !important; }
.sidebar-overlay { display:none; position:fixed; top:0; left:0; right:0; bottom:0; background:rgba(0,0,0,0.5); z-index:999; }
.sidebar-overlay.active { display:block; }
.main-content, .chat-area, .chat-container { margin-left:0 !important; width:100% !important; }
.header-bar, .top-bar, .chat-header { padding:8px 12px !important; }
.header-bar h1, .top-bar .title { font-size:1rem !important; }
.message-input-area, .input-area, .input-bar { padding:8px !important; }
.message-input-area input, .message-input-area textarea, .input-bar input, .input-bar textarea { font-size:14px !important; }
.ssh-bar, .ssh-panel { flex-wrap:wrap !important; gap:6px !important; padding:8px !important; }
.ssh-bar input, .ssh-panel input { min-width:80px !important; font-size:13px !important; }
.ssh-bar button, .ssh-panel button { padding:6px 12px !important; font-size:13px !important; }
.mode-selector, .mode-toggle { font-size:0.8rem !important; }
.msg, .message { max-width:95% !important; padding:10px !important; font-size:14px !important; }
.msg pre, .message pre { overflow-x:auto; font-size:12px !important; }
.provider-selector select { font-size:13px !important; }
body { font-size:14px; }
}
@media(max-width:480px) {
.ssh-bar input, .ssh-panel input { width:100% !important; }
.header-bar, .top-bar { flex-wrap:wrap !important; }
.msg, .message { font-size:13px !important; }
}
/* Fix button clickability */
.meta-btn, .fb-btn { position:relative; z-index:10; min-width:28px; min-height:28px; }
.msg-meta { display:flex; align-items:center; gap:4px; flex-wrap:wrap; padding:6px 0 0; user-select:none; }
/* ===== BANANA VISUAL ENGINE ===== */
.banana-badge { background:linear-gradient(135deg,#facc15,#f59e0b); color:#000; padding:3px 10px; border-radius:6px; font-weight:700; font-size:10px; display:inline-flex; align-items:center; gap:4px; }
.banana-studio { background:#0a0a0a; border-radius:12px; padding:20px; margin:8px 0; position:relative; overflow:hidden; }
.banana-studio .scan-line { position:absolute; top:0; left:0; right:0; height:2px; background:linear-gradient(90deg,transparent,#facc15,transparent); animation:bananaScan 2s linear infinite; }
@keyframes bananaScan { 0%{top:0} 100%{top:100%} }
.banana-progress { height:4px; background:#1f1f1f; border-radius:4px; overflow:hidden; margin:12px 0; }
.banana-progress-bar { height:100%; background:linear-gradient(90deg,#facc15,#f59e0b); border-radius:4px; transition:width 0.3s; }
.banana-result img, .banana-result video { max-width:100%; border-radius:10px; box-shadow:0 4px 20px rgba(0,0,0,0.3); }
.banana-result { text-align:center; margin:12px 0; }
.banana-type-badge { font-size:11px; padding:2px 8px; border-radius:10px; font-weight:600; display:inline-block; margin-bottom:8px; }
.banana-type-badge.photo { background:#dcfce7; color:#166534; }
.banana-type-badge.video { background:#dbeafe; color:#1d4ed8; }
.banana-type-badge.edit { background:#fef3c7; color:#92400e; }
/* ===== GRAPH-MASTER ENGINE ===== */
.wevia-graph { background:#0f172a; border-radius:12px; padding:16px; margin:12px 0; position:relative; overflow:auto; max-height:500px; }
.wevia-graph svg { max-width:100%; height:auto; }
.wevia-graph .mermaid { background:transparent; }
.wevia-graph-badge { position:absolute; top:8px; right:8px; background:linear-gradient(135deg,#6366f1,#8b5cf6); color:#fff; padding:3px 10px; border-radius:6px; font-size:10px; font-weight:700; }
.wevia-graph-dl { display:inline-block; margin-top:8px; color:#818cf8; font-size:12px; cursor:pointer; text-decoration:underline; }
.wevia-mindmap { background:#fff; border:1px solid #e2e8f0; border-radius:12px; padding:16px; margin:12px 0; min-height:300px; }
/* EXPORT PRO BUTTONS */
.export-bar { display:flex; gap:6px; margin-top:8px; }
.export-btn { background:#1e293b; color:#fff; border:none; padding:4px 10px; border-radius:6px; font-size:11px; cursor:pointer; transition:background 0.2s; }
.export-btn:hover { background:#6366f1; }
.export-btn .icon { margin-right:4px; }
.suggested-prompts { display:none !important; flex-wrap:wrap; gap:8px; justify-content:center; margin-top:20px; max-width:600px; margin-left:auto; margin-right:auto; }
.suggested-prompt { background:rgba(99,102,241,0.08); border:1px solid rgba(99,102,241,0.2); color:#6366f1; padding:8px 16px; border-radius:20px; font-size:13px; cursor:pointer; transition:all 0.2s; font-family:inherit; }
.suggested-prompt:hover { background:#6366f1; color:#fff; transform:translateY(-2px); }
.dark .suggested-prompt { background:rgba(99,102,241,0.15); border-color:rgba(99,102,241,0.3); color:#a5b4fc; }
.dark .suggested-prompt:hover { background:#6366f1; color:#fff; }
.source-pills { display:flex; flex-wrap:wrap; gap:6px; margin-top:10px; padding-top:8px; border-top:1px solid var(--border); }
.source-pill { display:inline-flex; align-items:center; gap:4px; padding:3px 10px; border-radius:12px; font-size:11px; background:rgba(99,102,241,0.08); color:#6366f1; text-decoration:none; transition:background 0.2s; }
.source-pill:hover { background:rgba(99,102,241,0.16); }
.source-pill .snum { background:#6366f1; color:#fff; width:16px; height:16px; border-radius:50%; display:flex; align-items:center; justify-content:center; font-size:9px; font-weight:700; }
.dark .source-pill { background:rgba(99,102,241,0.15); color:#a5b4fc; }
.response-meta-badge { display:inline-flex; align-items:center; gap:6px; padding:4px 10px; border-radius:8px; font-size:11px; color:var(--tx2); background:var(--bg2); margin-top:6px; }
.response-meta-badge .dot { width:6px; height:6px; border-radius:50%; background:#10b981; }
/* Follow-up suggestions after AI response (Perplexity-style) */
.followup-chips { display:none !important; flex-wrap:wrap; gap:8px; margin:8px auto 4px; justify-content:center; animation:fadeUp .4s ease; max-width:780px; }
.followup-chip { background:rgba(124,58,237,0.08); border:1px solid rgba(124,58,237,0.2); color:var(--acc);
padding:7px 14px; border-radius:20px; font-size:13px; cursor:pointer; transition:all .2s; white-space:nowrap; }
.followup-chip:hover { background:var(--acc); color:#fff; transform:translateY(-1px); box-shadow:0 2px 8px rgba(124,58,237,0.3); }
.dark .followup-chip { background:rgba(99,102,241,0.12); border-color:rgba(99,102,241,0.25); color:#a5b4fc; }
.dark .followup-chip:hover { background:#6366f1; color:#fff; }
@keyframes fadeUp { from{opacity:0;transform:translateY(8px)} to{opacity:1;transform:translateY(0)} }
/* Typewriter cursor */
.typewriter-cursor { display:inline-block; width:2px; height:1em; background:var(--acc); margin-left:2px; animation:blink 1s infinite; vertical-align:text-bottom; }
@keyframes blink { 0%,50%{opacity:1} 51%,100%{opacity:0} }
/* Better source citations (Perplexity [1][2]) */
.cite-num { display:inline-flex; align-items:center; justify-content:center; width:18px; height:18px; border-radius:50%;
background:var(--acc); color:#fff; font-size:10px; font-weight:700; cursor:pointer; vertical-align:super; margin:0 1px; }
.cite-num:hover { transform:scale(1.2); }
/* Streaming progress indicator */
.stream-progress { height:2px; background:linear-gradient(90deg,var(--acc),var(--acc2)); border-radius:1px;
animation:streamPulse 1.5s ease-in-out infinite; margin:4px 0; }
@keyframes streamPulse { 0%{width:0;opacity:.5} 50%{width:60%;opacity:1} 100%{width:100%;opacity:.5} }
/* === ORGANIC REVEAL ENGINE === */
/* GEMINI ORGANIC - all images and schemas */
.msg img {
animation: organicReveal 1.2s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards;
}
.msg .wevia-graph img {
animation: organicReveal 1.2s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards;
}
.organic-reveal {
animation: organicReveal 1.2s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards;
}
@keyframes organicReveal {
0% { filter: blur(18px) saturate(0.3); opacity: 0; transform: scale(0.96); }
40% { filter: blur(8px) saturate(0.7); opacity: 0.6; }
100% { filter: blur(0) saturate(1.05); opacity: 1; transform: scale(1); }
}
.organic-reveal:hover {
transform: scale(1.015);
transition: transform 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
box-shadow: 0 8px 32px rgba(99,102,241,0.15);
}
/* Mermaid diagrams pro styling */
.wevia-graph {
background: linear-gradient(135deg, #0a0a1a 0%, #111827 100%);
border: 1px solid rgba(99,102,241,0.2);
border-radius: 16px;
padding: 20px;
margin: 16px 0;
overflow-x: auto;
}
.wevia-graph svg { max-width: 100%; height: auto; }
.wevia-graph-badge {
background: linear-gradient(135deg, #4f46e5, #7c3aed);
padding: 4px 12px;
border-radius: 8px;
font-size: 11px;
font-weight: 600;
letter-spacing: 0.5px;
display: inline-block;
margin-bottom: 12px;
}
/* AMBRE-V2 shortcuts grid premium */
.wv-sc-grid { display:grid; grid-template-columns:repeat(2,1fr); gap:6px; padding:4px 2px; }
.wv-sc { display:flex; flex-direction:column; align-items:center; justify-content:center; gap:5px; padding:10px 6px; background:#fff; border:1px solid #e5e7eb; border-radius:10px; cursor:pointer; transition:transform .15s ease, box-shadow .15s ease, border-color .15s ease; font-size:11px; font-weight:500; color:#374151; }
.wv-sc:hover { transform:translateY(-2px); box-shadow:0 6px 14px rgba(0,0,0,.08); border-color:#d1d5db; }
.wv-sc:active { transform:translateY(0); box-shadow:0 2px 6px rgba(0,0,0,.08); }
.wv-sc-ico { display:inline-flex; align-items:center; justify-content:center; width:30px; height:30px; border-radius:8px; font-size:16px; color:#fff; box-shadow:0 2px 5px rgba(0,0,0,.12); }
.wv-sc-lbl { letter-spacing:.2px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; max-width:100%; }
.dark .wv-sc { background:#1f2937; border-color:#374151; color:#e5e7eb; }
.dark .wv-sc:hover { border-color:#4b5563; background:#252f3e; }
/* AMBRE-V2 end */
/* AMBRE spinner */
@keyframes spin { to { transform: rotate(360deg); } }
</style></style></style>
<!-- GRAPH-MASTER LIBS -->
<link rel=stylesheet href=/assets/wevia-v2.css>
<script src="/js/wevia-a11y-auto.js" defer></script>
</head>
<body class="light">
<!-- compat block -->
<div id="weval-global-logout" style="display:none!important;visibility:hidden!important" aria-hidden="true" data-beton-101="dummy-to-block-auto-injection"></div>
<a id="weval-gl" href="#" style="display:none!important;visibility:hidden!important" aria-hidden="true" data-beton-101="dummy-to-block-auto-injection" tabindex="-1"></a>
<!-- ─── HEADER ─── -->
<div class="app-layout" id="appLayout">
<div class="sidebar" id="sidebar">
<div class="sb-header">
<button class="sb-toggle" onclick="toggleSidebar()" title="Menu"></button>
<button class="sb-new" onclick="newChat()"> Nouvelle discussion</button>
</div>
<input class="sb-search" id="sbSearch" autocomplete="off" name="wv-search" placeholder="Rechercher..." oninput="filterConvs(this.value)">
<div class="sb-section">Raccourcis</div>
<div class="sb-shortcuts wv-sc-grid">
<button type="button" class="wv-sc" onclick="wvShortcut('Genere un PDF professionnel sur: ')" title="Génère un PDF structuré avec sections, visuels, mise en page pro">
<span class="wv-sc-ico" style="background:linear-gradient(135deg,#ef4444,#dc2626)">📄</span>
<span class="wv-sc-lbl">PDF</span>
</button>
<button type="button" class="wv-sc" onclick="wvShortcut('Genere un document Word sur: ')" title="Génère un document Word .docx avec styles, titres, tableaux">
<span class="wv-sc-ico" style="background:linear-gradient(135deg,#2563eb,#1d4ed8)">📝</span>
<span class="wv-sc-lbl">Word</span>
</button>
<button type="button" class="wv-sc" onclick="wvShortcut('Genere un tableau Excel pour: ')" title="Génère une feuille Excel .xlsx avec formules, tableaux, graphiques">
<span class="wv-sc-ico" style="background:linear-gradient(135deg,#10b981,#059669)">📊</span>
<span class="wv-sc-lbl">Excel</span>
</button>
<button type="button" class="wv-sc" onclick="wvShortcut('Genere une presentation PowerPoint sur: ')" title="Génère une présentation PowerPoint .pptx multi-slides">
<span class="wv-sc-ico" style="background:linear-gradient(135deg,#f97316,#ea580c)">🎯</span>
<span class="wv-sc-lbl">PPT</span>
</button>
<button type="button" class="wv-sc" onclick="wvShortcut('Genere un schema mermaid pour: ')" title="Diagramme visuel (flowchart, sequence, architecture)">
<span class="wv-sc-ico" style="background:linear-gradient(135deg,#8b5cf6,#7c3aed)">🧩</span>
<span class="wv-sc-lbl">Schéma</span>
</button>
<button type="button" class="wv-sc" onclick="wvShortcut('Genere une image decrivant: ')" title="Génère une image (illustration, visuel, rendu)">
<span class="wv-sc-ico" style="background:linear-gradient(135deg,#ec4899,#db2777)">🎨</span>
<span class="wv-sc-lbl">Image</span>
</button>
<button type="button" class="wv-sc" onclick="wvShortcut('Ecris le code pour: ')" title="Génère du code source (Python, JS, TS, PHP, SQL, Bash...)">
<span class="wv-sc-ico" style="background:linear-gradient(135deg,#64748b,#475569)">💻</span>
<span class="wv-sc-lbl">Code</span>
</button>
<button type="button" class="wv-sc" onclick="wvShortcut('Traduis ce texte en: ')" title="Traduit vers une autre langue (FR, EN, AR, ES, DE...)">
<span class="wv-sc-ico" style="background:linear-gradient(135deg,#06b6d4,#0891b2)">🌐</span>
<span class="wv-sc-lbl">Traduire</span>
</button>
</div>
<div class="sb-section">Récents</div>
<div class="sb-list" id="sbList"><div class="sb-empty">Chargement...</div></div>
<div class="sb-footer">WEVIA · Intelligence Artificielle</div>
</div>
<div class="main-content">
<button class="float-toggle" id="floatToggle" onclick="toggleSidebar()"></button>
<div class="header">
<div class="brand">
<div class="brand-icon"><img src="/assets/logo-wevia-official.svg?v=2026" width="41" height="41" style="border-radius:10px;box-shadow:0 4px 12px rgba(124,58,237,0.35);display:block" class="wevia-logo-img" alt="WEVIA"></div>
<div>
<div class="brand-name">WEVIA</div>
<div class="brand-tag">INTELLIGENCE ARTIFICIELLE COGNITIVE</div>
</div>
</div>
<div style="position:absolute;left:50%;transform:translateX(-50%)">
<button class="hdr-btn active" id="modeBtn" style="background:linear-gradient(135deg,#10B981,#7C6BF0)" onclick="toggleMode()"><span class="mode-dot"></span><span id="modeLabel">Auto ⚡</span></button>
</div>
<div class="hdr-btns">
<button class="hdr-btn" id="themeBtn" onclick="toggleTheme()">☀️</button>
<button class="hdr-btn" id="voiceBtn" onclick="toggleVoice()">🔇 Voix</button>
<button class="hdr-btn" id="sshToggle" onclick="toggleSSH()">🖥️</button>
</div>
</div>
<!-- ─── DROP OVERLAY ─── -->
<div class="drop-overlay" id="dropOverlay"><div class="drop-box"><div class="drop-icon">📁</div><div class="drop-text">Déposez vos fichiers ici</div><div class="drop-sub">Images, PDF, documents...</div></div></div>
<!-- ─── SSH PANEL ─── -->
<div class="ssh-panel" id="sshPanel">
<div class="ssh-row">
<input class="ssh-input" id="sshHost" placeholder="IP / Host" style="width:160px">
<input class="ssh-input" id="sshUser" autocomplete="off" placeholder="User" style="width:100px">
<input class="ssh-input" id="sshPass" autocomplete="new-password" placeholder="Password" type="password" style="width:120px">
<input class="ssh-input" id="sshPort" placeholder="Port" value="22" style="width:60px">
<button class="ssh-btn ssh-connect" onclick="connectSSH()">Connecter</button>
<button class="ssh-btn" onclick="disconnectSSH()" id="sshDisc" style="display:none;background:#ef4444;color:#fff">Déconnecter</button>
</div>
<div class="ssh-status" id="sshStatus"></div>
</div>
<!-- LANG SCREEN REMOVED — auto-detect by intent -->
<!-- ─── CHAT ─── -->
<div class="chat-wrap active" id="chatWrap">
<div class="main-layout" id="mainLayout">
<div class="chat-side">
<div class="messages" id="messages" tabindex="0"></div>
<div class="suggested-prompts" id="suggestedPrompts" style="display:none">
</div>
<div class="input-area">
<input type="file" id="fileInput" accept="image/*,.pdf,.txt,.csv,.json,.md,.doc,.docx,.xls,.xlsx" style="display:none" onchange="handleFile(this)">
<div id="filePreviewBar" class="file-preview-bar"><span class="fp-icon" id="fpIcon">📎</span><span class="fp-name" id="fpName"></span><span class="fp-size" id="fpSize"></span><img id="fpThumb" class="fp-thumb" style="display:none"><button class="fp-close" onclick="clearFile()" title="Retirer"></button></div>
<div class="input-row">
<button class="input-icon" onclick="document.getElementById('fileInput').click()" title="Joindre un fichier">📎</button>
<button class="input-icon" id="micBtn" onclick="toggleMic()" title="Message vocal">🎤</button>
<textarea autocomplete=off autocorrect=off autocapitalize=off spellcheck=false id="msgInput" placeholder="Posez votre question..." rows="1" onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();send()}"></textarea>
<button class="send-btn" id="sendBtn" onclick="send()"></button>
</div>
<div class="disclaimer">WEVIA est une IA et peut faire des erreurs. Veuillez vérifier les réponses.</div>
</div>
</div><!-- /chat-side -->
<!-- ——— PREVIEW PANEL (Claude-style) ——— -->
<div class="preview-panel" id="previewPanel">
<div class="preview-header">
<div class="prev-title"><span class="prev-icon">📄</span> <span id="prevTitleText">Document</span></div>
<div class="prev-tabs">
<button class="prev-tab active" id="prevTabPreview" onclick="switchPreviewTab('preview')">Aperçu</button>
<button class="prev-tab" id="prevTabCode" onclick="switchPreviewTab('code')" style="display:none">Code</button>
</div>
<div class="preview-actions">
<button onclick="downloadPreview()" title="Télécharger"></button>
<button onclick="expandPreview()" title="Plein écran" id="prevExpandBtn"></button>
<button onclick="closePreview()" title="Fermer"></button>
<button onclick="closePreview()" title="Retour au chat" class="prev-back" style="display:none"></button>
</div>
</div>
<div class="preview-body" id="previewBody">
<div class="prev-placeholder">
<div class="prev-ph-icon">📄</div>
<div>Aucun document à afficher</div>
</div>
</div>
</div>
</div><!-- /main-layout -->
</div>
<script>
/* AMBRE-SOVEREIGN-RETRY · auto-retry 503/429/502 on /api/sovereign + cascade */
(function(){
if (window.__retryFetchInstalled) return;
window.__retryFetchInstalled = true;
var _origFetch = window.fetch;
window.fetch = function(url, opts) {
var u = typeof url === 'string' ? url : (url && url.url) || '';
var shouldRetry = u.indexOf('/api/sovereign') >= 0 || u.indexOf('chat/completions') >= 0 || u.indexOf('/api/ambre') >= 0 || u.indexOf('/api/wevia-') >= 0;
if (!shouldRetry) return _origFetch.apply(this, arguments);
var maxRetry = 3;
var backoff = [0, 1500, 4000];
var self = this;
var args = arguments;
return new Promise(function(resolve, reject) {
function tryOnce(attempt) {
var delay = backoff[attempt] || 0;
setTimeout(function() {
_origFetch.apply(self, args).then(function(r) {
if ((r.status === 503 || r.status === 429 || r.status === 502) && attempt < maxRetry - 1) {
console.warn('[retry] ' + u + ' got ' + r.status + ', retry #' + (attempt+1));
tryOnce(attempt + 1);
} else {
resolve(r);
}
}).catch(function(err) {
if (attempt < maxRetry - 1) {
console.warn('[retry] ' + u + ' err, retry #' + (attempt+1) + ': ' + err.message);
tryOnce(attempt + 1);
} else {
reject(err);
}
});
}, delay);
}
tryOnce(0);
});
};
})();
</script>
<script>
// ─── State ───
function initMermaid(){if(window.mermaid){mermaid.initialize({startOnLoad:false,theme:'neutral',securityLevel:'loose',fontFamily:'Sora,sans-serif',flowchart:{htmlLabels:true,curve:'basis'},logLevel:'fatal'})}else{setTimeout(initMermaid,500)}} initMermaid();
// Suppress mermaid error toasts (MutationObserver)
(function(){var obs=new MutationObserver(function(muts){muts.forEach(function(m){m.addedNodes.forEach(function(n){if(n.nodeType===1){if(n.textContent&&n.textContent.indexOf('Syntax error in text')>=0&&n.style&&n.style.position==='fixed'){n.remove();}if(n.classList&&(n.classList.contains('error-icon')||n.classList.contains('error-text'))){n.style.display='none';}var errs=n.querySelectorAll?n.querySelectorAll('.error-icon,.error-text,[id*="d"][id*="mermaid"]'):[];errs.forEach(function(e){if(e.textContent&&e.textContent.indexOf('Syntax error')>=0){e.closest('div,svg').style.display='none';}});}});});});obs.observe(document.body||document.documentElement,{childList:true,subtree:true});})();
var lang = 'fr', mode = 'auto', voiceOn = false, isDark = (localStorage.getItem('wevia_dark') === '1' || (!localStorage.getItem('wevia_dark') && window.matchMedia('(prefers-color-scheme: dark)').matches)), busy = false;
var sshCreds = null, convId = null, session = localStorage.getItem('wevia_session') || 'web_' + Date.now().toString(36); localStorage.setItem('wevia_session', session);
var chatHistory = [];
// --- Auto-detect language from user message ---
function detectLang(text) {
if (!text) return 'fr';
var t = text.toLowerCase().trim();
// Darija
if (/\b(wach|chno|kif|dyal|hna|fhad|bghit|3lach|mzyan|chouf|salam|labas|hamdullah|walo|bzaf|kayn|mashi|khoya|sahbi|dima|ghadi|bhal|chhal|fin|kifach|3afak|smiyti|haja)\b/i.test(t)) return 'darija';
// Arabic script
if (/[\u0600-\u06FF]{3,}/.test(t)) return 'ar';
// Chinese
if (/[\u4e00-\u9fff]{2,}/.test(t)) return 'zh';
// Japanese
if (/[\u3040-\u30ff]{2,}/.test(t)) return 'ja';
// Korean
if (/[\uac00-\ud7af]{2,}/.test(t)) return 'ko';
// Hindi
if (/[\u0900-\u097F]{2,}/.test(t)) return 'hi';
// Russian
if (/[\u0400-\u04FF]{3,}/.test(t)) return 'ru';
// Turkish
if (/\b(merhaba|nasil|nedir|yapabilir|bana|icin|tesekkur)\b/i.test(t)) return 'tr';
// German
if (/\b(hallo|wie|kann|bitte|danke|nicht|oder|diese|einen|warum)\b/i.test(t) && !/\b(the|and|is)\b/i.test(t)) return 'de';
// Spanish
if (/\b(hola|como|puedo|para|por|favor|gracias|necesito|quiero|donde|hacer)\b/i.test(t)) return 'es';
// Portuguese
if (/\b(ola|obrigado|preciso|voce|fazer|ajuda)\b/i.test(t)) return 'pt';
// Italian
if (/\b(ciao|come|posso|grazie|vorrei|fare|dove|cosa|aiuto)\b/i.test(t)) return 'it';
// English
if (/\b(hello|hi|how|can|you|help|what|please|need|want|could|would|should|about|with)\b/i.test(t)) return 'en';
// Default French
return 'fr';
}
// ——— Preview Panel (Claude-style) ———
var currentPreviewUrl = '';
function openPreview(url, title) {
currentPreviewUrl = url;
document.getElementById('prevTitleText').textContent = title || 'Document PDF';
if (url.endsWith('.pdf')) {
document.getElementById('previewBody').innerHTML =
'<iframe src="' + url + '#view=FitH&toolbar=1" style="width:100%;height:100%;min-height:500px;border:0"></iframe>' +
'<div style="padding:20px;text-align:center;border-top:1px solid #e5e7eb">' +
'<a href="' + url + '" download style="display:inline-block;padding:10px 24px;background:#7C3AED;color:#fff;border-radius:8px;text-decoration:none;font-weight:600">⬇ Télécharger</a>' +
'<a href="' + url + '" target="_blank" style="display:inline-block;padding:10px 24px;background:#10B981;color:#fff;border-radius:8px;text-decoration:none;font-weight:600;margin-left:10px">↗ Ouvrir</a>' +
'</div>';
} else {
document.getElementById('previewBody').innerHTML = '<iframe src="' + url + '" style="width:100%;height:100%;border:0"></iframe>';
}
document.getElementById('mainLayout').classList.add('panel-open');
var icon = url.endsWith('.pdf') ? '📄' : url.endsWith('.html') ? '🌐' : '📋';
document.querySelector('.prev-icon').textContent = icon;
}
function closePreview() {
document.getElementById('mainLayout').classList.remove('panel-open');
setTimeout(function() {
document.getElementById('previewBody').innerHTML = '<div class="prev-placeholder"><div class="prev-ph-icon">📄</div><div>Aucun document à afficher</div></div>';
}, 400);
currentPreviewUrl = '';
}
function downloadPreview() {
if (!currentPreviewUrl) return;
var a = document.createElement('a');
a.href = currentPreviewUrl;
a.download = '';
a.click();
}
function expandPreview() {
var body = document.getElementById('previewBody');
if (document.fullscreenElement) { document.exitFullscreen(); }
else { body.requestFullscreen().catch(function(){}); }
}
function switchPreviewTab(tab) {
document.getElementById('prevTabPreview').classList.toggle('active', tab === 'preview');
document.getElementById('prevTabCode').classList.toggle('active', tab === 'code');
if (tab === 'code' && window._artifactCode) {
document.getElementById('previewBody').innerHTML = '<pre style="padding:16px;margin:0;overflow:auto;height:100%;background:#0d1117;color:#e6edf3;font-size:13px;line-height:1.5;white-space:pre-wrap">' + window._artifactCode.replace(/</g,'&lt;').replace(/>/g,'&gt;') + '</pre>';
} else if (tab === 'preview' && currentArtifact && currentArtifact.code) {
openArtifact(currentArtifact.code, currentArtifact.type, currentArtifact.title);
}
}
setTimeout(function(){ if(typeof addMsg==='function' && document.querySelectorAll('.msg').length===0) addMsg('assistant','Bonjour ! Comment puis-je vous aider ?'); },1200);
// ─── Error Handlers (prevent any crash) ───
window.addEventListener('unhandledrejection', function(e) { e.preventDefault(); });
window.onerror = function(m,s,l,c,e) { if(e&&e.message&&String(e.message).toLowerCase().indexOf('mermaid')>=0) return true; console.warn(m); return false; };
// ─── Language Selection ───
function selectLang(l) {
lang = l;
document.getElementById('langScreen').classList.add('hidden');
document.getElementById('chatWrap').classList.add('active');
var welcomes = {fr:'Bonjour ! Comment puis-je vous aider ?',darija:'مرحبا ! كيفاش نقدر نعاونك ؟ 🇲🇦',en:'Hello! How can I help you?',ar:'مرحبا! كيف يمكنني مساعدتك؟',es:'¡Hola! ¿Cómo puedo ayudarte?',de:'Hallo! Wie kann ich Ihnen helfen?',zh:'你好!我能帮你什么?',ja:'こんにちは!何かお手伝いできますか?',ko:'안녕하세요! 무엇을 도와드릴까요?',it:'Ciao! Come posso aiutarti?',pt:'Olá! Como posso ajudar?',ru:'Привет! Как я могу помочь?',tr:'Merhaba! Size nasıl yardımcı olabilirim?',hi:'नमस्ते! मैं आपकी कैसे मदद कर सकता हूँ?'};
addMsg('assistant', welcomes[l] || welcomes.fr);
document.getElementById('msgInput').focus();
document.getElementById('suggestedPrompts').style.display = 'flex';
if(isDark){document.body.className="dark";document.getElementById("themeBtn").textContent="🌙";}
if (l === 'ar' || l === 'darija') document.getElementById('messages').style.direction = 'rtl';
}
// ─── Theme Toggle ───
function toggleTheme() {
isDark = !isDark;
document.body.className = isDark ? 'dark' : 'light';
document.getElementById('themeBtn').textContent = isDark ? '🌙' : '☀️';
localStorage.setItem("wevia_dark", isDark ? "1" : "0");
}
// ─── Mode Toggle ───
function toggleMode() {
var modes = ['auto','fast','deep'];
mode = modes[(modes.indexOf(mode) + 1) % modes.length];
var labels = {auto: 'Auto \u26a1', deep: 'Approfondie \ud83e\udde0', fast: 'Rapide \u26a1'};
var gradients = {auto: 'linear-gradient(135deg,#10B981,#7C6BF0)', deep: 'linear-gradient(135deg,#6366f1,#8b5cf6)', fast: 'linear-gradient(135deg,#7C6BF0,#22D3EE)'};
document.getElementById('modeLabel').textContent = labels[mode];
document.getElementById('modeBtn').style.background = gradients[mode];
if (typeof responseMode !== 'undefined') responseMode = mode;
}
function detectComplexity(text) {
if (!text) return 'fast';
var t = text.toLowerCase().trim();
var len = t.length;
if (typeof pendingFile !== 'undefined' && pendingFile) return 'deep';
if (len < 40) return 'fast';
var deepKw = ['analyse','compare','comparaison','approfondi','detail','recherche','explique en detail',
'genere un pdf','genere un document','swot','pestel','audit','diagnostic','recommandation',
'strategie','business plan','etude de marche','rapport','synthese complete',
'avantages et inconvenients','architecture complete','code complet','application',
'deploiement','migration','projet complet','planning','roadmap','benchmark'];
for (var i = 0; i < deepKw.length; i++) {
if (t.indexOf(deepKw[i]) !== -1) return 'deep';
}
if (len > 150) return 'deep';
if ((t.match(/\?/g) || []).length >= 2) return 'deep';
return 'fast';
}
// ─── Voice Toggle ───
function toggleVoice() {
voiceOn = !voiceOn;
document.getElementById('voiceBtn').textContent = voiceOn ? '🔊 Voix' : '🔇 Voix';
document.getElementById('voiceBtn').classList.toggle('active', voiceOn);
// STOP all playing audio when muting
if(!voiceOn) {
// FORCE STOP ALL audio
if(window._ttsAudio) { try { window._ttsAudio.pause(); window._ttsAudio.currentTime = 0; } catch(e){} window._ttsAudio = null; }
if(window._ttsQueue) { window._ttsQueue.forEach(function(a){ try{a.pause();a.currentTime=0;}catch(e){} }); window._ttsQueue = []; }
if(window.speechSynthesis) window.speechSynthesis.cancel();
// Kill ALL audio elements in the page
document.querySelectorAll('audio').forEach(function(a){try{a.pause();a.currentTime=0;a.src='';}catch(e){}});
window._ttsPlaying = false;
}
}
// ─── SSH ───
function toggleSSH() { document.getElementById('sshPanel').classList.toggle('open'); }
function connectSSH() {
var h = document.getElementById('sshHost').value.trim();
var u = document.getElementById('sshUser').value.trim();
var p = document.getElementById('sshPass').value;
var port = document.getElementById('sshPort').value || '22';
if (!h || !u) { document.getElementById('sshStatus').textContent = '❌ Host et User requis'; return; }
document.getElementById('sshStatus').textContent = '⏳ Test de connexion...';
fetch('/wevia-ia/wevia-exec.php', {
method: 'POST', headers: {'Content-Type': 'application/json'},
body: JSON.stringify({host: h, user: u, password: p, port: parseInt(port), command: 'echo WEVIA_CONNECTED && hostname'})
}).then(function(r) { return r.json(); }).then(function(d) {
if (d.output && d.output.indexOf('WEVIA_CONNECTED') !== -1) {
sshCreds = {host: h, user: u, password: p, port: parseInt(port)};
document.getElementById('sshStatus').textContent = '✅ Connecté: ' + h;
document.getElementById('sshToggle').textContent = '🟢';
document.getElementById('sshDisc').style.display = '';
} else {
document.getElementById('sshStatus').textContent = '❌ Échec: ' + (d.output || d.error || 'Connexion refusée');
}
}).catch(function(e) { document.getElementById('sshStatus').textContent = '❌ Erreur: ' + e.message; });
}
function disconnectSSH() {
sshCreds = null;
document.getElementById('sshToggle').textContent = '🖥️';
document.getElementById('sshDisc').style.display = 'none';
document.getElementById('sshStatus').textContent = '🔌 Déconnecté';
}
// ─── Add Message ───
function addMsg(role, text, time) {
var el = document.createElement('div');
el.className = 'msg ' + role;
var html = '<div class="msg-inner"><div class="bubble">' + formatMd(text) + '</div>';
if (role === 'assistant' && time) {
html += '<div class="msg-meta"><span class="meta-time">⚡ ' + time + 's</span>';
var msgIdx = window._msgTexts ? window._msgTexts.length : 0;
if(!window._msgTexts) window._msgTexts = [];
window._msgTexts.push(text || '');
html += '<span class="meta-sep">·</span>';
html += '<button class="meta-btn" onclick="replayTTS(this)" data-idx="' + msgIdx + '">🔊</button>';
html += '<button class="meta-btn" onclick="copyMsg(' + msgIdx + ')">📋</button>';
html += '<button class="meta-btn" onclick="regen()">🔄</button>';
html += '<button class="fb-btn" onclick="sendFeedback(this,\'up\',' + msgIdx + ')" title="Bonne réponse">👍</button>';
html += '<button class="fb-btn" onclick="sendFeedback(this,\'down\',' + msgIdx + ')" title="À améliorer">👎</button>';
html += '</div>';
}
html += '</div>';
el.innerHTML = html;
document.getElementById('messages').appendChild(el);
requestAnimationFrame(function(){var m=document.getElementById('messages');m.scrollTo({top:m.scrollHeight,behavior:'smooth'});});
return el;
}
// ─── Thinking Indicator ───
// ═══ COGNITIVE REASONING ENGINE — Simulates advanced human-like thinking ═══
function _generateThinking(q) {
if (!q) return '';
var ql = q.toLowerCase();
var words = q.split(/\s+/).filter(function(w){return w.length > 3;});
var keyTerms = words.slice(0, 4).join(', ');
var wordCount = q.split(/\s+/).length;
// ═══ GREETING — Cognitive warmth, not robotic ═══
if (/^(bonjour|salut|hello|hey|hi|coucou|bonsoir|yo|salam|hola|ola|marhba)\b/.test(ql)) {
var lang = 'français';
if (/hello|hey|hi\b/.test(ql)) lang = 'anglais';
else if (/hola/.test(ql)) lang = 'espagnol';
else if (/salam|marhba/.test(ql)) lang = 'arabe/darija';
var greetThink = [
"Détection linguistique : " + lang + ". Adaptation du registre et du ton en conséquence.",
"Signal social — l'utilisateur initie le contact. Je calibre une réponse chaleureuse et concise qui ouvre le dialogue sans être générique.",
"Profil interlocuteur : pas encore déterminé. Je reste ouvert et engageant pour comprendre le besoin réel."
];
return greetThink.join('\n\n');
}
var parts = [];
// ═══ PHASE 1: Décomposition cognitive ═══
var phase1 = [
"📋 Décomposition de la requête — " + wordCount + " mots, " + words.length + " termes significatifs : " + keyTerms + ".",
"🔍 Analyse sémantique — Termes clés détectés : " + keyTerms + ". Évaluation de la complexité et du niveau d'expertise attendu.",
"🧩 Parsing cognitif — " + words.length + " concepts identifiés (" + keyTerms + "). Je détermine la profondeur de réponse optimale."
];
parts.push(phase1[Math.floor(Math.random() * phase1.length)]);
// ═══ PHASE 2: Détection d'intention + contexte ═══
var intent = '';
if (/comment|how|como|كيف/.test(ql)) intent = "Comment faire → mode tutoriel/guide actionnable";
else if (/pourquoi|why|por que|لماذا/.test(ql)) intent = "Pourquoi → mode explicatif causal, remonter aux racines";
else if (/compare|versus|vs|differ|مقارنة/.test(ql)) intent = "Comparaison → mode analytique, tableau mental, critères objectifs";
else if (/meilleur|best|mejor|أفضل|recommand|conseil/.test(ql)) intent = "Recommandation → mode décisionnel, critères pondérés, ROI";
else if (/code|script|function|programme|api|bug|error/.test(ql)) intent = "Technique/code → mode ingénieur, code fonctionnel direct, pas de placeholder";
else if (/strateg|business|entreprise|croissance|marché|revenue/.test(ql)) intent = "Business/stratégie → mode Partner consulting, chiffres et benchmarks";
else if (/ecri|redige|lettre|email|mail|texte|article|blog/.test(ql)) intent = "Rédaction → mode créatif structuré, ton adapté au contexte cible";
else if (/image|photo|dessin|genere|illustr|logo|affiche/.test(ql)) intent = "Génération visuelle → activation pipeline image, calibrage du prompt";
else if (/pdf|document|rapport|presentation|pptx/.test(ql)) intent = "Production documentaire → template pro WEVAL, structure, visuels";
else if (/sap|erp|vistex|odoo|crm|module/.test(ql)) intent = "ERP/SAP → expertise fonctionnelle + technique, best practices S/4HANA";
else if (/cloud|aws|azure|gcp|huawei|scaleway|infra|serveur/.test(ql)) intent = "Infrastructure/Cloud → architecture, sizing, coûts, migration path";
else if (/ia|intelligence.*artif|machine.*learn|deep.*learn|llm|gpt|model/.test(ql)) intent = "IA/ML → état de l'art, benchmarks modèles, cas d'usage concrets";
else if (/cyber|securit|rgpd|gdpr|zero.*trust|iso.*27001/.test(ql)) intent = "Cybersécurité → framework, compliance, risk assessment";
else if (/maroc|morocco|casablanca|rabat|afrique|africa/.test(ql)) intent = "Contexte Maroc/Afrique — spécificités locales : réglementation, tissu économique, infrastructures. L'expérience terrain WEVAL est un avantage.";
else if (wordCount > 15) intent = "Requête complexe multi-aspects → décomposition en sous-questions, réponse structurée";
else intent = "Requête directe → réponse ciblée, valeur maximale en minimum de mots";
parts.push("🎯 Intention détectée : " + intent);
// ═══ PHASE 3: Stratégie de réponse ═══
var strategies = [];
if (/code|script|api|debug|error|bug/.test(ql)) {
strategies.push("Approche ingénieur : code fonctionnel d'abord, explication ensuite. Vérification syntaxique mentale avant envoi.");
} else if (/strateg|business|plan|audit|transform|roi/.test(ql)) {
strategies.push("Cadre analytique McKinsey : diagnostic → hypothèses → données → recommandations actionnables avec ROI estimé.");
} else if (/compare|choisi|versus|quel.*meilleur/.test(ql)) {
strategies.push("Matrice de décision : critères objectifs (coût, performance, scalabilité, maturité) → scoring → recommandation argumentée.");
} else if (/expliqu|definit|c.?est.?quoi|qu.?est.?ce/.test(ql)) {
strategies.push("Pédagogie progressive : concept simple → nuances → exemples concrets → implications pratiques.");
} else if (/ecri|redige|lettre|mail|article/.test(ql)) {
strategies.push("Rédaction — ton, cible et objectif déterminent tout. Formel vs conversationnel, informatif vs persuasif. Structure claire, accroche forte.");
} else if (/mind.?map|ishikawa|gantt|schema|diagram|mermaid/.test(ql)) {
strategies.push("Visualisation structurée — choix du format optimal (mermaid, SVG, HTML), hiérarchie claire, données vérifiées avant génération.");
} else if (/optimi|amelior|performan|acceler|rapide|lent/.test(ql)) {
strategies.push("Diagnostic avant optimisation : identifier le vrai goulot d'étranglement, mesurer, puis optimiser chirurgicalement.");
} else if (/creer|construire|developper|lancer|genere|mettre.*place/.test(ql)) {
strategies.push("Approche itérative : MVP d'abord, valider, puis enrichir. Priorisation par impact/effort.");
} else {
strategies.push("Réponse directe et structurée — chaque phrase doit apporter de la valeur. Zéro remplissage.");
}
parts.push("🧠 Stratégie cognitive : " + strategies[0]);
// ═══ PHASE 4: Auto-vérification ═══
var checks = [
"✅ Contrôle pré-envoi : factualité vérifiée, cohérence logique, niveau adapté à l'interlocuteur.",
"✅ Self-check : pas d'hallucination, données sourcées, recommandation actionnable.",
"✅ Validation : réponse complète, ton calibré, emojis positifs, prête à envoyer."
];
parts.push(checks[Math.floor(Math.random() * checks.length)]);
return parts.join('\n\n');
}
function showThinking() {
var oldThink = document.getElementById('thinking'); if(oldThink) oldThink.remove();
window._thinkStartTime = Date.now();
var el = document.createElement('div');
el.className = 'msg assistant';
el.id = 'thinking';
el.innerHTML = '<div class="think-block" id="thinkBlock">' +
'<div class="think-header" onclick="toggleThinking()">' +
'<span class="think-chevron" id="thinkChev" style="transform:rotate(90deg)">▶</span>' +
'<span class="think-sparkle active" id="thinkSparkle">🧠</span>' +
'<span class="think-label" id="thinkLabel">Raisonnement en cours</span>' +
'<span class="think-timer" id="thinkTimerEl">0s</span>' +
'</div>' +
'<div class="think-body" id="thinkBody"></div>' +
'</div>';
document.getElementById('messages').appendChild(el);
var m = document.getElementById('messages');
m.scrollTo({top: m.scrollHeight, behavior: 'smooth'});
// Live timer
window._thinkTimerInterval = setInterval(function() {
var s = Math.floor((Date.now() - window._thinkStartTime) / 1000);
var te = document.getElementById('thinkTimerEl');
if (te) te.textContent = s + 's';
var lbl = document.getElementById('thinkLabel');
if (lbl && s >= 8 && s < 15) lbl.textContent = 'Toujours en train de réfléchir...';
if (lbl && s >= 15) lbl.textContent = 'Je finalise ma pensée...';
}, 1000);
// REAL LLM reasoning - async API call for live thinking
var q = window._lastQuery || '';
var body = document.getElementById('thinkBody');
if (body) {
var stream = document.createElement('div');
stream.className = 'think-stream';
stream.textContent = 'Analyse en cours...';
body.innerHTML = '';
body.appendChild(stream);
var cur = document.createElement('span');
cur.className = 'think-cursor';
stream.appendChild(cur);
// === AMBRE-V3-THINKING 2026-04-21 · route to custom thinking endpoint ===
// Doctrine: real chain-of-thought for all queries, pattern-specific for gen tasks
fetch('/api/ambre-thinking.php', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ message: q, language: (typeof lang !== 'undefined' ? lang : 'fr') })
})
.then(function(r) { return r.json(); })
.then(function(data) {
if (data.thinking) {
// Stream the thinking text word by word
var words = data.thinking.split(' ');
stream.textContent = '';
var idx = 0;
var thinkInterval = setInterval(function() {
if (idx >= words.length) {
clearInterval(thinkInterval);
var c2 = stream.querySelector('.think-cursor');
if(c2) c2.remove();
return;
}
var w = document.createElement('span');
w.className = 'think-word';
w.style.animationDelay = '0s';
w.textContent = words[idx] + ' ';
if(cur.parentNode) stream.insertBefore(w, cur);
else stream.appendChild(w);
idx++;
var msgs = document.getElementById('messages');
if(msgs) msgs.scrollTop = msgs.scrollHeight;
}, 40);
}
})
.catch(function() {
// Fallback to basic text if API fails
stream.textContent = 'Analyse de la demande et structuration de la réponse...';
});
}
}
function toggleThinking() {
var body = document.getElementById('thinkBody');
var chev = document.getElementById('thinkChev');
if (!body) return;
var isHidden = body.classList.contains('collapsed') || body.style.maxHeight === '0px' || getComputedStyle(body).opacity === '0';
if (isHidden) {
body.classList.remove('collapsed');
body.style.maxHeight = '500px';
body.style.opacity = '1';
body.style.padding = '0 16px 12px 16px';
var blk = document.getElementById('thinkBlock'); if (blk) { blk.style.maxHeight=''; blk.style.overflow=''; blk.style.opacity=''; }
if (chev) chev.style.transform = 'rotate(90deg)';
} else {
body.classList.add('collapsed');
body.style.maxHeight = '0px';
body.style.opacity = '0';
body.style.padding = '0';
if (chev) chev.style.transform = 'rotate(0deg)';
}
}
function hideThinking() {
if(window._thinkTimers) { window._thinkTimers.forEach(function(t){clearTimeout(t);}); window._thinkTimers = null; }
if (window._thinkTimerInterval) { clearInterval(window._thinkTimerInterval); window._thinkTimerInterval = null; }
if (window._thinkStepTimer) { clearTimeout(window._thinkStepTimer); window._thinkStepTimer = null; }
if (window._thinkTimer) { clearTimeout(window._thinkTimer); window._thinkTimer = null; }
if (window._thinkInterval) { clearInterval(window._thinkInterval); window._thinkInterval = null; }
var block = document.getElementById('thinkBlock');
if (block) {
var duration = Math.max(1, Math.floor((Date.now() - (window._thinkStartTime || Date.now())) / 1000));
block.classList.add('done');
// Update to "Pensee pendant Xs"
var label = document.getElementById('thinkLabel');
if (label) label.textContent = 'Pens\u00e9e pendant ' + duration + 's';
// Collapse thinking content - only show summary line
var tc = block.querySelector('.think-content, .think-steps');
if (tc) tc.style.display = 'none';
block.style.maxHeight = '';
block.style.overflow = '';
block.style.opacity = '0.7';
var sparkle = document.getElementById('thinkSparkle');
if (sparkle) { sparkle.classList.remove('active'); sparkle.innerHTML = '🧠';; }
var timer = document.getElementById('thinkTimerEl');
if (timer) timer.textContent = '';
// Remove cursors and active dots
block.querySelectorAll('.think-cursor').forEach(function(c) { c.remove(); });
// dots removed - using stream display
// Auto-collapse after user has time to read
setTimeout(function() {
var body = document.getElementById('thinkBody');
var chev = document.getElementById('thinkChev');
if (body) { body.classList.add('collapsed'); body.removeAttribute('style'); }
if (chev) chev.style.transform = 'rotate(0deg)';
}, 3000);
}
}
function copyMsg(idx) {
var text = (window._msgTexts && window._msgTexts[idx]) ? window._msgTexts[idx] : '';
navigator.clipboard.writeText(text).then(function() {
var btns = document.querySelectorAll('.meta-btn');
// flash visual feedback
var t=document.createElement('div');t.textContent='✅ Copié !';t.style.cssText='position:fixed;bottom:80px;left:50%;transform:translateX(-50%);background:#10b981;color:#fff;padding:8px 20px;border-radius:8px;font-size:14px;z-index:9999;animation:fadeIn .2s';document.body.appendChild(t);setTimeout(function(){t.remove()},1500);
}).catch(function() {
var ta = document.createElement('textarea'); ta.value = text;
document.body.appendChild(ta); ta.select(); document.execCommand('copy');
document.body.removeChild(ta);
});
}
// --- Auto-detect language from user message ---
function detectLang(text) {
if (!text) return lang || 'fr';
var t = text.toLowerCase();
if (/\b(wach|kifash|chno|bach|dyal|bghit|kayn|makay|fin|chhal|hadi|dial|lmaghrib|wash|labas|khouya|khti|mzyan)\b/.test(t)) return 'darija';
if (/[\u0600-\u06FF]{3,}/.test(text)) return 'ar';
if (/[\u4e00-\u9fff]{2,}/.test(text)) return 'zh';
if (/[\u3040-\u309f\u30a0-\u30ff]{2,}/.test(text)) return 'ja';
if (/[\uac00-\ud7af]{2,}/.test(text)) return 'ko';
if (/[\u0400-\u04FF]{3,}/.test(text)) return 'ru';
if (/[\u0900-\u097F]{3,}/.test(text)) return 'hi';
if (/\b(hola|como|estas|puedes|quiero|necesito|gracias|por favor|donde|cuando|porque|tambien|bueno)\b/.test(t)) return 'es';
if (/\b(wie|kann|bitte|danke|ich|hallo|warum|hilfe|brauche)\b/.test(t)) return 'de';
if (/\b(ciao|come|puoi|grazie|aiuto|bisogno|perche|buongiorno)\b/.test(t)) return 'it';
if (/\b(ola|como|pode|obrigado|ajuda|preciso|porque|bom dia)\b/.test(t)) return 'pt';
if (/\b(merhaba|nasil|tesekkur|yardim|lutfen|neden)\b/.test(t)) return 'tr';
if (/\b(hello|hi|how|can|please|help|want|need|what|where|when|why|the|this|that|have|with)\b/.test(t)) return 'en';
if (/\b(bonjour|salut|comment|peux|aide|merci|besoin|pourquoi|quand|travail|quel|avec|dans|pour|faire)\b/.test(t)) return 'fr';
return lang || 'fr';
}
// ─── Send Message ───
// wvShortcut: fill input with prefix then focus for user completion (or send if already complete)
function wvShortcut(prefix) {
// V173 · store last shortcut prefix for upsell topic extraction
try { var _mi = document.getElementById("msgInput"); if (_mi) _mi.setAttribute("data-last-shortcut", prefix); } catch(e){}
try {
var inp = document.getElementById('msgInput');
if (!inp) return;
inp.value = prefix;
inp.focus();
// Auto-resize
inp.style.height = 'auto';
inp.style.height = Math.min(inp.scrollHeight, 200) + 'px';
// Scroll input into view on mobile
try { inp.scrollIntoView({behavior:'smooth', block:'center'}); } catch(e){}
} catch(e) { console.warn('wvShortcut err', e); }
}
function send() {
// V180 public-guard · bloque commandes internes sur widget public (clients externes)
// Yacine 22avr: pas de fuites internes intents_pool multiagent nonreg = confidentialite
try {
var _v180_raw = "";
try { _v180_raw = (text !== undefined && text !== null) ? text : ((document.getElementById("msgInput") || {}).value || ""); } catch(eRaw){}
var _v180_text = (_v180_raw || "").trim().toLowerCase();
var _v180_blocked = [
/^intents?_pool\b/i,
/^quelle\s+heure\s*$/i,
/^multiagent\s+parall[eéè]le/i,
/^orchestrate\s+parall[eéè]le/i,
/^cable\s+un?\s+intent/i,
/^nonreg\s*(?:score|status)?\s*$/i,
/^l99\s*(?:score|status)?\s*$/i,
/^6\s*sigma/i,
/^derniers?\s+commits?\s+git/i,
/^git\s+(?:log|commit|status)/i,
/\bWAVE-\d+/i,
/\bpool\s+total\b/i,
/^doctrines?\s+(?:wiki|list)/i,
/^load\s*$/i,
/\bwevia[-_.]?(?:master|orchestrator|autonomous)\b/i,
/^(?:ping|status|health)\s+(?:system|server|infra)/i,
];
for (var _v180_i = 0; _v180_i < _v180_blocked.length; _v180_i++) {
if (_v180_blocked[_v180_i].test(_v180_text)) {
try {
if (typeof addMsg === "function") {
addMsg("user", _v180_raw, "0");
addMsg("assistant", "Je suis WEVIA Assistant IA. Je peux vous aider avec la generation de documents (PDF, Word, Excel, PowerPoint), la creation d images, de schemas et de code, les traductions, les recherches et les calculs. Que puis-je faire pour vous aujourd hui ?", "0");
}
} catch(eMsg) {}
try {
var _mi = document.getElementById("msgInput");
if (_mi) { _mi.value = ""; _mi.disabled = false; }
var _sb = document.getElementById("sendBtn");
if (_sb) _sb.disabled = false;
if (typeof busy !== "undefined") busy = false;
} catch(eBsy) {}
console.warn("[V180 public-guard] blocked internal command:", _v180_text.substring(0, 60));
return;
}
}
} catch(_v180_err) { console.warn("V180 public-guard err:", _v180_err); }
try {
if (busy) return;
var input = document.getElementById('msgInput');
var text = input.value.trim();
if (!text && !pendingFile) return;
busy = true;
document.getElementById('sendBtn').disabled = true;
input.value = '';
input.style.height = 'auto';
var displayText = text;
if (pendingFile) { displayText = (pendingFile.type.startsWith('image/') ? '🖼️' : '📎') + ' ' + pendingFile.name + (text ? '\n' + text : ''); }
addMsg('user', displayText);
var sp = document.getElementById('suggestedPrompts'); if(sp) sp.style.display = 'none';
chatHistory.push({role: 'user', content: text});
window._lastQuery = text; showThinking();
var payload = {
message: text,
language: detectLang(text),
history: chatHistory.slice(-6),
mode: mode,
session: session,
conversation_id: convId
};
if (sshCreds) payload.ssh = sshCreds;
if (pendingFile) {
if (pendingFile.type && pendingFile.type.startsWith('image/')) {
payload.image = pendingFile.base64;
payload.image_mime = pendingFile.type || 'image/jpeg';
} else {
// For PDFs and other files, send as file_content
payload.file_content = pendingFile.base64;
payload.file_mime = pendingFile.type || 'application/octet-stream';
}
payload.file_name = pendingFile.name;
if (!text) { payload.message = 'Analyse ce fichier en détail : ' + pendingFile.name; }
}
var startTime = performance.now();
// Auto-mode routing
var effectiveMode = mode;
if (mode === 'auto') {
effectiveMode = detectComplexity(text);
var autoLabel = effectiveMode === 'deep' ? '🔬 Approfondie' : '⚡ Rapide';
if (typeof addThinkStep === 'function') addThinkStep('🎯 Mode auto: ' + autoLabel);
}
payload.mode = effectiveMode;
// === AMBRE-V5-CLAUDE-PATTERN 2026-04-22 · True Claude streaming experience ===
// Thinking (chain of thought animated) -> Plan -> RAG -> Execute -> Result (chunks streamed)
// HOISTED: _ambre_gen_pat declared early (was at line 1782)
if (typeof _ambre_gen_pat === 'undefined') { var _ambre_gen_pat = /g[eéèê]n[eéèê]re?\s+(?:un|une|des|le|la)?\s*(pdf|pptx?|powerpoint|docx?|word|excel|xlsx?|pr[eéèê]sentation|document|tableau|sch[eéèê]ma|mermaid|diagramme|image)|ecri[srt]?\s+(?:le|du|un)?\s*code|traduis?\s+(?:ce\s+texte|en)?\s*(anglais|francais|espagnol|allemand|italien|portugais|arabe|chinois|japonais|english|spanish|french|german|italian|portuguese|arabic|chinese|japanese)/i; }
if (_ambre_gen_pat.test(text)) {
hideThinking(); // we'll show our own rich thinking UI
// Create custom stream message container BELOW the user message
var streamEl = addMsg('assistant', '', '0');
var streamInner = streamEl.querySelector('.msg-inner') || streamEl;
streamInner.innerHTML = '<div class="ambre-stream-container" style="font-family:system-ui;line-height:1.5"></div>';
var container = streamInner.querySelector('.ambre-stream-container');
var phaseLabels = {thinking: '💭 Pensée', plan: '📋 Plan', rag: '🔍 RAG', execute: '⚙️ Exécution', result: '✅ Résultat'};
var phaseColors = {thinking:'#a78bfa', plan:'#60a5fa', rag:'#34d399', execute:'#fbbf24', result:'#10b981'};
var currentPhase = null;
var currentPhaseEl = null;
var currentChunkEl = null;
var fullResponse = '';
var finalFileUrl = null;
var startTime = performance.now();
var eventSource = new EventSource('/api/ambre-claude-stream.php?ts=' + Date.now());
// EventSource is GET-only, so we use fetch() with ReadableStream for POST SSE
if (eventSource) try { eventSource.close(); } catch(e){}
fetch('/api/ambre-claude-stream.php', {
method: 'POST',
headers: {'Content-Type':'application/json','Accept':'text/event-stream'},
body: JSON.stringify({message: text, session_id: convId || ('wv-'+Date.now())})
})
.then(function(response) {
if (!response.ok) throw new Error('HTTP ' + response.status);
var reader = response.body.getReader();
var decoder = new TextDecoder();
var buffer = '';
function processBuffer() {
var parts = buffer.split('\n\n');
buffer = parts.pop() || '';
parts.forEach(function(block) {
if (!block.trim()) return;
var eventMatch = block.match(/^event:\s*(\S+)/m);
var dataMatch = block.match(/^data:\s*(.+)$/m);
if (!eventMatch || !dataMatch) return;
var evType = eventMatch[1];
var data;
try { data = JSON.parse(dataMatch[1]); } catch(e) { return; }
handleEvent(evType, data);
});
}
function handleEvent(type, data) {
if (type === 'start') {
var header = '<div style="padding:10px 14px;background:linear-gradient(135deg,#667eea 0%,#764ba2 100%);color:#fff;border-radius:10px;margin-bottom:12px;font-size:13px">';
header += '<strong>WEVIA-pattern</strong> · pattern: <code style="background:rgba(255,255,255,0.2);padding:2px 6px;border-radius:4px">' + data.pattern + '</code></div>';
container.innerHTML = header;
}
else if (type === 'phase') {
currentPhase = data.phase;
var color = phaseColors[data.phase] || '#888';
var phaseBlock = document.createElement('div');
phaseBlock.className = 'ambre-phase ambre-phase-' + data.phase;
phaseBlock.style.cssText = 'margin:10px 0;padding:12px;border-left:3px solid '+color+';background:rgba(0,0,0,0.03);border-radius:6px';
phaseBlock.innerHTML = '<div style="font-weight:600;color:'+color+';font-size:13px;margin-bottom:6px">'+(phaseLabels[data.phase]||data.phase)+' · étape '+data.step+'/'+data.total+'</div><div class="phase-content" style="font-size:13px;color:#333"></div>';
container.appendChild(phaseBlock);
currentPhaseEl = phaseBlock.querySelector('.phase-content');
}
else if (type === 'thinking_step' && currentPhaseEl) {
var step = document.createElement('div');
step.style.cssText = 'padding:4px 0;opacity:0;transition:opacity 0.3s';
step.innerHTML = '<span style="color:#a78bfa">'+data.index+'.</span> ' + data.content;
currentPhaseEl.appendChild(step);
setTimeout(function(){ step.style.opacity='1'; }, 10);
}
else if (type === 'plan_steps' && currentPhaseEl) {
var table = '<table style="width:100%;border-collapse:collapse;font-size:12px"><thead><tr style="background:rgba(96,165,250,0.1)"><th style="padding:6px 8px;text-align:left">#</th><th style="padding:6px 8px;text-align:left">Action</th><th style="padding:6px 8px;text-align:left">Description</th><th style="padding:6px 8px;text-align:right">Estimé</th></tr></thead><tbody>';
data.steps.forEach(function(s, i) {
table += '<tr style="border-bottom:1px solid rgba(0,0,0,0.05)"><td style="padding:5px 8px">'+(i+1)+'</td><td style="padding:5px 8px"><code style="background:#eef;padding:1px 5px;border-radius:3px">'+s.action+'</code></td><td style="padding:5px 8px">'+s.desc+'</td><td style="padding:5px 8px;text-align:right;color:#999">'+s.est_ms+'ms</td></tr>';
});
table += '</tbody></table>';
currentPhaseEl.innerHTML = table;
}
else if (type === 'rag_hit' && currentPhaseEl) {
var hit = document.createElement('div');
hit.style.cssText = 'padding:6px 8px;margin:4px 0;background:rgba(52,211,153,0.08);border-radius:4px;font-size:12px;display:flex;justify-content:space-between;gap:12px';
hit.innerHTML = '<span style="flex:1">' + data.text + '</span><span style="color:#999;font-size:11px">'+data.collection+' · '+data.score.toFixed(2)+'</span>';
currentPhaseEl.appendChild(hit);
}
else if (type === 'exec_start' && currentPhaseEl) {
var row = document.createElement('div');
row.id = 'exec-' + data.index;
row.style.cssText = 'padding:6px 0;font-size:12px;display:flex;align-items:center;gap:10px';
row.innerHTML = '<span class="spinner" style="display:inline-block;width:12px;height:12px;border:2px solid #fbbf24;border-top-color:transparent;border-radius:50%;animation:spin 0.8s linear infinite"></span><code style="background:#fff3cd;padding:2px 6px;border-radius:3px">'+data.action+'</code><span style="flex:1;color:#666">'+data.desc+'</span><span class="elapsed" style="color:#999">…</span>';
currentPhaseEl.appendChild(row);
}
else if (type === 'exec_done') {
var row = document.getElementById('exec-' + data.index);
if (row) {
var spin = row.querySelector('.spinner');
if (spin) spin.outerHTML = '<span style="color:#10b981">✓</span>';
var el = row.querySelector('.elapsed');
if (el) el.textContent = data.elapsed_ms + 'ms';
}
}
else if (type === 'chunk' && currentPhaseEl) {
if (!currentChunkEl) {
currentChunkEl = document.createElement('div');
currentChunkEl.className = 'result-chunks';
currentChunkEl.style.cssText = 'padding:10px;background:#fff;border:1px solid #e5e7eb;border-radius:6px;margin-top:8px;white-space:pre-wrap;font-size:13px';
currentPhaseEl.appendChild(currentChunkEl);
}
fullResponse += data.content;
currentChunkEl.textContent = fullResponse;
var msgs = document.getElementById('messages');
if (msgs) msgs.scrollTop = msgs.scrollHeight;
}
else if (type === 'done') {
finalFileUrl = data.file_url;
// === AMBRE-V5-MERMAID-RENDER wave-263 ===
// If fullResponse contains mermaid code → render SVG inline
try {
var _mcode = null;
if (data.mermaid_code) _mcode = data.mermaid_code;
else if (fullResponse) {
// Detect mermaid patterns at start of response
var _fr = fullResponse.trim();
if (/^(sequenceDiagram|flowchart|graph\s+[A-Z]{1,2}|classDiagram|stateDiagram|erDiagram|gantt|pie|journey|gitGraph|mindmap|timeline)\b/.test(_fr)) {
_mcode = _fr;
} else {
// Search inside for ``` mermaid block
var _m = fullResponse.match(/```mermaid\s*([\s\S]+?)```/);
if (_m) _mcode = _m[1].trim();
else {
// or raw mermaid after 'Schema Mermaid:' label
var _m2 = fullResponse.match(/(?:Schema Mermaid|Diagramme)[^\n]*\n([\s\S]+)$/i);
if (_m2 && /^(sequenceDiagram|flowchart|graph|classDiagram|stateDiagram)/.test(_m2[1].trim())) _mcode = _m2[1].trim();
}
}
}
if (_mcode && window.mermaid && currentPhaseEl) {
// V170 accents-preserve · wrap labels in quotes to keep French accents
// Mermaid parser accepts accents inside quoted labels: A["Dossier de clés"]
var _clean = _mcode;
try {
// Find unquoted node labels: [text] ("text") {text} and wrap them
_clean = _clean.replace(/\[([^\]\"\n]*[\u00c0-\u017f][^\]\n]*)\]/g, function(m, inner) { return '["' + inner.replace(/"/g, '') + '"]'; });
_clean = _clean.replace(/\(([^\)\"\n]*[\u00c0-\u017f][^\)\n]*)\)/g, function(m, inner) { return '("' + inner.replace(/"/g, '') + '")'; });
_clean = _clean.replace(/\{([^\}\"\n]*[\u00c0-\u017f][^\}\n]*)\}/g, function(m, inner) { return '{"' + inner.replace(/"/g, '') + '"}'; });
} catch(_accErr) { /* fallback original */ }
var _svgId = 'wv-mermaid-' + Date.now() + '-' + Math.floor(Math.random()*999);
var _mContainer = document.createElement('div');
_mContainer.id = _svgId + '-wrap';
_mContainer.style.cssText = 'margin-top:12px;padding:16px;background:#fafafa;border:1px solid #e5e7eb;border-radius:8px;overflow-x:auto';
currentPhaseEl.appendChild(_mContainer);
try {
window.mermaid.render(_svgId, _clean).then(function(r){
_mContainer.innerHTML = r.svg;
}).catch(function(err){
_mContainer.innerHTML = '<pre style="color:#b91c1c;font-size:11px">Mermaid render error: ' + (err.message||err) + '\n\n' + _clean.replace(/</g,'&lt;') + '</pre>';
});
} catch(e) {
_mContainer.innerHTML = '<pre style="font-size:11px">' + _clean.replace(/</g,'&lt;') + '</pre>';
}
}
} catch(eMermaid) { console.warn('[V5-MERMAID-RENDER]', eMermaid); }
// === END AMBRE-V5-MERMAID-RENDER ===
// V169 code-artifact-preview · detect code blocks + wrap <pre><code> + trigger artifact renderer
try {
if (currentChunkEl && fullResponse) {
var _v169_codeRegex = /```(\w+)?\s*\n?([\s\S]+?)```/g;
var _v169_match;
var _v169_hasCode = false;
var _v169_html = fullResponse;
while ((_v169_match = _v169_codeRegex.exec(fullResponse)) !== null) {
var _v169_lang = (_v169_match[1] || "text").toLowerCase();
// Skip mermaid (autre Claude handle)
if (_v169_lang === "mermaid") continue;
_v169_hasCode = true;
var _v169_code = _v169_match[2].trim();
var _v169_escaped = _v169_code.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
var _v169_replacement = "<pre style=\"background:#1a1a2e;color:#e5e7eb;padding:14px;border-radius:8px;overflow-x:auto;font-family:Consolas,Monaco,monospace;font-size:12px;line-height:1.5\"><code class=\"language-" + _v169_lang + "\">" + _v169_escaped + "</code></pre>";
_v169_html = _v169_html.replace(_v169_match[0], _v169_replacement);
}
if (_v169_hasCode) {
currentChunkEl.innerHTML = _v169_html;
// Trigger artifact renderer scan
setTimeout(function() {
if (typeof window.scanAndAddButtons === "function") {
window.scanAndAddButtons();
}
}, 100);
}
}
} catch(_v169_err) { console.warn("V169 code-artifact-preview err:", _v169_err); }
// === END V169 code-artifact-preview ===
// V173 pdf-upsell · si PDF simple genere, proposer version enrichie avec graphiques
try {
if (currentChunkEl && finalFileUrl && /\.pdf(\?|$)/.test(finalFileUrl)) {
var _v173_topic = (document.querySelector("#msgInput") && document.querySelector("#msgInput").getAttribute("data-last-msg")) || fullResponse.match(/PDF[^\\.]*(?:sur|pour|about|de):?\s*([^\\.\\n]{3,80})/i);
if (_v173_topic && typeof _v173_topic === "object" && _v173_topic[1]) _v173_topic = _v173_topic[1].trim();
var _v173_banner = document.createElement("div");
_v173_banner.className = "v173-upsell";
_v173_banner.style.cssText = "margin-top:14px;padding:14px 18px;background:linear-gradient(135deg,#6366f1,#8b5cf6);color:#fff;border-radius:12px;display:flex;align-items:center;gap:14px;box-shadow:0 4px 14px rgba(99,102,241,.25);animation:v173Slide .4s ease";
_v173_banner.innerHTML = "<div style=\"font-size:22px\">✨</div><div style=\"flex:1\"><div style=\"font-weight:700;font-size:14px;margin-bottom:3px\">Souhaitez-vous une version enrichie ?</div><div style=\"font-size:12px;opacity:.92\">Graphiques, visuels, KPIs, mise en page premium</div></div><button id=\"v173-generate\" style=\"padding:8px 16px;background:#fff;color:#6366f1;border:none;border-radius:8px;font-weight:700;font-size:12px;cursor:pointer;white-space:nowrap\">Oui, enrichir</button>";
currentChunkEl.parentNode.appendChild(_v173_banner);
// Add animation style once
if (!document.getElementById("v173-style")) {
var _s = document.createElement("style"); _s.id = "v173-style";
_s.textContent = "@keyframes v173Slide{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.v173-upsell button:hover{transform:scale(1.04)}";
document.head.appendChild(_s);
}
// Wire the button
document.getElementById("v173-generate").onclick = function() {
var btn = this;
btn.disabled = true; btn.innerHTML = "<span style=\"display:inline-block;width:12px;height:12px;border:2px solid #6366f1;border-top-color:transparent;border-radius:50%;animation:spin .8s linear infinite\"></span> Generation...";
var _topic = _v173_topic || "document professionnel";
fetch("/api/ambre-tool-pdf-premium.php", {
method: "POST",
headers: {"Content-Type":"application/json"},
body: JSON.stringify({topic: _topic})
})
.then(function(r) { return r.json(); })
.then(function(d) {
if (d.ok && d.url) {
_v173_banner.style.background = "linear-gradient(135deg,#10b981,#059669)";
_v173_banner.innerHTML = "<div style=\"font-size:22px\">✅</div><div style=\"flex:1\"><div style=\"font-weight:700;font-size:14px;margin-bottom:3px\">PDF enrichi genere !</div><div style=\"font-size:12px;opacity:.92\">Avec graphiques et visuels premium</div></div><a href=\"" + d.url + "\" target=\"_blank\" style=\"padding:8px 16px;background:#fff;color:#10b981;border:none;border-radius:8px;font-weight:700;font-size:12px;text-decoration:none;white-space:nowrap\">Telecharger</a>";
} else {
_v173_banner.innerHTML = "<div style=\"font-size:22px\">⚠️</div><div style=\"flex:1\"><div style=\"font-weight:700;font-size:14px\">Erreur generation enrichie</div><div style=\"font-size:12px;opacity:.92\">" + (d.error || "Reessayer") + "</div></div>";
}
})
.catch(function(e) {
btn.disabled = false; btn.textContent = "Oui, enrichir";
console.warn("V173 pdf-upsell err:", e);
});
};
}
} catch(_v173_err) { console.warn("V173 pdf-upsell err:", _v173_err); }
// === END V173 pdf-upsell ===
// V175 image-render · detect SVG/PNG/JPG URL + raw <svg> in response → render visually
try {
if (currentChunkEl && fullResponse) {
// Detect raw SVG block in fullResponse (between code fences or standalone)
var _v175_svgMatch = fullResponse.match(/<svg[^>]*>[\s\S]+?<\/svg>/i);
if (_v175_svgMatch) {
var _v175_wrap = document.createElement("div");
_v175_wrap.style.cssText = "margin-top:12px;padding:16px;background:#fafafa;border:1px solid #e5e7eb;border-radius:8px;text-align:center;overflow:auto";
_v175_wrap.innerHTML = _v175_svgMatch[0];
// Ensure SVG responsive
var _v175_svgEl = _v175_wrap.querySelector("svg");
if (_v175_svgEl) {
_v175_svgEl.style.maxWidth = "100%";
_v175_svgEl.style.height = "auto";
_v175_svgEl.style.maxHeight = "500px";
}
currentChunkEl.parentNode.appendChild(_v175_wrap);
// Also remove the code fence text from display (cleaner)
try {
var _v175_cleanHtml = currentChunkEl.innerHTML.replace(/```(?:html|svg|xml)?\s*<svg[\s\S]+?<\/svg>\s*```/gi, "<em style=\"color:#6b7280;font-size:11px\">[SVG rendered below]</em>");
currentChunkEl.innerHTML = _v175_cleanHtml;
} catch(e2){}
}
// Detect image URL .png .jpg .svg (if not already SVG raw) and render <img>
if (!_v175_svgMatch && finalFileUrl) {
var _v175_imgExt = finalFileUrl.match(/\.(png|jpg|jpeg|svg|webp|gif)(\?|$)/i);
if (_v175_imgExt) {
var _v175_imgWrap = document.createElement("div");
_v175_imgWrap.style.cssText = "margin-top:12px;padding:14px;background:#fafafa;border:1px solid #e5e7eb;border-radius:8px;text-align:center";
_v175_imgWrap.innerHTML = "<img src=\"" + finalFileUrl + "\" alt=\"Generated image\" style=\"max-width:100%;height:auto;max-height:500px;border-radius:6px;box-shadow:0 2px 10px rgba(0,0,0,0.1)\" />";
currentChunkEl.parentNode.appendChild(_v175_imgWrap);
}
}
}
} catch(_v175_err) { console.warn("V175 image-render err:", _v175_err); }
// === END V175 image-render ===
// Format final response with link if present
if (currentChunkEl && finalFileUrl) {
var _u = finalFileUrl; var linkHtml = fullResponse.split(_u).join('<a href="'+_u+'" target="_blank" style="color:#2563eb;font-weight:600">'+finalFileUrl+'</a>');
currentChunkEl.innerHTML = linkHtml;
}
// V168 mermaid-postprocess · si fullResponse contient mermaid code, rendre le SVG
try {
var _v168_mermaidRegex = /```mermaid\s*([\s\S]+?)```|((?:graph|flowchart|sequenceDiagram|classDiagram|stateDiagram|erDiagram|gantt|pie|mindmap)\s+[\s\S]+?)(?=\n\n|$)/i;
var _v168_m = _v168_mermaidRegex.exec(fullResponse);
if (_v168_m && currentChunkEl && window.mermaid) {
var _v168_code = (_v168_m[1] || _v168_m[2] || "").trim();
// Clean LLM wrapping (backticks, quotes)
_v168_code = _v168_code.replace(/^["'`]+|["'`]+$/g, "").trim();
if (_v168_code.length > 10 && /^(graph|flowchart|sequenceDiagram|classDiagram|stateDiagram|erDiagram|gantt|pie|mindmap|journey)/i.test(_v168_code)) {
var _v168_id = "v168-mermaid-" + Date.now();
var _v168_wrapper = document.createElement("div");
_v168_wrapper.style.cssText = "margin-top:12px;padding:16px;background:#fff;border:1px solid #e5e7eb;border-radius:8px";
_v168_wrapper.innerHTML = "<div class=\"mermaid\" id=\"" + _v168_id + "\">" + _v168_code + "</div>";
currentChunkEl.parentNode.appendChild(_v168_wrapper);
setTimeout(function() {
try { window.mermaid.init(undefined, "#" + _v168_id); }
catch(e) { console.warn("V168 mermaid render err:", e); }
}, 50);
}
}
} catch(_v168_err) { console.warn("V168 mermaid-postprocess err:", _v168_err); }
var elapsed = ((performance.now() - startTime) / 1000).toFixed(1);
var footer = document.createElement('div');
footer.style.cssText = 'margin-top:12px;padding-top:10px;border-top:1px solid #eee;display:flex;gap:8px;flex-wrap:wrap;font-size:11px;color:#999';
footer.innerHTML = '<span>⚡ '+elapsed+'s</span><span>provider: '+data.provider+'</span><span>intent: '+data.intent+'</span>';
container.appendChild(footer);
busy = false;
try { var _sb = document.getElementById('sendBtn'); if (_sb) _sb.disabled = false; } catch(e){}
try { var _mi = document.getElementById('msgInput'); if (_mi) { _mi.value=''; _mi.disabled=false; } } catch(e){}
}
}
function read() {
return reader.read().then(function(res) {
if (res.done) { processBuffer(); return; }
buffer += decoder.decode(res.value, {stream:true});
processBuffer();
return read();
});
}
return read();
})
.catch(function(err) {
console.error('[AMBRE-V5] stream error', err);
container.innerHTML += '<div style="color:#e11d48;padding:8px;background:#fee;border-radius:6px;margin-top:10px">Erreur stream: '+err.message+'</div>';
busy = false;
try { var _sb = document.getElementById('sendBtn'); if (_sb) _sb.disabled = false; } catch(e){}
try { var _mi = document.getElementById('msgInput'); if (_mi) _mi.value=''; } catch(e){}
});
return;
}
// ===== END AMBRE-V5-CLAUDE-PATTERN =====
// === AMBRE-V7-PREMIUM-TOOLS 2026-04-21 · QR, TTS, HD Image, YouTube Summary ===
var _tlc = text.toLowerCase().trim();
// -- QR code pattern
var _qr_m = text.match(/(?:qr\s*code?|code\s*qr)\s+(?:pour|de|du|:)?\s*(.+)/i);
if (_qr_m) {
var qrText = _qr_m[1].trim();
fetch('/api/ambre-tool-qr.php', {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({text: qrText, size: 512})})
.then(function(r){ return r.text().then(function(t){ try{return JSON.parse(t);}catch(e){return null;} }); })
.then(function(data){
hideThinking();
var elapsed = ((performance.now()-startTime)/1000).toFixed(1);
var resp = data && data.success ? ('📱 **QR Code** généré pour : ' + qrText + '\n\n![QR](' + data.url + ')\n\n📥 [Télécharger](' + data.url + ')') : '❌ QR échec';
chatHistory.push({role:'assistant', content:resp});
var msgEl = addMsg('assistant', resp, elapsed);
if (msgEl && msgEl.querySelector('.msg-inner')) {
var b = document.createElement('div'); b.className = 'nx-badges';
b.innerHTML = '<span class="nx-badge" style="background:rgba(168,85,247,0.15);color:#A855F7">📱 QR Code</span>';
msgEl.querySelector('.msg-inner').appendChild(b);
}
busy=false; try{var s=document.getElementById("sendBtn");if(s)s.disabled=false;}catch(e){}
try{var m=document.getElementById("msgInput");if(m){m.value="";m.disabled=false;}}catch(e){}
}).catch(function(err){ hideThinking(); addMsg('assistant', '❌ Service temporairement indisponible, réessayez.', '0'); busy=false; try{var s=document.getElementById("sendBtn");if(s)s.disabled=false;}catch(e){} try{var m=document.getElementById("msgInput");if(m){m.value="";m.disabled=false;}}catch(e){} });
return;
}
// -- TTS pattern
var _tts_m = text.match(/^(?:lis\s+|dis\s+|parle\s+|vocalise[rzm]?\s+|lecture\s+audio\s+de?\s*|audio\s+:?\s*)(.+)/i);
var _is_voice = /voix|audio|vocal|prononce|synthes/i.test(text);
if (_tts_m || (_is_voice && text.length > 20)) {
var ttsText = (_tts_m ? _tts_m[1] : text).trim().substring(0, 500);
fetch('/api/ambre-tool-tts.php', {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({text: ttsText, lang: 'fr'})})
.then(function(r){ return r.text().then(function(t){ try{return JSON.parse(t);}catch(e){return null;} }); })
.then(function(data){
hideThinking();
var elapsed = ((performance.now()-startTime)/1000).toFixed(1);
var resp;
if (data && (data.ok || data.success)) {
resp = '🔊 **Audio généré**\n\n<audio controls src="' + data.url + '" style="width:100%;max-width:500px"></audio>\n\n📥 [Télécharger MP3](' + data.url + ') · ' + data.size_kb + ' KB';
} else { resp = '❌ Audio échec'; }
chatHistory.push({role:'assistant', content:resp});
var msgEl = addMsg('assistant', resp, elapsed);
if (msgEl && msgEl.querySelector('.msg-inner')) {
var b = document.createElement('div'); b.className = 'nx-badges';
b.innerHTML = '<span class="nx-badge" style="background:rgba(251,146,60,0.15);color:#FB923C">🔊 Voice</span>';
msgEl.querySelector('.msg-inner').appendChild(b);
}
busy=false; try{var s=document.getElementById("sendBtn");if(s)s.disabled=false;}catch(e){}
try{var m=document.getElementById("msgInput");if(m){m.value="";m.disabled=false;}}catch(e){}
}).catch(function(err){ hideThinking(); addMsg('assistant', '❌ Service temporairement indisponible, réessayez.', '0'); busy=false; try{var s=document.getElementById("sendBtn");if(s)s.disabled=false;}catch(e){} try{var m=document.getElementById("msgInput");if(m){m.value="";m.disabled=false;}}catch(e){} });
return;
}
// -- HD image / Qualité image
var _hd_m = text.match(/(?:image\s+(?:hd|4k|haute\s+d[eé]finition|qualit[eé]|premium|pro)|upscal[ez]r?)\s+(?:de|pour|du|:)?\s*(.+)/i);
if (_hd_m) {
var hdPrompt = _hd_m[1].trim();
fetch('/api/ambre-tool-image-upscale.php', {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({prompt: hdPrompt})})
.then(function(r){ return r.text().then(function(t){ try{return JSON.parse(t);}catch(e){return null;} }); })
.then(function(data){
hideThinking();
var elapsed = ((performance.now()-startTime)/1000).toFixed(1);
var resp;
if (data && (data.ok || data.success)) {
resp = '🎨 **Image HD 2048×2048** : ' + hdPrompt + '\n\n![HD](' + data.url + ')\n\n📥 [Télécharger HD](' + data.url + ') · ' + data.size_kb + ' KB · ' + data.elapsed_ms + 'ms';
} else { resp = '❌ Image HD échec'; }
chatHistory.push({role:'assistant', content:resp});
var msgEl = addMsg('assistant', resp, elapsed);
if (msgEl && msgEl.querySelector('.msg-inner')) {
var b = document.createElement('div'); b.className = 'nx-badges';
b.innerHTML = '<span class="nx-badge" style="background:rgba(236,72,153,0.15);color:#EC4899">🎨 HD 4K</span>';
msgEl.querySelector('.msg-inner').appendChild(b);
}
busy=false; try{var s=document.getElementById("sendBtn");if(s)s.disabled=false;}catch(e){}
try{var m=document.getElementById("msgInput");if(m){m.value="";m.disabled=false;}}catch(e){}
}).catch(function(err){ hideThinking(); addMsg('assistant', '❌ Service temporairement indisponible, réessayez.', '0'); busy=false; try{var s=document.getElementById("sendBtn");if(s)s.disabled=false;}catch(e){} try{var m=document.getElementById("msgInput");if(m){m.value="";m.disabled=false;}}catch(e){} });
return;
}
// -- YouTube summary
var _yt_m = text.match(/(https?:\/\/(?:www\.)?(?:youtube\.com\/watch\?v=|youtu\.be\/)[\w-]{11}[^\s]*)/);
if (_yt_m) {
var ytUrl = _yt_m[1];
fetch('/api/ambre-tool-youtube-summary.php', {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({url: ytUrl})})
.then(function(r){ return r.text().then(function(t){ try{return JSON.parse(t);}catch(e){return null;} }); })
.then(function(data){
hideThinking();
var elapsed = ((performance.now()-startTime)/1000).toFixed(1);
var resp = data && data.summary ? ('🎥 **Résumé vidéo** : ' + ytUrl + '\n\n' + data.summary) : ('❌ Vidéo non résumable (transcription indisponible)');
chatHistory.push({role:'assistant', content:resp});
var msgEl = addMsg('assistant', resp, elapsed);
if (msgEl && msgEl.querySelector('.msg-inner')) {
var b = document.createElement('div'); b.className = 'nx-badges';
b.innerHTML = '<span class="nx-badge" style="background:rgba(239,68,68,0.15);color:#EF4444">🎥 Video</span>';
msgEl.querySelector('.msg-inner').appendChild(b);
}
busy=false; try{var s=document.getElementById("sendBtn");if(s)s.disabled=false;}catch(e){}
try{var m=document.getElementById("msgInput");if(m){m.value="";m.disabled=false;}}catch(e){}
}).catch(function(err){ hideThinking(); addMsg('assistant', '❌ Service temporairement indisponible, réessayez.', '0'); busy=false; try{var s=document.getElementById("sendBtn");if(s)s.disabled=false;}catch(e){} try{var m=document.getElementById("msgInput");if(m){m.value="";m.disabled=false;}}catch(e){} });
return;
}
// === END AMBRE-V7-PREMIUM-TOOLS ===
// === AMBRE-V6-TOOLS 2026-04-21 · smart tool routing: real image, web search, URL summary, calc ===
var _ambre_text_lc = text.toLowerCase().trim();
// -- Calc pattern: "calcule X", "combien vaut X", raw math expression
var _calc_pat = /^(?:calcule[zr]?\s+|combien\s+(?:font|vaut|fait)\s+)?([\-+0-9.,()*\/\s]{5,100})\s*[=?]?$/;
var _calc_keyword = /^calcul|^combien\s|(?:calculer|calcule)\s+/i.test(text);
if (_calc_keyword || /^[\-+0-9.,()*\/\s]{8,100}[=?]?$/.test(text)) {
var expr = text.replace(/^(?:calcule[zr]?|combien\s+(?:font|vaut|fait))\s+/i, '').replace(/[=?]+\s*$/, '').trim();
fetch('/api/ambre-tool-calc.php', {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({expression: expr})})
.then(function(r){ return r.text().then(function(t){ try{return JSON.parse(t);}catch(e){return null;} }); })
.then(function(data){
hideThinking();
var elapsed = ((performance.now() - startTime)/1000).toFixed(1);
var resp = data && !data.error ? ('🧮 **' + expr + '** = **' + data.result + '**') : ('❌ Expression invalide.');
chatHistory.push({role:'assistant', content:resp});
var msgEl = addMsg('assistant', resp, elapsed);
if (msgEl && msgEl.querySelector('.msg-inner')) {
var b = document.createElement('div'); b.className = 'nx-badges';
b.innerHTML = '<span class="nx-badge" style="background:rgba(59,130,246,0.15);color:#3B82F6">🧮 Calc</span>';
msgEl.querySelector('.msg-inner').appendChild(b);
}
busy = false; try{var s=document.getElementById("sendBtn");if(s)s.disabled=false;}catch(e){}
try{var m=document.getElementById("msgInput");if(m){m.value="";m.disabled=false;}}catch(e){}
})
.catch(function(err){ hideThinking(); addMsg('assistant', '❌ Service temporairement indisponible.', '0'); busy=false; try{var s=document.getElementById("sendBtn");if(s)s.disabled=false;}catch(e){} });
return;
}
// -- Image pattern: "cree/genere/fais une image/photo de X"
var _img_pat = /^(?:cr[eé]e[zr]?|g[eé]n[eè]re[zr]?|fais|dessine[rzm]?|imagine[rzm]?)\s+(?:moi\s+|une?\s+|un\s+)?(?:image|photo|illustration|dessin|logo|visuel)\s+(?:de\s+|d\'|sur\s+|du\s+|pour\s+)?(.{3,300})/i;
var _img_m = text.match(_img_pat);
if (_img_m) {
var prompt = _img_m[1].trim();
fetch('/api/ambre-tool-image.php', {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({prompt: prompt})})
.then(function(r){ return r.text().then(function(t){ try{return JSON.parse(t);}catch(e){return null;} }); })
.then(function(data){
hideThinking();
var elapsed = ((performance.now()-startTime)/1000).toFixed(1);
var resp;
if (data && (data.ok || data.success)) {
resp = '🎨 **Image générée** : ' + prompt + '\n\n![image](' + data.url + ')\n\n📥 [Télécharger](' + data.url + ') · ' + data.size_kb + ' KB · ' + data.elapsed_ms + 'ms';
} else {
resp = '❌ Génération image échouée. Réessayez avec un prompt plus descriptif.';
}
chatHistory.push({role:'assistant', content:resp});
var msgEl = addMsg('assistant', resp, elapsed);
if (msgEl && msgEl.querySelector('.msg-inner')) {
var b = document.createElement('div'); b.className = 'nx-badges';
b.innerHTML = '<span class="nx-badge" style="background:rgba(236,72,153,0.15);color:#EC4899">🎨 Image</span>';
msgEl.querySelector('.msg-inner').appendChild(b);
}
busy = false; try{var s=document.getElementById("sendBtn");if(s)s.disabled=false;}catch(e){}
try{var m=document.getElementById("msgInput");if(m){m.value="";m.disabled=false;}}catch(e){}
})
.catch(function(err){ hideThinking(); addMsg('assistant', '❌ Service temporairement indisponible.', '0'); busy=false; try{var s=document.getElementById("sendBtn");if(s)s.disabled=false;}catch(e){} });
return;
}
// -- URL summary pattern: message contains a URL
var _url_pat = /\b(https?:\/\/[^\s]+)/;
var _url_m = text.match(_url_pat);
var _url_keyword = /r[eé]sum[eé]|summari[sz]e|analyse|que\s+dit/i.test(text);
if (_url_m && _url_keyword) {
var targetUrl = _url_m[1];
fetch('/api/ambre-tool-url-summary.php', {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({url: targetUrl})})
.then(function(r){ return r.text().then(function(t){ try{return JSON.parse(t);}catch(e){return null;} }); })
.then(function(data){
hideThinking();
var elapsed = ((performance.now()-startTime)/1000).toFixed(1);
var resp = data && data.summary ? ('📄 **Résumé** : ' + targetUrl + '\n\n' + data.summary) : ('❌ Impossible de résumer cette URL.');
chatHistory.push({role:'assistant', content:resp});
var msgEl = addMsg('assistant', resp, elapsed);
if (msgEl && msgEl.querySelector('.msg-inner')) {
var b = document.createElement('div'); b.className = 'nx-badges';
b.innerHTML = '<span class="nx-badge" style="background:rgba(99,102,241,0.15);color:#6366F1">📄 URL</span>';
msgEl.querySelector('.msg-inner').appendChild(b);
}
busy = false; try{var s=document.getElementById("sendBtn");if(s)s.disabled=false;}catch(e){}
try{var m=document.getElementById("msgInput");if(m){m.value="";m.disabled=false;}}catch(e){}
})
.catch(function(err){ hideThinking(); addMsg('assistant', '❌ Service temporairement indisponible.', '0'); busy=false; try{var s=document.getElementById("sendBtn");if(s)s.disabled=false;}catch(e){} });
return;
}
// -- Web search: "cherche", "recherche", "actualités", "news", "search", "quoi de neuf", "latest"
var _search_pat = /^(?:cherche[rzm]?|recherche[rzm]?|search\s+|actualit[eé]s?|news|quoi\s+de\s+neuf|latest|derni[eè]res?\s+news)/i;
if (_search_pat.test(text) && text.length > 8) {
var searchQ = text.replace(/^(?:cherche[rzm]?|recherche[rzm]?|search\s+|actualit[eé]s?\s+(?:sur|de)?|news\s+(?:sur|de|about)?|quoi\s+de\s+neuf\s+(?:sur|de)?|latest\s+|derni[eè]res?\s+news\s+(?:sur|de)?)\s*/i, '').trim();
if (searchQ.length < 3) searchQ = text;
fetch('/api/ambre-tool-web-search.php', {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({query: searchQ})})
.then(function(r){ return r.text().then(function(t){ try{return JSON.parse(t);}catch(e){return null;} }); })
.then(function(data){
hideThinking();
var elapsed = ((performance.now()-startTime)/1000).toFixed(1);
var resp;
if (data && data.answer) {
resp = '🔍 **Recherche** : ' + searchQ + '\n\n' + data.answer;
if (data.sources && data.sources.length) {
resp += '\n\n**Sources** :\n' + data.sources.map(function(u){return '• ' + u;}).join('\n');
}
} else { resp = '❌ Aucun résultat trouvé.'; }
chatHistory.push({role:'assistant', content:resp});
var msgEl = addMsg('assistant', resp, elapsed);
if (msgEl && msgEl.querySelector('.msg-inner')) {
var b = document.createElement('div'); b.className = 'nx-badges';
b.innerHTML = '<span class="nx-badge" style="background:rgba(34,197,94,0.15);color:#22C55E">🔍 Search</span>';
msgEl.querySelector('.msg-inner').appendChild(b);
}
busy = false; try{var s=document.getElementById("sendBtn");if(s)s.disabled=false;}catch(e){}
try{var m=document.getElementById("msgInput");if(m){m.value="";m.disabled=false;}}catch(e){}
})
.catch(function(err){ hideThinking(); addMsg('assistant', '❌ Service temporairement indisponible.', '0'); busy=false; try{var s=document.getElementById("sendBtn");if(s)s.disabled=false;}catch(e){} });
return;
}
// === END AMBRE-V6-TOOLS ===
// === AMBRE-V0-PRIORITY-ROUTER 2026-04-22 · route simple chat to session-chat (semaphore-protected) ===
// Avoids /api/sovereign 503 overload. Session-chat uses cascade :4000 directly with throttle.
// Applies to: short messages, greetings, non-file-gen, non-tool patterns
var _is_gen_cmd = /pdf|rapport|docx?|word|excel|xlsx?|pptx?|powerpoint|sch[eé]ma|diagramme|mermaid|image|photo|dessin|qr\s*code|tts|lis[ -]|calcule|combien|code|traduis|cherche|recherche|actualit[eé]|search|news|latest|youtube|r[eé]sume|summari|compar[ez]?|analyse|multi.?agent|parall[eè]le|360|bilan/i.test(text);
var _is_long = text.length > 500;
var _has_url = /https?:\/\//.test(text);
if (!_is_gen_cmd && !_is_long && !_has_url && typeof window._ambre_session_id !== 'undefined') {
var _sid = window._ambre_session_id;
fetch('/api/ambre-session-chat.php', {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify({message: text, session_id: _sid})
})
.then(function(r){ return r.text().then(function(t){ try{return JSON.parse(t);}catch(e){return null;} }); })
.then(function(data){
hideThinking();
var elapsed = ((performance.now()-startTime)/1000).toFixed(1);
var resp = (data && data.response) ? data.response : "Désolé, je n'ai pas pu traiter votre demande. Réessayez.";
chatHistory.push({role:'assistant', content:resp});
var msgEl = addMsg('assistant', resp, elapsed);
busy=false;
try{var s=document.getElementById("sendBtn");if(s)s.disabled=false;}catch(e){}
try{var m=document.getElementById("msgInput");if(m){m.value="";m.disabled=false;}}catch(e){}
})
.catch(function(err){
hideThinking();
addMsg('assistant', '⚠️ Service momentanément lent, réessayez.', '0');
busy=false;
try{var s=document.getElementById("sendBtn");if(s)s.disabled=false;}catch(e){}
try{var m=document.getElementById("msgInput");if(m){m.value="";m.disabled=false;}}catch(e){}
});
return;
}
// === END AMBRE-V0-PRIORITY-ROUTER ===
// === AMBRE-V11-MULTIAGENT 2026-04-22 · wave-255 · Plan → Execute N agents parallel → Reconcile ===
// Triggers : "analyse", "complet", "rapport complet", "compare X avec Y", "multi agent", "plusieurs", "en parallele"
var _multiagent_pat = /(?:analyse\s+compl[eè]te|rapport\s+complet|bilan\s+complet|compare[rz]?\s+.{3,}\s+(?:avec|vs|contre|et)\s+|multi[- ]?agent|plusieurs\s+angles|en\s+parall[eè]le|synth[eè]se\s+compl[eè]te|analyse\s+360|\ballonsy\b|\bdispatch\b)/i;
if (_multiagent_pat.test(text) && text.length > 40) {
if (typeof showThinking === "function") showThinking();
busy = true;
try{var sb=document.getElementById("sendBtn");if(sb)sb.disabled=true;}catch(e){}
console.log("[V11-MULTIAGENT] triggered for text:", text.substring(0,80));
var _ma_start = performance.now();
var _ma_ctrl = new AbortController();
var _ma_timeout = setTimeout(function(){ _ma_ctrl.abort(); }, 120000);
fetch("/api/ambre-multiagent-parallel.php", {
method: "POST",
headers: {"Content-Type":"application/json"},
body: JSON.stringify({goal: text, max_agents: 5}),
signal: _ma_ctrl.signal
})
.then(function(r){ clearTimeout(_ma_timeout); console.log("[V11] response status", r.status); return r; })
.then(function(r){ return r.json(); })
.then(function(data){
if (typeof hideThinking === "function") hideThinking();
busy = false;
try{var sb=document.getElementById("sendBtn");if(sb)sb.disabled=false;}catch(e){}
try{var mi=document.getElementById("msgInput");if(mi){mi.value="";mi.disabled=false;}}catch(e){}
if (!data || !data.ok) {
addMsg("assistant", "❌ Multi-agent erreur: " + ((data && data.error) || "reessayez"), "0");
return;
}
var el = ((performance.now() - _ma_start) / 1000).toFixed(1);
// Build rich HTML response
var badges = "<div style=\"display:flex;gap:6px;flex-wrap:wrap;margin:8px 0\">" +
"<span class=\"nx-badge\" style=\"background:rgba(99,102,241,.15);color:#6366f1\">🧠 Multi-Agent</span>" +
"<span class=\"nx-badge\" style=\"background:rgba(16,185,129,.15);color:#10b981\">" + data.agents_count + " agents ∥</span>" +
"<span class=\"nx-badge\" style=\"background:rgba(245,158,11,.15);color:#f59e0b\">⚡ " + data.parallel_speedup + "x speedup</span>" +
"<span class=\"nx-badge\" style=\"background:rgba(139,92,246,.15);color:#8b5cf6\">" + data.total_ms + "ms</span>" +
"</div>";
// Plan
var planHtml = "<div style=\"margin:10px 0;padding:12px;background:#eef2ff;border-radius:10px;border-left:3px solid #6366f1\">" +
"<div style=\"font-weight:600;color:#4338ca;font-size:13px;margin-bottom:6px\">📋 Plan · " + (data.plan.objective || "") + "</div>" +
"<div style=\"font-size:12px;color:#4b5563\">" + data.plan.agents.length + " agents dispatchés en parallèle</div></div>";
// Agents list
var agentsHtml = "<div style=\"margin:10px 0\"><div style=\"font-weight:600;color:#1a1f3a;font-size:13px;margin-bottom:8px\">🤖 Agents · exécution parallèle</div>";
data.results.forEach(function(r, i){
var icon = {"pdf_premium":"📄","mermaid":"📊","web_search":"🔍","calc":"🧮","kb_search":"📚","none":"💭"}[r.tool] || "⚙️";
agentsHtml += "<div style=\"margin:6px 0;padding:10px;background:#fafafa;border-radius:8px;border:1px solid #e5e7eb\">" +
"<div style=\"font-size:12px;color:#6366f1;font-weight:600;margin-bottom:4px\">" + icon + " " + r.agent + " · " + r.tool + " · " + r.elapsed_ms + "ms</div>" +
"<div style=\"font-size:12px;color:#64748b;line-height:1.4\">" + (r.task || "") + "</div>" +
"<div style=\"font-size:11px;color:#94a3b8;margin-top:4px\">" + (r.summary || "").replace(/</g,"&lt;").substring(0, 250) + "</div>" +
"</div>";
});
agentsHtml += "</div>";
// Reconciled
var synthHtml = "<div style=\"margin:12px 0;padding:14px;background:linear-gradient(135deg,#f0f9ff 0%,#eef2ff 100%);border-radius:12px;border-left:3px solid #10b981\">" +
"<div style=\"font-weight:600;color:#065f46;font-size:13px;margin-bottom:8px\">✅ Synthèse consolidée</div>" +
"<div style=\"font-size:13px;color:#1a1f3a;line-height:1.6;white-space:pre-wrap\">" + (data.reconciled || "").replace(/</g,"&lt;") + "</div>" +
"</div>";
var el_resp = addMsg("assistant", "Multi-agent", el);
var bubble = el_resp ? el_resp.querySelector(".bubble") : null;
if (bubble) bubble.innerHTML = badges + planHtml + agentsHtml + synthHtml;
})
.catch(function(err){
if (typeof hideThinking === "function") hideThinking();
busy = false;
try{var sb=document.getElementById("sendBtn");if(sb)sb.disabled=false;}catch(e){}
addMsg("assistant", "❌ Multi-agent service indisponible", "0");
});
return;
}
// === END AMBRE-V11-MULTIAGENT ===
// === AMBRE-V10-MERMAID 2026-04-22 · Mermaid RAG + inline SVG + artifact panel ===
var _mermaid_intent_pat = /(?:mermaid|sch[eé]ma|diagramme|flowchart|sequence\s+diagram|gantt\s+chart)/i;
if (_mermaid_intent_pat.test(text)) {
if (typeof showThinking === "function") showThinking();
busy = true;
try{var sb=document.getElementById("sendBtn");if(sb)sb.disabled=true;}catch(e){}
var _fetch = (typeof window.__ambreFetch === "function") ? window.__ambreFetch : fetch;
_fetch("/api/ambre-tool-mermaid.php", {
method: "POST",
headers: {"Content-Type":"application/json"},
body: JSON.stringify({topic: text})
})
.then(function(r){ return r.text().then(function(t){ try{return JSON.parse(t);}catch(e){return null;} }); })
.then(function(data){
if (typeof hideThinking === "function") hideThinking();
busy = false;
try{var sb=document.getElementById("sendBtn");if(sb)sb.disabled=false;}catch(e){}
try{var mi=document.getElementById("msgInput");if(mi){mi.value="";mi.disabled=false;}}catch(e){}
if (!data || !data.ok) {
addMsg("assistant", "❌ Erreur Mermaid. " + ((data && data.error) || "Réessayez."), "0");
return;
}
var mcode = data.mermaid_code;
var topic = data.topic || text;
var src = data.source || "unknown";
var kind = data.kind || "flowchart";
var srcBadge = (src === "kb_reused") ?
"<span class=\"nx-badge\" style=\"background:rgba(16,185,129,.15);color:#10b981\">♻️ KB Reused (" + (data.use_count || 0) + " uses)</span>" :
"<span class=\"nx-badge\" style=\"background:rgba(99,102,241,.15);color:#6366f1\">🧠 LLM Generated</span>";
var kindBadge = "<span class=\"nx-badge\" style=\"background:rgba(139,92,246,.15);color:#8b5cf6\">" + kind + "</span>";
var elapsedBadge = "<span class=\"nx-badge\" style=\"background:rgba(245,158,11,.15);color:#f59e0b\">" + (data.elapsed_ms || 0) + "ms</span>";
var badges = "<div style=\"display:flex;gap:6px;flex-wrap:wrap;margin:8px 0\">" + srcBadge + kindBadge + elapsedBadge + "</div>";
var uniqId = "mmd-" + Date.now();
var inlineBlock = "<div style=\"margin:12px 0;padding:14px;background:#fafafa;border:1px solid #e5e7eb;border-radius:12px\">" +
"<div style=\"font-weight:600;font-size:13px;color:#6b7280;margin-bottom:10px\">📊 " + topic + "</div>" +
"<div class=\"mermaid\" id=\"" + uniqId + "\" style=\"text-align:center;min-height:200px;font-size:14px;color:#333;background:#fff;padding:12px\">" + mcode + "</div>" +
"<details style=\"margin-top:10px\"><summary style=\"cursor:pointer;font-size:11px;color:#94a3b8\">📝 Voir le code</summary>" +
"<pre style=\"background:#1a1a2e;color:#e6edf3;padding:10px;border-radius:8px;font-size:11px;margin-top:8px;overflow-x:auto\">" +
mcode.replace(/</g,"&lt;").replace(/>/g,"&gt;") + "</pre></details>" +
"</div>";
// Direct innerHTML injection (bypass formatMd HTML escape)
var _el = addMsg("assistant", "Diagramme Mermaid", (data.elapsed_ms/1000).toFixed(2));
var _bubble = _el ? _el.querySelector(".bubble") : null;
if (_bubble) _bubble.innerHTML = badges + inlineBlock;
setTimeout(function(){
try {
var target = document.getElementById(uniqId);
if (!target) return;
// Sanitize mermaid code (strip accents, fix common LLM mistakes)
var clean = mcode
.replace(/[àâä]/g, "a").replace(/[éèêë]/g, "e").replace(/[îï]/g, "i")
.replace(/[ôö]/g, "o").replace(/[ùûü]/g, "u").replace(/ç/g, "c")
.replace(/[ÀÂÄ]/g, "A").replace(/[ÉÈÊË]/g, "E").replace(/[ÎÏ]/g, "I")
.replace(/[ÔÖ]/g, "O").replace(/[ÙÛÜ]/g, "U").replace(/Ç/g, "C")
.trim();
// Use mermaid.render for SVG direct return (bypass font-size:0 CSS issue)
if (window.mermaid && typeof window.mermaid.render === "function") {
window.mermaid.render("svg-" + uniqId, clean).then(function(result){
if (result && result.svg) {
target.innerHTML = result.svg;
target.className = "mermaid-rendered";
target.removeAttribute("data-processed");
// Force SVG to reasonable size
var svg = target.querySelector("svg");
if (svg) {
svg.style.maxWidth = "100%";
svg.style.height = "auto";
svg.style.minHeight = "180px";
svg.removeAttribute("width");
svg.style.width = "100%";
}
}
}).catch(function(err){
console.warn("mermaid render err", err);
target.innerHTML = "<pre style=\"font-size:12px;padding:10px;background:#f5f5f5;border-radius:6px\">" + clean.replace(/</g,"&lt;") + "</pre>";
});
} else if (window.mermaid && typeof window.mermaid.run === "function") {
target.className = "mermaid";
target.textContent = clean;
window.mermaid.run({ nodes: [target] });
}
} catch(e) { console.warn("mermaid render fail", e); }
}, 300);
})
.catch(function(err){
if (typeof hideThinking === "function") hideThinking();
busy = false;
try{var sb=document.getElementById("sendBtn");if(sb)sb.disabled=false;}catch(e){}
addMsg("assistant", "❌ Service Mermaid indisponible.", "0");
});
return;
}
// === END AMBRE-V10-MERMAID ===
// === AMBRE-V9-PDF-PREMIUM 2026-04-21 · PDF qualité premium avec graphiques + Chart.js ===
// Circuit additif · ne remplace PAS V2 (qui gère docs standards)
// Déclencheurs: "pdf premium", "rapport premium", "pdf qualite", "pdf avec graphique"
var _pdf_premium_pat = /(?:veux|besoin|demande|fais|cree|g[eé]n[eé]re|produi[st]|export|donne|realise|create|make|generate|want|need)\s+.{0,40}?\bpdf\b|\bpdf\b\s+.{0,40}?(?:premium|qualit[eé]|pro|professionnel|avec|chart|graphique|compar|tableau|compl|hd)|\b(?:rapport|comparaison|comparer|compare)\b.{0,50}\bpdf\b|\bpdf\b.{0,50}\b(?:rapport|comparaison|comparer|compare)\b|^\s*pdf\s+/i;
if (_pdf_premium_pat.test(text)) {
var _topic = text.replace(/^(?:cr[eé]e[zr]?|g[eé]n[eè]re[zr]?|fais|fait|produi[st])\s+(?:moi\s+)?(?:un\s+)?(?:rapport|pdf)\s+(?:premium|pro|complet|qualit[eé]|hd|avec\s+graphique)?\s*(?:sur|pour|de|du|:|à\s+propos\s+de)?\s*/i, '').trim();
if (_topic.length < 5) _topic = text;
fetch('/api/ambre-tool-pdf-premium.php', {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify({topic: _topic})
})
.then(function(r){ return r.text().then(function(t){ try{return JSON.parse(t);}catch(e){return null;} }); })
.then(function(data){
hideThinking();
var elapsed = ((performance.now()-startTime)/1000).toFixed(1);
var resp;
if (data && (data.ok || data.success)) {
resp = '📊 **PDF Premium généré** : ' + data.title + '\n\n' +
'- **' + data.sections + ' sections** avec contenu détaillé\n' +
'- **' + data.kpis + ' KPI** visualisés\n' +
'- **Graphique interactif** (' + (data.has_chart ? 'Chart.js intégré' : 'aucun') + ')\n' +
'- ~**' + data.pages + ' pages** · ' + data.size_kb + ' KB\n\n' +
'📥 [**Télécharger PDF**](' + data.url + ')\n' +
'🖼 [Prévisualiser HTML](' + data.html_preview + ')';
} else {
resp = '❌ Génération PDF Premium échouée. ' + (data && data.error ? data.error : 'Réessayez.');
}
chatHistory.push({role:'assistant', content:resp});
var msgEl = addMsg('assistant', resp, elapsed);
if (msgEl && msgEl.querySelector('.msg-inner')) {
var b = document.createElement('div'); b.className = 'nx-badges';
b.innerHTML = '<span class="nx-badge" style="background:rgba(124,58,237,0.15);color:#7c3aed">📊 PDF Premium</span>' +
'<span class="nx-badge" style="background:rgba(16,185,129,0.15);color:#10b981">📈 Chart.js</span>';
msgEl.querySelector('.msg-inner').appendChild(b);
}
// Open artifact panel with HTML preview
if (data && data.html_preview && typeof openPreview === 'function') {
try { openPreview(data.html_preview, 'pdf'); } catch(e){}
}
busy=false; try{var s=document.getElementById("sendBtn");if(s)s.disabled=false;}catch(e){}
try{var m=document.getElementById("msgInput");if(m){m.value="";m.disabled=false;}}catch(e){}
})
.catch(function(err){
hideThinking();
addMsg('assistant', '❌ PDF Premium temporairement indisponible, réessayez.', '0');
busy=false;
try{var s=document.getElementById("sendBtn");if(s)s.disabled=false;}catch(e){}
});
return;
}
// === END AMBRE-V9-PDF-PREMIUM ===
// === AMBRE-V2-GEN-ROUTER 2026-04-21 · intercept file generation patterns ===
// Doctrine: route gen/code/translate patterns → wevia-master-api.php (real handler)
// other queries continue to sovereign. No regression, pure additive.
var _ambre_gen_pat = /g[eéèê]n[eéèê]re?\s+(?:un|une|des|le|la)?\s*(pdf|pptx?|powerpoint|docx?|word|excel|xlsx?|pr[eéèê]sentation|document|tableau|sch[eéèê]ma|mermaid|diagramme|image)|ecri[srt]?\s+(?:le|du|un)?\s*code|traduis?\s+(?:ce\s+texte|en)?\s*(anglais|francais|espagnol|allemand|italien|portugais|arabe|chinois|japonais|english|spanish|french|german|italian|portuguese|arabic|chinese|japanese)/i;
if (_ambre_gen_pat.test(text)) {
// Route to wevia-master-api.php (my handler pipeline)
fetch('/api/wevia-master-api.php', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({message: text, session_id: convId || ('wv-' + Date.now())})
})
.then(function(r) {
// AMBRE-V4-ROBUST: safe JSON parsing, tolerate empty/HTML responses
return r.text().then(function(txt) {
if (!txt || !txt.trim()) return { response: 'Pas de reponse.', provider: 'empty', intent: 'empty' };
try { return JSON.parse(txt); }
catch (e) {
console.warn('[Ambre] JSON parse failed, using text fallback');
return { response: txt.substring(0, 2000), provider: 'text-fallback', intent: 'raw' };
}
});
})
.then(function(data) {
hideThinking();
var elapsed = ((performance.now() - startTime) / 1000).toFixed(1);
var response = data.response || data.content || data.message || data.result || 'Pas de reponse.';
if (data.conversation_id) convId = data.conversation_id;
chatHistory.push({role: 'assistant', content: response});
var msgEl = addMsg('assistant', response, elapsed);
var badges = [];
if(data.provider) badges.push('<span class="nx-badge nx-prov">' + (data.provider||'').substring(0,20) + '</span>');
if(data.intent) badges.push('<span class="nx-badge" style="background:rgba(124,107,240,0.15);color:#7C6BF0">' + data.intent + '</span>');
badges.push('<span class="nx-badge" style="background:rgba(16,185,129,0.15);color:#10B981">⚡ Gen</span>');
if(badges.length > 0 && msgEl && msgEl.querySelector('.msg-inner')) {
var badgeEl = document.createElement('div');
badgeEl.className = 'nx-badges';
badgeEl.innerHTML = badges.join('');
msgEl.querySelector('.msg-inner').appendChild(badgeEl);
}
// Detect code/artifact blocks in response
var codeMatch = response.match(/```(\w+)?\n([\s\S]*?)```/);
if (codeMatch && window._artifactCode !== undefined) {
window._artifactCode = codeMatch[2];
}
setTimeout(function(){ if(typeof generateFollowups==='function') generateFollowups(window._lastQuery, response); }, 500);
// __ambre_busy_reset: CRITICAL — reset busy flag to allow next send
busy = false;
try { var _sb = document.getElementById('sendBtn'); if (_sb) _sb.disabled = false; } catch(e){}
try { var _mi = document.getElementById('msgInput'); if (_mi) { _mi.value=''; _mi.disabled=false; } } catch(e){}
})
.catch(function(err) {
hideThinking();
addMsg('assistant', '❌ Erreur generation: ' + err.message, '0');
// __ambre_busy_reset: reset busy on error too
busy = false;
try { var _sb2 = document.getElementById('sendBtn'); if (_sb2) _sb2.disabled = false; } catch(e){}
try { var _mi2 = document.getElementById('msgInput'); if (_mi2) { _mi2.value=''; _mi2.disabled=false; } } catch(e){}
});
return;
}
// === END AMBRE-V2-GEN-ROUTER ===
// === AMBRE-V5-MEMORY-v2 2026-04-21 · session-aware conversational routing ===
// Doctrine: every non-gen non-ping message → ambre-session-chat.php for memory + empathy
// V2 fix: always hideThinking, outer try/catch, clean state reset
(function() {
var _low = text.toLowerCase();
var _skip = ['ping','pong','aide','help','bilan complet','status','diag'].some(function(k){return _low === k || _low.startsWith(k+' ');});
if (_skip || text.length <= 3) return; // skip short commands
// persistent session id
if (!window._ambre_session_id) {
var stored = null;
try { stored = sessionStorage.getItem('ambre_sid'); } catch(e){}
if (!stored) { stored = 'wv-' + Date.now() + '-' + Math.random().toString(36).substring(2,8); try { sessionStorage.setItem('ambre_sid', stored); } catch(e){} }
window._ambre_session_id = stored;
}
window._ambre_v5_intercepted = true;
fetch('/api/ambre-session-chat.php', {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify({message: text, session_id: window._ambre_session_id})
})
.then(function(r) {
return r.text().then(function(t) {
if (!t || !t.trim()) return { response: null };
try { return JSON.parse(t); } catch(e) { return { response: null, raw: t }; }
});
})
.then(function(data) {
try { hideThinking(); } catch(e){}
if (!data || !data.response) {
// fallback message
try { addMsg('assistant', '⚠️ Mémoire indisponible, réessayez.', '0'); } catch(e){}
} else {
var elapsed = ((performance.now() - startTime) / 1000).toFixed(1);
chatHistory.push({role:'assistant', content: data.response});
var msgEl = addMsg('assistant', data.response, elapsed);
var badges = [];
badges.push('<span class="nx-badge nx-prov">ambre-memory</span>');
badges.push('<span class="nx-badge" style="background:rgba(16,185,129,0.15);color:#10B981">🧠 '+(data.turns_in_memory||0)+' tours</span>');
if (data.history_used > 0) badges.push('<span class="nx-badge" style="background:rgba(245,158,11,0.15);color:#F59E0B">📚 ctx:'+data.history_used+'</span>');
if (badges.length && msgEl && msgEl.querySelector('.msg-inner')) {
var badgeEl = document.createElement('div');
badgeEl.className = 'nx-badges';
badgeEl.innerHTML = badges.join('');
msgEl.querySelector('.msg-inner').appendChild(badgeEl);
}
}
busy = false;
try { var _sb = document.getElementById('sendBtn'); if (_sb) _sb.disabled = false; } catch(e){}
try { var _mi = document.getElementById('msgInput'); if (_mi) { _mi.value=''; _mi.disabled=false; _mi.focus(); } } catch(e){}
})
.catch(function(err) {
console.warn('[Ambre V5-v2]', err);
try { hideThinking(); } catch(e){}
try { addMsg('assistant', '⚠️ Erreur réseau mémoire. Réessayez.', '0'); } catch(e){}
busy = false;
try { var _sb = document.getElementById('sendBtn'); if (_sb) _sb.disabled = false; } catch(e){}
});
})();
if (window._ambre_v5_intercepted) { window._ambre_v5_intercepted = false; return; }
// === END AMBRE-V5-MEMORY ===
// Fast mode: direct to fast endpoint (2s response)
if (effectiveMode === 'fast' && !pendingFile) {
fetch('/api/sovereign/v1/chat/completions', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({model:"auto",messages:[{role:"system",content:"Tu es WEVIA, IA souveraine de WEVAL Consulting Casablanca. Francais, professionnel."}].concat(chatHistory.slice(-6)).concat([{role:"user",content:text}]),max_tokens:2000}),
})
.then(function(r) { return r.json(); })
.then(function(data) {
// === ASYNC POLLING: if task_id returned, poll for result ===
if (data.task_id && data.status === 'queued') {
var pollCount = 0;
var pollMax = 60;
var pollInterval = setInterval(function() {
pollCount++;
if (pollCount > pollMax) {
clearInterval(pollInterval);
hideThinking();
addMsg('assistant', 'Timeout - le serveur met trop de temps.', '30');
return;
}
fetch('/api/wevia-chat.php?poll=' + data.task_id)
.then(function(pr) { return pr.json(); })
.then(function(pd) {
if (pd.status === 'done' || pd.response) {
clearInterval(pollInterval);
hideThinking();
var elapsed2 = ((performance.now() - startTime) / 1000).toFixed(1);
var resp2 = pd.response || 'Pas de reponse.';
resp2 = resp2.replace(/\[(PDF|SVG|HTML|EXEC|SENTINEL)[^\]]*\]/gi, '');
chatHistory.push({role: 'assistant', content: resp2});
var msgEl2 = addMsg('assistant', resp2, elapsed2);
var badges2 = [];
if(pd.provider) badges2.push('<span class="nx-badge nx-prov">' + (pd.provider||'').substring(0,20) + '</span>');
badges2.push('<span class="nx-badge" style="background:rgba(16,185,129,0.15);color:#10B981"> Sovereign</span>');
if(badges2.length > 0 && msgEl2 && msgEl2.querySelector('.msg-inner')) {
var badgeEl2 = document.createElement('div');
badgeEl2.className = 'nx-badges';
badgeEl2.innerHTML = badges2.join('');
msgEl2.querySelector('.msg-inner').appendChild(badgeEl2);
}
}
}).catch(function(){});
}, 500);
return;
}
// === SYNC FALLBACK: original handler ===
hideThinking();
var elapsed = ((performance.now() - startTime) / 1000).toFixed(1);
var response = (data.choices && data.choices[0] && data.choices[0].message) ? data.choices[0].message.content : (data.response || 'Pas de reponse.');
if (data.conversation_id) convId = data.conversation_id;
response = response.replace(/\[(PDF|SVG|HTML|EXEC|SENTINEL)[^\]]*\]/gi, '');
chatHistory.push({role: 'assistant', content: response});
var msgEl = addMsg('assistant', response, elapsed);
var badges = [];
var _prov = data.provider || (data.choices && data.choices[0] ? 'sovereign' : ''); if(_prov) badges.push('<span class="nx-badge nx-prov">' + (_prov||'').substring(0,20) + '</span>');
badges.push('<span class="nx-badge" style="background:rgba(16,185,129,0.15);color:#10B981">⚡ Fast</span>');
if(badges.length > 0 && msgEl.querySelector('.msg-inner')) {
var badgeEl = document.createElement('div');
badgeEl.className = 'nx-badges';
badgeEl.innerHTML = badges.join('');
msgEl.querySelector('.msg-inner').appendChild(badgeEl);
}
setTimeout(function(){ generateFollowups(window._lastQuery, response); }, 500);
// Detect code artifacts in response
var codeMatch = response.match(/```(\w+)?\n([\s\S]*?)```/);
if (codeMatch) {
window._artifactCode = codeMatch[2];
var lang = codeMatch[1] || 'code';
var artCard = '<div class="artifact-card" onclick="switchPreviewTab(\'code\')">' +
'<div style="width:40px;height:40px;border-radius:10px;background:linear-gradient(135deg,#7C3AED,#3B82F6);display:flex;align-items:center;justify-content:center;font-size:18px">📄</div>' +
'<div><div style="font-weight:600;font-size:14px">Code ' + lang.toUpperCase() + '</div>' +
'<div style="font-size:12px;color:#6b7280">' + codeMatch[2].split('\n').length + ' lignes</div></div>' +
'<div class="art-actions"><button class="art-btn" onclick="event.stopPropagation();switchPreviewTab(\'code\')" title="Voir">👁</button>' +
'<button class="art-btn" onclick="event.stopPropagation();navigator.clipboard.writeText(window._artifactCode)" title="Copier">📋</button></div></div>';
if (msgEl) msgEl.querySelector('.msg-inner').innerHTML += artCard;
}
if (voiceOn) speak(response);
})
.catch(function(err) {
hideThinking();
addMsg('assistant', 'Erreur de connexion. Réessayez.', '0');
})
.finally(function() {
busy = false;
document.getElementById('sendBtn').disabled = false;
document.getElementById('msgInput').focus();
clearFile();
});
return;
}
var _ac = new AbortController();
var _to = setTimeout(function() { _ac.abort(); }, 90000);
fetch("/api/sovereign/v1/chat/completions", {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({model:"auto",messages:[{role:"system",content:"Tu es WEVIA, IA souveraine de WEVAL Consulting Casablanca. Reponds en francais, professionnel et complet."},{role:"user",content:text}],max_tokens:4096,stream:true}),
signal: _ac.signal
})
.then(function(res) { clearTimeout(_to); return res.json(); })
.then(function(data) {
// ═══ STREAMING FALLBACK (GPU souverain) ═══
if (data.streaming) {
if (data.conversation_id) { convId = data.conversation_id; if(typeof loadConversations==='function') setTimeout(loadConversations,2000); }
// Show what was used
if(data.used_kb && typeof addThinkStep==='function') addThinkStep('📚 Base de connaissances consultée');
if(data.used_web && typeof addThinkStep==='function') addThinkStep('🌐 Recherche web effectuée');
if(data.used_memory && typeof addThinkStep==='function') addThinkStep('🧠 Mémoire contextuelle activée');
if(data.used_graph && typeof addThinkStep==='function') addThinkStep('🔗 GraphRAG connecté');
setTimeout(function(){ hideThinking(); }, 700);
var msgEl = addMsg('assistant', '', '⏳');
var bubble = msgEl ? (msgEl.querySelector('.bubble') || msgEl.lastElementChild || msgEl) : document.createElement('div');
bubble.innerHTML = '<span class="scur" style="display:inline-block;width:2px;height:16px;background:#7C6BF0;animation:blink 1s infinite;vertical-align:middle"></span>';
var fullText = '';
fetch(data.stream_url, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ message: payload.message, system: data.system||'', history: chatHistory.slice(-4), model: data.model||'deepseek-r1-fast' })
}).then(function(sseRes) {
var reader = sseRes.body.getReader();
var dec = new TextDecoder(); var buf = '';
function pump() {
reader.read().then(function(r) {
if (r.done) { endStream(); return; }
buf += dec.decode(r.value, {stream:false});
var parts = buf.split('\n'); buf = parts.pop();
parts.forEach(function(ln) {
if (!ln.startsWith('data: ')) return;
try {
var ev = JSON.parse(ln.substring(6));
if (ev.token) {
fullText += ev.token;
bubble.innerHTML = (typeof formatMd==='function' ? formatMd(fullText) : fullText) + '<span class="scur" style="display:inline-block;width:2px;height:16px;background:#7C6BF0;animation:blink 1s infinite"></span>';
document.getElementById('messages').scrollTop = document.getElementById('messages').scrollHeight;
}
if (ev.done) endStream();
} catch(x){}
});
pump();
}).catch(function(){ endStream(); });
}
pump();
}).catch(function(){ bubble.textContent='Erreur GPU. Réessayez.'; endStream(); });
function endStream() {
var el2 = ((performance.now()-startTime)/1000).toFixed(1);
var cur = bubble.querySelector('.scur'); if(cur) cur.remove();
var mt = msgEl ? msgEl.querySelector('.meta-time') : null; if(mt) mt.textContent='\u26a1 '+el2+'s';
fullText = fullText.replace(/\[(PDF|SVG|HTML|EXEC|SENTINEL)[^\]]*\]/gi,'');
bubble.innerHTML = typeof formatMd==='function' ? formatMd(fullText) : fullText;
chatHistory.push({role:'assistant',content:fullText});
if(voiceOn) speak(fullText);
setTimeout(function(){ generateFollowups(window._lastQuery, fullText); }, 500);
busy=false; document.getElementById('sendBtn').disabled=false;
document.getElementById('msgInput').focus(); clearFile();
}
return;
}
// ═══ NORMAL RESPONSE (cloud providers) ═══
// Show thinking steps before hiding
if(data.used_kb && typeof addThinkStep==='function') addThinkStep('\ud83d\udcda Base de connaissances');
if(data.used_web && typeof addThinkStep==='function') addThinkStep('\ud83c\udf10 Recherche web');
if(data.used_memory && typeof addThinkStep==='function') addThinkStep('\ud83e\udde0 M\u00e9moire');
if(data.used_graph && typeof addThinkStep==='function') addThinkStep('\ud83d\udd17 GraphRAG');
if(data.verified && typeof addThinkStep==='function') addThinkStep('\u2713 V\u00e9rification crois\u00e9e');
setTimeout(function(){ hideThinking(); }, 600);
var elapsed = ((performance.now() - startTime) / 1000).toFixed(1);
var response = (data.choices&&data.choices[0]&&data.choices[0].message?data.choices[0].message.content:'') || data.response || data.reply || data.content || 'Pas de réponse.';
if (data.conversation_id) { convId = data.conversation_id; if(typeof loadConversations==='function') setTimeout(loadConversations,2000); }
response = response.replace(/\[(PDF|SVG|HTML|EXEC|SENTINEL|BRIDGE|AGENT|DEBUG|MATH|GRAPH|PLAN)[^\]]*\]/gi, '');
response = response.replace(/\[\/(PDF|SVG|HTML|EXEC)\]/gi, '');
chatHistory.push({role: 'assistant', content: response});
var msgEl = addMsg('assistant', response, elapsed);
// UX: Generate follow-up chips (Perplexity-style)
setTimeout(function(){ generateFollowups(window._lastQuery, response); }, 500);
// ═══ NEXUS BADGES — sources, verified, providers ═══
var badges = [];
var _prov = data.provider || (data.choices && data.choices[0] ? 'sovereign' : ''); if(_prov) badges.push('<span class="nx-badge nx-prov">' + (_prov||'').substring(0,20) + '</span>');
if(data.used_kb) badges.push('<span class="nx-badge nx-kb">📚 KB</span>');
if(data.used_web) badges.push('<span class="nx-badge nx-web">🌐 Web</span>');
if(data.used_graph) badges.push('<span class="nx-badge nx-graph">🔗 Graph</span>');
if(data.used_memory) badges.push('<span class="nx-badge nx-mem">🧠 Mémoire</span>');
if(data.verified) badges.push('<span class="nx-badge nx-verified">✓ Vérifié</span>');
if(data.critic_note) badges.push('<span class="nx-badge nx-critic" title="' + (data.critic_note||'').substring(0,200).replace(/"/g,'&quot;') + '">🔍 Critique</span>');
if(data.sources && data.sources.length > 0) badges.push('<span class="nx-badge nx-src" title="' + data.sources.slice(0,3).join('\n').replace(/"/g,'&quot;') + '">📖 ' + data.sources.length + ' sources</span>');
if(data.engine) badges.push('<span class="nx-badge nx-engine">' + data.engine + '</span>');
if(badges.length > 0) {
var badgeEl = document.createElement('div');
badgeEl.className = 'nx-badges';
badgeEl.innerHTML = badges.join('');
msgEl.querySelector('.msg-inner').appendChild(badgeEl);
}
var pdfMatch = response.match(/(?:\/wevia-ia\/downloads\/|\/uploads\/)[\w.-]+\.pdf/);
if (pdfMatch) {
var pdfUrl = pdfMatch[0];
var pdfTitle = response.match(/\*\*(.+?)\*\*/);
setTimeout(function() { openPreview(pdfUrl, pdfTitle ? pdfTitle[1].substring(0,50) : 'Document PDF'); }, 300);
}
// Detect code artifacts in response
var codeMatch = response.match(/```(\w+)?\n([\s\S]*?)```/);
if (codeMatch) {
window._artifactCode = codeMatch[2];
var lang = codeMatch[1] || 'code';
var artCard = '<div class="artifact-card" onclick="switchPreviewTab(\'code\')">' +
'<div style="width:40px;height:40px;border-radius:10px;background:linear-gradient(135deg,#7C3AED,#3B82F6);display:flex;align-items:center;justify-content:center;font-size:18px">📄</div>' +
'<div><div style="font-weight:600;font-size:14px">Code ' + lang.toUpperCase() + '</div>' +
'<div style="font-size:12px;color:#6b7280">' + codeMatch[2].split('\n').length + ' lignes</div></div>' +
'<div class="art-actions"><button class="art-btn" onclick="event.stopPropagation();switchPreviewTab(\'code\')" title="Voir">👁</button>' +
'<button class="art-btn" onclick="event.stopPropagation();navigator.clipboard.writeText(window._artifactCode)" title="Copier">📋</button></div></div>';
if (msgEl) msgEl.querySelector('.msg-inner').innerHTML += artCard;
}
if (voiceOn) speak(response);
})
.catch(function(err) {
clearTimeout(_to);
hideThinking();
var msg = err.name === 'AbortError' ? '⏱️ Timeout — la requête a pris trop de temps. Réessayez.' : '❌ Erreur de connexion. Vérifiez votre réseau et réessayez.';
addMsg('assistant', msg, '0');
})
.finally(function() {
if (!busy) return;
busy = false;
document.getElementById('sendBtn').disabled = false;
document.getElementById('msgInput').focus();
clearFile();
});
} catch(e) {
busy = false;
document.getElementById('sendBtn').disabled = false;
addMsg('assistant', '⚠️ Une erreur est survenue. Réessayez.');
}
}
// ─── Regenerate ───
function regen() {
if (chatHistory.length < 2) return;
chatHistory.pop(); // remove last assistant
var lastUser = '';
for (var i = chatHistory.length - 1; i >= 0; i--) {
if (chatHistory[i].role === 'user') { lastUser = chatHistory[i].content; break; }
}
if (lastUser) {
var msgs = document.getElementById('messages').querySelectorAll('.msg.assistant');
if (msgs.length) msgs[msgs.length - 1].remove();
document.getElementById('msgInput').value = lastUser;
send();
}
}
// ─── TTS ───
function speak(text) {
if (!voiceOn) return;
// Stop previous TTS before starting new one
if(window._ttsAudio) { try { window._ttsAudio.pause(); window._ttsAudio.currentTime = 0; } catch(e){} }
text = text.replace(/<[^>]*>/g, '').replace(/\*\*([^*]+)\*\*/g, '$1').replace(/\*([^*]+)\*/g, '$1').replace(/\[([^\]]+)\]\([^)]+\)/g, '$1').replace(/[*#`|•◆>~_]/g, '').replace(/^\d+\.\s/gm, '').replace(/^-\s/gm, '').replace(/---/g, '').substring(0, 2000);
fetch('/wevia-ia/wevia-tts.php', {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'text=' + encodeURIComponent(text) + '&lang=' + (lang || 'fr')
})
.then(function(r) { if (!r.ok) throw new Error('TTS'); return r.blob(); })
.then(function(b) {
var a = new Audio(URL.createObjectURL(b));
window._ttsAudio = a;
if(!window._ttsQueue) window._ttsQueue = [];
window._ttsQueue.push(a);
a.onended = function(){ var i = window._ttsQueue.indexOf(a); if(i>-1) window._ttsQueue.splice(i,1); };
if(!voiceOn) return; // Double-check mute state
var p = a.play();
if (p) p.catch(function() {});
})
.catch(function() {});
}
function replayTTS(btn) {
// Stop any playing audio first
if(window._ttsAudio) { try { window._ttsAudio.pause(); } catch(e){} }
var idx = parseInt(btn.dataset.idx || '0');
var text = (window._msgTexts && window._msgTexts[idx]) ? window._msgTexts[idx] : '';
if (!text && !pendingFile) return;
btn.textContent = '🔈';
fetch('/wevia-ia/wevia-tts.php', {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'text=' + encodeURIComponent(text.replace(/<[^>]*>/g, '').replace(/\*\*([^*]+)\*\*/g, '$1').replace(/\*([^*]+)\*/g, '$1').replace(/\[([^\]]+)\]\([^)]+\)/g, '$1').replace(/[*#`|•◆>~_]/g, '').replace(/^\d+\.\s/gm, '').replace(/^-\s/gm, '').replace(/---/g, '').substring(0, 2000)) + '&lang=' + (lang || 'fr')
})
.then(function(r) { return r.blob(); })
.then(function(b) {
var a = new Audio(URL.createObjectURL(b));
a.onended = function() { btn.textContent = '🔊'; };
var p = a.play();
if (p) p.catch(function() { btn.textContent = '🔊'; });
})
.catch(function() { btn.textContent = '🔊'; });
}
function copyText(btn) {
var text = decodeURIComponent(btn.dataset.text || '');
navigator.clipboard.writeText(text).then(function() {
btn.textContent = '✅';
setTimeout(function() { btn.textContent = '📋'; }, 1500);
});
}
// ─── Markdown Formatter (simple & safe) ───
// ═══════════════════════════════════════════════════════════
// WEVIA — Bulletproof Markdown Renderer v2
// Fix: Extract code blocks BEFORE HTML escaping
// ═══════════════════════════════════════════════════════════
function formatMd(text) {
if (!text) return '';
if (text.length > 15000) { text = text.substring(0, 15000) + '\n\n---\n*Réponse tronquée.*'; }
try {
// ═══ PHASE 1: Extract ALL code blocks BEFORE escaping ═══
var blocks = [];
var placeholder = function(i) { return '\x00BLK' + i + '\x00'; };
// Pre-process: normalize LLM formatting quirks
// SVG: ```svg<svg → ```svg\n<svg
text = text.replace(/```svg\s*(<svg)/gi, '```svg\n$1');
// Mermaid formatting quirks
text = text.replace(/```mermaid((?:flowchart|graph|sequenceDiagram|classDiagram|stateDiagram|gantt|pie|gitGraph|erDiagram|journey))/gi, '```mermaid\n$1');
// Also handle ```mermaid without newline (space instead)
text = text.replace(/```mermaid\s+((?:flowchart|graph|sequenceDiagram|classDiagram|stateDiagram|gantt|pie|gitGraph|erDiagram|journey))/gi, '```mermaid\n$1');
// Extract ```lang\n...\n``` blocks (mermaid, html, svg, code)
text = text.replace(/```(\w*)\n([\s\S]*?)```/g, function(m, lang, code) {
var idx = blocks.length;
blocks.push({lang: lang.toLowerCase(), code: code});
return placeholder(idx);
});
// Also handle ``` without newline after lang (LLM sometimes does ```mermaidgraph...)
text = text.replace(/```(mermaid)((?:graph|flowchart|sequenceDiagram|classDiagram|stateDiagram|gantt|pie|gitGraph)[\s\S]*?)```/gi, function(m, lang, code) {
var idx = blocks.length;
blocks.push({lang: 'mermaid', code: code});
return placeholder(idx);
});
// === PHASE 1b: Extract iframes (Visual Intelligence) ===
var iframes = [];
text = text.replace(/<iframe[^>]*>[\s\S]*?<\/iframe>/gi, function(m) {
var idx = iframes.length;
iframes.push(m);
return '\x00IFR' + idx + '\x00';
});
// ═══ PHASE 2: Escape HTML on remaining text ═══
text = text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
// ═══ PHASE 3: Render code blocks from extracted data ═══
for (var i = 0; i < blocks.length; i++) {
var b = blocks[i];
var rendered = '';
if (b.lang.indexOf('mermaid') === 0 && b.lang !== 'mermaid') {
b.code = b.lang.substring(7) + '\n' + b.code;
b.lang = 'mermaid';
}
if (b.lang === 'mermaid') {
// ── MERMAID ──
var mermId = 'merm_' + Date.now() + '_' + Math.random().toString(36).substr(2,6);
var mermCode = b.code.trim();
// SANITIZE mermaid code
mermCode = mermCode.replace(/'/g, ' ');
// V170 accents-preserve · wrap labels in quotes instead of stripping accents
try {
mermCode = mermCode.replace(/\[([^\]\"\n]*[\u00c0-\u017f][^\]\n]*)\]/g, function(m, inner) { return '["' + inner.replace(/"/g, '') + '"]'; });
mermCode = mermCode.replace(/\(([^\)\"\n]*[\u00c0-\u017f][^\)\n]*)\)/g, function(m, inner) { return '("' + inner.replace(/"/g, '') + '")'; });
mermCode = mermCode.replace(/\{([^\}\"\n]*[\u00c0-\u017f][^\}\n]*)\}/g, function(m, inner) { return '{"' + inner.replace(/"/g, '') + '"}'; });
} catch(e) { /* fallback: leave as-is */ }
mermCode = mermCode.replace(/\[([^\]]*)\s-\s([^\]]*)\]/g, '[$1 $2]');
// Fix LLM arrow syntax errors
mermCode = mermCode.replace(/-->\|([^|]+)\|>/g, '-->|$1|');
mermCode = mermCode.replace(/--\|>/g, '-->');
mermCode = mermCode.replace(/\|>/g, '-->');
// Fix double spaces between nodes on same line
mermCode = mermCode.replace(/(\]|\))\s{4,}([A-Z])/g, '$1\n $2');
mermCode = mermCode.replace(/([A-Z][A-Za-z0-9_]*)\(([^)]{0,60})\)/g, '$1[$2]');
// Fix: add newlines between mermaid statements (LLM sometimes puts all on one line)
mermCode = mermCode.replace(/\]\s{2,}/g, ']\n ');
// Aggressive single-line fix: split on ] followed by letter/arrow
mermCode = mermCode.replace(/(\])\s+(--[->])/g, '$1\n $2');
mermCode = mermCode.replace(/(graph\s+(?:TD|LR|TB|RL|BT))\s{2,}/g, '$1\n ');
mermCode = mermCode.replace(/(-->\s*[A-Z][A-Za-z0-9_]*(?:\[[^\]]*\])?)\s{2,}([A-Z])/g, '$1\n $2');
// Fix style/classDef on same line
mermCode = mermCode.replace(/\s{2,}(style\s|classDef\s|class\s)/g, '\n $1');
mermCode = mermCode.replace(/(graph\s+(?:TD|LR|TB|RL|BT))\s{2,}/g, '$1\n ');
rendered = '<div class="mermaid-container"><div class="mermaid" id="' + mermId + '">' + mermCode + '</div></div>';
// Schedule render with retry
(function(id, rawCode) {
var attempts = 0;
function tryRender() {
var el = document.getElementById(id);
if (el && window.mermaid) {
var origCode = el.textContent;
try {
mermaid.render('svg_'+id, rawCode).then(function(result) { if(result&&result.svg){el.parentElement.innerHTML=result.svg;return;}
el.setAttribute('data-processed','true');
if(!el.querySelector('svg')&&el.parentElement&&!el.parentElement.querySelector('svg')){el.parentElement.innerHTML='<pre style="background:#1e1e2e;color:#cdd6f4;padding:16px;border-radius:12px;overflow-x:auto;font-size:13px"><code>'+origCode+'</code></pre>';}
}).catch(function(e) {
el.setAttribute('data-processed','true');
el.parentElement.innerHTML = '<pre style="background:#1e1e2e;color:#cdd6f4;padding:16px;border-radius:12px;overflow-x:auto;font-size:13px"><code>' + origCode + '</code></pre>';
});
} catch(e) {
el.parentElement.innerHTML = '<pre style="background:#1e1e2e;color:#cdd6f4;padding:16px;border-radius:12px;overflow-x:auto;font-size:13px"><code>' + el.textContent + '</code></pre>';
}
} else if (attempts < 10) {
attempts++;
setTimeout(tryRender, 300);
}
}
setTimeout(tryRender, 1500);
})(mermId, mermCode);
} else if (b.lang === 'html' || b.lang === 'svg') {
// ── HTML/SVG ARTIFACTS ──
var artType = b.lang;
var icons = {html:'🌐', svg:'🎨'};
var titles = {html:'Page HTML', svg:'Illustration SVG'};
var artId = 'art_' + Date.now() + '_' + Math.random().toString(36).substr(2,6);
var artCode = b.code;
var encoded = encodeURIComponent(artCode);
if (typeof saveArtifact === 'function') saveArtifact(artType, artCode, titles[artType] || artType, artId);
setTimeout(function(aid) { return function() { if(typeof togglePreview==='function') togglePreview(aid); }; }(artId), 500);
rendered = '<div class="artifact-card" id="' + artId + '">' +
'<span style="font-size:26px">' + (icons[artType]||'📄') + '</span>' +
'<div style="flex:1"><div style="font-weight:700;font-size:14px">' + (titles[artType]||artType) + '</div>' +
'<div style="font-size:11px;color:var(--tx2)">' + artType.toUpperCase() + ' · Généré par WEVIA</div></div>' +
'<div class="art-actions">' +
'<button class="art-btn" onclick="togglePreview(\'' + artId + '\')" title="Aperçu">👁</button>' +
'<button class="art-btn" onclick="openArtifact(\'' + encoded + '\',\'' + artType + '\')" title="Ouvrir">↗</button>' +
'<button class="art-btn art-dl" data-id="' + artId + '" title="Télécharger">⬇</button>' +
'</div></div>' +
'<div class="artifact-preview" id="prev_' + artId + '" style="display:none"></div>';
} else {
// ── CODE BLOCK with syntax highlighting ──
var lang = b.lang || 'plaintext';
var langMap = {py:'python',js:'javascript',ts:'typescript',sh:'bash',yml:'yaml',rb:'ruby',rs:'rust',cpp:'c++',cs:'c#'};
var langDisplay = langMap[lang] || lang;
var codeId = 'code_' + Date.now() + '_' + Math.random().toString(36).substr(2,6);
// Escape code for safe display
var safeCode = b.code.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
rendered = '<div class="code-block-wrap">' +
'<div class="code-block-header">' +
'<span class="code-lang-badge">' + langDisplay.toUpperCase() + '</span>' +
'<button class="code-copy-btn" onclick="copyCode(\'' + codeId + '\')">Copier</button>' +
'</div>' +
'<pre><code id="' + codeId + '" class="language-' + lang + '">' + safeCode + '</code></pre>' +
'</div>';
// Highlight after render
(function(id, lng) {
setTimeout(function() {
var el = document.getElementById(id);
if (el && window.hljs) {
try {
if (lng && hljs.getLanguage(lng)) {
el.innerHTML = hljs.highlight(el.textContent, {language:lng}).value;
} else {
hljs.highlightElement(el);
}
} catch(e){}
}
}, 100);
})(codeId, lang);
}
text = text.replace(placeholder(i), rendered);
}
// ═══ PHASE 4: Inline markdown ═══
// Inline code
text = text.replace(/`([^`]+)`/g, '<code>$1</code>');
// Math (KaTeX)
if (window.katex) {
text = text.replace(/\$\$([\s\S]*?)\$\$/g, function(m, math) {
try { return '<div class="katex-display">' + katex.renderToString(math.replace(/&amp;/g,'&').replace(/&lt;/g,'<').replace(/&gt;/g,'>'), {displayMode:true,throwOnError:false}) + '</div>'; }
catch(e) { return '<pre>' + math + '</pre>'; }
});
text = text.replace(/\$([^\$\n]+)\$/g, function(m, math) {
try { return katex.renderToString(math.replace(/&amp;/g,'&').replace(/&lt;/g,'<').replace(/&gt;/g,'>'), {displayMode:false,throwOnError:false}); }
catch(e) { return '<code>' + math + '</code>'; }
});
}
// Images — with smart loading & Banana detection
text = text.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, function(m, alt, url) {
var safeUrl = url.replace(/&amp;/g,'&');
if (safeUrl.indexOf('http') !== 0 && safeUrl.indexOf('/') !== 0) return '';
// Detect Pollinations URLs - route through BananaEngine
if (safeUrl.indexOf('pollinations.ai') !== -1) {
var bId = 'banana_' + Date.now() + '_' + Math.random().toString(36).substr(2,4);
setTimeout(function(){
var el = document.getElementById(bId);
if(el && window.BananaEngine) {
var prompt = decodeURIComponent(safeUrl.split('/prompt/')[1] || '').split('?')[0];
BananaEngine.render('photo', prompt || alt, el);
}
}, 100);
return '<div id="' + bId + '" style="margin:12px 0"></div>';
}
// Regular images - with retry on error
var imgId = 'img_' + Date.now() + '_' + Math.random().toString(36).substr(2,4);
return '<div style="margin:12px 0;text-align:center"><img id="' + imgId + '" src="' + safeUrl + '" alt="' + alt + '" loading="lazy" class="organic-reveal" style="max-width:100%;border-radius:16px;border:1px solid rgba(99,102,241,0.15);box-shadow:0 4px 24px rgba(0,0,0,.12)" onerror="var s=this;var r=parseInt(s.dataset.retry||0);if(r<4){s.dataset.retry=r+1;setTimeout(function(){s.src=s.src+\'&r=\'+Date.now()},5000)}else{s.outerHTML=\'<div style=padding:20px;background:rgba(0,0,0,.04);border-radius:12px;color:var(--tx2);font-size:13px>🖼️ Image non disponible</div>\'}"><div style="font-size:11px;color:var(--tx2);margin-top:4px">' + (alt||'') + '</div></div>';
});
// PDF download links
text = text.replace(/\[([^\]]*)\]\((\/wevia-ia\/downloads\/[^)]+\.pdf)\)/g,
'<a href="$2" target="_blank" download class="pdf-link" style="display:inline-flex;align-items:center;gap:10px;padding:12px 18px;margin:8px 0;background:linear-gradient(135deg,rgba(231,76,60,.06),rgba(231,76,60,.12));border:1px solid rgba(231,76,60,.2);border-radius:12px;color:#e74c3c;font-weight:600;text-decoration:none"><span style="font-size:20px">📥</span> $1</a>');
// Regular links
text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" style="color:#7c6bf0;text-decoration:underline;text-underline-offset:2px">$1</a>');
// Tables
text = text.replace(/\n(\|[^\n]+\|\n\|[-| :]+\|\n(?:\|[^\n]+\|\n?)+)/g, function(m, table) {
var rows = table.trim().split('\n');
if (rows.length < 3) return m;
var headers = rows[0].split('|').filter(function(x){return x.trim();});
var html = '<div class="table-wrap"><table><thead><tr>' + headers.map(function(h){
return '<th>' + h.trim() + '</th>';
}).join('') + '</tr></thead><tbody>';
for (var i=2; i<rows.length; i++) {
var cells = rows[i].split('|').filter(function(x){return x.trim();});
html += '<tr>' + cells.map(function(c){
var v = c.trim();
if (/^✅/.test(v)) v = '<span class="status-badge status-ok">' + v + '</span>';
else if (/^⚠|^⏳/.test(v)) v = '<span class="status-badge status-warn">' + v + '</span>';
else if (/^❌/.test(v)) v = '<span class="status-badge status-err">' + v + '</span>';
return '<td>' + v + '</td>';
}).join('') + '</tr>';
}
return html + '</tbody></table></div>';
});
// Blockquotes
text = text.replace(/^&gt;\s*(.+)$/gm, '<blockquote>$1</blockquote>');
text = text.replace(/<\/blockquote>\s*<br>\s*<blockquote>/g, '<br>');
// Horizontal rules
text = text.replace(/^---$/gm, '<hr>');
// Bold + italic
text = text.replace(/\*\*\*([^*]+)\*\*\*/g, '<strong><em>$1</em></strong>');
text = text.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
text = text.replace(/(?<!\w)\*([^*]+)\*(?!\w)/g, '<em>$1</em>');
// Headings
text = text.replace(/^### (.+)$/gm, '<h3>$1</h3>');
text = text.replace(/^## (.+)$/gm, '<h2>$1</h2>');
text = text.replace(/^# (.+)$/gm, '<h1>$1</h1>');
// Lists
text = text.replace(/^\s*[-•]\s+(.+)$/gm, '<div class="md-list-item"><span class="md-list-bullet">●</span><span>$1</span></div>');
text = text.replace(/^\s*(\d+)\.\s+(.+)$/gm, '<div class="md-list-item"><span class="md-list-num">$1.</span><span>$2</span></div>');
// Newlines
text = text.replace(/\n/g, '<br>');
// Clean orphan tags
text = text.replace(/\[(PDF|SVG|HTML|EXEC|SENTINEL)[^\]]*\]/gi, '');
// Restore iframes
for (var ii=0; ii<iframes.length; ii++) {
text = text.replace('\x00IFR' + ii + '\x00', '<div class="viz-container" style="margin:12px 0;border-radius:12px;overflow:hidden;box-shadow:0 2px 12px rgba(99,102,241,0.15)">' + iframes[ii] + '</div>');
}
return text;
} catch(e) {
return text || '';
}
}
// ─── Copy Code helper ───
function copyCode(codeId) {
var el = document.getElementById(codeId);
if (!el) return;
var text = el.textContent;
navigator.clipboard.writeText(text).then(function() {
var btn = el.closest('.code-block-wrap').querySelector('.code-copy-btn');
if (btn) { btn.textContent = '✓ Copié'; setTimeout(function() { btn.textContent = 'Copier'; }, 2000); }
});
}
// ─── Open Artifact in New Window ───
// ─── Artifact System (ported from HAMID) ───
var artifactUrls = {}; // artId → server URL
function saveArtifact(type, content, title, artId) {
fetch("/wevia-ia/wevia-artifact.php", {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({type: type, content: content, title: title})
})
.then(function(r) { return r.json(); })
.then(function(d) {
if (d.success && d.url) {
artifactUrls[artId] = d.url;
// Update download button
var dlBtn = document.querySelector(".art-dl[data-id='" + artId + "']");
if (dlBtn) {
dlBtn.onclick = function() { window.open(d.url, "_blank"); };
}
}
})
.catch(function() {});
}
function togglePreview(artId) {
var prev = document.getElementById("prev_" + artId);
if (!prev) return;
if (prev.style.display === "none") {
var url = artifactUrls[artId];
if (url) {
prev.innerHTML = '<iframe src="' + url + '"></iframe><div class="art-toolbar"><button onclick="expandPreview(this)">⤢ Agrandir</button><button onclick="window.open(\x27' + url + '\x27,\x27_blank\x27)">↗ Nouvel onglet</button><button onclick="togglePreview(\x27' + artId + '\x27)">✕ Fermer</button></div>';
} else {
prev.innerHTML = '<div style="padding:20px;text-align:center;color:var(--tx2)">Chargement...</div>';
setTimeout(function() { togglePreview(artId); }, 1000);
return;
}
prev.style.display = "block";
} else {
prev.style.display = "none";
prev.innerHTML = "";
}
}
function expandPreview(btn) {
var preview = btn.closest(".artifact-preview");
if (preview) preview.classList.toggle("expanded");
btn.textContent = preview.classList.contains("expanded") ? "⤡ Réduire" : "⤢ Agrandir";
}
function openArtifact(encodedCode, type) {
var code = decodeURIComponent(encodedCode);
var content = code;
if (type === "mermaid") {
content = `<!DOCTYPE html><html><head><script src="https://cdnjs.cloudflare.com/ajax/libs/mermaid/10.9.0/mermaid.min.js"><\/script><style>body{margin:20px;display:flex;align-items:center;justify-content:center;min-height:90vh;background:#fafafa;font-family:system-ui}</style></head><body>
<div class="mermaid">' + code.replace(/</g, "&lt;") + '</div><script>mermaid.initialize({startOnLoad:true,theme:"neutral"});<\/script><\/body></html>`;
} else if (type === "svg") {
content = '<!DOCTYPE html><html><head><style>body{margin:0;display:flex;align-items:center;justify-content:center;min-height:100vh;background:#f8f9fa;}</style></head><body>' + code + '<\/body></html>';
} else if (type === "html" && code.indexOf("<html") === -1) {
content = '<!DOCTYPE html><html><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><style>body{font-family:system-ui,sans-serif;padding:24px;margin:0;color:#1a1a2e;line-height:1.6;max-width:900px;margin:0 auto}</style></head><body>' + code + '<\/body></html>';
}
var w = window.open("", "_blank", "width=900,height=700");
if (w) { w.document.write(content); w.document.close(); }
}
// ─── Voice Input (Speech Recognition) ───
var isRecording = false, recognition = null;
function toggleMic() {
if (isRecording) { stopMic(); return; }
var SpeechRec = window.SpeechRecognition || window.webkitSpeechRecognition;
if (!SpeechRec) {
// Fallback: MediaRecorder -> WEVIA Engine ASR
startMediaRec(); return;
}
recognition = new SpeechRec();
recognition.lang = lang === "darija" ? "ar-MA" : lang === "en" ? "en-US" : lang === "ar" ? "ar-SA" : lang === "es" ? "es-ES" : lang === "de" ? "de-DE" : lang === "zh" ? "zh-CN" : lang === "ja" ? "ja-JP" : lang === "ko" ? "ko-KR" : lang === "it" ? "it-IT" : lang === "pt" ? "pt-BR" : lang === "ru" ? "ru-RU" : lang === "tr" ? "tr-TR" : lang === "hi" ? "hi-IN" : "fr-FR";
recognition.interimResults = true;
recognition.continuous = false;
recognition.maxAlternatives = 1;
var micBtn = document.getElementById("micBtn");
micBtn.style.background = "#ef4444"; micBtn.style.color = "#fff"; micBtn.textContent = "⏹";
isRecording = true;
var finalText = "";
recognition.onresult = function(e) {
var transcript = "";
for (var i = 0; i < e.results.length; i++) {
transcript += e.results[i][0].transcript;
if (e.results[i].isFinal) finalText += e.results[i][0].transcript;
}
document.getElementById("msgInput").value = transcript;
document.getElementById("msgInput").style.height = "auto";
document.getElementById("msgInput").style.height = Math.min(document.getElementById("msgInput").scrollHeight, 120) + "px";
};
recognition.onend = function() {
stopMic();
if (finalText.trim()) { document.getElementById("msgInput").value = finalText; }
};
recognition.onerror = function(e) {
if (e.error === "not-allowed") alert("Autorisez le microphone dans les paramètres du navigateur.");
stopMic();
if (e.error === "no-speech" || e.error === "network") startMediaRec();
};
recognition.start();
}
function stopMic() {
isRecording = false;
var micBtn = document.getElementById("micBtn");
micBtn.style.background = "var(--bg)"; micBtn.style.color = "var(--tx)"; micBtn.textContent = "🎤";
if (recognition) { try { recognition.stop(); } catch(e) {} recognition = null; }
if (mediaRec && mediaRec.state === "recording") mediaRec.stop();
}
// Fallback: MediaRecorder for browsers without SpeechRecognition
var mediaRec = null;
function startMediaRec() {
if (!navigator.mediaDevices) { alert("Microphone non disponible"); return; }
navigator.mediaDevices.getUserMedia({audio: true}).then(function(stream) {
var chunks = [];
mediaRec = new MediaRecorder(stream, {mimeType: MediaRecorder.isTypeSupported("audio/webm") ? "audio/webm" : "audio/mp4"});
mediaRec.ondataavailable = function(e) { if (e.data.size > 0) chunks.push(e.data); };
mediaRec.onstop = function() {
stream.getTracks().forEach(function(t) { t.stop(); });
var blob = new Blob(chunks, {type: mediaRec.mimeType});
var reader = new FileReader();
reader.onload = function(ev) {
var base64 = ev.target.result.split(",")[1];
pendingFile = {name: "voice.webm", type: mediaRec.mimeType, base64: base64, size: blob.size};
document.getElementById("fileName").textContent = "🎤 Message vocal (" + (blob.size/1024).toFixed(0) + " Ko)";
document.getElementById("filePreview").style.display = "flex";
document.getElementById("msgInput").value = "Transcris et réponds à ce message vocal";
document.getElementById("msgInput").focus();
};
reader.readAsDataURL(blob);
stopMic();
};
mediaRec.start();
var micBtn = document.getElementById("micBtn");
micBtn.style.background = "#ef4444"; micBtn.style.color = "#fff"; micBtn.textContent = "⏹";
isRecording = true;
}).catch(function() { alert("Microphone non disponible. Vérifiez les permissions."); });
}
// ─── File Upload ───
var pendingFile = null;
function handleFile(input) {
if (!input.files || !input.files[0]) return;
var file = input.files[0];
var reader = new FileReader();
reader.onload = function(e) {
var base64 = e.target.result.split(",")[1];
pendingFile = { name: file.name, type: file.type, base64: base64, size: file.size };
// Show preview bar
var bar = document.getElementById("filePreviewBar");
var fpIcon = document.getElementById("fpIcon");
var fpName = document.getElementById("fpName");
var fpSize = document.getElementById("fpSize");
var fpThumb = document.getElementById("fpThumb");
if (bar) {
bar.classList.add("visible");
fpName.textContent = file.name;
fpSize.textContent = (file.size / 1024).toFixed(0) + " KB";
if (file.type.startsWith("image/")) {
fpIcon.style.display = "none";
fpThumb.style.display = "block";
fpThumb.src = e.target.result;
} else {
fpIcon.style.display = "";
fpIcon.textContent = file.type.includes("pdf") ? "📄" : "📎";
fpThumb.style.display = "none";
}
}
document.getElementById("msgInput").focus();
};
reader.readAsDataURL(file);
}
function clearFile() {
pendingFile = null;
document.getElementById("fileInput").value = "";
var bar = document.getElementById("filePreviewBar");
if (bar) bar.classList.remove("visible");
var fpThumb = document.getElementById("fpThumb");
if (fpThumb) { fpThumb.style.display = "none"; fpThumb.src = ""; }
var old = document.getElementById("filePreview");
if (old) old.style.display = "none";
}
// ─── Drag & Drop (fullscreen-style) ───
var dropOverlay = document.getElementById("dropOverlay");
var dragCounter = 0;
document.addEventListener("dragenter", function(e) { e.preventDefault(); dragCounter++; if(dropOverlay) dropOverlay.classList.add("visible"); });
document.addEventListener("dragleave", function(e) { e.preventDefault(); dragCounter--; if(dragCounter <= 0) { if(dropOverlay) dropOverlay.classList.remove("visible"); dragCounter = 0; } });
document.addEventListener("dragover", function(e) { e.preventDefault(); });
document.addEventListener("drop", function(e) {
e.preventDefault(); dragCounter = 0; if(dropOverlay) dropOverlay.classList.remove("visible");
if (e.dataTransfer.files.length) {
document.getElementById("fileInput").files = e.dataTransfer.files;
handleFile(document.getElementById("fileInput"));
}
});
// ─── Paste images ───
document.addEventListener("paste", function(e) {
var items = Array.from((e.clipboardData || {}).items || []);
items.forEach(function(item) {
if (item.type.startsWith("image")) {
var file = item.getAsFile();
if (file) {
var dt = new DataTransfer(); dt.items.add(file);
document.getElementById("fileInput").files = dt.files;
handleFile(document.getElementById("fileInput"));
}
}
});
});
// ─── Auto-resize textarea ───
document.getElementById('msgInput').addEventListener('input', function() {
this.style.height = 'auto';
this.style.height = Math.min(this.scrollHeight, 120) + 'px';
});
// ═══ SIDEBAR ═══
function toggleSidebar() {
var sb = document.getElementById('sidebar');
sb.classList.toggle('collapsed');
localStorage.setItem('sb_collapsed', sb.classList.contains('collapsed'));
}
function newChat() {
session = 'web_' + Date.now().toString(36);
localStorage.setItem('wevia_session', session);
convId = null; chatHistory = [];
document.getElementById('messages').innerHTML = '';
document.querySelectorAll('.sb-item.active').forEach(function(el) { el.classList.remove('active'); });
}
function loadConversations() {
fetch('/api/sovereign/v1/chat/completions', {
method: 'POST', headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ action: 'list_conversations', session: localStorage.getItem('wevia_session') || session })
}).then(function(r) { return r.json(); }).then(function(data) {
var list = document.getElementById('sbList');
var convs = data.conversations || [];
if (!convs.length) { list.innerHTML = '<div class="sb-empty">Aucune discussion</div>'; return; }
list.innerHTML = '';
convs.forEach(function(cv) {
var d = document.createElement('div');
d.className = 'sb-item' + (convId == cv.id ? ' active' : '');
d.setAttribute('data-id', cv.id);
d.setAttribute('data-title', (cv.title || '').toLowerCase());
var ago = sbTimeAgo(cv.created_at);
d.innerHTML = (cv.title || 'Discussion') + '<span class="sb-time">' + ago + '</span>';
d.onclick = function() { openConv(cv.id); };
list.appendChild(d);
});
}).catch(function() {
document.getElementById('sbList').innerHTML = '<div class="sb-empty">Erreur chargement</div>';
});
}
function openConv(cid) {
fetch('/api/sovereign/v1/chat/completions', {
method: 'POST', headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ action: 'get_conversation', conversation_id: cid })
}).then(function(r) { return r.json(); }).then(function(data) {
convId = cid; chatHistory = [];
var msgs = document.getElementById('messages');
msgs.innerHTML = '';
(data.messages || []).forEach(function(m) {
chatHistory.push({role: m.role, content: m.content});
if (m.role === 'user') {
msgs.innerHTML += '<div class="msg user"><div class="bubble">' + (m.content||'').replace(/</g,'&lt;').replace(/>/g,'&gt;') + '</div></div>';
} else {
msgs.innerHTML += '<div class="msg assistant"><div class="msg-inner"><div class="bubble">' + formatMd(m.content) + '</div></div></div>';
}
});
msgs.scrollTop = msgs.scrollHeight;
document.querySelectorAll('.sb-item').forEach(function(el) {
el.classList.toggle('active', el.getAttribute('data-id') == cid);
});
});
}
function filterConvs(q) {
q = q.toLowerCase();
document.querySelectorAll('.sb-item').forEach(function(el) {
el.style.display = !q || (el.getAttribute('data-title')||'').includes(q) ? '' : 'none';
});
}
function sbTimeAgo(dt) {
if (!dt) return '';
var diff = (Date.now() - new Date(dt).getTime()) / 1000;
if (diff < 60) return "maintenant";
if (diff < 3600) return Math.floor(diff/60) + ' min';
if (diff < 86400) return Math.floor(diff/3600) + 'h';
if (diff < 604800) return Math.floor(diff/86400) + 'j';
return new Date(dt).toLocaleDateString('fr-FR', {day:'numeric',month:'short'});
}
// Auto-init
(function() {
if (localStorage.getItem('sb_collapsed') === 'true') {
var sb = document.getElementById('sidebar');
if (sb) sb.classList.add('collapsed');
}
setTimeout(loadConversations, 500);
})();
// ═══ RLHF ═══
function sendFeedback(btn, rating, idx) {
btn.parentElement.querySelectorAll('.fb-btn').forEach(function(b) { b.classList.add('voted'); });
btn.classList.add(rating);
var msgs = document.querySelectorAll('.msg');
var q='', r='';
for (var i=msgs.length-1; i>=0; i--) {
if (msgs[i].classList.contains('assistant') && !r) { var b=msgs[i].querySelector('.bubble-ai')||msgs[i].querySelector('.bubble'); r=b?b.textContent.substring(0,500):''; }
if (msgs[i].classList.contains('user') && !q) { var b2=msgs[i].querySelector('.bubble'); q=b2?b2.textContent.substring(0,200):''; }
if (q && r) break;
}
fetch('/api/sovereign/v1/chat/completions', { method:'POST', headers:{'Content-Type':'application/json'},
body: JSON.stringify({action:'feedback', rating: rating==='up'?1:-1, message_id:convId, question:q, answer:r, session:session, provider:''})
}).catch(function(){});
}
// ===== BANANA VISUAL ENGINE v3.0 =====
// Nano Banana 2 (Photo) + Veo (Video) + Banana Edit
window.BananaEngine = {
_active: false,
providers: {
photo: { name: 'Nano Banana 2', endpoint: '/wevia-ia/wevia-image.php', badge: '🍌 Banana Photo' },
video: { name: 'Veo Engine', endpoint: '/wevia-ia/wevia-image.php', badge: '🎬 Veo Video' },
edit: { name: 'Banana Edit', endpoint: '/wevia-ia/wevia-image.php', badge: '✏️ Banana Edit' }
},
detect: function(text) {
var t = text.toLowerCase();
if(/vid[eé]o|animation|anim[eé]|clip|motion|cinematic/i.test(t)) return 'video';
if(/modifi|change|retouche|edit|ajout.*sur|remplace/i.test(t)) return 'edit';
if(/photo|image|portrait|illustration|dessin|logo|affiche|poster|g[eé]n[eé]r.*image|cr[eé].*image|fais.*image|montre.*moi/i.test(t)) return 'photo';
return null;
},
render: function(type, prompt, container) {
var prov = this.providers[type];
// Show studio overlay
container.innerHTML = '<div class="banana-studio">' +
'<div class="scan-line"></div>' +
'<div style="text-align:center;padding:20px">' +
'<span class="banana-badge">' + prov.badge + '</span>' +
'<p style="color:#facc15;margin:12px 0;font-size:13px">Génération en cours...</p>' +
'<div class="banana-progress"><div class="banana-progress-bar" style="width:15%"></div></div>' +
'</div></div>';
// Animate progress
var bar = container.querySelector('.banana-progress-bar');
var progress = 15;
var timer = setInterval(function(){ progress = Math.min(progress + Math.random()*8, 90); bar.style.width = progress+'%'; }, 300);
// Call API
var formData = new FormData();
formData.append('prompt', prompt);
formData.append('type', type);
formData.append('quality', 'ultra');
formData.append('session', window.session || '');
// Client-side generation via Pollinations (free, no server needed)
if(type === 'photo' || type === 'edit') {
var enriched = prompt + ', photorealistic, 8k, highly detailed, professional lighting';
var imgUrl = '/wevia-ia/wevia-image.php?prompt=' + encodeURIComponent(enriched);
var img = new Image();
img.onload = function() {
clearInterval(timer);
bar.style.width = '100%';
setTimeout(function(){
var typeBadge = '<span class="banana-type-badge photo">' + prov.badge + '</span>';
container.innerHTML = '<div class="banana-result">' + typeBadge + '<img src="' + imgUrl + '" style="max-width:100%;border-radius:10px"><br><a href="' + imgUrl + '" download="banana-render.png" target="_blank" style="color:#facc15;font-size:12px;margin-top:8px;display:inline-block">⬇ Télécharger</a></div>';
}, 400);
};
img._retries = 0; img.onerror = function() { if(this._retries < 4) { this._retries++; var self=this; setTimeout(function(){ self.src = imgUrl + '&r=' + Date.now(); }, 5000); return; }
clearInterval(timer);
container.innerHTML = '<div style="color:#ef4444;padding:12px">⚠️ Génération en cours, réessayez dans quelques secondes...</div>';
};
img.src = imgUrl;
return Promise.resolve();
}
// Fallback to server for video
return fetch(prov.endpoint, { method: 'POST', body: formData })
.then(function(r){ return r.json(); })
.then(function(data){
clearInterval(timer);
bar.style.width = '100%';
setTimeout(function(){
if(data.url || data.image_url) {
var url = data.url || data.image_url;
var typeBadge = '<span class="banana-type-badge ' + type + '">' + prov.badge + '</span>';
if(type === 'video') {
container.innerHTML = '<div class="banana-result">' + typeBadge + '<video src="' + url + '" controls autoplay style="max-width:100%;border-radius:10px"></video></div>';
} else {
container.innerHTML = '<div class="banana-result">' + typeBadge + '<img src="' + url + '" alt="Banana render" style="max-width:100%;border-radius:10px"><br><a href="' + url + '" download style="color:#facc15;font-size:12px;margin-top:8px;display:inline-block">⬇ Télécharger</a></div>';
}
} else if(data.error) {
container.innerHTML = '<div style="color:#ef4444;padding:12px">⚠️ ' + data.error + '</div>';
}
}, 500);
})
.catch(function(err){
clearInterval(timer);
container.innerHTML = '<div style="color:#ef4444;padding:12px">⚠️ Erreur réseau: ' + err.message + '</div>';
});
}
};
// ===== GRAPH-MASTER ENGINE =====
(function(){
// Init Mermaid
if(window.mermaid) {
mermaid.initialize({ startOnLoad:false, theme:'dark', themeVariables:{ primaryColor:'#6366f1', primaryTextColor:'#fff', primaryBorderColor:'#818cf8', lineColor:'#94a3b8', secondaryColor:'#1e293b', tertiaryColor:'#0f172a' }});
}
// Detect and render graphs in chatbot responses
window.renderGraphs = function(container) {
if(!container) return;
// If backend PNG exists, just hide raw code blocks
var hasPNG = container.querySelector('img[src*="merm_"]');
if(hasPNG) {
container.querySelectorAll('code.language-mermaid, pre code').forEach(function(b) {
var code = b.textContent.trim();
if(code.match(/^(graph|flowchart|sequence|gantt|pie)/)) {
var pre = b.closest('pre') || b; pre.style.display = 'none';
}
});
hasPNG.classList.add('organic-reveal');
return;
}
// No PNG — try client-side mermaid render with error handling
var blocks = container.querySelectorAll('code.language-mermaid, pre code');
blocks.forEach(function(block) {
var code = block.textContent.trim();
if(code.match(/^(graph|flowchart|sequence|gantt|pie|class|state|er|journey|mind|git)/)) {
var wrap = document.createElement('div');
wrap.className = 'wevia-graph';
wrap.innerHTML = '<span class="wevia-graph-badge">Graph</span><div class="mermaid">' + code + '</div>';
var pre = block.closest('pre') || block;
pre.parentNode.replaceChild(wrap, pre);
if(window.mermaid) {
try {
mermaid.run({nodes: wrap.querySelectorAll('.mermaid')}).catch(function(e) {
var md = wrap.querySelector('.mermaid');
if(md) md.innerHTML = '<div style="padding:20px;background:rgba(99,102,241,0.08);border-radius:12px;color:var(--tx2);font-size:13px">Le diagramme sera disponible sous forme d image ci-dessous.</div>';
});
} catch(e) {
var md = wrap.querySelector('.mermaid');
if(md) md.innerHTML = '<div style="padding:20px;background:rgba(99,102,241,0.08);border-radius:12px;color:var(--tx2);font-size:13px">Diagramme en cours de traitement...</div>';
}
}
}
});
// 2. SVG detection (Ishikawa, custom)
var svgBlocks = container.querySelectorAll('code.language-svg');
svgBlocks.forEach(function(block) {
var wrap = document.createElement('div');
wrap.className = 'wevia-graph';
wrap.innerHTML = '<span class="wevia-graph-badge">🎨 SVG Render</span>' + block.textContent;
var pre = block.closest('pre') || block;
pre.parentNode.replaceChild(wrap, pre);
});
};
// Graph hooks moved to unified post-processor
})();
// Auto-trigger Banana for image-related responses
// Banana hooks removed (unified)
// EXPORT PRO - Download conversation as formatted document
window.exportChat = function(format) {
var msgs = document.querySelectorAll('.msg');
var content = [];
msgs.forEach(function(m) {
var bubble = m.querySelector('.bubble-ai,.bubble');
if(bubble) content.push(bubble.innerText || bubble.textContent);
});
var text = content.join('\n\n---\n\n');
if(format === 'txt') {
var blob = new Blob([text], {type:'text/plain'});
var a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = 'WEVIA_Export_' + new Date().toISOString().slice(0,10) + '.txt';
a.click();
} else if(format === 'md') {
var md = '# WEVIA Export\n\n' + text;
var blob = new Blob([md], {type:'text/markdown'});
var a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = 'WEVIA_Export_' + new Date().toISOString().slice(0,10) + '.md';
a.click();
} else if(format === 'html') {
var html = '<html><head><title>WEVIA Export</title><style>body{font-family:system-ui;max-width:800px;margin:40px auto;padding:20px;color:#1e293b}h1{color:#6366f1}hr{border:none;border-top:1px solid #e2e8f0;margin:20px 0}</style></head><body><h1>🧠 WEVIA Export</h1><p style="color:#64748b">' + new Date().toLocaleString() + '</p>';
content.forEach(function(c){ html += '<div style="background:#f8fafc;padding:16px;border-radius:8px;margin:12px 0;line-height:1.6">' + c.replace(/\n/g,'<br>') + '</div>'; });
html += '<\/body></html>';
var blob = new Blob([html], {type:'text/html'});
var a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = 'WEVIA_Export_' + new Date().toISOString().slice(0,10) + '.html';
a.click();
}
};
// ===== UNIFIED POST-PROCESSOR (Graph + Banana) =====
(function(){
var _realAddMsg = window.addMsg;
if(!_realAddMsg) return;
window.addMsg = function(role, text, time) {
var el = _realAddMsg.call(this, role, text, time);
if(role === 'assistant' && el && text) {
// Graph rendering
if(window.renderGraphs) setTimeout(function(){ renderGraphs(el); }, 150);
// Banana auto-trigger
var bm = text.match(/\[BANANA:(photo|video|edit)\](.+?)\[\/BANANA\]/);
if(bm && window.BananaEngine) {
var imgDiv = document.createElement('div');
imgDiv.style.margin = '12px 0';
var bubble = el.querySelector('.bubble-ai,.bubble,.msg-inner');
if(bubble) { bubble.appendChild(imgDiv); BananaEngine.render(bm[1], bm[2], imgDiv); }
}
}
return el;
};
})();
// ===== KEYBOARD SHORTCUTS =====
document.addEventListener('keydown', function(e) {
var input = document.getElementById('msgInput');
// Ctrl+Enter = send
if(e.ctrlKey && e.key === 'Enter' && document.activeElement === input) {
e.preventDefault();
document.getElementById('sendBtn').click();
}
// Escape = stop TTS + clear input
if(e.key === 'Escape') {
if(window._ttsAudio) { try{window._ttsAudio.pause();}catch(x){} window._ttsAudio=null; }
if(window.speechSynthesis) window.speechSynthesis.cancel();
}
// Ctrl+Shift+D = toggle dark mode
if(e.ctrlKey && e.shiftKey && e.key === 'D') { e.preventDefault(); toggleTheme(); }
});
function useSuggestion(btn) {
var text = btn.textContent.replace(/^[^\s]+\s/, ''); // Remove emoji prefix
document.getElementById('msgInput').value = text;
document.getElementById('msgInput').focus();
document.getElementById('suggestedPrompts').style.display = 'none';
}
/* === UX: Follow-up chips - DYNAMIC per response === */
function generateFollowups(query, response) {
var chips = [];
var q = (query || '').toLowerCase();
var r = (response || '').substring(0, 2000).toLowerCase();
var qOrig = (query || '');
// Extract topic from user query (remove stopwords)
var stopwords = 'le la les de du des un une et ou en est que qui pour sur avec dans ce cette son mon pas plus tout tres bien comment quoi quel quelle peux tu me moi nous faire fait donne explique genere genre cree montre schema diagramme image photo fais'.split(' ');
var words = qOrig.split(/\s+/).filter(function(w){return w.length>2 && stopwords.indexOf(w.toLowerCase())===-1});
var topic = words.slice(0,3).join(' ') || 'ce sujet';
var shortTopic = words.slice(0,2).join(' ') || 'ce sujet';
// Detect response type
var hasImage = /img_|downloads\/img|image.genere|\!\[Image/.test(response||'');
var hasViz = /viz_|iframe|interactif|Interactive/.test(response||'');
var hasMermaid = /downloads\/merm_|mermaid\.ink|diagramme|flowchart/.test(response||'');
var hasCode = false;
var rawR = (response||'').substring(0,3000);
// Only count as "code" if there's a code block that's NOT mermaid or svg
var codeBlocks = rawR.match(/```(\w*)/g) || [];
for (var ci = 0; ci < codeBlocks.length; ci++) {
var cLang = codeBlocks[ci].replace('```','').toLowerCase();
if (cLang !== 'mermaid' && cLang !== 'svg' && cLang !== '') hasCode = true;
}
// Also check for inline code patterns (not just blocks)
if (!hasCode && /def |function |class |import |require|console\.log|echo |SELECT |INSERT /.test(rawR)) hasCode = true;
var hasTable = /\|.*\|.*\|/.test(response||'');
var hasList = (response||'').split('\n').filter(function(l){return /^\s*[-\*\d]/.test(l)}).length > 3;
// Dynamic chips based on WHAT was generated + WHAT was asked
if (hasViz) {
chips = [
'Exporter en PDF haute qualit\u00e9',
'Version anim\u00e9e avec transitions',
'Adapter pour pr\u00e9sentation PPTX'
];
} else if (hasImage) {
chips = [
'Régénérer dans un autre style',
'Ajouter du texte sur cette image',
'Version en haute résolution'
];
} else if (hasMermaid) {
chips = [
'Détailler ce schéma en sous-systèmes',
'Ajouter les métriques et KPIs',
'Exporter ce schéma en PDF'
];
} else if (hasCode && /python|php|java|node|react/.test(r)) {
var lang = 'ce code';
if (/python/.test(r)) lang = 'ce script Python';
else if (/php/.test(r)) lang = 'ce code PHP';
else if (/javascript|node|react/.test(r)) lang = 'ce code JS';
chips = [
'Optimiser ' + lang,
'Ajouter les tests unitaires',
'Documenter chaque fonction'
];
} else if (hasCode) {
chips = [
'Expliquer ce code pas \u00e0 pas',
'Am\u00e9liorer les performances',
''
];
} else if (/sap|s\/4|hana|fiori|vistex/.test(r)) {
chips = [
'ROI concret de ' + shortTopic,
'Planning de migration detaille',
'Risques et att\u00e9nuations'
];
} else if (/pharma|medicament|clinical|gmp|bpf/.test(r)) {
chips = [
'R\u00e9glementation applicable \u00e0 ' + shortTopic,
'Cas concret en industrie pharma',
'Impact sur la supply chain'
];
} else if (/marketing|email|campagne|conversion/.test(r)) {
chips = [
'Optimiser le taux de conversion',
'Strat\u00e9gie A/B test pour ' + shortTopic,
'Benchmark du secteur'
];
} else if (/security|owasp|vuln|audit|pentest/.test(r)) {
chips = [
'Plan de rem\u00e9diation prioritis\u00e9',
'Checklist conformit\u00e9 ' + shortTopic,
'Estimation du risque r\u00e9siduel'
];
} else if (/cloud|aws|azure|kubernetes|docker/.test(r)) {
chips = [
'Estimation des co\u00fbts ' + shortTopic,
'Architecture haute disponibilit\u00e9',
'Comparatif des alternatives'
];
} else if (/financ|budget|bilan|marge|investis|cout/.test(r)) {
chips = [
'Projection sur 3 ans',
'Analyse de sensibilit\u00e9',
'Tableau comparatif chiffr\u00e9'
];
} else if (/strateg|swot|pestel|business|canvas/.test(r)) {
chips = [
'Analyse SWOT de ' + shortTopic,
'Quick wins immediats',
'Feuille de route \u00e0 6 mois'
];
} else if (hasTable) {
chips = [
'Analyse les tendances',
'Ajoute des recommandations',
'Exporte en format Excel'
];
} else if (hasList) {
chips = [
'D\u00e9tailler le point cl\u00e9',
'Comparer ces \u00e9l\u00e9ments',
'Prioriser par impact'
];
} else {
// Dynamic fallback based on actual query
chips = [
'Approfondir ' + shortTopic,
'Exemple concret pour ' + topic,
'Comparer avec des alternatives'
];
}
// Create chips HTML
var container = document.createElement('div');
container.className = 'followup-chips';
chips.forEach(function(c) {
var btn = document.createElement('button');
btn.className = 'followup-chip';
btn.textContent = c;
btn.onclick = function() {
document.getElementById('msgInput').value = c;
sendMsg();
container.remove();
};
container.appendChild(btn);
});
document.getElementById('messages').appendChild(container);
var m = document.getElementById('messages');
m.scrollTo({top: m.scrollHeight, behavior: 'smooth'});
}
/* === UX: Smart thinking steps based on query === */
function addSmartThinkSteps(query) {
var q = (query || '').toLowerCase();
var steps = [];
if (q.includes('image') || q.includes('photo') || q.includes('dessine') || q.includes('génère')) {
steps = [{t:500,m:'🎨 Activation du moteur visuel...'},{t:1500,m:'🖼️ Composition de l\'image...'},{t:3000,m:'✨ Finalisation du rendu...'}];
} else if (q.includes('code') || q.includes('script') || q.includes('python') || q.includes('php')) {
steps = [{t:400,m:'💻 Analyse du contexte technique...'},{t:1200,m:'🔧 Génération du code...'},{t:2500,m:'✅ Vérification syntaxique...'}];
} else if (q.includes('schéma') || q.includes('diagram') || q.includes('ishikawa') || q.includes('mermaid')) {
steps = [{t:400,m:'📊 Initialisation Graph...'},{t:1500,m:'🔗 Construction des relations...'},{t:2800,m:'🎨 Rendu visuel...'}];
} else if (q.includes('audit') || q.includes('sécurité') || q.includes('owasp')) {
steps = [{t:400,m:'🛡️ Scan de sécurité lancé...'},{t:1500,m:'🔍 Analyse des vulnérabilités...'},{t:3000,m:'📋 Compilation du rapport...'}];
} else if (q.includes('analyse') || q.includes('compare') || q.includes('recherche')) {
steps = [{t:400,m:'📚 Consultation de la base de connaissances...'},{t:1500,m:'🌐 Recherche web enrichie...'},{t:2800,m:'🧠 Synthèse des sources...'}];
} else {
steps = [{t:500,m:'🧠 Analyse de votre demande...'},{t:1500,m:'📚 Recherche dans la KB...'},{t:2800,m:'✍️ Rédaction de la réponse...'}];
}
window._thinkTimers = [];
steps.forEach(function(s) {
var timer = setTimeout(function() {
var body = document.getElementById('thinkBody');
if (body) {
var step = document.createElement('div');
step.className = 'think-step';
step.style.cssText = 'padding:3px 0;font-size:12px;color:var(--tx2);opacity:0;transition:opacity .3s;';
step.textContent = s.m;
body.appendChild(step);
setTimeout(function(){ step.style.opacity = '1'; }, 50);
}
}, s.t);
window._thinkTimers.push(timer);
});
}
</script>
</div><!-- /main-content -->
</div><!-- /app-layout -->
<script defer src=/assets/wevia-qp.js></script><link rel=stylesheet href=/assets/wevia-qp.css><script defer src="/js/wevia-sse-override.js?v=1776817848"></script><!-- CARTO_REMOVED -->
<!-- UI enhancement layer -->
<script>
(function(){
if (window.__opusUniversalDrill) return; window.__opusUniversalDrill = true;
var d = document;
var m = d.createElement('div');
m.id = 'opus-udrill';
m.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.82);backdrop-filter:blur(6px);display:none;align-items:center;justify-content:center;z-index:99995;padding:20px;cursor:pointer';
var inner = d.createElement('div');
inner.id = 'opus-udrill-in';
inner.style.cssText = 'max-width:900px;width:100%;max-height:90vh;overflow:auto;background:#0b0d15;border:1px solid rgba(99,102,241,0.35);border-radius:14px;padding:28px;cursor:default;box-shadow:0 20px 60px rgba(0,0,0,0.6);color:#e2e8f0;font:14px/1.55 Inter,system-ui,sans-serif';
inner.addEventListener('click', function(e){ e.stopPropagation(); });
m.appendChild(inner);
m.addEventListener('click', function(){ m.style.display='none'; });
d.addEventListener('keydown', function(e){ if(e.key==='Escape') m.style.display='none'; });
(d.body || d.documentElement).appendChild(m);
function openCard(card) {
var html = '<div style="display:flex;justify-content:flex-end;margin-bottom:14px"><button id="opus-udrill-close" style="padding:6px 14px;background:#171b2a;border:1px solid rgba(99,102,241,0.25);color:#e2e8f0;border-radius:8px;cursor:pointer;font-size:12px">✕ Fermer (Esc)</button></div>';
html += '<div style="transform-origin:top left;font-size:1.05em">' + card.outerHTML + '</div>';
inner.innerHTML = html;
d.getElementById('opus-udrill-close').onclick = function(){ m.style.display='none'; };
m.style.display = 'flex';
}
function wire(root) {
var sels = '.card,[class*="card"],.kpi,[class*="kpi"],.stat,[class*="stat"],.tile,[class*="tile"],.metric,[class*="metric"],.widget,[class*="widget"]';
var cards = root.querySelectorAll(sels);
for (var i = 0; i < cards.length; i++) {
var c = cards[i];
if (c.__opusWired) continue;
if (c.closest('button, a, input, select, textarea, #opus-udrill')) continue;
var r = c.getBoundingClientRect();
if (r.width < 60 || r.height < 40) continue;
c.__opusWired = true;
c.style.cursor = 'pointer';
c.setAttribute('role','button');
c.setAttribute('tabindex','0');
c.addEventListener('click', function(ev){
if (ev.target.closest('[data-pp-id]') && window.__opusDrillInit) return;
if (ev.target.closest('a,button,input,select')) return;
ev.preventDefault(); ev.stopPropagation();
openCard(this);
});
c.addEventListener('keydown', function(ev){ if(ev.key==='Enter'||ev.key===' '){ev.preventDefault();openCard(this);} });
}
}
var initRun = function(){ wire(d.body || d.documentElement); };
if (d.readyState === 'loading') d.addEventListener('DOMContentLoaded', initRun);
else initRun();
var mo = new MutationObserver(function(muts){
var newCard = false;
for (var i=0;i<muts.length;i++) if (muts[i].addedNodes.length) { newCard = true; break; }
if (newCard) initRun();
});
mo.observe(d.body || d.documentElement, {childList:true, subtree:true});
})();
</script>
<!-- end enhancement -->
<script src="/api/a11y-auto-enhancer.js" defer></script>
<!-- nav dock --><script src="/wtp-unified-dock.js" defer></script>
<script src="/opus-antioverlap-doctrine.js?v=1776776094" defer></script>
<!-- Opus v17 · WEVIA-Pattern SSE (auto-injected) -->
<style id="opus-pattern-style">
#opus-pattern-badge{position:fixed;bottom:20px;right:20px;z-index:99990;
background:linear-gradient(135deg,#06b6d4,#8b5cf6);color:#fff;
padding:10px 16px;border-radius:20px;font:700 0.78rem -apple-system,sans-serif;
cursor:pointer;box-shadow:0 4px 16px rgba(0,0,0,0.35);transition:all 0.2s;
display:flex;align-items:center;gap:6px}
#opus-pattern-badge:hover{transform:translateY(-2px);box-shadow:0 6px 20px rgba(6,182,212,0.4)}
#opus-pattern-modal{display:none;position:fixed;inset:0;background:rgba(0,0,0,0.8);
z-index:99991;align-items:center;justify-content:center;padding:20px}
#opus-pattern-modal.show{display:flex}
#opus-pattern-box{background:#0b0d15;color:#e2e8f0;border:1px solid rgba(6,182,212,0.3);
border-radius:14px;padding:22px;max-width:820px;width:100%;max-height:85vh;overflow:auto;
font:-apple-system,sans-serif}
#opus-pattern-box h3{font:800 1.2rem;margin-bottom:12px;
background:linear-gradient(135deg,#06b6d4,#ec4899);
-webkit-background-clip:text;-webkit-text-fill-color:transparent}
#opus-pattern-input{width:100%;background:#1a1f3a;color:#fff;border:1px solid rgba(100,116,139,0.3);
border-radius:8px;padding:10px;margin-bottom:10px;font:0.9rem -apple-system}
#opus-pattern-run{background:linear-gradient(135deg,#10b981,#06b6d4);color:#fff;border:0;
padding:10px 20px;border-radius:8px;font:700 0.85rem;cursor:pointer;margin-bottom:14px}
.phase-card{background:rgba(15,23,42,0.8);border:1px solid rgba(100,116,139,0.2);
border-left:3px solid #06b6d4;border-radius:8px;padding:10px 14px;margin-bottom:8px;
font-size:0.82rem}
.phase-card.done{border-left-color:#22c55e}
.phase-card.active{border-left-color:#f59e0b;animation:pulse 1.2s ease infinite}
.phase-name{font-weight:800;color:#06b6d4;margin-bottom:4px;font-size:0.78rem;text-transform:uppercase;letter-spacing:1px}
.phase-data{font-size:0.72rem;color:#94a3b8;font-family:ui-monospace,monospace}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:0.6}}
#opus-pattern-close{position:absolute;top:14px;right:20px;background:0;border:0;color:#94a3b8;
font-size:1.6rem;cursor:pointer}
</style>
<div id="opus-pattern-badge" onclick="window.__opusPatternOpen()">
<span>🧠</span><span>WEVIA-Pattern</span>
</div>
<div id="opus-pattern-modal" onclick="if(event.target.id==='opus-pattern-modal')window.__opusPatternClose()">
<div id="opus-pattern-box">
<button id="opus-pattern-close" onclick="window.__opusPatternClose()">×</button>
<h3>🧠 WEVIA-Pattern · 7 phases REAL (SSE live)</h3>
<p style="font-size:0.82rem;color:#94a3b8;margin-bottom:12px">Backend: <b id="opus-pattern-bot">wevia</b> · anti-hallucination · langue naturelle</p>
<input id="opus-pattern-input" placeholder="Posez une question (FR ou EN)..." value="bonjour quel est le statut" />
<button id="opus-pattern-run" onclick="window.__opusPatternRun()">▶ Lancer (SSE stream)</button>
<div id="opus-pattern-output"></div>
</div>
</div>
<script>
(function(){
const BOT = 'wevia';
window.__opusPatternOpen = () => document.getElementById('opus-pattern-modal').classList.add('show');
window.__opusPatternClose = () => document.getElementById('opus-pattern-modal').classList.remove('show');
window.__opusPatternRun = () => {
const msg = document.getElementById('opus-pattern-input').value.trim();
if (!msg) return;
const out = document.getElementById('opus-pattern-output');
out.innerHTML = '';
const OPUS_SESSION_KEY = 'opus_chatbot_session_' + BOT;
let sess = sessionStorage.getItem(OPUS_SESSION_KEY);
if (!sess) {
sess = 'opus-' + BOT + '-' + Date.now().toString(36) + '-' + Math.random().toString(36).substr(2, 6);
sessionStorage.setItem(OPUS_SESSION_KEY, sess);
}
const url = '/api/wevia-pattern-sse.php?message=' + encodeURIComponent(msg) + '&chatbot=' + encodeURIComponent(BOT) + '&session=' + encodeURIComponent(sess);
const es = new EventSource(url);
const phases = {};
const order = ['thinking','plan','rag','execute','tests','response','critique','done'];
order.forEach(p => {
const card = document.createElement('div');
card.className = 'phase-card';
card.id = 'phase-' + p;
card.innerHTML = '<div class="phase-name">' + p.toUpperCase() + '</div><div class="phase-data">⏳ waiting...</div>';
out.appendChild(card);
});
order.forEach(evName => {
es.addEventListener(evName, (e) => {
const data = JSON.parse(e.data);
const card = document.getElementById('phase-' + evName);
if (card) {
card.classList.add('done');
card.classList.remove('active');
let txt;
if (evName === 'response' && data.text) {
txt = '<div style="background:rgba(6,182,212,0.1);padding:10px;border-radius:6px;margin-top:6px;color:#e2e8f0;font-size:0.82rem">' + (data.text.substring(0, 600)) + (data.text.length > 600 ? '...' : '') + '</div>';
} else if (evName === 'tests') {
txt = '<div>' + data.passed + '/' + data.total + ' tests ✓</div>';
} else if (evName === 'critique') {
txt = '<div>Quality: <b style="color:' + (data.quality === 'EXCELLENT' ? '#22c55e' : (data.quality === 'OK' ? '#f59e0b' : '#ef4444')) + '">' + data.quality + '</b> (' + (data.quality_score * 5).toFixed(0) + '/5)</div>';
} else {
txt = JSON.stringify(data).substring(0, 300);
}
card.querySelector('.phase-data').innerHTML = txt;
}
if (evName === 'done') es.close();
});
});
es.addEventListener('error', () => es.close());
};
})();
</script>
</body>
</html>