Spaces:
Sleeping
Sleeping
| import { useRef, useState, useTransition } from "react" | |
| import Link from "next/link" | |
| import { encode, decode } from 'js-base64' | |
| // import throttle from "@jcoreio/async-throttle" | |
| import debounce from "lodash.debounce" | |
| import { GoSearch } from "react-icons/go" | |
| import { useStore } from "@/app/state/useStore" | |
| import { cn } from "@/lib/utils/cn" | |
| import { Input } from "@/components/ui/input" | |
| import { Button } from "@/components/ui/button" | |
| import { MediaInfo } from "@/types/general" | |
| import { search } from "@/app/api/v1/search" | |
| export function LatentSearchInput() { | |
| const [_pending, startTransition] = useTransition() | |
| const setSearchAutocompleteQuery = useStore(s => s.setSearchAutocompleteQuery) | |
| const showAutocompleteBox = useStore(s => s.showAutocompleteBox) | |
| const setShowAutocompleteBox = useStore(s => s.setShowAutocompleteBox) | |
| const searchAutocompleteResults = useStore(s => s.searchAutocompleteResults) | |
| const setSearchAutocompleteResults = useStore(s => s.setSearchAutocompleteResults) | |
| const setSearchQuery = useStore(s => s.setSearchQuery) | |
| const [searchDraft, setSearchDraft] = useState("") | |
| const ref = useRef<HTMLInputElement>(null) | |
| // called when pressing enter or clicking on search | |
| const debouncedSearch = debounce((query: string) => { | |
| startTransition(async () => { | |
| console.log(`searching the latent space for "${query}"..`) | |
| if (query.length < 2) { console.log("search term is too short") } | |
| console.log("imaginating medias..") | |
| const imaginedMedias = await search({ | |
| prompt: query, | |
| nbResults: 4 | |
| }) | |
| console.log(`imagined ${imaginedMedias.length} results:`, imaginedMedias) | |
| setSearchAutocompleteResults(imaginedMedias.map(item => ({ | |
| title: item.title, | |
| tags: item.tags, | |
| }))) | |
| // TODO: only close the show autocomplete box if we found something | |
| // setShowAutocompleteBox(false) | |
| }) | |
| }, 500) | |
| // called when pressing enter or clicking on search | |
| const handleSearch = () => { | |
| ref.current?.focus() | |
| setSearchQuery(searchDraft) | |
| setShowAutocompleteBox(true) | |
| debouncedSearch(searchDraft) | |
| } | |
| return ( | |
| <div className="flex flex-row flex-grow w-[380px] lg:w-[600px]"> | |
| <div className="flex flex-row w-full"> | |
| <Input | |
| ref={ref} | |
| placeholder="Search the latent space" | |
| className={cn( | |
| `bg-neutral-900 text-neutral-200 dark:bg-neutral-900 dark:text-neutral-200`, | |
| `rounded-l-full rounded-r-none`, | |
| // we increase the line height to better catch the clicks | |
| `py-0 h-10 leading-7`, | |
| `border-neutral-700 dark:border-neutral-700 border-r-0`, | |
| )} | |
| onFocus={() => { | |
| handleSearch() | |
| }} | |
| onChange={(e) => { | |
| setSearchDraft(e.target.value) | |
| setShowAutocompleteBox(true) | |
| // handleSearch() | |
| }} | |
| onKeyDown={({ key }) => { | |
| if (key === 'Enter') { | |
| handleSearch() | |
| } | |
| }} | |
| value={searchDraft} | |
| /> | |
| <Button | |
| className={cn( | |
| `rounded-l-none rounded-r-full border border-neutral-700 dark:border-neutral-700`, | |
| `cursor-pointer`, | |
| `transition-all duration-200 ease-in-out`, | |
| `text-neutral-200 dark:text-neutral-200 bg-neutral-800 dark:bg-neutral-800 hover:bg-neutral-700 disabled:bg-neutral-900` | |
| )} | |
| onClick={() => { | |
| handleSearch() | |
| // console.log("submit") | |
| // setShowAutocompleteBox(false) | |
| // setSearchDraft("") | |
| }} | |
| disabled={false} | |
| > | |
| <GoSearch className="w-6 h-6" /> | |
| </Button> | |
| </div> | |
| <div | |
| className={cn( | |
| `absolute z-50 ml-1`, | |
| // please keep this in sync with the parent | |
| `w-[320px] lg:w-[540px]`, | |
| `text-neutral-200 dark:text-neutral-200 bg-neutral-900 dark:bg-neutral-900`, | |
| `border border-neutral-800 dark:border-neutral-800`, | |
| `rounded-xl shadow-2xl`, | |
| `flex flex-col p-2 space-y-1`, | |
| `transition-all duration-200 ease-in-out`, | |
| showAutocompleteBox | |
| ? `opacity-100 scale-100 mt-11 pointer-events-auto` | |
| : `opacity-0 scale-95 mt-6 pointer-events-none` | |
| )} | |
| > | |
| {searchAutocompleteResults.length === 0 ? <div>Nothing to show, type something and press enter</div> : null} | |
| {searchAutocompleteResults.map(item => ( | |
| <Link key={item.id} href={ | |
| item.id | |
| ? `${process.env.NEXT_PUBLIC_DOMAIN}/watch?v=${item.id}` | |
| : `${process.env.NEXT_PUBLIC_DOMAIN}/latent/watch?p=${encode(JSON.stringify(item))}` | |
| }> | |
| <div | |
| className={cn( | |
| `dark:hover:bg-neutral-800 hover:bg-neutral-800`, | |
| `text-sm`, | |
| `px-3 py-2`, | |
| `rounded-xl` | |
| )} | |
| > | |
| {item.title} | |
| </div> | |
| </Link> | |
| ))} | |
| </div> | |
| </div> | |
| ) | |
| } | |