wasrusgen1-crm/docs/cabinet.html

416 lines
37 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;800&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;--sb:#0F0F1A;--bg:#F5F6F8;--white:#fff;--border:#E5E7EB;--text:#1A1A2E;--muted:#6B7280;--subtle:#F9FAFB}
html,body{height:100%;overflow:hidden}
body{font-family:'Inter',sans-serif;background:var(--bg);color:var(--text);display:flex;flex-direction:column}
/* Header */
.hdr{height:54px;background:var(--dark);display:flex;align-items:center;padding:0 20px;gap:12px;flex-shrink:0}
.hdr-ic{width:30px;height:30px;background:var(--primary);border-radius:8px;display:flex;align-items:center;justify-content:center;font-family:'Montserrat';font-weight:800;font-size:16px;color:#fff}
.hdr-t{font-family:'Montserrat';font-weight:700;font-size:14px;color:rgba(255,255,255,.6);display:flex;align-items:center;gap:9px;letter-spacing:-.2px}
.hdr-sep{width:1.5px;height:15px;background:rgba(255,255,255,.25);flex-shrink:0}
.hdr-t b{font-weight:800;color:#fff}
.hdr-client{font-size:13px;color:rgba(255,255,255,.6);margin-left:6px}
.hdr-r{margin-left:auto}
.elena-chip{display:flex;align-items:center;gap:7px;background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.12);border-radius:100px;padding:5px 13px 5px 6px}
.elena-av{width:24px;height:24px;border-radius:50%;background:var(--primary);display:flex;align-items:center;justify-content:center;font-family:'Montserrat';font-weight:800;font-size:11px;color:#fff}
.elena-nm{font-size:12px;font-weight:600;color:rgba(255,255,255,.85)}
.elena-dot{width:7px;height:7px;background:var(--mid);border-radius:50%;box-shadow:0 0 4px var(--mid)}
/* Layout */
.layout{flex:1;display:flex;overflow:hidden}
.sb{width:240px;flex-shrink:0;background:var(--sb);display:flex;flex-direction:column}
.sb-nav{flex:1;padding:14px 0;overflow-y:auto}
.sb-cap{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.1em;color:rgba(255,255,255,.2);padding:10px 18px 6px}
.si{display:flex;align-items:flex-start;gap:11px;padding:10px 18px;cursor:pointer;border-left:2px solid transparent;transition:background .12s}
.si:hover:not(.locked){background:rgba(255,255,255,.04)}
.si.active{background:rgba(4,120,87,.14);border-left-color:var(--primary)}
.si.locked{opacity:.4;cursor:default}
.si-num{width:28px;height:28px;border-radius:50%;flex-shrink:0;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700}
.si.done .si-num{background:rgba(16,185,129,.15);color:var(--mid);font-size:14px}
.si.active .si-num{background:var(--primary);color:#fff;box-shadow:0 4px 10px rgba(4,120,87,.4)}
.si:not(.active):not(.done) .si-num{background:rgba(255,255,255,.06);color:rgba(255,255,255,.4)}
.si.locked .si-num{background:rgba(255,255,255,.05);color:rgba(255,255,255,.2);font-size:11px}
.si-body{flex:1;min-width:0}
.si-lbl{font-size:10px;color:rgba(255,255,255,.25);font-weight:600;letter-spacing:.04em;text-transform:uppercase}
.si-name{font-size:13px;font-weight:600;color:rgba(255,255,255,.7);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.si.active .si-name{color:#fff}
.si-sub{font-size:11px;color:rgba(255,255,255,.3);margin-top:1px}
.sb-foot{padding:14px;border-top:1px solid rgba(255,255,255,.06)}
.pb-box{background:rgba(255,255,255,.04);border-radius:10px;padding:12px}
.pb-row{display:flex;justify-content:space-between;margin-bottom:6px}
.pb-l{font-size:12px;font-weight:600;color:rgba(255,255,255,.45)}
.pb-p{font-size:12px;font-weight:700;color:var(--mid)}
.pb-track{height:4px;background:rgba(255,255,255,.08);border-radius:2px;overflow:hidden}
.pb-fill{height:100%;background:linear-gradient(90deg,var(--primary),var(--mid));border-radius:2px;transition:width .4s}
/* Main */
.main{flex:1;display:flex;flex-direction:column;overflow:hidden;min-width:0}
.sv{display:none;flex:1;flex-direction:column;overflow:hidden}
.sv.active{display:flex}
.hero{background:var(--white);border-bottom:1px solid var(--border);padding:18px 26px;display:flex;align-items:center;gap:16px;flex-shrink:0}
.hero-ic{width:46px;height:46px;border-radius:12px;background:linear-gradient(135deg,var(--dark),var(--primary));display:flex;align-items:center;justify-content:center;font-size:21px;flex-shrink:0}
.hero-tag{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:var(--primary);margin-bottom:3px}
.hero-h{font-size:18px;font-weight:700;color:var(--text)}
.hero-d{font-size:13px;color:var(--muted);margin-top:2px}
.hero-r{margin-left:auto}
.btn{font-family:'Inter';font-weight:600;border:none;cursor:pointer;border-radius:10px;display:inline-flex;align-items:center;gap:7px;white-space:nowrap}
.btn-p{background:var(--primary);color:#fff;font-size:13px;padding:10px 18px}
.btn-p:hover{background:var(--dark)}.btn-p:disabled{opacity:.5;cursor:default}
.scroll{flex:1;overflow-y:auto}
/* Chat */
.chat{padding:24px 26px;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:30px;height:30px;border-radius:50%;flex-shrink:0;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:12px;color:#fff}
.av.e{background:var(--primary)}.av.u{background:#6366F1}
.bb{padding:11px 15px;border-radius:14px;font-size:14px;line-height:1.55;white-space:pre-wrap}
.bb.in{background:var(--white);border:1px solid var(--border);border-top-left-radius:4px}
.bb.out{background:var(--primary);color:#fff;border-top-right-radius:4px}
.typing{display:flex;gap:4px;padding:13px 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 26px;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)}
.icon-btn{width:44px;height:44px;border-radius:11px;cursor:pointer;display:flex;align-items:center;justify-content:center;flex-shrink:0;position:relative}
.mic{background:var(--light);border:1.5px solid rgba(4,120,87,.2);color:var(--primary)}
.mic:hover{background:#D1FAE5}
.mic.rec{background:#FEF2F2;border-color:#EF4444;color:#EF4444}
.mic.rec::after{content:'';position:absolute;inset:-4px;border-radius:14px;border:2px solid #EF4444;animation:mp 1.3s ease-out infinite}
@keyframes mp{0%{transform:scale(1);opacity:.7}100%{transform:scale(1.25);opacity:0}}
.send{background:var(--primary);border:none;color:#fff}.send:hover{background:var(--dark)}.send:disabled{opacity:.4}
.mic-hint{position:absolute;bottom:56px;right:0;background:var(--sb);color:#fff;font-size:12px;padding:6px 12px;border-radius:8px;white-space:nowrap;display:none}
.mic-hint.show{display:block}
/* Analysis panes */
.pad{padding:24px 26px}
.run-card{background:var(--white);border:1.5px solid var(--border);border-radius:14px;padding:28px;text-align:center;max-width:520px;margin:8px auto 16px}
.run-ic{font-size:34px;margin-bottom:12px}
.run-t{font-family:'Montserrat';font-weight:800;font-size:19px;color:var(--text);margin-bottom:8px}
.run-d{font-size:14px;color:var(--muted);line-height:1.5;margin-bottom:18px}
.an-tabs{display:flex;gap:8px;margin-bottom:18px}
.an-tab{padding:9px 16px;border-radius:9px;font-size:13px;font-weight:600;cursor:pointer;background:var(--white);border:1.5px solid var(--border);color:var(--muted)}
.an-tab.active{background:var(--primary);border-color:var(--primary);color:#fff}
.an-pane{display:none}.an-pane.active{display:block}
/* Canvas */
.canvas-grid{display:grid;grid-template-columns:repeat(5,1fr);grid-auto-rows:minmax(84px,auto);gap:8px}
.cv{background:var(--white);border:1px solid var(--border);border-radius:10px;padding:11px}
.cv-h{font-size:10px;font-weight:700;text-transform:uppercase;color:var(--primary);margin-bottom:6px;display:flex;justify-content:space-between}
.cv-h span{color:#9ca3af}
.cv li{font-size:11.5px;color:#374151;line-height:1.35;list-style:none;padding-left:10px;position:relative;margin-bottom:3px}
.cv li::before{content:'';position:absolute;left:2px;top:7px;width:4px;height:4px;border-radius:50%;background:var(--mid)}
.cv-note{font-size:10px;color:#92400e;margin-top:6px;font-style:italic}
.cv.vp{grid-column:3;grid-row:span 2;border-color:var(--primary);border-width:1.5px}.cv.kp{grid-row:span 2}.cv.cs{grid-column:5;grid-row:span 2}
.cv-ins{grid-column:1/-1;background:var(--sb);color:#fff;border-radius:11px;padding:14px 18px;font-size:13px;line-height:1.5}
.cv-ins b{color:var(--mid)}
/* IDEF0 */
.idef-lbl{font-size:11px;font-weight:700;text-transform:uppercase;color:#9ca3af;margin:14px 0 8px}
.idef{margin-bottom:12px}
.idef-c,.idef-m{display:flex;flex-wrap:wrap;gap:4px;justify-content:center;padding:4px 0}
.idef-c{border-bottom:2px dashed #cbd5e1}.idef-m{border-top:2px dashed #cbd5e1}
.idef-mid{display:grid;grid-template-columns:auto 1fr auto;gap:8px;align-items:stretch}
.idef-i,.idef-o{display:flex;flex-direction:column;gap:4px;justify-content:center;max-width:140px}
.idef-fn{background:var(--white);border:2px solid var(--primary);border-radius:10px;padding:11px 9px;text-align:center;font-size:12.5px;font-weight:700;color:var(--dark);display:flex;flex-direction:column;gap:3px;justify-content:center}
.idef-fn b{font-size:10px;color:var(--primary);font-family:'Montserrat'}.idef-fn i{font-style:normal;font-size:10px;font-weight:700}
.ar{font-size:10px;line-height:1.3;padding:3px 7px;border-radius:6px;background:#F1F5F9;color:#475569}
.idef-c .ar{background:#FEF3C7;color:#92400E}.idef-c .ar.nomiss{background:#FEF2F2;color:#DC2626;border:1px dashed #FECACA}
.idef-i .ar{background:#EFF6FF;color:#1E40AF}.idef-o .ar{background:#ECFDF5;color:#047857}.idef-o .ar.dead{background:#FEF2F2;color:#DC2626}
.idef-m .ar{background:#F5F3FF;color:#6D28D9}
.blk{background:var(--white);border:1px solid var(--border);border-radius:12px;padding:14px;margin-bottom:10px}
.blk-pain{font-size:12px;color:#92400e;padding-left:14px;position:relative;margin-bottom:3px;line-height:1.4}
.blk-pain::before{content:'•';position:absolute;left:3px;color:#f59e0b}
/* Spec */
.spec-h{font-family:'Montserrat';font-weight:800;font-size:16px;color:var(--text);margin:18px 0 12px;display:flex;align-items:center;gap:8px}
.spec-h .pl{width:24px;height:24px;border-radius:7px;background:var(--primary);color:#fff;display:flex;align-items:center;justify-content:center;font-size:13px;font-weight:800}
.mod{background:var(--white);border:1px solid var(--border);border-radius:12px;padding:15px;margin-bottom:10px}
.mod-top{display:flex;align-items:center;gap:8px;margin-bottom:6px}
.mod-node{font-size:10px;font-weight:700;color:var(--primary);background:var(--light);padding:2px 7px;border-radius:5px}
.mod-name{font-size:14px;font-weight:700}
.scr{font-size:11px;background:#EFF6FF;color:#1E40AF;border:1px solid #BFDBFE;border-radius:6px;padding:3px 9px;display:inline-block;margin:3px 3px 0 0}
.mod-data{font-size:12px;color:#374151;margin-top:6px}
.ent{background:var(--white);border:1px solid var(--border);border-radius:11px;padding:13px;margin-bottom:8px}
.ent-name{font-size:13px;font-weight:700;font-family:'Montserrat';margin-bottom:8px}
.ent-fields{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:5px;margin-bottom:8px}
.fld{font-size:11px;background:var(--bg);border-radius:6px;padding:4px 8px}.fld em{font-style:normal;color:#6366F1;font-size:10px}
.ent-ex{font-size:10.5px;color:#6b7280;font-family:monospace;background:var(--bg);border-radius:6px;padding:6px 9px;line-height:1.4}
/* Docs */
.pf-l{display:block;font-size:13px;font-weight:600;color:var(--text);margin-bottom:7px}
.pf-i{width:100%;border:1.5px solid var(--border);border-radius:10px;padding:11px 14px;font-size:14px;font-family:'Inter';outline:none}
.pf-i:focus{border-color:var(--mid);box-shadow:0 0 0 3px rgba(16,185,129,.1)}
.pf-t{width:100%;min-height:120px;border:1.5px solid var(--border);border-radius:10px;padding:11px 14px;font-size:14px;font-family:'Inter';outline:none;resize:vertical;line-height:1.5}
.pf-t:focus{border-color:var(--mid);box-shadow:0 0 0 3px rgba(16,185,129,.1)}
.drop{border:2px dashed #CBD5E1;border-radius:14px;padding:32px;text-align:center;background:var(--subtle);max-width:520px;margin:8px auto}
.drop-ic{font-size:30px;margin-bottom:8px}
.spin{display:inline-block;animation:sp 1s linear infinite}@keyframes sp{to{transform:rotate(360deg)}}
::-webkit-scrollbar{width:6px}::-webkit-scrollbar-thumb{background:rgba(0,0,0,.12);border-radius:4px}
</style>
</head>
<body>
<header class="hdr">
<div class="hdr-ic">@</div>
<div class="hdr-t">wasrusgen1<span class="hdr-sep"></span><b>КОНСАЛТИНГ</b></div>
<div class="hdr-client" id="hdrClient"></div>
<div class="hdr-r"><div class="elena-chip"><div class="elena-av">Е</div><div class="elena-nm">Елена</div><div class="elena-dot"></div></div></div>
</header>
<div class="layout">
<aside class="sb">
<div class="sb-nav">
<div class="si" id="si0" onclick="go(0)" style="margin-bottom:4px"><div class="si-num" style="background:rgba(255,255,255,.1);color:rgba(255,255,255,.7)">👤</div><div class="si-body"><div class="si-name">Профиль компании</div><div class="si-sub">О вашей деятельности</div></div></div>
<div style="height:1px;background:rgba(255,255,255,.06);margin:4px 18px 8px"></div>
<div class="sb-cap">Путь к результату</div>
<div class="si active" id="si1" onclick="go(1)"><div class="si-num">1</div><div class="si-body"><div class="si-lbl">Этап 1</div><div class="si-name">Знакомство</div><div class="si-sub">Интервью с Еленой</div></div></div>
<div class="si" id="si2" onclick="go(2)"><div class="si-num">2</div><div class="si-body"><div class="si-lbl">Этап 2</div><div class="si-name">Диагностика</div><div class="si-sub">Углубление</div></div></div>
<div class="si" id="si3" onclick="go(3)"><div class="si-num">3</div><div class="si-body"><div class="si-lbl">Этап 3</div><div class="si-name">Документы</div><div class="si-sub">Материалы</div></div></div>
<div class="si" id="si4" onclick="go(4)"><div class="si-num">4</div><div class="si-body"><div class="si-lbl">Этап 4</div><div class="si-name">Анализ</div><div class="si-sub">Модель бизнеса</div></div></div>
<div class="si" id="si5" onclick="go(5)"><div class="si-num">5</div><div class="si-body"><div class="si-lbl">Этап 5</div><div class="si-name">План</div><div class="si-sub">ТЗ на программу</div></div></div>
</div>
<div class="sb-foot"><div class="pb-box"><div class="pb-row"><span class="pb-l">Прогресс</span><span class="pb-p" id="pbPct">20%</span></div><div class="pb-track"><div class="pb-fill" id="pbFill" style="width:20%"></div></div></div></div>
</aside>
<main class="main">
<!-- Профиль компании -->
<div class="sv" id="sv0">
<div class="hero"><div class="hero-ic">👤</div><div><div class="hero-tag">Профиль компании</div><div class="hero-h">Расскажите о вашей деятельности</div><div class="hero-d">Базовая информация — Елена начнёт разговор уже зная контекст</div></div></div>
<div class="scroll"><div class="pad">
<div style="max-width:600px">
<div style="margin-bottom:18px"><label class="pf-l">Ваше имя или название компании</label><input class="pf-i" id="pfName" placeholder="Например: Игорь / ООО «Автодом»"></div>
<div style="margin-bottom:18px"><label class="pf-l">Сфера деятельности</label><input class="pf-i" id="pfNiche" placeholder="Например: автосервис, нутрициология, швейное производство"></div>
<div style="margin-bottom:22px"><label class="pf-l">Описание деятельности</label><textarea class="pf-t" id="pfDesc" placeholder="Чем занимаетесь, сколько человек, как сейчас всё устроено, что беспокоит. Чем подробнее — тем точнее анализ."></textarea></div>
<button class="btn btn-p" id="pfSave" onclick="saveProfile()" style="padding:12px 24px;font-size:14px">Сохранить и начать с Еленой →</button>
</div>
</div></div>
</div>
<!-- Этап 1 — Знакомство (живой чат) -->
<div class="sv active" id="sv1">
<div class="hero"><div class="hero-ic">💬</div><div><div class="hero-tag">Этап 1 из 5 · В процессе</div><div class="hero-h">Знакомство</div><div class="hero-d">Расскажите Елене о вашем бизнесе — текстом или голосом</div></div></div>
<div class="scroll"><div class="chat" id="chat"></div></div>
<div class="inbar">
<textarea class="inp" id="inp" rows="1" placeholder="Напишите или скажите Елене..." onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();sendMsg()}"></textarea>
<button class="icon-btn mic" id="micBtn" onclick="toggleMic()" title="Голосовой ввод"><div class="mic-hint" id="micHint">Слушаю... говорите</div><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="12" y1="19" x2="12" y2="23"/><line x1="8" y1="23" x2="16" y2="23"/></svg></button>
<button class="icon-btn 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>
<!-- Этап 2 — Диагностика -->
<div class="sv" id="sv2">
<div class="hero"><div class="hero-ic">🔍</div><div><div class="hero-tag">Этап 2 из 5</div><div class="hero-h">Диагностика</div><div class="hero-d">Елена уточняет детали — продолжайте разговор</div></div></div>
<div class="scroll"><div class="pad"><div class="run-card"><div class="run-ic">🔍</div><div class="run-t">Диагностика идёт в чате</div><div class="run-d">Елена задаёт уточняющие вопросы прямо в интервью (Этап 1). Когда данных достаточно — переходите к Анализу.</div><button class="btn btn-p" onclick="go(1)">← Вернуться к интервью</button></div></div></div>
</div>
<!-- Этап 3 — Документы -->
<div class="sv" id="sv3">
<div class="hero"><div class="hero-ic">📁</div><div><div class="hero-tag">Этап 3 из 5</div><div class="hero-h">Документы</div><div class="hero-d">Загрузите материалы — Елена учтёт их в анализе</div></div></div>
<div class="scroll"><div class="pad">
<div class="drop" id="dropZone" onclick="document.getElementById('fileInp').click()">
<div class="drop-ic">📎</div>
<div style="font-size:14px;font-weight:600;margin-bottom:4px">Нажмите или перетащите документы</div>
<div style="font-size:12px;color:#9ca3af">PDF, Word, Excel, txt · прайс, оргструктура, отчёты</div>
<input type="file" id="fileInp" multiple style="display:none" onchange="handleFiles(this.files)">
</div>
<div id="docList" style="max-width:520px;margin:16px auto 0"></div>
</div></div>
</div>
<!-- Этап 4 — Анализ -->
<div class="sv" id="sv4">
<div class="hero"><div class="hero-ic">🧠</div><div><div class="hero-tag">Этап 4 из 5</div><div class="hero-h">Анализ бизнеса</div><div class="hero-d">Елена строит модель вашего бизнеса (Opus 4.8)</div></div></div>
<div class="scroll"><div class="pad" id="anPad"></div></div>
</div>
<!-- Этап 5 — План -->
<div class="sv" id="sv5">
<div class="hero"><div class="hero-ic">🚀</div><div><div class="hero-tag">Этап 5 из 5</div><div class="hero-h">План — ТЗ на программу</div><div class="hero-d">Проект системы для вашего бизнеса</div></div></div>
<div class="scroll"><div class="pad" id="specPad"></div></div>
</div>
</main>
</div>
<script>
const API="https://claude-83-172-150-111.sslip.io/elena";
// Доступ: токен из ссылки-приглашения (?t=) имеет приоритет
const urlToken=new URLSearchParams(location.search).get("t");
if(urlToken)localStorage.setItem("cab_token",urlToken);
let token=urlToken||localStorage.getItem("cab_token"), state=null, cur=1;
const chat=document.getElementById("chat"), inp=document.getElementById("inp");
const PCTS={1:20,2:40,3:60,4:80,5:100};
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 go(n){
if(document.getElementById('si'+n).classList.contains('locked'))return;
cur=n;
document.querySelectorAll('.sv').forEach(s=>s.classList.remove('active'));
document.getElementById('sv'+n).classList.add('active');
document.querySelectorAll('.si').forEach(s=>s.classList.remove('active'));
document.getElementById('si'+n).classList.add('active');
if(PCTS[n]!=null){document.getElementById('pbPct').textContent=PCTS[n]+'%';document.getElementById('pbFill').style.width=PCTS[n]+'%';}
if(n===3)renderDocs();
if(n===4)renderAnalysis();
if(n===5)renderSpecPane();
}
async function saveProfile(){
const name=document.getElementById("pfName").value.trim();
const niche=document.getElementById("pfNiche").value.trim();
const desc=document.getElementById("pfDesc").value.trim();
if(!name&&!desc){alert("Заполните хотя бы имя и описание");return}
const btn=document.getElementById("pfSave");btn.disabled=true;btn.innerHTML='<span class="spin">⏳</span> Елена знакомится...';
try{
const r=await fetch(`${API}/api/project/profile`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token,client_name:name,niche,description:desc})});
const d=await r.json();
if(d.error){alert("Ошибка: "+d.error);btn.disabled=false;btn.textContent="Сохранить и начать →";return}
// Обновляем состояние и чат
const r2=await fetch(`${API}/api/project/${token}`);state=await r2.json();
renderAll();go(1);
}catch(e){alert("Ошибка: "+e.message);btn.disabled=false}
}
/* ── Voice ── */
let recog=null,recording=false;
const SR=window.SpeechRecognition||window.webkitSpeechRecognition;
if(SR){recog=new SR();recog.lang="ru-RU";recog.continuous=true;recog.interimResults=true;let base="";
recog.onresult=e=>{let fin="",intr="";for(let i=e.resultIndex;i<e.results.length;i++){const t=e.results[i][0].transcript;if(e.results[i].isFinal)fin+=t;else intr+=t}if(fin)base+=fin;inp.value=(base+intr).trim();inp.style.height="auto";inp.style.height=Math.min(inp.scrollHeight,120)+"px"};
recog.onstart=()=>{base=inp.value?inp.value+" ":""};
recog.onend=()=>{if(recording){try{recog.start()}catch(e){}}};
recog.onerror=e=>{if(e.error==="not-allowed"){alert("Разрешите доступ к микрофону");stopMic()}};
}
function toggleMic(){if(!recog){alert("Голосовой ввод работает в Chrome");return}recording?stopMic():startMic()}
function startMic(){recording=true;document.getElementById("micBtn").classList.add("rec");document.getElementById("micHint").classList.add("show");try{recog.start()}catch(e){}}
function stopMic(){recording=false;document.getElementById("micBtn").classList.remove("rec");document.getElementById("micHint").classList.remove("show");try{recog.stop()}catch(e){}inp.focus()}
/* ── Chat ── */
function addMsg(role,text){const m=document.createElement("div");m.className="msg "+(role==="user"?"user":"");m.innerHTML=`<div class="av ${role==='user'?'u':'e'}">${role==='user'?'Я':'Е'}</div><div class="bb ${role==='user'?'out':'in'}">${fmt(text)}</div>`;chat.appendChild(m);chat.scrollTop=chat.scrollHeight}
function showTyping(){const t=document.createElement("div");t.className="msg";t.id="typing";t.innerHTML=`<div class="av e">Е</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){state=await r.json();renderAll();
fillProfile();
// Если профиль не заполнен — открыть вкладку Профиль
if(!state.description && !state.client_name) go(0); else go(1);
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("cab_token",token);
state={messages:[],client_name:"",niche:"",description:""};
go(0); // новый клиент → сразу профиль
}
function fillProfile(){
if(state.client_name)document.getElementById("pfName").value=state.client_name;
if(state.niche)document.getElementById("pfNiche").value=state.niche;
if(state.description)document.getElementById("pfDesc").value=state.description;
}
/* ── Документы (Этап 3) ── */
function renderDocs(){
const dl=document.getElementById("docList");if(!dl)return;
const docs=state.documents||[];
dl.innerHTML=docs.length?docs.map(d=>`<div style="display:flex;align-items:center;gap:10px;background:var(--white);border:1px solid var(--border);border-radius:10px;padding:11px 14px;margin-bottom:8px"><span style="font-size:18px">📄</span><div style="flex:1"><div style="font-size:13px;font-weight:600">${esc(d.filename)}</div><div style="font-size:11px;color:#9ca3af">${(d.size/1024).toFixed(0)} КБ · учтён в анализе</div></div><span style="color:var(--primary);font-weight:700;font-size:13px">✓</span></div>`).join(""):'<div style="text-align:center;color:#cbd5e1;font-size:13px;padding:10px">Документов пока нет</div>';
}
async function handleFiles(files){
const dl=document.getElementById("docList");
for(const f of files){
const tmp=document.createElement("div");tmp.style.cssText="background:var(--white);border:1px solid var(--border);border-radius:10px;padding:11px 14px;margin-bottom:8px;font-size:13px";tmp.innerHTML=`<span class="spin">⏳</span> ${esc(f.name)} — загрузка...`;dl.prepend(tmp);
try{
const b64=await new Promise((res,rej)=>{const r=new FileReader();r.onload=()=>res(r.result);r.onerror=rej;r.readAsDataURL(f)});
const r=await fetch(`${API}/api/upload`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token,filename:f.name,content:b64})});
const d=await r.json();
if(d.error){tmp.innerHTML=`${esc(f.name)}: ${d.error}`;continue}
state.documents=state.documents||[];state.documents.push({filename:d.filename,size:d.size});
}catch(e){tmp.innerHTML=`${esc(f.name)}: ${e.message}`;continue}
}
renderDocs();
}
function renderAll(){
if(state.client_name)document.getElementById("hdrClient").textContent="· "+state.client_name;
chat.innerHTML="";
state.messages.forEach(m=>addMsg(m.role==="user"?"user":"elena",m.content));
unlockStages();
}
function unlockStages(){
// Этап 4 доступен если есть достаточно сообщений
const enough=state.messages.length>=3;
[2,3,4,5].forEach(n=>{const si=document.getElementById('si'+n);if(enough||n<=3)si.classList.remove('locked');});
}
async function sendMsg(){
if(recording)stopMic();
const text=inp.value.trim();if(!text)return;
inp.value="";inp.style.height="auto";
addMsg("user",text);state.messages.push({role:"user",content: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||"?")));state.messages.push({role:"assistant",content:d.reply||""});unlockStages();
}catch(e){hideTyping();addMsg("elena","Ошибка связи: "+e.message)}
document.getElementById("sendBtn").disabled=false;
}
/* ── Analysis (Этап 4) ── */
let anTab="canvas";
function renderAnalysis(){
const pad=document.getElementById("anPad");
pad.innerHTML=`<div class="an-tabs"><div class="an-tab ${anTab==='canvas'?'active':''}" onclick="setAnTab('canvas')">📊 Стратегия</div><div class="an-tab ${anTab==='idef0'?'active':''}" onclick="setAnTab('idef0')">🔧 Функции</div></div><div id="anContent"></div>`;
renderAnContent();
}
function setAnTab(t){anTab=t;renderAnalysis()}
function renderAnContent(){
const c=document.getElementById("anContent");
if(anTab==='canvas'){
if(!state.canvas){c.innerHTML=runCard("canvas","📊","Стратегическая модель","Елена построит Business Model Canvas — как устроен ваш бизнес и как он зарабатывает.","Построить стратегию →");return}
c.innerHTML=renderCanvas(state.canvas);
}else{
if(!state.model){c.innerHTML=runCard("model","🔧","Функциональная модель","Елена разложит бизнес на функции (IDEF0): входы, выходы, нормы, ресурсы и разрывы.","Построить модель →");return}
c.innerHTML=renderIdef(state.model);
}
}
function renderSpecPane(){
const pad=document.getElementById("specPad");
if(!state.spec){
if(!state.model){pad.innerHTML=runCard(null,"⚠️","Сначала Анализ","ТЗ собирается из функциональной модели. Перейдите на Этап 4 и постройте модель.","← К анализу",()=>go(4));return}
pad.innerHTML=runCard("spec","📋","Техническое задание","Из модели бизнеса Елена спроектирует программу: роли, модули, экраны, данные.","Собрать ТЗ →");return}
pad.innerHTML=renderSpec(state.spec);
}
function runCard(stage,ic,t,d,btn,custom){
const id=stage?`id="rb-${stage}"`:'';
const onclick=custom?'':`onclick="runBuild('${stage}')"`;
return `<div class="run-card"><div class="run-ic">${ic}</div><div class="run-t">${t}</div><div class="run-d">${d}</div><button class="btn btn-p" ${id} ${custom?'':onclick}>${btn}</button></div>`;
}
const BUILD={canvas:["build-canvas","canvas"],model:["build-model","model"],spec:["build-spec","spec"]};
async function runBuild(stage){
const [ep,key]=BUILD[stage];const btn=document.getElementById(`rb-${stage}`);
if(btn){btn.disabled=true;btn.innerHTML=`<span class="spin">⏳</span> Елена анализирует...`}
try{const r=await fetch(`${API}/api/${ep}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token})});const d=await r.json();
if(d.error){alert("Ошибка: "+d.error);if(btn){btn.disabled=false;btn.textContent="Повторить"}return}
state[key]=d[key];
if(stage==='spec')renderSpecPane();else renderAnContent();
}catch(e){alert("Ошибка: "+e.message);if(btn)btn.disabled=false}
}
function renderCanvas(c){const B=(k,cls,l)=>{const b=c[k];return `<div class="cv ${cls}"><div class="cv-h">${l}<span>${b.completeness}%</span></div><ul>${b.items.map(i=>`<li>${esc(i)}</li>`).join("")}</ul>${b.note?`<div class="cv-note">${esc(b.note)}</div>`:''}</div>`};
return `<div class="canvas-grid">${B("key_partners","kp","Партнёры")}${B("key_activities","ka","Активности")}${B("value_propositions","vp","Ценность")}${B("customer_relationships","cr","Отношения")}${B("customer_segments","cs","Сегменты")}${B("key_resources","kr","Ресурсы")}${B("channels","ch","Каналы")}${B("cost_structure","co","Издержки")}${B("revenue_streams","rev","Доходы")}<div class="cv-ins"><b>Вывод:</b> ${esc(c.insight)}</div></div>`}
function renderIdef(m){const box=(fn,ct,ins,outs,me,id,pct)=>{const C=(ct&&ct.length)?ct.map(c=>`<span class="ar">${esc(c.name)}</span>`).join(""):`<span class="ar nomiss">нет управления</span>`;const I=(ins||[]).map(a=>`<span class="ar">${esc(a.name)}</span>`).join("")||'<span class="ar">—</span>';const O=(outs||[]).map(a=>`<span class="ar ${a.target==='НИКУДА'?'dead':''}">${esc(a.name)}${a.target==='НИКУДА'?' ⊘':''}</span>`).join("")||'<span class="ar">—</span>';const M=(me||[]).map(x=>`<span class="ar">${esc(x.name)}</span>`).join("")||'<span class="ar">—</span>';return `<div class="idef"><div class="idef-c">${C}</div><div class="idef-mid"><div class="idef-i">${I}</div><div class="idef-fn">${id?`<b>${id}</b>`:''}${esc(fn)}${pct!=null?`<i style="color:${pct>=70?'#047857':pct>=45?'#F59E0B':'#EF4444'}">${pct}%</i>`:''}</div><div class="idef-o">${O}</div></div><div class="idef-m">${M}</div></div>`};
let h=`<div style="background:var(--sb);color:#fff;border-radius:11px;padding:12px 16px;margin-bottom:14px;font-size:13px"><b style="color:var(--mid)">Паттерн:</b> ${esc(m.business_pattern)}</div>`;
if(m.context){h+=`<div class="idef-lbl">A-0 Контекст</div>`+box(m.context.function,m.context.controls,m.context.inputs,m.context.outputs,m.context.mechanisms,"A0")}
h+=`<div class="idef-lbl">Декомпозиция · ${m.activities.length} функций</div>`;m.activities.forEach(a=>h+=box(a.function,a.controls,a.inputs,a.outputs,a.mechanisms,a.node_id,a.completeness));
if(m.arrow_issues&&m.arrow_issues.length){h+=`<div class="idef-lbl">Разрывы · ${m.arrow_issues.length}</div>`;m.arrow_issues.forEach(g=>{const col=g.severity==='critical'?'#DC2626':g.severity==='high'?'#92400E':'#1E40AF';h+=`<div class="blk" style="border-left:3px solid ${col};padding:11px 14px"><div style="font-size:10px;font-weight:700;color:#9ca3af">${esc(g.node_id)} · ${g.type}</div><div style="font-size:13px;font-weight:700">${esc(g.title)}</div><div style="font-size:12px;color:#6b7280;margin-top:3px">${esc(g.description)}</div></div>`})}
return h}
function renderSpec(s){let h=`<div class="spec-h"><span class="pl">A</span>Обзор</div><div class="blk">${esc(s.overview)}</div>`;
h+=`<div class="spec-h"><span class="pl">A</span>Роли (${s.roles.length})</div>`;s.roles.forEach(r=>h+=`<div class="blk" style="padding:12px 15px"><b>${esc(r.name)}</b> — ${esc(r.does)}<div style="font-size:11px;color:#9ca3af;margin-top:3px">Доступ: ${esc(r.access)}</div></div>`);
h+=`<div class="spec-h"><span class="pl">B</span>Модули (${s.modules.length})</div>`;s.modules.forEach(m=>h+=`<div class="mod"><div class="mod-top"><span class="mod-node">${esc(m.source_node)}</span><span class="mod-name">${esc(m.name)}</span></div><div style="font-size:12px;color:var(--muted)">${esc(m.purpose)}</div><div style="margin:6px 0">${m.screens.map(s=>`<span class="scr">${esc(s)}</span>`).join("")}</div><div class="mod-data">📥 ${esc(m.inputs_data)} · 📤 ${esc(m.outputs_data)}</div>${m.rules.length?`<div style="margin-top:6px">${m.rules.map(r=>`<div class="blk-pain">${esc(r)}</div>`).join("")}</div>`:''}</div>`);
h+=`<div class="spec-h"><span class="pl">C</span>Модель данных (${s.entities.length} таблиц)</div>`;s.entities.forEach(e=>h+=`<div class="ent"><div class="ent-name">◆ ${esc(e.name)}</div><div class="ent-fields">${e.fields.map(f=>`<div class="fld"><b>${esc(f.field)}</b> <em>${esc(f.type)}</em></div>`).join("")}</div>${e.relations.length?`<div style="font-size:11px;color:#6b7280;margin-bottom:6px">🔗 ${e.relations.map(esc).join(" · ")}</div>`:''}<div class="ent-ex">${esc(e.example)}</div></div>`);
if(s.open_questions&&s.open_questions.length){h+=`<div class="blk" style="border-color:#FDE68A;background:#FFFBEB"><div style="font-weight:700;margin-bottom:8px">Уточнить перед разработкой</div>${s.open_questions.map(q=>`<div class="blk-pain">${esc(q)}</div>`).join("")}</div>`}
return h}
inp.addEventListener("input",()=>{inp.style.height="auto";inp.style.height=Math.min(inp.scrollHeight,120)+"px"});
init();
</script>
</body>
</html>