DocDoeAI / src /components /study-chat /RightWorkspacePanel.tsx
asnannp's picture
deploy: sync backend to Space root (learn-lesson HF cache fix)
65d68f5
Raw History Blame Contribute Delete
7.93 kB
"use client";
import type { ElementType } from "react";
import {
CheckCircle2,
ClipboardList,
FileQuestion,
FileText,
GraduationCap,
Layers3,
Moon,
Video,
} from "lucide-react";
import type { StudentDashboard } from "@/lib/api/dashboard";
import type { ApiDocument, VideoRenderJob } from "@/lib/api/types";
import type { StudyIntent } from "@/lib/study-intents";
import type { ChatMessage } from "./types";
import { cardDisplayTitle, contextLabel, documentKindLabel, documentStatusLabel, formatDate } from "./utils";
import { PanelBlock, ToolCard } from "./WorkspacePanelPrimitives";
export function RecentOutputs({
messages,
dashboard,
videoJobs,
}: {
messages: ChatMessage[];
dashboard: StudentDashboard | null;
videoJobs: VideoRenderJob[];
}) {
const generatedOutputs = messages
.filter((message) => message.card && message.card.kind !== "loading" && message.card.kind !== "error")
.slice(-4)
.reverse();
const dashboardOutputs = dashboard?.recent_results.slice(0, 3) ?? [];
return (
<div className="space-y-2">
{generatedOutputs.length === 0 && dashboardOutputs.length === 0 && videoJobs.length === 0 ? (
<div className="rounded-2xl border border-dashed border-white/[0.08] px-4 py-5 text-sm leading-6 text-white/38">
Recent notes, quizzes, flashcards, video plans, and render jobs will appear here.
</div>
) : null}
{generatedOutputs.map((message) => (
<div key={message.id} className="rounded-2xl border border-white/[0.07] bg-white/[0.025] p-3">
<p className="text-sm font-semibold text-white/82">{message.card ? cardDisplayTitle(message.card) : message.text}</p>
<p className="mt-1 line-clamp-2 text-xs leading-5 text-white/38">{message.text}</p>
</div>
))}
{dashboardOutputs.map((result) => (
<div key={result.id} className="rounded-2xl border border-white/[0.07] bg-white/[0.025] p-3">
<p className="text-sm font-semibold text-white/82">{result.label}</p>
<p className="mt-1 text-xs text-white/38">
{result.source_title} - {formatDate(result.created_at)}
</p>
</div>
))}
{videoJobs.slice(0, 3).map((job) => (
<div key={job.id} className="rounded-2xl border border-white/[0.07] bg-white/[0.025] p-3">
<div className="flex items-center justify-between gap-3">
<p className="min-w-0 truncate text-sm font-semibold text-white/82">{job.title}</p>
<span className="shrink-0 text-[10px] font-semibold uppercase text-white/38">{job.status}</span>
</div>
<div className="mt-2 h-1 rounded-full bg-white/[0.06]">
<div className="h-1 rounded-full bg-white/55" style={{ width: `${Math.max(4, Math.min(job.progress ?? 0, 100))}%` }} />
</div>
</div>
))}
</div>
);
}
export function RightWorkspacePanel({
documents,
selectedDocuments,
messages,
dashboard,
videoJobs,
onAction,
onUpload,
}: {
documents: ApiDocument[];
selectedDocuments: ApiDocument[];
messages: ChatMessage[];
dashboard: StudentDashboard | null;
videoJobs: VideoRenderJob[];
onAction: (intent: StudyIntent, prompt: string) => void;
onUpload: () => void;
}) {
const sourceContext = selectedDocuments.length ? contextLabel(selectedDocuments, documents) : "your selected source";
const readyCount = documents.filter((document) => document.status === "ready").length;
const paperCount = documents.filter((document) => document.material_type === "question_paper" || /pyq|question paper|previous/i.test(document.title)).length;
const tools: Array<{ label: string; description: string; intent: StudyIntent; prompt: string; icon: ElementType }> = [
{ label: "Notes", description: "Exam-ready scoring notes", intent: "generate_notes", prompt: `Make exam-ready notes from ${sourceContext}.`, icon: FileText },
{ label: "Quiz", description: "Active recall with traps", intent: "generate_quiz", prompt: `Create a quiz from ${sourceContext} with answers and explanations.`, icon: FileQuestion },
{ label: "Flashcards", description: "Fast memory cards", intent: "generate_flashcards", prompt: `Make flashcards from ${sourceContext}.`, icon: Layers3 },
{ label: "Study Video", description: "Scene plan plus builder handoff", intent: "create_video_plan", prompt: `Create a study video plan from ${sourceContext}.`, icon: Video },
{ label: "PYQ Analysis", description: paperCount ? "Use uploaded papers" : "Material-only until papers exist", intent: "analyze_pyq", prompt: `Analyze PYQ patterns from ${sourceContext}. Be explicit about available evidence.`, icon: GraduationCap },
{ label: "Study Plan", description: "Rank what scores first", intent: "continue_learning", prompt: `Build a study plan from ${sourceContext}.`, icon: ClipboardList },
{ label: "Last Night Revision", description: "Tonight-only priorities", intent: "create_revision_plan", prompt: `Make a last-night revision plan from ${sourceContext}.`, icon: Moon },
{ label: "Exam Mode", description: "Board-style answers", intent: "generate_exam_answer", prompt: `Create exam-mode answers from ${sourceContext}.`, icon: CheckCircle2 },
];
return (
<aside className="hidden min-h-0 w-[288px] shrink-0 flex-col border-l border-white/[0.075] bg-[#050505]/88 p-3 xl:flex">
<div className="min-h-0 flex-1 overflow-y-auto pr-1 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden">
<PanelBlock title="Sources" actionLabel="Add" onAction={onUpload}>
{selectedDocuments.length > 0 ? (
<div className="space-y-2">
{selectedDocuments.map((document) => (
<div key={document.id} className="rounded-2xl border border-white/[0.07] bg-white/[0.025] p-3">
<p className="line-clamp-2 text-sm font-semibold text-white/82">{document.title}</p>
<p className="mt-1 text-xs text-white/38">
{documentKindLabel(document)} - {documentStatusLabel(document.status)}
</p>
</div>
))}
</div>
) : (
<div className="rounded-2xl border border-dashed border-white/[0.08] px-4 py-5 text-sm leading-6 text-white/38">
Select sources to ground DocDoe&apos;s answers.
</div>
)}
</PanelBlock>
<PanelBlock title="Study Tools">
<div className="grid gap-2">
{tools.map((tool) => (
<ToolCard
key={tool.label}
icon={tool.icon}
label={tool.label}
description={tool.description}
onClick={() => onAction(tool.intent, tool.prompt)}
/>
))}
</div>
</PanelBlock>
<PanelBlock title="Recent Outputs">
<RecentOutputs messages={messages} dashboard={dashboard} videoJobs={videoJobs} />
</PanelBlock>
<PanelBlock title="Mission Snapshot">
<div className="grid gap-2">
{[
["Ready sources", `${readyCount}/${documents.length || 0}`],
["Paper evidence", paperCount ? `${paperCount} paper${paperCount === 1 ? "" : "s"}` : "Not uploaded"],
["Video jobs", String(videoJobs.length)],
["Focus", dashboard?.study_focus.subject || selectedDocuments[0]?.subject || "Choose source"],
].map(([label, value]) => (
<div key={label} className="flex items-center justify-between rounded-2xl border border-white/[0.07] bg-white/[0.025] px-3 py-2.5">
<span className="text-xs text-white/38">{label}</span>
<span className="max-w-[9rem] truncate text-sm font-semibold text-white/78">{value}</span>
</div>
))}
</div>
</PanelBlock>
</div>
</aside>
);
}