wasrusgen1-crm/docs/elena_live.html

207 lines
13 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Елена — живой кабинет · @wasrusgen1 | КОНСАЛТИНГ</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Montserrat:wght@700;800&display=swap" rel="stylesheet">
<style>
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
:root{--primary:#047857;--dark:#064E3B;--mid:#10B981;--light:#ECFDF5;--bg:#F5F6F8;--white:#fff;--border:#E5E7EB;--text:#1A1A2E;--muted:#6B7280}
html,body{height:100%}
body{font-family:'Inter',sans-serif;background:var(--bg);color:var(--text);display:flex;flex-direction:column;height:100vh}
.hdr{height:56px;background:var(--dark);display:flex;align-items:center;padding:0 20px;gap:12px;flex-shrink:0}
.hdr-ic{width:32px;height:32px;background:var(--primary);border-radius:8px;display:flex;align-items:center;justify-content:center;font-family:'Montserrat';font-weight:800;color:#fff;font-size:17px}
.hdr-t{font-family:'Montserrat';font-weight:700;font-size:15px;color:#fff}
.hdr-t em{color:rgba(255,255,255,.4);font-style:normal;margin:0 5px}
.hdr-elena{margin-left:auto;display:flex;align-items:center;gap:8px;color:rgba(255,255,255,.85);font-size:13px;font-weight:600}
.hdr-dot{width:8px;height:8px;border-radius:50%;background:var(--mid);box-shadow:0 0 6px var(--mid)}
.wrap{flex:1;display:flex;overflow:hidden}
.chat-col{flex:1;display:flex;flex-direction:column;min-width:0;border-right:1px solid var(--border)}
.chat{flex:1;overflow-y:auto;padding:24px;display:flex;flex-direction:column;gap:14px}
.msg{display:flex;gap:10px;max-width:80%}
.msg.user{align-self:flex-end;flex-direction:row-reverse}
.av{width:32px;height:32px;border-radius:50%;flex-shrink:0;display:flex;align-items:center;justify-content:center;font-family:'Montserrat';font-weight:800;font-size:13px;color:#fff}
.av.elena{background:var(--primary)}
.av.user{background:#6366F1}
.bubble{padding:11px 15px;border-radius:14px;font-size:14px;line-height:1.55;white-space:pre-wrap}
.bubble.elena{background:var(--white);border:1px solid var(--border);border-top-left-radius:4px}
.bubble.user{background:var(--primary);color:#fff;border-top-right-radius:4px}
.bubble strong{font-weight:700}
.typing{display:flex;gap:4px;padding:14px 16px;background:var(--white);border:1px solid var(--border);border-radius:14px;border-top-left-radius:4px;width:fit-content}
.typing span{width:7px;height:7px;border-radius:50%;background:#cbd5e1;animation:b 1.2s infinite}
.typing span:nth-child(2){animation-delay:.2s}.typing span:nth-child(3){animation-delay:.4s}
@keyframes b{0%,80%,100%{transform:scale(.7);opacity:.4}40%{transform:scale(1);opacity:1}}
.inbar{padding:14px 20px;border-top:1px solid var(--border);background:var(--white);display:flex;gap:10px;align-items:flex-end;flex-shrink:0}
.inp{flex:1;border:1.5px solid var(--border);border-radius:12px;padding:11px 15px;font-size:14px;font-family:'Inter';resize:none;outline:none;max-height:120px;line-height:1.5}
.inp:focus{border-color:var(--mid);box-shadow:0 0 0 3px rgba(16,185,129,.1)}
.send{width:44px;height:44px;border-radius:11px;background:var(--primary);border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;flex-shrink:0}
.send:hover{background:var(--dark)}.send:disabled{opacity:.4;cursor:default}
.build-btn{margin:0 20px 14px;padding:13px;border-radius:12px;background:linear-gradient(135deg,var(--dark),var(--primary));color:#fff;border:none;cursor:pointer;font-family:'Inter';font-weight:700;font-size:14px;flex-shrink:0;display:flex;align-items:center;justify-content:center;gap:8px}
.build-btn:hover{opacity:.92}.build-btn:disabled{opacity:.5;cursor:default}
.model-col{width:42%;max-width:560px;overflow-y:auto;padding:24px;background:#fafbfc;display:none}
.model-col.show{display:block}
.mc-head{font-family:'Montserrat';font-weight:800;font-size:18px;color:var(--dark);margin-bottom:6px}
.mc-sum{font-size:13px;color:var(--muted);line-height:1.5;margin-bottom:18px;padding-bottom:18px;border-bottom:1px solid var(--border)}
.blk{background:var(--white);border:1px solid var(--border);border-radius:13px;padding:16px;margin-bottom:12px}
.blk-top{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}
.blk-title{font-size:14px;font-weight:700;color:var(--text)}
.blk-pct{font-size:11px;font-weight:700;padding:2px 8px;border-radius:10px}
.blk-row{margin-bottom:9px}
.blk-lbl{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:#9ca3af;margin-bottom:3px}
.blk-asis{font-size:13px;color:#374151;line-height:1.5}
.blk-pain{font-size:12px;color:#92400e;line-height:1.45;padding-left:14px;position:relative;margin-bottom:3px}
.blk-pain::before{content:'•';position:absolute;left:3px;color:#f59e0b}
.blk-tobe{font-size:13px;color:var(--primary);line-height:1.5;background:var(--light);padding:8px 10px;border-radius:8px}
.empty{text-align:center;color:#cbd5e1;padding:60px 20px;font-size:14px}
@media(max-width:860px){.model-col{position:fixed;inset:56px 0 0 0;width:100%;max-width:none;z-index:50}.chat-col{border:none}}
</style>
</head>
<body>
<div class="hdr">
<div class="hdr-ic">@</div>
<div class="hdr-t">wasrusgen1 <em>/</em> КОНСАЛТИНГ</div>
<div class="hdr-elena"><span class="hdr-dot"></span>Елена · онлайн</div>
</div>
<div class="wrap">
<div class="chat-col">
<div class="chat" id="chat"></div>
<button class="build-btn" id="buildBtn" onclick="buildModel()">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></svg>
Построить мою бизнес-модель →
</button>
<div class="inbar">
<textarea class="inp" id="inp" rows="1" placeholder="Напишите Елене..." onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();sendMsg()}"></textarea>
<button class="send" id="sendBtn" onclick="sendMsg()">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>
</button>
</div>
</div>
<div class="model-col" id="modelCol">
<div class="empty">Бизнес-модель появится здесь после анализа</div>
</div>
</div>
<script>
const API = "https://claude-83-172-150-111.sslip.io/elena";
let token = localStorage.getItem("elena_token");
const chat = document.getElementById("chat");
const inp = document.getElementById("inp");
function esc(s){return s.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}
function fmt(s){return esc(s).replace(/\*\*(.+?)\*\*/g,"<strong>$1</strong>")}
function addMsg(role, text){
const m = document.createElement("div");
m.className = "msg " + (role==="user"?"user":"elena");
m.innerHTML = `<div class="av ${role==='user'?'user':'elena'}">${role==='user'?'Я':'Е'}</div><div class="bubble ${role==='user'?'user':'elena'}">${fmt(text)}</div>`;
chat.appendChild(m);
chat.scrollTop = chat.scrollHeight;
}
function showTyping(){
const t = document.createElement("div");
t.className="msg elena";t.id="typing";
t.innerHTML=`<div class="av elena">Е</div><div class="typing"><span></span><span></span><span></span></div>`;
chat.appendChild(t);chat.scrollTop=chat.scrollHeight;
}
function hideTyping(){const t=document.getElementById("typing");if(t)t.remove()}
async function init(){
if(token){
const r = await fetch(`${API}/api/project/${token}`);
if(r.ok){
const d = await r.json();
d.messages.forEach(m=>addMsg(m.role==="user"?"user":"elena", m.content));
if(d.model) renderModel(d.model);
return;
}
}
const r = await fetch(`${API}/api/project/new`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({})});
const d = await r.json();
token = d.token; localStorage.setItem("elena_token", token);
addMsg("elena", d.greeting);
}
async function sendMsg(){
const text = inp.value.trim();
if(!text) return;
inp.value=""; inp.style.height="auto";
addMsg("user", text);
document.getElementById("sendBtn").disabled=true;
showTyping();
try{
const r = await fetch(`${API}/api/chat`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token,message:text})});
const d = await r.json();
hideTyping();
addMsg("elena", d.reply || ("Ошибка: "+(d.error||"нет ответа")));
}catch(e){hideTyping();addMsg("elena","Ошибка соединения: "+e.message)}
document.getElementById("sendBtn").disabled=false;
}
function pctColor(p){return p>=70?"#047857":p>=45?"#F59E0B":"#EF4444"}
function pctBg(p){return p>=70?"#ECFDF5":p>=45?"#FEF3C7":"#FEF2F2"}
const SEV={critical:["#DC2626","#FEF2F2","КРИТИЧНО"],high:["#92400E","#FEF3C7","ВЫСОКИЙ"],medium:["#1E40AF","#EFF6FF","СРЕДНИЙ"]};
function renderModel(m){
const col = document.getElementById("modelCol");
col.classList.add("show");
let html = `<div class="mc-head">Операционная карта</div><div class="mc-sum">${esc(m.client_summary)}</div>`;
if(m.business_pattern){
html += `<div style="background:#0F0F1A;color:#fff;border-radius:11px;padding:12px 14px;margin-bottom:16px;font-size:12px;line-height:1.5"><div style="font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:#6EE7B7;margin-bottom:4px">Паттерн бизнеса</div>${esc(m.business_pattern)}</div>`;
}
// Узлы
html += `<div style="font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:#9ca3af;margin-bottom:8px">Узлы бизнеса · ${m.nodes.length}</div>`;
m.nodes.forEach(n=>{
html += `<div class="blk">
<div class="blk-top"><div class="blk-title">${esc(n.name)}</div>
<div class="blk-pct" style="color:${pctColor(n.completeness)};background:${pctBg(n.completeness)}">${n.completeness}%</div></div>
<div style="font-size:12px;color:#6366F1;font-weight:600;margin-bottom:8px">${esc(n.actor)}</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:8px">
<div style="background:#EFF6FF;border-radius:8px;padding:7px 9px"><div class="blk-lbl" style="color:#3B82F6">→ Вход</div><div style="font-size:12px;color:#374151;line-height:1.4">${esc(n.input)}</div></div>
<div style="background:#ECFDF5;border-radius:8px;padding:7px 9px"><div class="blk-lbl" style="color:#047857">Выход →</div><div style="font-size:12px;color:#374151;line-height:1.4">${esc(n.output)}</div></div>
</div>
<div class="blk-row"><div class="blk-lbl">Нормы</div><div style="font-size:12px;color:${n.norms.toLowerCase().includes('нет норм')?'#DC2626':'#374151'};line-height:1.4">${esc(n.norms)}</div></div>
<div class="blk-row"><div class="blk-lbl">Ресурсы</div><div style="font-size:12px;color:#374151;line-height:1.4">${esc(n.resources)}</div></div>`;
if(n.connections&&n.connections.length) html+=`<div class="blk-row"><div class="blk-lbl">Связи</div><div style="font-size:11px;color:#6b7280">${n.connections.map(esc).join(' · ')}</div></div>`;
if(n.issues&&n.issues.length){html+=`<div class="blk-row"><div class="blk-lbl" style="color:#92400e">Проблемы</div>`;n.issues.forEach(p=>html+=`<div class="blk-pain">${esc(p)}</div>`);html+=`</div>`}
html += `</div>`;
});
// Паттерны проблем
if(m.gaps&&m.gaps.length){
html += `<div style="font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:#9ca3af;margin:18px 0 8px">Найденные разрывы · ${m.gaps.length}</div>`;
m.gaps.forEach(g=>{
const s=SEV[g.severity]||SEV.medium;
html+=`<div class="blk" style="border-left:3px solid ${s[0]}">
<div style="display:flex;align-items:center;gap:8px;margin-bottom:5px"><span style="font-size:9px;font-weight:800;color:${s[0]};background:${s[1]};padding:2px 7px;border-radius:5px">${s[2]}</span><span class="blk-title">${esc(g.title)}</span></div>
<div style="font-size:12px;color:#6b7280;line-height:1.45">${esc(g.description)}</div></div>`;
});
}
if(m.missing_info&&m.missing_info.length){
html+=`<div class="blk" style="border-color:#FDE68A;background:#FFFBEB"><div class="blk-title" style="margin-bottom:8px">Елена уточнит ещё</div>`;
m.missing_info.forEach(q=>html+=`<div class="blk-pain">${esc(q)}</div>`);html+=`</div>`;
}
col.innerHTML = html;
}
async function buildModel(){
const btn = document.getElementById("buildBtn");
btn.disabled=true; btn.innerHTML="Елена анализирует ваш бизнес... ⏳";
try{
const r = await fetch(`${API}/api/build-model`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token})});
const d = await r.json();
if(d.model){renderModel(d.model);btn.innerHTML="✓ Модель построена · обновить";}
else{btn.innerHTML="Ошибка: "+(d.error||"?");}
}catch(e){btn.innerHTML="Ошибка: "+e.message}
btn.disabled=false;
}
inp.addEventListener("input",()=>{inp.style.height="auto";inp.style.height=Math.min(inp.scrollHeight,120)+"px"});
init();
</script>
</body>
</html>