Skip to content

Commit

Permalink
Allow initial search in url
Browse files Browse the repository at this point in the history
  • Loading branch information
mruwnik committed Aug 20, 2023
1 parent 227ba95 commit 9b8a6c4
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 57 deletions.
73 changes: 41 additions & 32 deletions app/components/search.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import {useState, useEffect, useRef, MutableRefObject, FocusEvent} from 'react'
import {useState, useEffect, useCallback, useRef, MutableRefObject, FocusEvent} from 'react'
import debounce from 'lodash/debounce'
import {
Searcher,
Question as QuestionType,
SearchResult,
} from 'stampy-search'
import {Searcher, Question as QuestionType, SearchResult} from 'stampy-search'
import {AddQuestion} from '~/routes/questions/add'
import {Action, ActionType} from '~/routes/questions/actions'
import {MagnifyingGlass, Edit} from '~/components/icons-generated'
Expand Down Expand Up @@ -32,33 +28,42 @@ export default function Search({

const [arePendingSearches, setPendingSearches] = useState(false)
const [results, setResults] = useState([] as SearchResult[])
const [searcher, setSearcher] = useState()
const [searcher, setSearcher] = useState<Searcher>()

useEffect(() => {
setSearcher(new Searcher({
getAllQuestions: () => onSiteAnswersRef.current,
}))
}, [onSiteAnswersRef])
useEffect(() => {
setSearcher(
new Searcher({
getAllQuestions: () => onSiteAnswersRef.current,
onResolveCallback: (query, res: SearchResult[] | null) => {
if (res) {
setPendingSearches(false)
setResults(res)
}
},
})
)
}, [onSiteAnswersRef])

const searchFn = (rawValue: string) => {
const value = rawValue.trim()
if (value === searchInputRef.current) return
const searchFn = useCallback(
(rawValue: string) => {
const value = rawValue.trim()
if (value === searchInputRef.current) return

searchInputRef.current = value
searchInputRef.current = value

setPendingSearches(true)
searcher.searchLive(value).then((res) => {
if (res) {
setPendingSearches(false)
setResults(res as SearchResult[])
}
})
logSearch(value)
}
setPendingSearches(true)
searcher && searcher.searchLive(value)
logSearch(value)
},
[searcher]
)

// Show the url query if defined
useEffect(() => {
initialQuery && searchFn(initialQuery)
}, [initialQuery, searchFn])
if (initialQuery && searcher) {
initialQuery && searchFn(initialQuery)
}
}, [initialQuery, searcher, searchFn])

const handleChange = debounce(searchFn, 100)

