SokratesAI / backend /socraticai_onboarding.html
Alleinzellgaenger's picture
Implement comprehensive onboarding flow with white theme
70e74ea
Raw
History Blame Contribute Delete
19.6 kB
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>SocraticAI – Onboarding Prototype</title>
<meta name="description" content="Clickable onboarding prototype for SocraticAI" />
<!-- Tailwind (CDN) -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- React 18 + ReactDOM (UMD) -->
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
<!-- Babel for in-browser JSX transform (prototype only) -->
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<style>
html, body { height: 100%; }
body { background: #0f172a; } /* slate-900 baseline while tailwind loads */
</style>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
const { useEffect, useMemo, useRef, useState } = React;
// ---- Options ----
const options = {
goal: [
{ key: "key_message", title: "Key Message", desc: "Grasp the main takeaways quickly." },
{ key: "deep_dive", title: "Deep Dive", desc: "Work through the full paper with tutoring." },
{ key: "methods", title: "Methods", desc: "Understand the experimental/modeling approach." },
{ key: "assumptions", title: "Assumptions & Claims", desc: "Surface assumptions, limits, and key claims." },
{ key: "figure_focus", title: "Figure Focus", desc: "Explain a specific figure step-by-step." },
],
guidance: [
{ key: "guided", title: "Guide me with a structured path", badge: "Recommended", desc: "We’ll sequence the key sections and keep you on track." },
{ key: "manual", title: "I’ll choose what to discuss", desc: "You decide the order and topics as we go." },
],
chunking: [
{ key: "large", title: "Larger sections", desc: "Faster pace, fewer stops." },
{ key: "medium", title: "Standard", desc: "Balanced pace and depth." },
{ key: "small", title: "Short, focused chunks", desc: "Slower pace, more checkpoints." },
],
};
const StepKeys = {
GOAL: "goal",
GUIDANCE: "guidance",
CHUNKING: "chunking",
FAMILIARITY: "familiarity",
};
function SocraticAIOnboarding() {
const [hasFile, setHasFile] = useState(false);
const [fileName, setFileName] = useState("");
const fileRef = useRef(null);
const [showAbout, setShowAbout] = useState(false);
const [currentStep, setCurrentStep] = useState(StepKeys.GOAL);
const [goal, setGoal] = useState(null);
const [guidance, setGuidance] = useState(null);
const [chunking, setChunking] = useState("medium");
const [familiarity, setFamiliarity] = useState(2);
const [showWizard, setShowWizard] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [loadingPhase, setLoadingPhase] = useState(0);
const stepNumber = useMemo(() => {
switch (currentStep) {
case StepKeys.GOAL: return 1;
case StepKeys.GUIDANCE: return 2;
case StepKeys.CHUNKING: return 3;
case StepKeys.FAMILIARITY: return 4;
default: return 1;
}
}, [currentStep]);
const totalSteps = 4;
const progressPct = (stepNumber / totalSteps) * 100;
useEffect(() => {
if (!isLoading) return;
const phases = [1200, 1200, 1400];
let i = 0;
const timer = setInterval(() => {
setLoadingPhase(p => p + 1);
i += 1;
if (i >= phases.length) clearInterval(timer);
}, phases[0]);
return () => clearInterval(timer);
}, [isLoading]);
function openFilePicker() {
if (fileRef.current) fileRef.current.click();
}
function fakeAssignFile(f) {
setHasFile(true);
setFileName(f?.name || "Demo_Paper.pdf");
setTimeout(() => setShowAbout(true), 250);
}
function onNext() {
if (currentStep === StepKeys.GOAL) return setCurrentStep(StepKeys.GUIDANCE);
if (currentStep === StepKeys.GUIDANCE) {
if (guidance === "manual") return setCurrentStep(StepKeys.FAMILIARITY);
return setCurrentStep(StepKeys.CHUNKING);
}
if (currentStep === StepKeys.CHUNKING) return setCurrentStep(StepKeys.FAMILIARITY);
}
function onBack() {
if (currentStep === StepKeys.GOAL) { setShowWizard(false); setShowAbout(true); return; }
if (currentStep === StepKeys.GUIDANCE) return setCurrentStep(StepKeys.GOAL);
if (currentStep === StepKeys.CHUNKING) return setCurrentStep(StepKeys.GUIDANCE);
if (currentStep === StepKeys.FAMILIARITY) return setCurrentStep(guidance === "guided" ? StepKeys.CHUNKING : StepKeys.GUIDANCE);
}
function startWizard() { setShowWizard(true); setCurrentStep(StepKeys.GOAL); }
function startLoading() { setIsLoading(true); setLoadingPhase(0); }
return (
<div className="min-h-screen w-full bg-gradient-to-br from-slate-950 via-slate-900 to-slate-800 text-slate-100 flex items-center justify-center p-6">
<div className="max-w-4xl w-full">
<div className="mb-6 flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="h-10 w-10 rounded-2xl bg-indigo-500/20 border border-indigo-400/30 flex items-center justify-center">
<svg viewBox='0 0 24 24' className='h-6 w-6 text-indigo-300'><path d='M12 3l7.5 4.5v9L12 21 4.5 16.5v-9L12 3z' fill='currentColor'/></svg>
</div>
<div>
<h1 className="text-2xl font-semibold tracking-tight">SocraticAI</h1>
<p className="text-sm text-slate-400">Guided comprehension for complex papers</p>
</div>
</div>
<div className="text-xs text-slate-400">Prototype · Clickable UI</div>
</div>
{!hasFile && !showAbout && !showWizard && !isLoading && (
<Landing openFilePicker={openFilePicker} fakeAssignFile={fakeAssignFile} fileRef={fileRef} />
)}
{hasFile && showAbout && !showWizard && !isLoading && (
<About fileName={fileName} onContinue={startWizard} openFilePicker={openFilePicker} />
)}
{showWizard && !isLoading && (
<Wizard
currentStep={currentStep}
onNext={onNext}
onBack={onBack}
goal={goal}
setGoal={setGoal}
guidance={guidance}
setGuidance={setGuidance}
chunking={chunking}
setChunking={setChunking}
familiarity={familiarity}
setFamiliarity={setFamiliarity}
stepNumber={stepNumber}
totalSteps={totalSteps}
progressPct={progressPct}
onStart={startLoading}
/>
)}
{isLoading && <Loading fileName={fileName} phase={loadingPhase} />}
</div>
</div>
);
}
function Landing({ openFilePicker, fakeAssignFile, fileRef }) {
return (
<div className="rounded-3xl border border-white/10 bg-white/5 shadow-2xl backdrop-blur p-8 md:p-12">
<div className="grid md:grid-cols-2 gap-8 items-center">
<div>
<h2 className="text-3xl md:text-4xl font-semibold leading-tight">Welcome to SocraticAI</h2>
<p className="mt-3 text-slate-300">
Your mentor-like companion for mastering research papers. Upload a paper and we’ll turn it into a guided
learning path — with questions, feedback, and visible progress.
</p>
<div className="mt-8 flex flex-wrap gap-3">
<button onClick={openFilePicker} className="inline-flex items-center gap-2 rounded-xl px-5 py-3 bg-indigo-500 hover:bg-indigo-600 transition text-white font-medium shadow-lg shadow-indigo-900/30">
<UploadIcon /> Upload a paper (PDF)
</button>
<input ref={fileRef} type="file" accept="application/pdf" className="hidden" onChange={(e) => fakeAssignFile(e.target.files?.[0])} />
<button onClick={() => fakeAssignFile({ name: "Demo_Paper.pdf" })} className="inline-flex items-center gap-2 rounded-xl px-5 py-3 bg-white/10 hover:bg-white/20 transition text-slate-100 font-medium">
Try demo paper
</button>
</div>
<p className="mt-3 text-xs text-slate-400">No upload actually leaves your browser in this prototype.</p>
</div>
<div className="relative h-56 md:h-72 rounded-2xl bg-gradient-to-tr from-indigo-900/60 via-indigo-700/40 to-cyan-700/40 border border-white/10 overflow-hidden">
<div className="absolute inset-0 grid grid-cols-3 grid-rows-3 opacity-20">
{Array.from({ length: 9 }).map((_, i) => <div key={i} className="border border-white/10" />)}
</div>
<div className="absolute inset-0 flex items-center justify-center">
<div className="text-center">
<div className="text-sm uppercase tracking-wide text-slate-300">Prototype Preview</div>
<div className="mt-2 text-4xl font-semibold">Onboarding Flow</div>
</div>
</div>
</div>
</div>
</div>
);
}
function About({ fileName, onContinue, openFilePicker }) {
return (
<div className="rounded-3xl border border-white/10 bg-white/5 shadow-2xl backdrop-blur p-8 md:p-12">
<div className="flex items-start gap-4">
<div className="h-10 w-10 rounded-xl bg-emerald-500/20 border border-emerald-400/30 flex items-center justify-center">
<CheckIcon />
</div>
<div className="flex-1">
<h3 className="text-2xl font-semibold">Paper added</h3>
<p className="text-slate-300 mt-1">{fileName}</p>
<p className="mt-4 text-slate-200">Here’s how SocraticAI helps you learn deeply:</p>
<ul className="mt-3 text-slate-300 space-y-2 list-disc list-inside">
<li><span className="font-medium text-slate-200">Automatic inflection points.</span> We flag hypotheses, assumptions, method shifts, and key claims.</li>
<li><span className="font-medium text-slate-200">Guided micro-conversations.</span> Short question/answer loops validate and extend your understanding.</li>
<li><span className="font-medium text-slate-200">Visible progress.</span> Confidence builds as you complete focused checkpoints.</li>
</ul>
<div className="mt-6 flex flex-wrap gap-3">
<button onClick={onContinue} className="rounded-xl px-5 py-3 bg-indigo-500 hover:bg-indigo-600 transition text-white font-medium shadow-lg shadow-indigo-900/30">Continue</button>
<button onClick={openFilePicker} className="rounded-xl px-5 py-3 bg-white/10 hover:bg-white/20 transition text-slate-100 font-medium">Choose another paper</button>
</div>
</div>
</div>
</div>
);
}
function Wizard({
currentStep, onNext, onBack,
goal, setGoal, guidance, setGuidance,
chunking, setChunking, familiarity, setFamiliarity,
stepNumber, totalSteps, progressPct, onStart,
}) {
return (
<div className="rounded-3xl border border-white/10 bg-white/5 shadow-2xl backdrop-blur overflow-hidden">
<div className="h-2 bg-white/10">
<div className="h-2 bg-indigo-500" style={{ width: progressPct + '%' }} />
</div>
<div className="p-8 md:p-10">
<div className="flex items-center justify-between">
<div className="text-sm text-slate-400">Step {stepNumber} of {totalSteps}</div>
<div className="text-sm text-slate-400">Onboarding</div>
</div>
<div className="mt-6">
{currentStep === StepKeys.GOAL && <StepGoal goal={goal} setGoal={setGoal} />}
{currentStep === StepKeys.GUIDANCE && <StepGuidance guidance={guidance} setGuidance={setGuidance} />}
{currentStep === StepKeys.CHUNKING && <StepChunking chunking={chunking} setChunking={setChunking} />}
{currentStep === StepKeys.FAMILIARITY && <StepFamiliarity familiarity={familiarity} setFamiliarity={setFamiliarity} />}
</div>
<div className="mt-8 flex items-center justify-between">
<button onClick={onBack} className="rounded-xl px-4 py-2 bg-white/5 hover:bg-white/10 border border-white/10 text-slate-200">Back</button>
{currentStep !== StepKeys.FAMILIARITY && (
<button
onClick={onNext}
disabled={(currentStep === StepKeys.GOAL && !goal) || (currentStep === StepKeys.GUIDANCE && !guidance)}
className="rounded-xl px-5 py-2.5 bg-indigo-500 disabled:bg-indigo-900/40 hover:bg-indigo-600 transition text-white font-medium shadow-md"
>Next</button>
)}
{currentStep === StepKeys.FAMILIARITY && (
<button onClick={onStart} className="rounded-xl px-5 py-2.5 bg-emerald-500 hover:bg-emerald-600 transition text-white font-medium shadow-md">
Let’s go — start
</button>
)}
</div>
</div>
</div>
);
}
function StepGoal({ goal, setGoal }) {
return (
<div>
<h3 className="text-2xl font-semibold">What’s your goal for this paper?</h3>
<p className="mt-2 text-slate-300">Choose the outcome you care about. We’ll tailor the path.</p>
<div className="mt-6 grid sm:grid-cols-2 lg:grid-cols-3 gap-4">
{options.goal.map(o => (
<SelectableCard key={o.key} selected={goal === o.key} onClick={() => setGoal(o.key)} title={o.title} desc={o.desc} />
))}
</div>
</div>
);
}
function StepGuidance({ guidance, setGuidance }) {
return (
<div>
<h3 className="text-2xl font-semibold">How should we structure your learning path?</h3>
<p className="mt-2 text-slate-300">Pick the level of guidance that fits how you like to study.</p>
<div className="mt-6 grid sm:grid-cols-2 gap-4">
{options.guidance.map(o => (
<SelectableCard key={o.key} selected={guidance === o.key} onClick={() => setGuidance(o.key)} title={o.title} desc={o.desc} badge={o.badge} />
))}
</div>
{guidance === "manual" && (<p className="mt-4 text-sm text-slate-400">You’ll skip the chunk size step and go straight to familiarity.</p>)}
</div>
);
}
function StepChunking({ chunking, setChunking }) {
return (
<div>
<h3 className="text-2xl font-semibold">Preferred chunk size</h3>
<p className="mt-2 text-slate-300">We’ll slice the paper into checkpoints at the pace you choose.</p>
<div className="mt-6 grid sm:grid-cols-3 gap-4">
{options.chunking.map(o => (
<SelectableCard key={o.key} selected={chunking === o.key} onClick={() => setChunking(o.key)} title={o.title} desc={o.desc} />
))}
</div>
</div>
);
}
function StepFamiliarity({ familiarity, setFamiliarity }) {
const labels = ["New to it", "Somewhat new", "Comfortable", "Very familiar", "I’ve taught this"];
return (
<div>
<h3 className="text-2xl font-semibold">How familiar are you with this topic?</h3>
<p className="mt-2 text-slate-300">This helps us pick the right starting point and vocabulary.</p>
<div className="mt-8">
<input type="range" min={0} max={4} step={1} value={familiarity} onChange={e => setFamiliarity(parseInt(e.target.value))} className="w-full accent-indigo-500" />
<div className="flex justify-between text-xs text-slate-400 mt-2">
{labels.map((lab, i) => (<div key={i} className={"w-20 " + (i === familiarity ? "text-slate-200" : "")}>{lab}</div>))}
</div>
</div>
</div>
);
}
function Loading({ fileName, phase }) {
const messages = ["Preparing your paper…", "Detecting inflection points…", "Setting up your guided path…", "All set. Launching the tutor…"];
const message = messages[Math.min(phase, messages.length - 1)];
return (
<div className="rounded-3xl border border-white/10 bg-white/5 shadow-2xl backdrop-blur p-10 md:p-16 text-center">
<div className="mx-auto h-16 w-16 rounded-full border-4 border-white/10 border-t-indigo-500 animate-spin" />
<h3 className="mt-6 text-2xl font-semibold">{message}</h3>
<p className="mt-2 text-slate-300">{fileName}</p>
<p className="mt-6 text-xs text-slate-400">(Prototype) This screen loops here — in the real app you’d enter the conversation next.</p>
</div>
);
}
function SelectableCard({ selected, onClick, title, desc, badge }) {
return (
<button onClick={onClick} className={"text-left rounded-2xl border transition p-4 hover:-translate-y-0.5 active:translate-y-0 bg-white/5 hover:bg-white/10 w-full " + (selected ? "border-indigo-400/60 ring-2 ring-indigo-400/50" : "border-white/10") }>
<div className="flex items-start justify-between">
<div className="text-base font-medium">{title}</div>
{badge && (<span className="text-[10px] uppercase tracking-wide bg-indigo-500/20 text-indigo-200 px-2 py-1 rounded-md border border-indigo-400/30">{badge}</span>)}
</div>
{desc && <p className="text-sm text-slate-300 mt-1">{desc}</p>}
</button>
);
}
function UploadIcon() {
return (
<svg viewBox="0 0 24 24" className="h-5 w-5" fill="none" stroke="currentColor" strokeWidth="1.8">
<path d="M12 16V4m0 0l-4 4m4-4l4 4"/>
<path d="M20 16v2a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-2"/>
</svg>
);
}
function CheckIcon() {
return (
<svg viewBox="0 0 24 24" className="h-5 w-5 text-emerald-300" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M20 6L9 17l-5-5" />
</svg>
);
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<SocraticAIOnboarding />);
</script>
</body>
</html>