2625 lines
159 KiB
HTML
2625 lines
159 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="fr">
|
||
<head>
|
||
<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="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;
|
||
}
|
||
|
||
</style></style></style>
|
||
|
||
<!-- GRAPH-MASTER LIBS -->
|
||
<link rel=stylesheet href=/assets/wevia-v2.css>
|
||
</head>
|
||
<body class="light">
|
||
<!-- BETON-DOCTRINE-101 dual-dummy block (pages pub) -->
|
||
<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">
|
||
<button type="button" class="sb-item" onclick="wvShortcut('wevcode Planifie et execute: ')" title="Mode plan + execute autonome (SSE streaming)">⚙️ WevCode</button>
|
||
<button type="button" class="sb-item" onclick="wvShortcut('Genere un PDF professionnel sur: ')" title="Genere un PDF">📄 PDF</button>
|
||
<button type="button" class="sb-item" onclick="wvShortcut('Genere un schema mermaid pour: ')" title="Diagramme Mermaid">📊 Schema</button>
|
||
<button type="button" class="sb-item" onclick="wvShortcut('Genere un composant React pour: ')" title="Composant React">⚛️ React</button>
|
||
<button type="button" class="sb-item" onclick="wvShortcut('Ecris le code pour: ')" title="Code / Script">💻 Code</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="/wevia-ia/logo-wevia.svg" alt="WEVIA" style="width:41px;height:41px;border-radius:9px;box-shadow:0 2px 8px rgba(124,58,237,0.3)" class="wevia-logo-img"></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>
|
||
// ─── 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,'<').replace(/>/g,'>') + '</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&&/mermaid/i.test(e.message)) 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\ud83d\udd2c', deep: 'Approfondie \ud83d\udd2c', 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);
|
||
|
||
// Call fast LLM for real reasoning about this specific query
|
||
fetch('/api/sovereign/v1/chat/completions', {
|
||
method: 'POST',
|
||
headers: {'Content-Type': 'application/json'},
|
||
body: JSON.stringify({ message: q, language: 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) {
|
||
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() {
|
||
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;
|
||
|
||
// 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,'"') + '">🔍 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,'"') + '">📖 ' + 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, '&').replace(/</g, '<').replace(/>/g, '>');
|
||
|
||
// ═══ 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, ' ');
|
||
try { mermCode = mermCode.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); } catch(e) {}
|
||
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,'&').replace(/</g,'<').replace(/>/g,'>');
|
||
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(/&/g,'&').replace(/</g,'<').replace(/>/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(/&/g,'&').replace(/</g,'<').replace(/>/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(/&/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(/^>\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, "<") + '</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,'<').replace(/>/g,'>') + '</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-Master</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-Master 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-Master...'},{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></script><!-- CARTO_REMOVED -->
|
||
|
||
<!-- === OPUS UNIVERSAL DRILL-DOWN v1 19avr — append-only, doctrine #14 === -->
|
||
<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>
|
||
<!-- === OPUS UNIVERSAL DRILL-DOWN END === -->
|
||
|
||
|
||
<script src="/api/a11y-auto-enhancer.js" defer></script>
|
||
<!-- WTP_UDOCK_V1 (Opus 21-avr tour30) --><script src="/wtp-unified-dock.js" defer></script>
|
||
</body>
|
||
</html>
|