feat: company profile tab — context-first onboarding before interview

This commit is contained in:
wasrusgen 2026-05-30 12:54:58 +03:00
parent 1eb611a95e
commit 6c5af31aae

View File

@ -139,6 +139,11 @@ body{font-family:'Inter',sans-serif;background:var(--bg);color:var(--text);displ
.fld{font-size:11px;background:var(--bg);border-radius:6px;padding:4px 8px}.fld em{font-style:normal;color:#6366F1;font-size:10px} .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} .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 */ /* 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{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} .drop-ic{font-size:30px;margin-bottom:8px}
.spin{display:inline-block;animation:sp 1s linear infinite}@keyframes sp{to{transform:rotate(360deg)}} .spin{display:inline-block;animation:sp 1s linear infinite}@keyframes sp{to{transform:rotate(360deg)}}
@ -155,6 +160,8 @@ body{font-family:'Inter',sans-serif;background:var(--bg);color:var(--text);displ
<div class="layout"> <div class="layout">
<aside class="sb"> <aside class="sb">
<div class="sb-nav"> <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="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 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="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>
@ -166,6 +173,19 @@ body{font-family:'Inter',sans-serif;background:var(--bg);color:var(--text);displ
</aside> </aside>
<main class="main"> <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 — Знакомство (живой чат) --> <!-- Этап 1 — Знакомство (живой чат) -->
<div class="sv active" id="sv1"> <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="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>
@ -217,13 +237,29 @@ function go(n){
cur=n; cur=n;
document.querySelectorAll('.sv').forEach(s=>s.classList.remove('active')); document.querySelectorAll('.sv').forEach(s=>s.classList.remove('active'));
document.getElementById('sv'+n).classList.add('active'); document.getElementById('sv'+n).classList.add('active');
document.querySelectorAll('.si').forEach((s,i)=>{s.classList.toggle('active',i+1===n)}); document.querySelectorAll('.si').forEach(s=>s.classList.remove('active'));
document.getElementById('pbPct').textContent=PCTS[n]+'%'; document.getElementById('si'+n).classList.add('active');
document.getElementById('pbFill').style.width=PCTS[n]+'%'; if(PCTS[n]!=null){document.getElementById('pbPct').textContent=PCTS[n]+'%';document.getElementById('pbFill').style.width=PCTS[n]+'%';}
if(n===4)renderAnalysis(); if(n===4)renderAnalysis();
if(n===5)renderSpecPane(); 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 ── */ /* ── Voice ── */
let recog=null,recording=false; let recog=null,recording=false;
const SR=window.SpeechRecognition||window.webkitSpeechRecognition; const SR=window.SpeechRecognition||window.webkitSpeechRecognition;
@ -243,10 +279,20 @@ function showTyping(){const t=document.createElement("div");t.className="msg";t.
function hideTyping(){const t=document.getElementById("typing");if(t)t.remove()} function hideTyping(){const t=document.getElementById("typing");if(t)t.remove()}
async function init(){ async function init(){
if(token){const r=await fetch(`${API}/api/project/${token}`);if(r.ok){state=await r.json();renderAll();return}} 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 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); const d=await r.json();token=d.token;localStorage.setItem("cab_token",token);
state={messages:[],client_name:"",niche:""};addMsg("elena",d.greeting); 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;
} }
function renderAll(){ function renderAll(){
if(state.client_name)document.getElementById("hdrClient").textContent="· "+state.client_name; if(state.client_name)document.getElementById("hdrClient").textContent="· "+state.client_name;