/* ============================================================ screen_chat.jsx — AI onboarding advisor (the wow) Conversational, builds the profile live in a tray, then hands off to the assembled profile / home. ============================================================ */ /* ---------- profile tray fact chip ---------- */ function TrayChip({ fk, lang, t, index }) { const fact = TRAY[fk]; if (!fact) return null; return (
{t[fact.key]} {tr(fact.val, lang)}
); } function ProfileTray({ revealed, lang, t }) { const pct = Math.min(86, Math.round((revealed.length / 6) * 86)); if (revealed.length === 0) return null; return (
{t.trayTitle}
{t.profileAssembling}…
{revealed.map((fk, i) => )}
); } /* ---------- typing dots ---------- */ function Typing() { return (
{[0, 1, 2].map(i => ( ))}
); } function AdvisorDot() { return (
); } /* ---------- bubbles ---------- */ function AiBubble({ children }) { return (
{children}
); } function UserBubble({ children, file }) { return (
{file && } {children}
); } /* ---------- upload card ---------- */ function UploadCard({ onUpload, t }) { return (
); } /* ---------- parsing overlay (inline) ---------- */ function ParsingCard({ t }) { return (
{t.parsing}…
{[88, 64, 76].map((w, i) => (
))}
); } /* ---------- assemble overlay ---------- */ function AssembleOverlay({ t }) { return (
{[0, 1, 2].map(i => ( ))}
{t.profileAssembling}…
{t.value} · {t.skills} · {t.role}
); } /* ---------- main chat screen ---------- */ function ChatScreen({ lang, theme, setLang, setTheme, onFinish, onSkip, t }) { const [items, setItems] = useState([]); const [revealed, setRevealed] = useState([]); const [typing, setTyping] = useState(false); const [awaiting, setAwaiting] = useState(null); const [special, setSpecial] = useState(null); // 'upload' | 'parsing' | 'assemble' const [finished, setFinished] = useState(false); const idx = useRef(0); const scroller = useRef(null); const started = useRef(false); const push = (it) => setItems(p => [...p, { id: Math.random(), ...it }]); const reveal = (keys) => { keys.forEach((k, i) => setTimeout(() => setRevealed(p => p.includes(k) ? p : [...p, k]), i * 280)); }; const run = () => { const step = CHAT[idx.current]; if (!step) { setFinished(true); return; } if (step.who === 'ai') { setTyping(true); const len = tr(step.text, 'ru').length; setTimeout(() => { setTyping(false); push({ who: 'ai', textObj: step.text }); if (step.add) reveal(step.add); idx.current++; setTimeout(run, 420); }, Math.min(1500, 700 + len * 7)); } else if (step.who === 'choices') { setAwaiting(step); } else if (step.who === 'upload') { setSpecial('upload'); } else if (step.who === 'assemble') { setSpecial('assemble'); setTimeout(() => { setSpecial(null); idx.current++; run(); }, 2400); } }; useEffect(() => { if (started.current) return; started.current = true; setTimeout(run, 500); }, []); useEffect(() => { const el = scroller.current; if (el) el.scrollTop = el.scrollHeight + 200; }, [items, typing, special, awaiting, finished]); const pick = (opt) => { const step = awaiting; setAwaiting(null); push({ who: 'user', textObj: opt }); if (step.add) reveal(step.add); idx.current++; setTimeout(run, 350); }; const doUpload = () => { setSpecial('parsing'); setTimeout(() => { push({ who: 'user', file: true, textObj: { ru: 'Диплом_маркетинг.pdf', uz: 'Diplom_marketing.pdf' } }); setSpecial(null); idx.current++; setTimeout(run, 450); }, 2300); }; return (
{special === 'assemble' && } {/* advisor header */}
{t.advisor}
{typing ? t.typing + '…' : t.online}
{onSkip && ( )}
{/* messages */}
{items.map(it => ( it.who === 'ai' ? {tr(it.textObj, lang)} : {tr(it.textObj, lang)} ))} {special === 'upload' && } {special === 'parsing' && } {typing && } {finished && (
)}
{/* quick replies + input */}
{awaiting && (
{awaiting.options.map((o, i) => ( ))}
)}
{t.inputPh}
); } Object.assign(window, { ChatScreen });