Skip to content

Commit

Permalink
Allow abortion of current search
Browse files Browse the repository at this point in the history
  • Loading branch information
mruwnik committed Oct 3, 2023
1 parent 3f39eac commit b563ed8
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 32 deletions.
20 changes: 14 additions & 6 deletions web/src/components/chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ const Chat = ({sessionId, settings, onQuery, onNewEntry}: ChatParams) => {
const [entries, setEntries] = useState<Entry[]>([])
const [current, setCurrent] = useState<CurrentSearch>()
const [citations, setCitations] = useState<Citation[]>([])
const [controller, setController] = useState(new AbortController())

const updateCurrent = (current: CurrentSearch) => {
setCurrent(current)
Expand Down Expand Up @@ -102,22 +103,28 @@ const Chat = ({sessionId, settings, onQuery, onNewEntry}: ChatParams) => {
role: 'user',
content: query_source === 'search' ? query : query.split('\n', 2)[1]!,
}
addEntry(userEntry)
disable()

const controller = new AbortController()
setController(controller)
const {result, followups} = await runSearch(
query,
query_source,
settings,
entries,
updateCurrent,
sessionId
sessionId,
controller
)
if (result.content !== 'aborted') {
addEntry(userEntry)
addEntry(result)
enable(followups || [])
scroll30()
} else {
enable([])
}
setCurrent(undefined)

addEntry(result)
enable(followups || [])
scroll30()
}

var last_entry = <></>
Expand Down Expand Up @@ -156,6 +163,7 @@ const Chat = ({sessionId, settings, onQuery, onNewEntry}: ChatParams) => {
<SearchBox search={search} onQuery={onQuery} />

{last_entry}
{current && <button onClick={() => controller.abort()}>Cancel</button>}
</ul>
)
}
Expand Down
26 changes: 12 additions & 14 deletions web/src/components/searchbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,12 @@ const SearchBoxInternal: React.FC<{
// point we do a search. Instead it needs to be passed into the search
// method, for some reason.
const enable = (f_set: Followup[] | ((fs: Followup[]) => Followup[])) => {
setQuery('')
setLoading(false)
setFollowups(f_set)
}
const disable = () => {
setLoading(true)
setQuery('')
}

useEffect(() => {
Expand All @@ -70,7 +70,9 @@ const SearchBoxInternal: React.FC<{
inputRef.current.selectionEnd = inputRef.current.textLength
}, [])

if (loading) return <></>
const runSearch = () =>
!loading && query.trim() !== '' && search(query, 'search', disable, enable)

return (
<>
<div className="mt-1 flex flex-col items-end">
Expand All @@ -91,13 +93,7 @@ const SearchBoxInternal: React.FC<{
})}
</div>

<form
className="mt-1 mb-2 flex"
onSubmit={(e) => {
e.preventDefault()
search(query, 'search', disable, enable)
}}
>
<div className="mt-1 mb-2 flex">
<TextareaAutosize
className="flex-1 resize-none border border-gray-300 px-1"
ref={inputRef}
Expand All @@ -112,14 +108,16 @@ const SearchBoxInternal: React.FC<{
// if <enter> without <shift>, submit the form (if it's not empty)
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault()
if (query.trim() !== '') search(query, 'search', disable, enable)
runSearch()
}
}}
/>
<button className="ml-2" type="submit" disabled={loading}>
{loading ? 'Loading...' : 'Search'}
</button>
</form>
{!loading && (
<button className="ml-2" type="button" onClick={runSearch}>
Search
</button>
)}
</div>
</>
)
}
Expand Down
45 changes: 33 additions & 12 deletions web/src/hooks/useSearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ type HistoryEntry = {
content: string
}

const ignoreAbort = (error: Error) => {
if (error.name !== 'AbortError') {
throw error
}
}

export async function* iterateData(res: Response) {
const reader = res.body!.getReader()
var message = ''
Expand Down Expand Up @@ -104,9 +110,11 @@ const fetchLLM = async (
sessionId: string,
query: string,
settings: LLMSettings,
history: HistoryEntry[]
): Promise<Response> =>
history: HistoryEntry[],
controller: AbortController
): Promise<Response | void> =>
fetch(API_URL + '/chat', {
signal: controller.signal,
method: 'POST',
cache: 'no-cache',
keepalive: true,
Expand All @@ -116,25 +124,31 @@ const fetchLLM = async (
},

body: JSON.stringify({sessionId, query, history, settings}),
})
}).catch(ignoreAbort)

export const queryLLM = async (
query: string,
settings: LLMSettings,
history: HistoryEntry[],
setCurrent: (e?: CurrentSearch) => void,
sessionId: string
sessionId: string,
controller: AbortController
): Promise<SearchResult> => {
// do SSE on a POST request.
const res = await fetchLLM(sessionId, query, settings, history)
const res = await fetchLLM(sessionId, query, settings, history, controller)

if (!res.ok) {
if (!res) {
return {result: {role: 'error', content: 'No response from server'}}
} else if (!res.ok) {
return {result: {role: 'error', content: 'POST Error: ' + res.status}}
}

try {
return await extractAnswer(res, setCurrent)
} catch (e) {
if ((e as Error)?.name === 'AbortError') {
return {result: {role: 'error', content: 'aborted'}}
}
return {
result: {role: 'error', content: e ? e.toString() : 'unknown error'},
}
Expand All @@ -147,16 +161,22 @@ const cleanStampyContent = (contents: string) =>
(_, pre, linkParts, post) => `<a${pre}href="${STAMPY_URL}/?state=${linkParts}"${post}</a>`
)

export const getStampyContent = async (questionId: string): Promise<SearchResult> => {
export const getStampyContent = async (
questionId: string,
controller: AbortController
): Promise<SearchResult> => {
const res = await fetch(`${STAMPY_CONTENT_URL}/${questionId}`, {
method: 'GET',
signal: controller.signal,
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
})
}).catch(ignoreAbort)

if (!res.ok) {
if (!res) {
return {result: {role: 'error', content: 'No response from server'}}
} else if (!res.ok) {
return {result: {role: 'error', content: 'POST Error: ' + res.status}}
}

Expand Down Expand Up @@ -193,7 +213,8 @@ export const runSearch = async (
settings: LLMSettings,
entries: Entry[],
setCurrent: (c: CurrentSearch) => void,
sessionId: string
sessionId: string,
controller: AbortController
): Promise<SearchResult> => {
if (query_source === 'search') {
const history = entries
Expand All @@ -203,12 +224,12 @@ export const runSearch = async (
content: entry.content.trim(),
}))

return await queryLLM(query, settings, history, setCurrent, sessionId)
return await queryLLM(query, settings, history, setCurrent, sessionId, controller)
} else {
// ----------------- HUMAN AUTHORED CONTENT RETRIEVAL ------------------
const [questionId] = query.split('\n', 2)
if (questionId) {
return await getStampyContent(questionId)
return await getStampyContent(questionId, controller)
}
const result = {
role: 'error',
Expand Down

0 comments on commit b563ed8

Please sign in to comment.