"use client"; import { useAtomValue, useSetAtom } from "jotai"; import { Plus, Zap } from "lucide-react"; import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState, } from "react"; import { promptsAtom } from "@/atoms/prompts/prompts-query.atoms"; import { userSettingsDialogAtom } from "@/atoms/settings/settings-dialog.atoms"; import { Spinner } from "@/components/ui/spinner"; import { cn } from "@/lib/utils"; export interface PromptPickerRef { selectHighlighted: () => void; moveUp: () => void; moveDown: () => void; } interface PromptPickerProps { onSelect: (action: { name: string; prompt: string; mode: "transform" | "explore" }) => void; onDone: () => void; externalSearch?: string; containerStyle?: React.CSSProperties; } export const PromptPicker = forwardRef(function PromptPicker( { onSelect, onDone, externalSearch = "", containerStyle }, ref ) { const setUserSettingsDialog = useSetAtom(userSettingsDialogAtom); const { data: prompts, isLoading, isError } = useAtomValue(promptsAtom); const [highlightedIndex, setHighlightedIndex] = useState(0); const scrollContainerRef = useRef(null); const shouldScrollRef = useRef(false); const itemRefs = useRef>(new Map()); const filtered = useMemo(() => { const list = prompts ?? []; if (!externalSearch) return list; return list.filter((a) => a.name.toLowerCase().includes(externalSearch.toLowerCase())); }, [prompts, externalSearch]); const prevSearchRef = useRef(externalSearch); if (prevSearchRef.current !== externalSearch) { prevSearchRef.current = externalSearch; if (highlightedIndex !== 0) { setHighlightedIndex(0); } } const handleSelect = useCallback( (index: number) => { const action = filtered[index]; if (!action) return; onSelect({ name: action.name, prompt: action.prompt, mode: action.mode }); }, [filtered, onSelect] ); useEffect(() => { if (!shouldScrollRef.current) return; shouldScrollRef.current = false; const rafId = requestAnimationFrame(() => { const item = itemRefs.current.get(highlightedIndex); const container = scrollContainerRef.current; if (item && container) { const itemRect = item.getBoundingClientRect(); const containerRect = container.getBoundingClientRect(); if (itemRect.top < containerRect.top || itemRect.bottom > containerRect.bottom) { item.scrollIntoView({ block: "nearest" }); } } }); return () => cancelAnimationFrame(rafId); }, [highlightedIndex]); useImperativeHandle( ref, () => ({ selectHighlighted: () => handleSelect(highlightedIndex), moveUp: () => { if (filtered.length === 0) return; shouldScrollRef.current = true; setHighlightedIndex((prev) => (prev > 0 ? prev - 1 : filtered.length - 1)); }, moveDown: () => { if (filtered.length === 0) return; shouldScrollRef.current = true; setHighlightedIndex((prev) => (prev < filtered.length - 1 ? prev + 1 : 0)); }, }), [filtered.length, highlightedIndex, handleSelect] ); return (
{isLoading ? (
) : isError ? (

Failed to load prompts

) : filtered.length === 0 ? (

No matching prompts

) : ( filtered.map((action, index) => ( )) )}
); });