From be2ce116e433841742a996f6996796fec54fbabd Mon Sep 17 00:00:00 2001 From: ibraheemshaikh5 Date: Mon, 9 Feb 2026 16:55:18 -0500 Subject: [PATCH 1/9] feat: add generation settings popover with content type toggles --- src/components/home/HomePromptInput.tsx | 326 +++++++++++++++++++----- 1 file changed, 258 insertions(+), 68 deletions(-) diff --git a/src/components/home/HomePromptInput.tsx b/src/components/home/HomePromptInput.tsx index 90707ea..1ff94c8 100644 --- a/src/components/home/HomePromptInput.tsx +++ b/src/components/home/HomePromptInput.tsx @@ -7,7 +7,7 @@ import { toast } from "sonner"; import { useCreateWorkspaceFromPrompt } from "@/hooks/workspace/use-create-workspace"; import type { UploadedPdfMetadata } from "@/hooks/workspace/use-pdf-upload"; // import { useImageUpload } from "@/hooks/workspace/use-image-upload"; -import { ArrowUp, FileText, Loader2, X, Link as LinkIcon } from "lucide-react"; +import { ArrowUp, FileText, Loader2, X, Link as LinkIcon, SlidersHorizontal } from "lucide-react"; // import { ImageIcon } from "lucide-react"; import { cn } from "@/lib/utils"; import { Input } from "@/components/ui/input"; @@ -21,7 +21,43 @@ import { DialogFooter, DialogClose, } from "@/components/ui/dialog"; +import { Popover, PopoverTrigger, PopoverContent } from "@/components/ui/popover"; import type { PdfData } from "@/lib/workspace-state/types"; + +// --- Generation Settings --- +type GenContentType = 'note' | 'quiz' | 'flashcard' | 'youtube'; + +interface GenerationSettings { + auto: boolean; + types: GenContentType[]; +} + +const DEFAULT_GEN_SETTINGS: GenerationSettings = { + auto: true, + types: ['note', 'quiz', 'flashcard', 'youtube'], +}; + +const ALL_CONTENT_TYPES: { key: GenContentType; label: string }[] = [ + { key: 'note', label: 'Notes' }, + { key: 'quiz', label: 'Quizzes' }, + { key: 'flashcard', label: 'Flashcards' }, + { key: 'youtube', label: 'YouTube Videos' }, +]; + +function loadGenSettings(): GenerationSettings { + if (typeof window === 'undefined') return DEFAULT_GEN_SETTINGS; + try { + const stored = localStorage.getItem('thinkex-gen-settings'); + if (stored) { + const parsed = JSON.parse(stored); + return { + auto: typeof parsed.auto === 'boolean' ? parsed.auto : true, + types: Array.isArray(parsed.types) ? parsed.types : DEFAULT_GEN_SETTINGS.types, + }; + } + } catch {} + return DEFAULT_GEN_SETTINGS; +} // import type { ImageData } from "@/lib/workspace-state/types"; const PLACEHOLDER_OPTIONS = [ @@ -69,9 +105,27 @@ export function HomePromptInput({ shouldFocus, uploadedFiles, isUploading, remov const [typedPrefix, setTypedPrefix] = useState(""); const [isUrlDialogOpen, setIsUrlDialogOpen] = useState(false); const [urlInput, setUrlInput] = useState(""); + const [genSettings, setGenSettings] = useState(loadGenSettings); const inputRef = useRef(null); const typingKeyRef = useRef(0); + // Persist generation settings to localStorage + useEffect(() => { + try { + localStorage.setItem('thinkex-gen-settings', JSON.stringify(genSettings)); + } catch {} + }, [genSettings]); + + const toggleGenType = (key: GenContentType) => { + setGenSettings(prev => { + const has = prev.types.includes(key); + return { + ...prev, + types: has ? prev.types.filter(t => t !== key) : [...prev.types, key], + }; + }); + }; + const createFromPrompt = useCreateWorkspaceFromPrompt(); // const { // uploadFiles: uploadImages, @@ -175,6 +229,15 @@ export function HomePromptInput({ shouldFocus, uploadedFiles, isUploading, remov const hasUploads = uploadedFiles.length > 0; + // Determine effective content types based on settings + // If auto or no valid types selected, use all defaults + const effectiveTypes = new Set( + !genSettings.auto && genSettings.types.length > 0 + ? genSettings.types + : ['note', 'quiz', 'flashcard', 'youtube'] + ); + const isCustom = !genSettings.auto && genSettings.types.length > 0; + // Construct initial state with file cards AND empty placeholder cards if files were uploaded let initialState = undefined; if (hasUploads) { @@ -186,7 +249,7 @@ export function HomePromptInput({ shouldFocus, uploadedFiles, isUploading, remov type: 'pdf' as const, name: file.name, subtitle: '', - color: '#6366F1' as const, // Indigo for PDFs + color: '#6366F1' as const, layout: { x: 0, y: index * fileHeight, w: 4, h: fileHeight }, lastSource: 'user' as const, data: { @@ -198,61 +261,51 @@ export function HomePromptInput({ shouldFocus, uploadedFiles, isUploading, remov const totalUploadY = uploadedFiles.length * fileHeight; - // const pdfEndY = uploadedFiles.length * fileHeight; - - // // Create Image card items (stacked below PDFs) - // const imageItems = uploadedImages.map((file, index) => ({ - // id: crypto.randomUUID(), - // type: 'image' as const, - // name: file.name, - // subtitle: '', - // color: '#8B5CF6' as const, // Violet for Images - // layout: { x: 0, y: pdfEndY + index * fileHeight, w: 4, h: fileHeight }, - // lastSource: 'user' as const, - // data: { - // fileUrl: file.fileUrl, - // filename: file.filename, - // fileSize: file.fileSize, - // } as ImageData, - // })); - - // const totalUploadY = pdfEndY + uploadedImages.length * fileHeight; - - // Create empty placeholder cards with fixed layout and colors - const emptyNote = { - id: crypto.randomUUID(), - type: 'note' as const, - name: 'Update me', - subtitle: '', - color: '#10B981' as const, - layout: { x: 0, y: totalUploadY, w: 4, h: 13 }, - lastSource: 'user' as const, - data: { blockContent: [], field1: '' }, - }; + // Build placeholder cards based on effective types, stacking layouts dynamically + const placeholders: any[] = []; + let currentY = totalUploadY; + + if (effectiveTypes.has('note')) { + placeholders.push({ + id: crypto.randomUUID(), + type: 'note' as const, + name: 'Update me', + subtitle: '', + color: '#10B981' as const, + layout: { x: 0, y: currentY, w: 4, h: 13 }, + lastSource: 'user' as const, + data: { blockContent: [], field1: '' } as any, + }); + currentY += 13; + } - const emptyQuiz = { - id: crypto.randomUUID(), - type: 'quiz' as const, - name: 'Update me', - subtitle: '', - color: '#F59E0B' as const, - layout: { x: 0, y: totalUploadY + 13, w: 2, h: 13 }, - lastSource: 'user' as const, - data: { questions: [] }, - }; + if (effectiveTypes.has('quiz')) { + placeholders.push({ + id: crypto.randomUUID(), + type: 'quiz' as const, + name: 'Update me', + subtitle: '', + color: '#F59E0B' as const, + layout: { x: 0, y: currentY, w: 2, h: 13 }, + lastSource: 'user' as const, + data: { questions: [] } as any, + }); + } - const emptyFlashcard = { - id: crypto.randomUUID(), - type: 'flashcard' as const, - name: 'Update me', - subtitle: '', - color: '#EC4899' as const, - layout: { x: 2, y: totalUploadY + 13, w: 2, h: 8 }, - lastSource: 'user' as const, - data: { cards: [] }, - }; + if (effectiveTypes.has('flashcard')) { + placeholders.push({ + id: crypto.randomUUID(), + type: 'flashcard' as const, + name: 'Update me', + subtitle: '', + color: '#EC4899' as const, + layout: { x: effectiveTypes.has('quiz') ? 2 : 0, y: currentY, w: 2, h: 8 }, + lastSource: 'user' as const, + data: { cards: [] } as any, + }); + } - const allItems = [...pdfItems, emptyNote, emptyQuiz, emptyFlashcard]; + const allItems: any[] = [...pdfItems, ...placeholders]; initialState = { workspaceId: '', @@ -263,13 +316,74 @@ export function HomePromptInput({ shouldFocus, uploadedFiles, isUploading, remov }; } + // For no-uploads path with custom settings, build a custom initial state + // instead of using the getting_started template + let template: "blank" | "getting_started"; + if (hasUploads) { + template = "blank"; + } else if (isCustom) { + template = "blank"; + const placeholders: any[] = []; + let currentY = 0; + const colors = ['#10B981', '#F59E0B', '#EC4899']; + let colorIdx = 0; + + if (effectiveTypes.has('note')) { + placeholders.push({ + id: crypto.randomUUID(), + type: 'note', + name: 'Update me', + subtitle: '', + color: colors[colorIdx++ % colors.length], + layout: { x: 0, y: currentY, w: 4, h: 9 }, + data: { blockContent: [], field1: '' }, + }); + currentY += 9; + } + + if (effectiveTypes.has('quiz')) { + placeholders.push({ + id: crypto.randomUUID(), + type: 'quiz', + name: 'Update me', + subtitle: '', + color: colors[colorIdx++ % colors.length], + layout: { x: 0, y: currentY, w: 2, h: 13 }, + data: { questions: [] }, + }); + } + + if (effectiveTypes.has('flashcard')) { + placeholders.push({ + id: crypto.randomUUID(), + type: 'flashcard', + name: 'Update me', + subtitle: '', + color: colors[colorIdx++ % colors.length], + layout: { x: effectiveTypes.has('quiz') ? 2 : 0, y: currentY, w: 2, h: 9 }, + data: { cards: [] }, + }); + } + + if (placeholders.length > 0) { + initialState = { + workspaceId: '', + globalTitle: '', + globalDescription: '', + items: placeholders, + itemsCreated: placeholders.length, + }; + } + } else { + template = "getting_started"; + } + createFromPrompt.mutate(prompt, { - template: hasUploads ? "blank" : "getting_started", + template, initialState, onSuccess: (workspace) => { typingKeyRef.current += 1; clearFiles(); - // clearImages(); const url = `/workspace/${workspace.slug}`; const params = new URLSearchParams(); @@ -279,6 +393,11 @@ export function HomePromptInput({ shouldFocus, uploadedFiles, isUploading, remov params.set('createFrom', prompt); } + // Pass custom generation types so AssistantPanel builds the right prompt + if (isCustom) { + params.set('genTypes', Array.from(effectiveTypes).join(',')); + } + router.push(`${url}?${params.toString()}`); }, onError: (err) => { @@ -488,18 +607,89 @@ export function HomePromptInput({ shouldFocus, uploadedFiles, isUploading, remov Add URL - {/* */} + +
+ { + // Snap auto back on if user closes popover with no types selected + if (!open && !genSettings.auto && genSettings.types.length === 0) { + setGenSettings(prev => ({ ...prev, auto: true })); + } + }}> + + + + e.stopPropagation()} + > +
+ {ALL_CONTENT_TYPES.map(({ key, label }) => { + const active = genSettings.types.includes(key); + return ( + + ); + })} +
+
+ + + +