Download src/components/study-chat/StudyChatShell.tsx from asnannp/DocDoeAI: direct link, hf CLI and curl.
- Browser
- Download file 10.1 kB
-
https://huggingface.co/spaces/asnannp/DocDoeAI/resolve/main/src/components/study-chat/StudyChatShell.tsx
- Command line
-
hf download hf://spaces/asnannp/DocDoeAI/src/components/study-chat/StudyChatShell.tsx
-
curl -L -o StudyChatShell.tsx https://huggingface.co/spaces/asnannp/DocDoeAI/resolve/main/src/components/study-chat/StudyChatShell.tsx
10.1 kB
| "use client"; | |
| import { useEffect, useRef, useState, Suspense } from "react"; | |
| import { cn } from "@/lib/utils"; | |
| import NextLink from "next/link"; | |
| import { useSearchParams } from "next/navigation"; | |
| import { GraduationCap } from "lucide-react"; | |
| import { useAuth } from "@/lib/auth/useAuth"; | |
| import { normalizeStudyIntent } from "@/lib/study-intents"; | |
| import { MainWorkspace } from "./MainWorkspace"; | |
| import { MobileDrawerContent } from "./MobileDrawerContent"; | |
| import { | |
| LeftWorkspaceRail, | |
| MobileBottomNav, | |
| MobilePanelDrawer, | |
| SourceRail, | |
| } from "./WorkspacePanels"; | |
| import { RightPanelTabs } from "./RightPanelTabs"; | |
| import type { MobilePanel, StudyChatShellProps } from "./types"; | |
| import { useStudyConversation } from "./useStudyConversation"; | |
| import { useStudySources } from "./useStudySources"; | |
| import { useStudyUploads } from "./useStudyUploads"; | |
| import { useWorkspaceResources } from "./useWorkspaceResources"; | |
| import { firstName } from "./utils"; | |
| import { SourceClassificationModal } from "./SourceClassificationModal"; | |
| import { MATERIAL_UPLOAD_ACCEPT, PDF_UPLOAD_ACCEPT } from "@/lib/uploads/document-upload-contract"; | |
| function StudyChatShellInner({ | |
| initialIntent = "teach_topic", | |
| title, | |
| subtitle, | |
| hideLeftRail = false, | |
| }: StudyChatShellProps) { | |
| const searchParams = useSearchParams(); | |
| const { user } = useAuth(); | |
| const queryIntent = normalizeStudyIntent(searchParams.get("intent") ?? initialIntent); | |
| const queryPrompt = searchParams.get("prompt"); | |
| const querySourceId = searchParams.get("sourceId"); | |
| const runOnOpen = searchParams.get("run") === "1"; | |
| const [workspaceSearch, setWorkspaceSearch] = useState(""); | |
| const [plusOpen, setPlusOpen] = useState(false); | |
| const [mobilePanel, setMobilePanel] = useState<MobilePanel>(null); | |
| const materialInputRef = useRef<HTMLInputElement | null>(null); | |
| const paperInputRef = useRef<HTMLInputElement | null>(null); | |
| const { | |
| documents, | |
| documentsLoading, | |
| selectedSourceIds, | |
| selectedDocuments, | |
| sourceSearch, | |
| setSourceSearch, | |
| setSelectedSourceIds, | |
| refreshSources, | |
| toggleSource, | |
| clearSelectedSources, | |
| upsertAndSelectDocument, | |
| } = useStudySources(querySourceId); | |
| const { dashboard, videoJobs } = useWorkspaceResources(); | |
| const { | |
| activeIntent, | |
| chatNotice, | |
| chatSessions, | |
| input, | |
| isHistoryLoading, | |
| isLoading, | |
| messages, | |
| newChat, | |
| openSession, | |
| runAction, | |
| startVideoRender, | |
| setInput, | |
| setMessages, | |
| submit, | |
| threadEndRef, | |
| } = useStudyConversation({ | |
| closeMobilePanel: () => setMobilePanel(null), | |
| closePlusMenu: () => setPlusOpen(false), | |
| documents, | |
| queryIntent, | |
| queryPrompt, | |
| querySourceId, | |
| runOnOpen, | |
| selectedDocuments, | |
| selectedSourceIds, | |
| setSelectedSourceIds, | |
| user, | |
| }); | |
| const { | |
| sourceDraftMode, | |
| setSourceDraftMode, | |
| draftTitle, | |
| setDraftTitle, | |
| draftText, | |
| setDraftText, | |
| isSavingDraft, | |
| isUploading, | |
| uploadNotice, | |
| canRetryUpload, | |
| retryUpload, | |
| handleMaterialFile, | |
| handlePaperFile, | |
| saveDraftSource, | |
| classificationModal, | |
| openClassificationModal, | |
| closeClassificationModal, | |
| handleClassificationComplete, | |
| } = useStudyUploads({ | |
| documents, | |
| selectedDocuments, | |
| setMessages, | |
| refreshSources, | |
| upsertAndSelectDocument, | |
| }); | |
| const studentName = firstName(user?.name); | |
| const promptPlaceholder = "Ask anything..."; | |
| const heroTitle = title ?? "How can I help you today?"; | |
| const heroSubtitle = subtitle ?? "Ask anything, upload a readable PDF, or use the video plugin."; | |
| useEffect(() => { | |
| function onKeyDown(event: KeyboardEvent) { | |
| if (event.key === "Escape") { | |
| setPlusOpen(false); | |
| setMobilePanel(null); | |
| } | |
| } | |
| window.addEventListener("keydown", onKeyDown); | |
| return () => window.removeEventListener("keydown", onKeyDown); | |
| }, []); | |
| const composerProps = { | |
| value: input, | |
| onChange: setInput, | |
| onSubmit: () => void submit(), | |
| placeholder: promptPlaceholder, | |
| disabled: isLoading, | |
| selectedDocuments, | |
| onRemoveSource: toggleSource, | |
| onMaterialFile: (file: File) => void handleMaterialFile(file), | |
| onOpenMaterialPicker: () => materialInputRef.current?.click(), | |
| onOpenPaperPicker: () => paperInputRef.current?.click(), | |
| sourceDraftMode, | |
| onSourceDraftModeChange: setSourceDraftMode, | |
| draftTitle, | |
| onDraftTitleChange: setDraftTitle, | |
| draftText, | |
| onDraftTextChange: setDraftText, | |
| onSaveDraftSource: () => void saveDraftSource(), | |
| isSavingDraft, | |
| isUploading, | |
| uploadNotice: uploadNotice || chatNotice, | |
| onRetryUpload: canRetryUpload ? retryUpload : undefined, | |
| plusOpen, | |
| onPlusOpenChange: setPlusOpen, | |
| }; | |
| const sourceRail = ( | |
| <SourceRail | |
| documents={documents} | |
| selectedSourceIds={selectedSourceIds} | |
| isLoading={documentsLoading} | |
| search={sourceSearch} | |
| onSearchChange={setSourceSearch} | |
| onToggleSource={toggleSource} | |
| onUpload={() => materialInputRef.current?.click()} | |
| onEditClassification={openClassificationModal} | |
| /> | |
| ); | |
| const rightPanel = ( | |
| <RightPanelTabs | |
| documents={documents} | |
| selectedDocuments={selectedDocuments} | |
| messages={messages} | |
| dashboard={dashboard} | |
| videoJobs={videoJobs} | |
| onAction={runAction} | |
| onUpload={() => materialInputRef.current?.click()} | |
| onMaterialFile={(file) => void handleMaterialFile(file)} | |
| /> | |
| ); | |
| return ( | |
| <section className="kb-workspace relative h-full min-h-0 overflow-hidden bg-[#030303] text-white"> | |
| <NextLink | |
| href="/tuition" | |
| className="absolute right-4 top-4 z-30 hidden min-h-9 items-center gap-2 rounded-xl border border-white/[0.1] bg-[#080808]/90 px-3 text-xs font-bold text-white/72 shadow-lg backdrop-blur transition hover:border-white/[0.18] hover:text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/24 lg:inline-flex" | |
| > | |
| <GraduationCap className="size-3.5" aria-hidden="true" /> | |
| Today's Tuition | |
| </NextLink> | |
| <input | |
| id="study-material-file-input" | |
| name="study-material-file" | |
| ref={materialInputRef} | |
| type="file" | |
| accept={MATERIAL_UPLOAD_ACCEPT} | |
| className="hidden" | |
| onChange={(event) => { | |
| const file = event.target.files?.[0]; | |
| event.target.value = ""; | |
| if (file) void handleMaterialFile(file); | |
| }} | |
| /> | |
| <input | |
| id="study-paper-file-input" | |
| name="study-paper-file" | |
| ref={paperInputRef} | |
| type="file" | |
| accept={PDF_UPLOAD_ACCEPT} | |
| className="hidden" | |
| onChange={(event) => { | |
| const file = event.target.files?.[0]; | |
| event.target.value = ""; | |
| if (file) void handlePaperFile(file); | |
| }} | |
| /> | |
| <div className={cn( | |
| "relative z-10 grid h-full min-h-0", | |
| hideLeftRail | |
| ? "grid-cols-1 xl:grid-cols-[minmax(0,1fr)_260px]" | |
| : "grid-cols-1 lg:grid-cols-[220px_minmax(0,1fr)] xl:grid-cols-[220px_minmax(0,1fr)_260px]", | |
| )}> | |
| {hideLeftRail ? null : <LeftWorkspaceRail | |
| documents={documents} | |
| sessions={chatSessions} | |
| selectedSourceIds={selectedSourceIds} | |
| search={workspaceSearch} | |
| onSearchChange={setWorkspaceSearch} | |
| onNewChat={newChat} | |
| onOpenSession={(sessionId) => void openSession(sessionId)} | |
| onToggleSource={toggleSource} | |
| onUpload={() => materialInputRef.current?.click()} | |
| isHistoryLoading={isHistoryLoading} | |
| />} | |
| <MainWorkspace | |
| activeIntent={activeIntent} | |
| composerProps={composerProps} | |
| documents={documents} | |
| heroSubtitle={heroSubtitle} | |
| heroTitle={heroTitle} | |
| isLoading={isLoading} | |
| messages={messages} | |
| onAction={runAction} | |
| onClearSources={clearSelectedSources} | |
| onNewChat={newChat} | |
| onOpenProfile={() => setMobilePanel("profile")} | |
| onOpenSources={() => setMobilePanel("sources")} | |
| onStartRender={startVideoRender} | |
| onToggleSource={toggleSource} | |
| onUpload={() => materialInputRef.current?.click()} | |
| selectedDocuments={selectedDocuments} | |
| studentName={studentName} | |
| threadEndRef={threadEndRef} | |
| /> | |
| {rightPanel} | |
| </div> | |
| <MobilePanelDrawer panel={mobilePanel} onClose={() => setMobilePanel(null)}> | |
| <MobileDrawerContent | |
| activeIntent={activeIntent} | |
| dashboard={dashboard} | |
| isLoading={isLoading} | |
| messages={messages} | |
| onAction={runAction} | |
| panel={mobilePanel} | |
| sourceRail={sourceRail} | |
| userName={user?.name ?? "Student"} | |
| videoJobs={videoJobs} | |
| /> | |
| </MobilePanelDrawer> | |
| <MobileBottomNav | |
| activePanel={mobilePanel} | |
| onOpenPanel={(panel) => setMobilePanel(panel)} | |
| onClosePanels={() => setMobilePanel(null)} | |
| /> | |
| <SourceClassificationModal | |
| isOpen={classificationModal.isOpen} | |
| onClose={closeClassificationModal} | |
| documentId={classificationModal.documentId ?? ""} | |
| documentTitle={classificationModal.documentTitle} | |
| currentMaterialType={classificationModal.currentMaterialType} | |
| autoDetectedType={classificationModal.autoDetectedType} | |
| onClassificationComplete={handleClassificationComplete} | |
| /> | |
| </section> | |
| ); | |
| } | |
| export function StudyChatShell({ | |
| mode = "home", | |
| initialIntent = "teach_topic", | |
| title, | |
| subtitle, | |
| hideLeftRail = false, | |
| }: StudyChatShellProps) { | |
| return ( | |
| <Suspense fallback={ | |
| <div className="flex min-h-screen items-center justify-center"> | |
| <div className="text-white/50">Loading Study Chat...</div> | |
| </div> | |
| }> | |
| <StudyChatShellInner | |
| mode={mode} | |
| initialIntent={initialIntent} | |
| title={title} | |
| subtitle={subtitle} | |
| hideLeftRail={hideLeftRail} | |
| /> | |
| </Suspense> | |
| ); | |
| } | |