import { Button, Group, Stack, Textarea } from "@mantine/core"; import { usePubSub } from "create-pubsub/react"; import { type ChangeEvent, type KeyboardEvent, type ReactNode, useCallback, useEffect, useRef, useState, } from "react"; import { useLocation } from "wouter"; import { useHistoryRestore } from "../../../hooks/useHistoryRestore"; import { useSearchHistory } from "../../../hooks/useSearchHistory"; import { resetSearchRunId } from "../../../modules/history"; import { handleEnterKeyDown } from "../../../modules/keyboard"; import { addLogEntry } from "../../../modules/logEntries"; import { postMessageToParentWindow } from "../../../modules/parentWindow"; import { imageSearchResultsPubSub, isRestoringFromHistoryPubSub, responsePubSub, settingsPubSub, textSearchResultsPubSub, } from "../../../modules/pubSub"; import { getRandomQuerySuggestion } from "../../../modules/querySuggestions"; import { sleepUntilIdle } from "../../../modules/sleep"; import { searchAndRespond } from "../../../modules/textGeneration"; import HistoryButton from "../History/HistoryButton"; interface SearchFormState { textAreaValue: string; suggestedQuery: string; } export default function SearchForm({ query, updateQuery, additionalButtons, }: { query: string; updateQuery: (query: string) => void; additionalButtons?: ReactNode; }) { const textAreaRef = useRef(null); const autoInitializedQueriesRef = useRef(new Set()); const defaultSuggestedQuery = "Anything you need!"; const [state, setState] = useState({ textAreaValue: query, suggestedQuery: defaultSuggestedQuery, }); const [, navigate] = useLocation(); const [pubSubSettings] = usePubSub(settingsPubSub); const [isRestoringFromHistory] = usePubSub(isRestoringFromHistoryPubSub); const [textSearchResults] = usePubSub(textSearchResultsPubSub); const [imageSearchResults] = usePubSub(imageSearchResultsPubSub); const [responseValue] = usePubSub(responsePubSub); const { addToHistory } = useSearchHistory(); const { restoreSearch } = useHistoryRestore((newQuery) => { setState((prev) => ({ ...prev, textAreaValue: newQuery })); updateQuery(newQuery); }, textAreaRef); const fetchQuerySuggestion = useCallback(async () => { try { return await getRandomQuerySuggestion(); } catch { return defaultSuggestedQuery; } }, []); useEffect(() => { const initializeComponent = async () => { const urlParams = new URLSearchParams(window.location.search); const urlQuery = urlParams.get("q"); const normalizedUrlQuery = urlQuery?.trim(); const hasRestoredResults = textSearchResults.length > 0 || imageSearchResults.length > 0; const hasRestoredResponse = responseValue.trim().length > 0; if ( normalizedUrlQuery && !autoInitializedQueriesRef.current.has(normalizedUrlQuery) && !isRestoringFromHistory && !hasRestoredResults && !hasRestoredResponse ) { autoInitializedQueriesRef.current.add(normalizedUrlQuery); await sleepUntilIdle(); try { resetSearchRunId(); await addToHistory(normalizedUrlQuery, { type: "text" as const, items: [], }); } catch (error) { addLogEntry(`Failed to add search to history: ${error}`); } searchAndRespond(); } }; void initializeComponent(); }, [ isRestoringFromHistory, textSearchResults.length, imageSearchResults.length, responseValue, addToHistory, ]); useEffect(() => { if (state.textAreaValue.length === 0) { fetchQuerySuggestion() .then((suggestion) => { setState((prev) => ({ ...prev, suggestedQuery: suggestion })); }) .catch(() => { setState((prev) => ({ ...prev, suggestedQuery: defaultSuggestedQuery, })); }); } }, [state.textAreaValue.length, fetchQuerySuggestion]); const handleInputChange = async (event: ChangeEvent) => { const text = event.target.value; setState((prev) => ({ ...prev, textAreaValue: text })); if (text.length === 0) { try { const suggestion = await getRandomQuerySuggestion(); setState((prev) => ({ ...prev, suggestedQuery: suggestion })); } catch { addLogEntry("Failed to get query suggestion"); setState((prev) => ({ ...prev, suggestedQuery: defaultSuggestedQuery, })); } } }; const handleClearButtonClick = async () => { textAreaRef.current?.focus(); setState((prev) => ({ ...prev, textAreaValue: "", })); addLogEntry("User cleaned the search query field"); const suggestion = await fetchQuerySuggestion(); setState((prev) => ({ ...prev, suggestedQuery: suggestion, })); }; const startSearching = useCallback(async () => { const queryToEncode = state.textAreaValue.trim().length >= 1 ? state.textAreaValue : state.suggestedQuery; const normalizedQuery = queryToEncode.trim(); if (normalizedQuery.length > 0) { autoInitializedQueriesRef.current.add(normalizedQuery); } setState((prev) => ({ ...prev, textAreaValue: queryToEncode })); updateQuery(queryToEncode); const queryString = `q=${encodeURIComponent(queryToEncode)}`; postMessageToParentWindow({ queryString, hash: "" }); navigate(`/?${queryString}`, { replace: true }); try { resetSearchRunId(); await addToHistory(queryToEncode, { type: "text" as const, items: [], }); } catch (error) { addLogEntry(`Failed to add search to history: ${error}`); } searchAndRespond(); addLogEntry( `User submitted a search with ${queryToEncode.length} characters length`, ); }, [ state.textAreaValue, state.suggestedQuery, updateQuery, navigate, addToHistory, ]); const handleSubmit = (event: React.FormEvent) => { event.preventDefault(); startSearching(); }; const handleKeyDown = (event: KeyboardEvent) => { if (event.key === "Enter" && !event.shiftKey) { handleEnterKeyDown(event, pubSubSettings, startSearching); } }; return (