Spaces:
Sleeping
Sleeping
| <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> | |