Download src/components/study-chat/ConversationWorkspace.tsx from asnannp/DocDoeAI: direct link, hf CLI and curl.
- Browser
- Download file 6.47 kB
-
https://huggingface.co/spaces/asnannp/DocDoeAI/resolve/main/src/components/study-chat/ConversationWorkspace.tsx
- Command line
-
hf download hf://spaces/asnannp/DocDoeAI/src/components/study-chat/ConversationWorkspace.tsx
-
curl -L -o ConversationWorkspace.tsx https://huggingface.co/spaces/asnannp/DocDoeAI/resolve/main/src/components/study-chat/ConversationWorkspace.tsx
6.47 kB
| "use client"; | |
| import type { ComponentProps, RefObject } from "react"; | |
| import { LoaderCircle, Plus } from "lucide-react"; | |
| import type { ApiDocument } from "@/lib/api/types"; | |
| import { studyIntentConfig, type StudyIntent } from "@/lib/study-intents"; | |
| import { cn } from "@/lib/utils"; | |
| import { QuickActions } from "./QuickActions"; | |
| import { StagedThinkingLine } from "./StagedThinking"; | |
| import { SelectedSourcesStrip } from "./SourceControls"; | |
| import { PlainAssistantMessage, StudyCardRenderer } from "./StudyCards"; | |
| import { StudyComposer } from "./StudyComposer"; | |
| import type { ChatMessage, StudyActionHandler } from "./types"; | |
| type ConversationWorkspaceProps = { | |
| activeIntent: StudyIntent; | |
| composerProps: ComponentProps<typeof StudyComposer>; | |
| documents: ApiDocument[]; | |
| isLoading: boolean; | |
| messages: ChatMessage[]; | |
| onAction: StudyActionHandler; | |
| onClearSources: () => void; | |
| onNewChat: () => void; | |
| onStartRender?: (topic: string, sourceId: string | null, minutes: number) => void; | |
| onToggleSource: (sourceId: string) => void; | |
| onUpload?: () => void; | |
| selectedDocuments: ApiDocument[]; | |
| threadEndRef: RefObject<HTMLDivElement | null>; | |
| }; | |
| export function ConversationWorkspace({ | |
| activeIntent, | |
| composerProps, | |
| documents, | |
| isLoading, | |
| messages, | |
| onAction, | |
| onClearSources, | |
| onNewChat, | |
| onStartRender, | |
| onToggleSource, | |
| onUpload, | |
| selectedDocuments, | |
| threadEndRef, | |
| }: ConversationWorkspaceProps) { | |
| const intentLabel = studyIntentConfig[activeIntent].shortLabel ?? studyIntentConfig[activeIntent].label; | |
| const lastMessage = messages[messages.length - 1]; | |
| const showTypingIndicator = | |
| isLoading && (!lastMessage || lastMessage.role !== "assistant" || !lastMessage.card || lastMessage.card.kind !== "loading"); | |
| return ( | |
| <> | |
| <div className="kb-thread-scroll min-h-0 flex-1 overflow-y-auto px-4 pb-6 pt-4 sm:px-6 lg:px-8"> | |
| <div className="mx-auto w-full max-w-4xl xl:max-w-[min(100%,52rem)]"> | |
| <div className="mb-5 flex flex-wrap items-center justify-between gap-3"> | |
| <div className="min-w-0"> | |
| <p className="text-[11px] font-bold uppercase tracking-[0.14em] text-white/32">Study Chat</p> | |
| <div className="mt-1 flex flex-wrap items-center gap-2"> | |
| <h1 className="text-lg font-semibold text-white sm:text-xl">Study session</h1> | |
| <span className="inline-flex items-center rounded-full border border-white/[0.1] bg-white/[0.04] px-2.5 py-1 text-[11px] font-semibold text-white/52"> | |
| {intentLabel} | |
| </span> | |
| </div> | |
| </div> | |
| <button | |
| type="button" | |
| onClick={onNewChat} | |
| className="inline-flex min-h-10 items-center gap-2 rounded-xl border border-white/[0.1] bg-white/[0.035] px-3 text-sm font-semibold text-white/62 transition hover:border-white/[0.18] hover:bg-white/[0.05] hover:text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/22" | |
| > | |
| <Plus className="size-4" /> | |
| New chat | |
| </button> | |
| </div> | |
| <SelectedSourcesStrip | |
| selectedDocuments={selectedDocuments} | |
| documents={documents} | |
| onToggleSource={onToggleSource} | |
| onClear={onClearSources} | |
| /> | |
| <ConversationThread | |
| messages={messages} | |
| onAction={onAction} | |
| onUpload={onUpload} | |
| onStartRender={onStartRender} | |
| showTypingIndicator={showTypingIndicator} | |
| activeIntent={activeIntent} | |
| threadEndRef={threadEndRef} | |
| /> | |
| </div> | |
| </div> | |
| <div className="kb-composer-dock relative shrink-0 border-t border-white/[0.075] bg-[#030303]/92 p-3 pb-[calc(5rem+env(safe-area-inset-bottom))] backdrop-blur-xl lg:p-4 lg:pb-4"> | |
| <div className="pointer-events-none absolute inset-x-0 -top-8 h-8 bg-gradient-to-t from-[#030303] to-transparent" aria-hidden="true" /> | |
| <div className="mx-auto w-full max-w-4xl xl:max-w-[min(100%,52rem)]"> | |
| <StudyComposer {...composerProps} compact /> | |
| <div className="mt-3 hidden lg:block"> | |
| <QuickActions activeIntent={activeIntent} disabled={isLoading} onAction={onAction} layout="grid" /> | |
| </div> | |
| </div> | |
| </div> | |
| </> | |
| ); | |
| } | |
| function ConversationThread({ | |
| messages, | |
| onAction, | |
| onUpload, | |
| onStartRender, | |
| showTypingIndicator, | |
| activeIntent, | |
| threadEndRef, | |
| }: { | |
| messages: ChatMessage[]; | |
| onAction: StudyActionHandler; | |
| onUpload?: () => void; | |
| onStartRender?: (topic: string, sourceId: string | null, minutes: number) => void; | |
| showTypingIndicator: boolean; | |
| activeIntent: StudyIntent; | |
| threadEndRef: RefObject<HTMLDivElement | null>; | |
| }) { | |
| return ( | |
| <div className="mt-5 space-y-6"> | |
| {messages.map((message) => ( | |
| <div key={message.id} className={cn("flex", message.role === "user" ? "justify-end" : "justify-start")}> | |
| {message.role === "user" ? ( | |
| <div className="animate-msg-user max-w-[min(42rem,88vw)] rounded-[1.15rem] border border-white/[0.09] bg-white px-4 py-3 text-sm leading-6 text-black shadow-[0_14px_40px_rgba(0,0,0,0.24)]"> | |
| {message.text} | |
| </div> | |
| ) : message.card ? ( | |
| <div className="w-full"> | |
| <StudyCardRenderer card={message.card} onAction={onAction} onUpload={onUpload} onStartRender={onStartRender} /> | |
| </div> | |
| ) : ( | |
| <PlainAssistantMessage text={message.text} /> | |
| )} | |
| </div> | |
| ))} | |
| {showTypingIndicator ? <TypingIndicator intent={activeIntent} /> : null} | |
| <div ref={threadEndRef} /> | |
| </div> | |
| ); | |
| } | |
| function TypingIndicator({ intent }: { intent: StudyIntent }) { | |
| return ( | |
| <div className="flex justify-start"> | |
| <div | |
| className="animate-msg-assistant inline-flex items-center gap-3 rounded-[1.15rem] border border-white/[0.08] bg-white/[0.03] px-4 py-3 text-sm text-white/52" | |
| aria-live="polite" | |
| aria-label="DocDoe is preparing your answer" | |
| > | |
| <LoaderCircle className="size-4 shrink-0 animate-spin text-white/42" /> | |
| <span className="inline-flex items-center gap-1"> | |
| <span className="typing-dot" /> | |
| <span className="typing-dot" /> | |
| <span className="typing-dot" /> | |
| </span> | |
| <StagedThinkingLine intent={intent} /> | |
| </div> | |
| </div> | |
| ); | |
| } | |