From 145d36c6577c99bb3e167b06394b8cdb8c1097b0 Mon Sep 17 00:00:00 2001 From: Pierre-Henry Soria Date: Fri, 3 May 2024 18:28:35 +1000 Subject: [PATCH 1/2] Refactoring: Add React Router DOM and make search results being instant (without refreshing page each time) --- client/components/App.tsx | 79 +++++++------------------------- client/components/SearchForm.tsx | 56 +++++++++++----------- package-lock.json | 39 ++++++++++++++++ package.json | 1 + 4 files changed, 82 insertions(+), 93 deletions(-) diff --git a/client/components/App.tsx b/client/components/App.tsx index ab5d090..a303efb 100644 --- a/client/components/App.tsx +++ b/client/components/App.tsx @@ -1,71 +1,24 @@ -import { usePubSub } from "create-pubsub/react"; -import { - promptPubSub, - responsePubSub, - searchResultsPubSub, - urlsDescriptionsPubSub, -} from "../modules/pubSub"; -import { SearchForm } from "./SearchForm"; -import { Toaster } from "react-hot-toast"; -import { SettingsButton } from "./SettingsButton"; -import Markdown from "markdown-to-jsx"; -import { getDisableAiResponseSetting } from "../modules/pubSub"; -import { SearchResultsList } from "./SearchResultsList"; -import { useEffect } from "react"; -import { prepareTextGeneration } from "../modules/textGeneration"; +import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'; -export function App() { - const [query, updateQuery] = usePubSub(promptPubSub); - const [response] = usePubSub(responsePubSub); - const [searchResults] = usePubSub(searchResultsPubSub); - const [urlsDescriptions] = usePubSub(urlsDescriptionsPubSub); +import { SearchPage } from "../pages/search/SearchPage"; +import { ComparisonPage } from "../pages/static/ComparisonPage"; - useEffect(() => { - prepareTextGeneration(); - }, []); +function InnerApp() { return ( <> - - {!getDisableAiResponseSetting() && response.length > 0 && ( -
- {response} -
- )} - {searchResults.length > 0 && ( -
- -
- )} -
- -
- + + } /> + } /> + ); } + +export const App = () => { + return ( + + + + ); +} diff --git a/client/components/SearchForm.tsx b/client/components/SearchForm.tsx index 7e9bd5a..36a9972 100644 --- a/client/components/SearchForm.tsx +++ b/client/components/SearchForm.tsx @@ -1,6 +1,8 @@ -import { useEffect, useRef, FormEvent, useState, useCallback } from "react"; +import { useEffect, useRef, useState, useCallback } from "react"; +import { useNavigate } from 'react-router-dom'; import TextareaAutosize from "react-textarea-autosize"; import { getRandomQuerySuggestion } from "../modules/querySuggestions"; +import { debounce } from "../utils/debounce"; export function SearchForm({ query, @@ -15,45 +17,42 @@ export function SearchForm({ getRandomQuerySuggestion(), ); +const navigate = useNavigate(); + +const startSearching = useCallback((queryToEncode: string) => { + updateQuery(queryToEncode); + navigate(`/?q=${encodeURIComponent(queryToEncode)}`); + }, [updateQuery, navigate]); + + const debouncedStartSearching = debounce(startSearching, 3000); // 3000ms = 3s + const handleInputChange = (event: React.ChangeEvent) => { - const userQueryIsBlank = event.target.value.trim().length === 0; + const userQuery = event.target.value.trim(); + const userQueryIsBlank = userQuery.length === 0; const suggestedQueryIsBlank = suggestedQuery.trim().length === 0; - + if (userQueryIsBlank && suggestedQueryIsBlank) { setSuggestedQuery(getRandomQuerySuggestion()); } else if (!userQueryIsBlank && !suggestedQueryIsBlank) { setSuggestedQuery(""); } - }; - - const startSearching = useCallback(() => { - let queryToEncode = suggestedQuery; - - if (textAreaRef.current && textAreaRef.current.value.trim().length > 0) { - queryToEncode = textAreaRef.current.value; + + // Start searching immediately when user types + if (!userQueryIsBlank) { + debouncedStartSearching(userQuery); } - - self.history.pushState( - null, - "", - `/?q=${encodeURIComponent(queryToEncode)}`, - ); - - updateQuery(queryToEncode); - - location.reload(); - }, [suggestedQuery, updateQuery]); - - const handleSubmit = (event: FormEvent) => { - event.preventDefault(); - startSearching(); }; useEffect(() => { const keyboardEventHandler = (event: KeyboardEvent) => { if (event.code === "Enter" && !event.shiftKey) { event.preventDefault(); - startSearching(); + if (textAreaRef.current) { + const userQuery = textAreaRef.current.value.trim(); + if (userQuery.length > 0) { + startSearching(userQuery); + } + } } }; const textArea = textAreaRef.current; @@ -76,7 +75,7 @@ export function SearchForm({ : undefined } > -
+ - ); diff --git a/package-lock.json b/package-lock.json index a14c81b..8063b2b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-hot-toast": "^2.4.1", + "react-router-dom": "^6.23.0", "react-textarea-autosize": "^8.5.3", "react-tooltip": "^5.21.6", "temp-dir": "^3.0.0", @@ -1091,6 +1092,14 @@ "node": ">= 8" } }, + "node_modules/@remix-run/router": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.16.0.tgz", + "integrity": "sha512-Quz1KOffeEf/zwkCBM3kBtH4ZoZ+pT3xIXBG4PPW/XFtDP7EGhtTiC2+gpL9GnR7+Qdet5Oa6cYSvwKYg6kN9Q==", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.13.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.0.tgz", @@ -3138,6 +3147,36 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.23.0.tgz", + "integrity": "sha512-wPMZ8S2TuPadH0sF5irFGjkNLIcRvOSaEe7v+JER8508dyJumm6XZB1u5kztlX0RVq6AzRVndzqcUh6sFIauzA==", + "dependencies": { + "@remix-run/router": "1.16.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.23.0.tgz", + "integrity": "sha512-Q9YaSYvubwgbal2c9DJKfx6hTNoBp3iJDsl+Duva/DwxoJH+OTXkxGpql4iUK2sla/8z4RpjAm6EWx1qUDuopQ==", + "dependencies": { + "@remix-run/router": "1.16.0", + "react-router": "6.23.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, "node_modules/react-textarea-autosize": { "version": "8.5.3", "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.5.3.tgz", diff --git a/package.json b/package.json index c5f01f2..8c2f2a1 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-hot-toast": "^2.4.1", + "react-router-dom": "^6.23.0", "react-textarea-autosize": "^8.5.3", "react-tooltip": "^5.21.6", "temp-dir": "^3.0.0", From ee9f9ffd1914a35e6fc66a2bd51b2c8ada73a275 Mon Sep 17 00:00:00 2001 From: Pierre-Henry Soria Date: Fri, 3 May 2024 18:39:03 +1000 Subject: [PATCH 2/2] Add pages --- client/components/SearchForm.tsx | 2 +- client/pages/search/SearchPage.tsx | 80 ++++++++++++++++++++++++++ client/pages/static/ComparisonPage.tsx | 10 ++++ client/utils/debounce.ts | 16 ++++++ 4 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 client/pages/search/SearchPage.tsx create mode 100644 client/pages/static/ComparisonPage.tsx create mode 100644 client/utils/debounce.ts diff --git a/client/components/SearchForm.tsx b/client/components/SearchForm.tsx index 36a9972..436fcba 100644 --- a/client/components/SearchForm.tsx +++ b/client/components/SearchForm.tsx @@ -36,7 +36,7 @@ const startSearching = useCallback((queryToEncode: string) => { } else if (!userQueryIsBlank && !suggestedQueryIsBlank) { setSuggestedQuery(""); } - + // Start searching immediately when user types if (!userQueryIsBlank) { debouncedStartSearching(userQuery); diff --git a/client/pages/search/SearchPage.tsx b/client/pages/search/SearchPage.tsx new file mode 100644 index 0000000..fedb3cc --- /dev/null +++ b/client/pages/search/SearchPage.tsx @@ -0,0 +1,80 @@ +import { usePubSub } from "create-pubsub/react"; +import { + promptPubSub, + responsePubSub, + searchResultsPubSub, + urlsDescriptionsPubSub, +} from "../../modules/pubSub"; +import { SearchForm } from "../../components/SearchForm"; +import { Toaster } from "react-hot-toast"; +import { SettingsButton } from "../../components/SettingsButton"; +import Markdown from "markdown-to-jsx"; +import { getDisableAiResponseSetting } from "../../modules/pubSub"; +import { SearchResultsList } from "../../components/SearchResultsList"; +import { useEffect } from "react"; +import { prepareTextGeneration } from "../../modules/textGeneration"; +import { useLocation } from 'react-router-dom'; + +export const SearchPage = () => { + const [query, setQuery] = usePubSub(promptPubSub); + const [response] = usePubSub(responsePubSub); + const [searchResults] = usePubSub(searchResultsPubSub); + const [urlsDescriptions] = usePubSub(urlsDescriptionsPubSub); + + useEffect(() => { + prepareTextGeneration(); + }, []); + + const location = useLocation(); + + useEffect(() => { + const params = new URLSearchParams(location.search); + const newQuery = params.get('q'); + if (newQuery !== null) { + setQuery(newQuery); + } + }, [location.search, setQuery]); + + + return ( + <> + + {!getDisableAiResponseSetting() && response.length > 0 && ( +
+

AI's thoughts:

+
+ {response} +
+
+ )} + {searchResults.length > 0 && ( +
+ +
+ )} +
+ +
+ + + ); +}; diff --git a/client/pages/static/ComparisonPage.tsx b/client/pages/static/ComparisonPage.tsx new file mode 100644 index 0000000..44c9665 --- /dev/null +++ b/client/pages/static/ComparisonPage.tsx @@ -0,0 +1,10 @@ +import React from 'react'; + +export const ComparisonPage = () => { + return ( +
+

Comparison Page

+ {/* Add your comparison logic here */} +
+ ); +}; diff --git a/client/utils/debounce.ts b/client/utils/debounce.ts new file mode 100644 index 0000000..788ac57 --- /dev/null +++ b/client/utils/debounce.ts @@ -0,0 +1,16 @@ +export const debounce = void>( + func: T, + wait: number, +): ((...args: Parameters) => void) => { + let timeout: NodeJS.Timeout; + + return (...args: Parameters): void => { + const later = () => { + clearTimeout(timeout); + func(...args); + }; + + clearTimeout(timeout); + timeout = setTimeout(later, wait); + }; +};