Expand Down Expand Up @@ -136,12 +141,13 @@ export default function Search({
</div>
</AutoHeight>
</div>
{showMore && (
{showMore && searcher && (
<ShowMoreSuggestions
onClose={() => {
setShowMore(false)
setHide(true)
}}
searcher={searcher}
question={searchInputRef.current}
relatedQuestions={results.map(({title}) => title)}
/>
Expand Down Expand Up @@ -190,22 +196,25 @@ const ShowMoreSuggestions = ({
question,
relatedQuestions,
onClose,
searcher,
}: {
question: string
relatedQuestions: string[]
onClose: (e: unknown) => void
searcher: Searcher
}) => {
const [extraQuestions, setExtraQuestions] = useState<SearchResult[]>(empty)
const [error, setError] = useState<string>()

useEffect(() => {
searchUnpublished(question, 5)
.then(setExtraQuestions)
.catch((e) => {
searcher
.searchUnpublished(question, 5)
.then((res) => res && setExtraQuestions(res))
.catch((e: any) => {
console.error(e)
setError(e)
})
}, [question])
}, [question, searcher])

if (extraQuestions === empty) {
return (
Expand Down
Empty file removed app/hooks/search.tsx
Empty file.
2 changes: 2 additions & 0 deletions app/routes/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ const Bottom = ({

export default function App() {
const minLogo = useOutletContext<boolean>()
const [remixSearchParams] = useSearchParams()
const {initialQuestionsData} = useLoaderData<ReturnType<typeof loader>>()
const {data: initialQuestions = [], timestamp} = initialQuestionsData ?? {}

Expand Down Expand Up @@ -183,6 +184,7 @@ export default function App() {
onSiteAnswersRef={onSiteAnswersRef}
openQuestionTitles={openQuestionTitles}
onSelect={selectQuestion}
initialQuery={remixSearchParams.get('query') || ''}
/>

{/* Add an extra, draggable div here, so that questions can be moved to the top of the list */}
Expand Down
2 changes: 1 addition & 1 deletion public/tfWorker.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use.load().then(function (model) {
// successfully loaded model & downloaded encodings
isReady = true
// warm up model for faster response later
runSemanticSearch('What is AGI Safety?', 1)
runSemanticSearch({query: 'What is AGI Safety?'}, 1)
self.postMessage({status: 'ready', numQs: questions.length})
})
})
Expand Down
2 changes: 1 addition & 1 deletion stampy-search/example/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<script>
console.log('Starting search')
const searcher = new stampySearch.Searcher({
server: 'http://localhost:8787',
server: 'https://stampy-ui.ai688.workers.dev',
onResolveCallback: (query, res) => console.log('callback', query, res),
})
console.log(searcher)
Expand Down
48 changes: 25 additions & 23 deletions stampy-search/src/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ type Search = {
type SearchConfig = {
numResults?: number
getAllQuestions?: () => Question[]
onResolveCallback: (query: string, res: SearchResult[] | null) => void | null
onResolveCallback?: null | ((query: string | undefined, res: SearchResult[] | null) => void)
server?: string
searchEndpoint?: string
workerPath?: string
Expand All @@ -32,8 +32,8 @@ export type WorkerMessage =
query?: string
}
export enum SearchType {
LiveOnSite,
Unpublished
LiveOnSite,
Unpublished,
}

/**
Expand Down Expand Up @@ -118,28 +118,26 @@ export const normalize = (question: string) =>
.replace(/\s+|_|&\s*/g, ' ')
.trim()

// let currentSearch: Search = null
// let worker: Worker
const defaultSearchConfig = {
getAllQuestions: () => [] as Question[],
onResolveCallback: null,
numResults: 5,
searchEndpoint: '/questions/search',
workerPath: '/tfWorker.js',
server: '',
}
} as SearchConfig

export class Searcher {
currentSearch: Search
worker: Worker
currentSearch: Search = null
worker: Worker | undefined
searchConfig = defaultSearchConfig

constructor(config: SearchConfig) {
this.searchConfig = {
...defaultSearchConfig,
...config,
}
this.initialiseWorker().then(console.log)
this.initialiseWorker()
}

initialiseWorker = async () => {
Expand All @@ -156,41 +154,40 @@ export class Searcher {
if (data.status == 'ready') {
this.worker = workerInstance
} else if (data.searchResults) {
this.resolveSearch(data)
this.resolveSearch(data.query, data.searchResults)
}
})
}

resolveSearch = ({searchResults, query}: WorkerResultMessage) => {
if (this.currentSearch) {
this.currentSearch.resolve(query === this.currentSearch.query ? searchResults : null)
resolveSearch = (query?: string, searchResults: SearchResult[] | null = null) => {
if (this.currentSearch && this.currentSearch.query == query) {
this.currentSearch.resolve(searchResults)
this.currentSearch = null
if (this.searchConfig.onResolveCallback) {
this.searchConfig.onResolveCallback(query, searchResults)
}
}
}

rejectSearch = (query: string, reason: string) => {
rejectSearch = (query?: string, reason?: string) => {
if (this.currentSearch && this.currentSearch.query == query) {
this.currentSearch.reject(reason)
this.currentSearch = null
}
}

runLiveSearch = (query: string, resultsNum?: number) => {
const questions = this.searchConfig.getAllQuestions ? this.searchConfig.getAllQuestions() : []
if (query != this.currentSearch?.query) {
return // this search has been superceeded with a newer one, so just give up
} else if (this.worker || this.searchConfig.getAllQuestions().length > 0) {
} else if (this.worker || questions.length > 0) {
const numResults = resultsNum || this.searchConfig.numResults
const wordCount = query.split(' ').length

if (wordCount > 2 && this.worker) {
this.worker.postMessage({query, numResults})
} else {
baselineSearch(query, this.searchConfig.getAllQuestions(), numResults).then((res) =>
this.resolveSearch({searchResults: res, query})
)
baselineSearch(query, questions, numResults).then((res) => this.resolveSearch(query, res))
}
} else {
setTimeout(() => this.runLiveSearch(query, resultsNum), 100)
Expand All @@ -205,7 +202,7 @@ export class Searcher {
return fetch(url + params)
.then(async (result) => {
if (result.status == 200) {
this.resolveSearch({searchResults: await result.json(), query})
this.resolveSearch(query, await result.json())
} else {
this.rejectSearch(query, await result.text())
}
Expand All @@ -217,15 +214,20 @@ export class Searcher {
return this.search(SearchType.LiveOnSite, query, resultsNum)
}

searchUnpublished = (query: string, resultsNum?: number): Promise<SearchResult[]> => {
searchUnpublished = (query: string, resultsNum?: number): Promise<SearchResult[] | null> => {
return this.search(SearchType.Unpublished, query, resultsNum)
}

search = (type_: SearchType, query: string, numResults?: number): Promise<SearchResult[]> => {
search = (
type_: SearchType,
query: string,
numResults?: number
): Promise<SearchResult[] | null> => {
// Cancel any previous searches
this.resolveSearch({searchResults: []})
this.resolveSearch()

const runSearch = type_ == SearchType.LiveOnSite ? this.runLiveSearch : this.runUnpublishedSearch
const runSearch =
type_ == SearchType.LiveOnSite ? this.runLiveSearch : this.runUnpublishedSearch

return new Promise((resolve, reject) => {
this.currentSearch = {resolve, reject, query}
Expand Down

0 comments on commit 9b8a6c4

Please sign in to comment.