File size: 7,928 Bytes
65d68f5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
"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>
  );
}