Download src/components/study-chat/StudyComposer.tsx from asnannp/DocDoeAI: direct link, hf CLI and curl.
- Browser
- Download file 7.16 kB
-
https://huggingface.co/spaces/asnannp/DocDoeAI/resolve/main/src/components/study-chat/StudyComposer.tsx
- Command line
-
hf download hf://spaces/asnannp/DocDoeAI/src/components/study-chat/StudyComposer.tsx
-
curl -L -o StudyComposer.tsx https://huggingface.co/spaces/asnannp/DocDoeAI/resolve/main/src/components/study-chat/StudyComposer.tsx
7.16 kB
| "use client"; | |
| import { useState } from "react"; | |
| import { | |
| ArrowUp, | |
| LoaderCircle, | |
| } from "lucide-react"; | |
| import type { ApiDocument } from "@/lib/api/types"; | |
| import { cn } from "@/lib/utils"; | |
| import { ComposerPlusMenu } from "./ComposerPlusMenu"; | |
| import { ComposerSourceDraft } from "./ComposerSourceDraft"; | |
| import { SourcePill } from "./SourceControls"; | |
| import type { SourceDraftMode } from "./types"; | |
| export function StudyComposer({ | |
| value, | |
| onChange, | |
| onSubmit, | |
| placeholder, | |
| disabled, | |
| selectedDocuments, | |
| onRemoveSource, | |
| onMaterialFile, | |
| onOpenMaterialPicker, | |
| onOpenPaperPicker, | |
| sourceDraftMode, | |
| onSourceDraftModeChange, | |
| draftTitle, | |
| onDraftTitleChange, | |
| draftText, | |
| onDraftTextChange, | |
| onSaveDraftSource, | |
| isSavingDraft, | |
| isUploading, | |
| uploadNotice, | |
| onRetryUpload, | |
| plusOpen, | |
| onPlusOpenChange, | |
| compact = false, | |
| variant = "default", | |
| }: { | |
| value: string; | |
| onChange: (value: string) => void; | |
| onSubmit: () => void; | |
| placeholder: string; | |
| disabled: boolean; | |
| selectedDocuments: ApiDocument[]; | |
| onRemoveSource: (id: string) => void; | |
| onMaterialFile: (file: File) => void; | |
| onOpenMaterialPicker: () => void; | |
| onOpenPaperPicker: () => void; | |
| sourceDraftMode: SourceDraftMode; | |
| onSourceDraftModeChange: (mode: SourceDraftMode) => void; | |
| draftTitle: string; | |
| onDraftTitleChange: (value: string) => void; | |
| draftText: string; | |
| onDraftTextChange: (value: string) => void; | |
| onSaveDraftSource: () => void; | |
| isSavingDraft: boolean; | |
| isUploading: boolean; | |
| uploadNotice: string | null; | |
| onRetryUpload?: () => void; | |
| plusOpen: boolean; | |
| onPlusOpenChange: (open: boolean) => void; | |
| compact?: boolean; | |
| variant?: "default" | "hero"; | |
| }) { | |
| const [dragging, setDragging] = useState(false); | |
| function handleDrop(event: React.DragEvent<HTMLDivElement>) { | |
| event.preventDefault(); | |
| setDragging(false); | |
| const file = event.dataTransfer.files?.[0]; | |
| if (file) onMaterialFile(file); | |
| } | |
| return ( | |
| <div | |
| onDragOver={(event) => { | |
| event.preventDefault(); | |
| setDragging(true); | |
| }} | |
| onDragLeave={() => setDragging(false)} | |
| onDrop={handleDrop} | |
| className={cn( | |
| "kb-composer relative rounded-[1.35rem] border border-white/[0.095] bg-[#080808]/92 p-3 shadow-[0_18px_70px_rgba(0,0,0,0.38),inset_0_1px_0_rgba(255,255,255,0.04)] transition duration-200 focus-within:border-white/[0.22] focus-within:bg-[#0b0b0c] sm:p-4", | |
| variant === "hero" && !compact | |
| ? "kb-composer-hero border-white/[0.11] p-3.5 shadow-[0_22px_80px_rgba(0,0,0,0.45),inset_0_1px_0_rgba(255,255,255,0.05)] sm:p-5" | |
| : "", | |
| compact ? "rounded-[1.15rem] p-2.5 sm:p-3" : "", | |
| dragging | |
| ? "border-white/[0.28] bg-white/[0.07] shadow-[0_24px_90px_rgba(0,0,0,0.5)]" | |
| : "", | |
| )} | |
| > | |
| {dragging ? ( | |
| <div className="pointer-events-none absolute inset-2 z-10 grid place-items-center rounded-[1.05rem] border border-dashed border-white/[0.22] bg-black/72 text-sm font-semibold text-white/72"> | |
| Drop your study source | |
| </div> | |
| ) : null} | |
| {selectedDocuments.length > 0 ? ( | |
| <div className="mb-2 flex gap-2 overflow-x-auto pb-1 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"> | |
| <span className="text-[9px] self-center px-1.5 py-0.5 rounded bg-emerald-500/10 text-emerald-300 border border-emerald-400/20">grounded to {selectedDocuments.length} source(s) • multi-turn</span> | |
| {selectedDocuments.map((document) => ( | |
| <SourcePill | |
| key={document.id} | |
| document={document} | |
| active | |
| onToggle={() => onRemoveSource(document.id)} | |
| onRemove={() => onRemoveSource(document.id)} | |
| /> | |
| ))} | |
| </div> | |
| ) : null} | |
| {sourceDraftMode ? ( | |
| <ComposerSourceDraft | |
| sourceDraftMode={sourceDraftMode} | |
| draftTitle={draftTitle} | |
| onDraftTitleChange={onDraftTitleChange} | |
| draftText={draftText} | |
| onDraftTextChange={onDraftTextChange} | |
| onSaveDraftSource={onSaveDraftSource} | |
| onClose={() => onSourceDraftModeChange(null)} | |
| isSavingDraft={isSavingDraft} | |
| /> | |
| ) : null} | |
| <label className="sr-only" htmlFor={compact ? "docdoe-study-composer-sticky" : "docdoe-study-composer"}> | |
| Ask DocDoe | |
| </label> | |
| <textarea | |
| id={compact ? "docdoe-study-composer-sticky" : "docdoe-study-composer"} | |
| aria-label="Ask DocDoe" | |
| value={value} | |
| onChange={(event) => onChange(event.target.value)} | |
| placeholder={placeholder} | |
| rows={compact ? 1 : 2} | |
| className={cn( | |
| "w-full resize-none border-0 bg-transparent px-1 text-[16px] leading-7 text-white outline-none placeholder:text-white/34 focus:border-0 focus:shadow-none focus:ring-0", | |
| compact ? "min-h-[3rem]" : variant === "hero" ? "min-h-[5.5rem] sm:min-h-[6.25rem]" : "min-h-[5.15rem] sm:min-h-[5.7rem]", | |
| )} | |
| onKeyDown={(event) => { | |
| if (event.key === "Enter" && !event.shiftKey) { | |
| event.preventDefault(); | |
| onSubmit(); | |
| } | |
| }} | |
| /> | |
| <div className="flex items-center justify-between gap-2"> | |
| <div className="flex items-center gap-1"> | |
| <ComposerPlusMenu | |
| plusOpen={plusOpen} | |
| onPlusOpenChange={onPlusOpenChange} | |
| onOpenMaterialPicker={onOpenMaterialPicker} | |
| onOpenPaperPicker={onOpenPaperPicker} | |
| onSourceDraftModeChange={onSourceDraftModeChange} | |
| /> | |
| {uploadNotice ? ( | |
| <span className="ml-2 hidden text-xs text-white/42 sm:inline">{uploadNotice}</span> | |
| ) : null} | |
| </div> | |
| <div className="flex items-center gap-2"> | |
| {onRetryUpload ? ( | |
| <button | |
| type="button" | |
| onClick={onRetryUpload} | |
| className="inline-flex h-9 items-center rounded-full border border-white/[0.12] px-3 text-xs font-semibold text-white/64 transition hover:border-white/24 hover:text-white" | |
| > | |
| Retry | |
| </button> | |
| ) : null} | |
| <button | |
| type="button" | |
| disabled={disabled || isUploading || !value.trim()} | |
| onClick={onSubmit} | |
| aria-label="Send message" | |
| aria-busy={disabled || isUploading} | |
| className="inline-flex size-10 items-center justify-center rounded-full bg-white text-black shadow-[0_8px_24px_rgba(255,255,255,0.1)] transition hover:bg-white/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/30 disabled:cursor-not-allowed disabled:opacity-40" | |
| > | |
| {disabled || isUploading ? <LoaderCircle className="size-3.5 animate-spin" /> : <ArrowUp className="size-4" />} | |
| </button> | |
| </div> | |
| </div> | |
| {uploadNotice ? <p className="mt-1.5 text-xs leading-5 text-white/42 sm:hidden">{uploadNotice}</p> : null} | |
| {!compact ? ( | |
| <p className="mt-2 hidden text-[11px] text-white/28 sm:block">Enter to send · Shift+Enter for a new line</p> | |
| ) : null} | |
| </div> | |
| ); | |
| } | |