Search: async answer/results
This commit is contained in:
@@ -4,7 +4,7 @@ import { AuthScreen } from "@/components/auth/auth-screen";
|
||||
import { SearchResultsPanel } from "@/components/search/search-results-panel";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { createSearch, runSearch, type SearchDetail } from "@/lib/api";
|
||||
import { createSearch, runSearchStream, type SearchDetail } from "@/lib/api";
|
||||
import { useSessionAuth } from "@/hooks/use-session-auth";
|
||||
|
||||
function readQueryFromUrl() {
|
||||
@@ -41,6 +41,7 @@ export default function SearchRoutePage() {
|
||||
const [isRunning, setIsRunning] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const requestCounterRef = useRef(0);
|
||||
const streamAbortRef = useRef<AbortController | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const onPopState = () => {
|
||||
@@ -52,6 +53,13 @@ export default function SearchRoutePage() {
|
||||
return () => window.removeEventListener("popstate", onPopState);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
streamAbortRef.current?.abort();
|
||||
streamAbortRef.current = null;
|
||||
};
|
||||
}, []);
|
||||
|
||||
const runQuery = async (query: string) => {
|
||||
const trimmed = query.trim();
|
||||
if (!trimmed) {
|
||||
@@ -61,6 +69,9 @@ export default function SearchRoutePage() {
|
||||
}
|
||||
|
||||
const requestId = ++requestCounterRef.current;
|
||||
streamAbortRef.current?.abort();
|
||||
const abortController = new AbortController();
|
||||
streamAbortRef.current = abortController;
|
||||
setError(null);
|
||||
setIsRunning(true);
|
||||
|
||||
@@ -86,16 +97,78 @@ export default function SearchRoutePage() {
|
||||
query: trimmed,
|
||||
title: trimmed.slice(0, 80),
|
||||
});
|
||||
const result = await runSearch(created.id, {
|
||||
query: trimmed,
|
||||
title: trimmed.slice(0, 80),
|
||||
type: "auto",
|
||||
numResults: 10,
|
||||
});
|
||||
if (requestId === requestCounterRef.current) {
|
||||
setSearch(result);
|
||||
}
|
||||
if (requestId !== requestCounterRef.current) return;
|
||||
|
||||
setSearch((current) =>
|
||||
current
|
||||
? {
|
||||
...current,
|
||||
id: created.id,
|
||||
title: created.title,
|
||||
query: created.query,
|
||||
createdAt: created.createdAt,
|
||||
updatedAt: created.updatedAt,
|
||||
}
|
||||
: current
|
||||
);
|
||||
|
||||
await runSearchStream(
|
||||
created.id,
|
||||
{
|
||||
query: trimmed,
|
||||
title: trimmed.slice(0, 80),
|
||||
type: "auto",
|
||||
numResults: 10,
|
||||
},
|
||||
{
|
||||
onSearchResults: (payload) => {
|
||||
if (requestId !== requestCounterRef.current) return;
|
||||
setSearch((current) =>
|
||||
current
|
||||
? {
|
||||
...current,
|
||||
requestId: payload.requestId ?? current.requestId,
|
||||
error: null,
|
||||
results: payload.results,
|
||||
}
|
||||
: current
|
||||
);
|
||||
},
|
||||
onSearchError: (payload) => {
|
||||
if (requestId !== requestCounterRef.current) return;
|
||||
setSearch((current) => (current ? { ...current, error: payload.error } : current));
|
||||
},
|
||||
onAnswer: (payload) => {
|
||||
if (requestId !== requestCounterRef.current) return;
|
||||
setSearch((current) =>
|
||||
current
|
||||
? {
|
||||
...current,
|
||||
answerText: payload.answerText,
|
||||
answerRequestId: payload.answerRequestId,
|
||||
answerCitations: payload.answerCitations,
|
||||
answerError: null,
|
||||
}
|
||||
: current
|
||||
);
|
||||
},
|
||||
onAnswerError: (payload) => {
|
||||
if (requestId !== requestCounterRef.current) return;
|
||||
setSearch((current) => (current ? { ...current, answerError: payload.error } : current));
|
||||
},
|
||||
onDone: (payload) => {
|
||||
if (requestId !== requestCounterRef.current) return;
|
||||
setSearch(payload.search);
|
||||
},
|
||||
onError: (payload) => {
|
||||
if (requestId !== requestCounterRef.current) return;
|
||||
setError(payload.message);
|
||||
},
|
||||
},
|
||||
{ signal: abortController.signal }
|
||||
);
|
||||
} catch (err) {
|
||||
if (abortController.signal.aborted) return;
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
if (message.includes("bearer token")) {
|
||||
handleAuthFailure(message);
|
||||
@@ -104,6 +177,7 @@ export default function SearchRoutePage() {
|
||||
}
|
||||
} finally {
|
||||
if (requestId === requestCounterRef.current) {
|
||||
streamAbortRef.current = null;
|
||||
setIsRunning(false);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user