DocDoeAI / src /components /study-chat /EmptyStudyWorkspace.tsx
asnannp's picture
deploy: sync backend to Space root (learn-lesson HF cache fix)
65d68f5
Raw History Blame Contribute Delete
12 kB
"use client";
import type { ComponentProps } from "react";
import NextLink from "next/link";
import {
ArrowRight,
BookOpen,
CheckCircle2,
FileQuestion,
FileText,
GraduationCap,
Layers3,
Moon,
Network,
Sparkles,
Target,
Upload,
Video,
} from "lucide-react";
import type { ApiDocument } from "@/lib/api/types";
import type { StudyIntent } from "@/lib/study-intents";
import { QuickActions } from "./QuickActions";
import { SelectedSourcesStrip } from "./SourceControls";
import { StudyComposer } from "./StudyComposer";
import type { StudyActionHandler } from "./types";
import { greetingForNow } from "./utils";
type EmptyStudyWorkspaceProps = {
activeIntent: StudyIntent;
composerProps: ComponentProps<typeof StudyComposer>;
documents: ApiDocument[];
heroSubtitle: string;
heroTitle: string;
isLoading: boolean;
onAction: StudyActionHandler;
onClearSources: () => void;
onToggleSource: (sourceId: string) => void;
onUpload?: () => void;
selectedDocuments: ApiDocument[];
studentName: string;
};
export function EmptyStudyWorkspace({
activeIntent,
composerProps,
documents,
heroSubtitle,
heroTitle,
isLoading,
onAction,
onClearSources,
onToggleSource,
onUpload,
selectedDocuments,
studentName,
}: EmptyStudyWorkspaceProps) {
const readySourceCount = documents.filter((document) => document.status === "ready").length;
const selectedSourceLabel =
selectedDocuments.length > 0
? `${selectedDocuments.length} active source${selectedDocuments.length === 1 ? "" : "s"}`
: documents.length > 0
? `${readySourceCount} ready source${readySourceCount === 1 ? "" : "s"}`
: "No source selected";
const starterPrompts: Array<{
label: string;
detail: string;
intent: StudyIntent;
prompt: string;
icon: typeof BookOpen;
}> = [
{
label: "Explain a concept",
detail: "Definition, keywords, mark-wise structure",
intent: "teach_topic",
prompt: "Explain photosynthesis with exam keywords and a 4-mark answer frame",
icon: BookOpen,
},
{
label: "Scoring notes",
detail: "Definitions, traps, revision checklist",
intent: "generate_notes",
prompt: "Generate exam-ready notes on chemical bonding with common mistakes",
icon: FileText,
},
{
label: "Practice quiz",
detail: "MCQs with explanations and traps",
intent: "generate_quiz",
prompt: "Create a 5-question quiz on trigonometry with answer explanations",
icon: FileQuestion,
},
{
label: "Flashcards",
detail: "Active recall with memory hooks",
intent: "generate_flashcards",
prompt: "Build flashcards for organic chemistry reactions and named examples",
icon: Layers3,
},
{
label: "Video plan",
detail: "Scene script with exam focus",
intent: "create_video_plan",
prompt: "Create a 10-minute exam-focused study video plan for electromagnetic induction",
icon: Video,
},
{
label: "Revision plan",
detail: "Time-blocked cram schedule",
intent: "create_revision_plan",
prompt: "I have one night before my physics exam. Build a focused revision plan",
icon: Moon,
},
];
const groundingHint = selectedDocuments.length
? `Grounding in ${selectedDocuments[0].title}`
: documents.length
? "Select a source for textbook-matched retrieval, or ask any topic."
: "Upload material for source-grounded answers, or ask a topic directly.";
const workspaceSignals = [
{ label: selectedSourceLabel, icon: CheckCircle2 },
{ label: "Textbook-aware answers", icon: Network },
{ label: "Exam answer practice", icon: Target },
];
return (
<div className="kb-empty-scroll min-h-0 flex-1 overflow-y-auto px-4 pb-28 pt-5 sm:px-6 lg:flex lg:items-center lg:px-8 lg:pb-10">
<div className="mx-auto flex w-full max-w-3xl flex-col py-4 sm:py-8 lg:max-w-none lg:py-6 xl:max-w-[min(100%,56rem)]">
<div className="kb-empty-enter lg:grid lg:grid-cols-[minmax(0,1fr)_17.5rem] lg:items-end lg:gap-8 xl:grid-cols-[minmax(0,1fr)_19rem] xl:gap-10">
<div className="text-center lg:text-left">
<div className="inline-flex items-center gap-2 rounded-full border border-white/[0.09] bg-white/[0.035] px-3 py-1.5 text-xs font-semibold text-white/58">
<BookOpen className="size-3.5" />
{greetingForNow()}, {studentName}
</div>
<h1 className="mt-4 text-balance text-[1.85rem] font-semibold leading-[1.08] tracking-[-0.02em] text-white sm:text-[2.35rem] lg:text-[2.75rem] xl:text-[3rem]">
{heroTitle}
</h1>
<p className="mx-auto mt-3 max-w-xl text-balance text-sm leading-6 text-white/52 sm:text-[15px] lg:mx-0 lg:max-w-2xl lg:text-base">
{heroSubtitle}
</p>
<div className="mt-5 hidden flex-wrap gap-2 lg:flex">
{workspaceSignals.map((signal) => {
const Icon = signal.icon;
return (
<span
key={signal.label}
className="inline-flex min-h-8 items-center gap-2 rounded-full border border-white/[0.08] bg-white/[0.025] px-3 text-[11px] font-semibold text-white/44"
>
<Icon className="size-3.5 text-white/48" />
{signal.label}
</span>
);
})}
</div>
</div>
<div className="mt-6 hidden rounded-[1.2rem] border border-white/[0.08] bg-white/[0.028] p-4 lg:mt-0 lg:block">
<div className="flex items-start gap-3">
<span className="grid size-10 shrink-0 place-items-center rounded-2xl bg-white text-black">
<Sparkles className="size-5" />
</span>
<div className="min-w-0">
<p className="text-sm font-semibold text-white">Study tools ready</p>
<p className="mt-1 text-xs leading-5 text-white/42">{groundingHint}</p>
</div>
</div>
{onUpload ? (
<button
type="button"
onClick={onUpload}
className="mt-4 inline-flex min-h-10 w-full items-center justify-center gap-2 rounded-xl border border-white/[0.1] bg-white/[0.045] px-3 text-sm font-semibold text-white/70 transition hover:border-white/[0.18] hover:text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/22"
>
<Upload className="size-4" />
Add study source
</button>
) : null}
</div>
</div>
<div className="kb-empty-enter kb-empty-enter-d1 mt-5">
<NextLink
href="/tuition"
className="mx-auto flex min-h-12 w-full max-w-xl items-center justify-between gap-3 rounded-2xl border border-white/[0.1] bg-white/[0.04] px-4 text-left transition hover:border-white/[0.18] hover:bg-white/[0.065] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/22 lg:mx-0"
>
<span className="flex min-w-0 items-center gap-3">
<span className="grid size-9 shrink-0 place-items-center rounded-xl bg-white text-black">
<GraduationCap className="size-4" aria-hidden="true" />
</span>
<span className="min-w-0">
<span className="block text-sm font-bold text-white">Continue Today&apos;s Tuition</span>
<span className="mt-0.5 block text-xs leading-5 text-white/44">
Study Chat is for doubts. Your official class stays in Tuition.
</span>
</span>
</span>
<ArrowRight className="size-4 shrink-0 text-white/40" aria-hidden="true" />
</NextLink>
</div>
<div className="kb-empty-enter kb-empty-enter-d2 mt-7 sm:mt-8 lg:mt-8">
<StudyComposer {...composerProps} variant="hero" />
</div>
<div className="kb-empty-enter kb-empty-enter-d2 mt-4 lg:hidden">
<QuickActions activeIntent={activeIntent} disabled={isLoading} onAction={onAction} layout="scroll" />
</div>
<div className="kb-empty-enter kb-empty-enter-d2 mt-4 hidden lg:block xl:hidden">
<QuickActions activeIntent={activeIntent} disabled={isLoading} onAction={onAction} layout="grid" />
</div>
<div className="kb-empty-enter kb-empty-enter-d3 mt-4 flex flex-wrap items-center justify-center gap-2 lg:hidden">
{workspaceSignals.map((signal) => {
const Icon = signal.icon;
return (
<span
key={signal.label}
className="inline-flex min-h-8 items-center gap-2 rounded-full border border-white/[0.08] bg-white/[0.025] px-3 text-[11px] font-semibold text-white/44"
>
<Icon className="size-3.5 text-white/48" />
{signal.label}
</span>
);
})}
</div>
{onUpload ? (
<div className="kb-empty-enter kb-empty-enter-d3 mt-4 lg:hidden">
<button
type="button"
onClick={onUpload}
className="mx-auto flex min-h-10 w-full max-w-sm items-center justify-center gap-2 rounded-xl border border-white/[0.1] bg-white/[0.04] px-4 text-sm font-semibold text-white/68 transition hover:border-white/[0.18] hover:text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/22"
>
<Upload className="size-4" />
Add study source
</button>
</div>
) : null}
<div className="kb-empty-enter kb-empty-enter-d4 mt-5 hidden min-[390px]:block">
<SelectedSourcesStrip
selectedDocuments={selectedDocuments}
documents={documents}
onToggleSource={onToggleSource}
onClear={onClearSources}
/>
</div>
<div className="kb-empty-enter kb-empty-enter-d5 mt-8 lg:mt-10">
<div className="flex items-baseline justify-between gap-3 border-b border-white/10 pb-3">
<p className="text-[11px] font-bold uppercase tracking-[0.14em] text-white/32">Start here</p>
<p className="hidden text-sm text-white/46 sm:block">or type your own question above</p>
</div>
<ol className="dd-stagger sm:columns-2 sm:gap-10">
{starterPrompts.map((item, index) => (
<li key={item.label} className="break-inside-avoid border-b border-white/[0.07]">
<button
type="button"
disabled={isLoading}
onClick={() => onAction(item.intent, item.prompt)}
className="dd-press group grid w-full grid-cols-[28px_1fr_auto] items-baseline gap-3 py-3.5 text-left transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/22 disabled:cursor-not-allowed disabled:opacity-45"
>
<span className="font-mono text-[11px] text-white/30 transition group-hover:text-white/60">
{String(index + 1).padStart(2, "0")}
</span>
<span className="min-w-0">
<span className="block text-sm font-semibold text-white/84 transition group-hover:text-white">
{item.label}
</span>
<span className="mt-0.5 block text-xs leading-5 text-white/40">{item.detail}</span>
</span>
<ArrowRight className="size-4 shrink-0 self-center text-white/20 transition group-hover:translate-x-0.5 group-hover:text-white/70" />
</button>
</li>
))}
</ol>
</div>
</div>
</div>
);
}