(null);
+ const handleFinish = useCallback(
+ (result: LocalHistory) => {
+ const localHistory = window.localStorage.getItem(historyQueryKey);
+ let history: LocalHistory[];
+ try {
+ history = JSON.parse(localHistory || "[]");
+ } catch {
+ history = [];
+ }
+ window.localStorage.setItem(
+ historyQueryKey,
+ JSON.stringify([result, ...history]),
+ );
+ },
+ [rid, query],
+ );
useEffect(() => {
const controller = new AbortController();
void parseStreaming(
@@ -22,6 +40,7 @@ export const Result: FC<{ query: string; rid: string }> = ({ query, rid }) => {
setSources,
setMarkdown,
setRelates,
+ handleFinish,
setError,
);
return () => {
diff --git a/web/src/app/interfaces/history.ts b/web/src/app/interfaces/history.ts
new file mode 100644
index 0000000..1e8a94d
--- /dev/null
+++ b/web/src/app/interfaces/history.ts
@@ -0,0 +1,11 @@
+import { Relate } from "@/app/interfaces/relate";
+import { Source } from "@/app/interfaces/source";
+
+export interface LocalHistory {
+ markdown: string;
+ relates: Relate[];
+ sources: Source[];
+ rid: string;
+ query: string;
+ timestamp: number;
+}
diff --git a/web/src/app/search/page.tsx b/web/src/app/search/page.tsx
index 48800a8..ba5ea06 100644
--- a/web/src/app/search/page.tsx
+++ b/web/src/app/search/page.tsx
@@ -3,6 +3,7 @@ import { Result } from "@/app/components/result";
import { Search } from "@/app/components/search";
import { Title } from "@/app/components/title";
import { useSearchParams } from "next/navigation";
+import { HistoryResult } from "@/app/components/history";
export default function SearchPage() {
const searchParams = useSearchParams();
const query = decodeURIComponent(searchParams.get("q") || "");
@@ -14,6 +15,7 @@ export default function SearchPage() {
+
diff --git a/web/src/app/utils/local-storage.ts b/web/src/app/utils/local-storage.ts
new file mode 100644
index 0000000..1363c85
--- /dev/null
+++ b/web/src/app/utils/local-storage.ts
@@ -0,0 +1 @@
+export const historyQueryKey = "lepton_previous_query";
diff --git a/web/src/app/utils/parse-streaming.ts b/web/src/app/utils/parse-streaming.ts
index 0667514..9b5338d 100644
--- a/web/src/app/utils/parse-streaming.ts
+++ b/web/src/app/utils/parse-streaming.ts
@@ -1,6 +1,7 @@
import { Relate } from "@/app/interfaces/relate";
import { Source } from "@/app/interfaces/source";
import { fetchStream } from "@/app/utils/fetch-stream";
+import { LocalHistory } from "@/app/interfaces/history";
const LLM_SPLIT = "__LLM_RESPONSE__";
const RELATED_SPLIT = "__RELATED_QUESTIONS__";
@@ -12,6 +13,7 @@ export const parseStreaming = async (
onSources: (value: Source[]) => void,
onMarkdown: (value: string) => void,
onRelates: (value: Relate[]) => void,
+ onFinish: (result: LocalHistory) => void,
onError?: (status: number) => void,
) => {
const decoder = new TextDecoder();
@@ -30,18 +32,19 @@ export const parseStreaming = async (
search_uuid,
}),
});
+ let finalRelates: Relate[] = [];
+ let finalMarkdown: string = "";
+ let finalSources: Source[] = [];
if (response.status !== 200) {
onError?.(response.status);
return;
}
const markdownParse = (text: string) => {
- onMarkdown(
- text
- .replace(/\[\[([cC])itation/g, "[citation")
- .replace(/[cC]itation:(\d+)]]/g, "citation:$1]")
- .replace(/\[\[([cC]itation:\d+)]](?!])/g, `[$1]`)
- .replace(/\[[cC]itation:(\d+)]/g, "[citation]($1)"),
- );
+ return text
+ .replace(/\[\[([cC])itation/g, "[citation")
+ .replace(/[cC]itation:(\d+)]]/g, "citation:$1]")
+ .replace(/\[\[([cC]itation:\d+)]](?!])/g, `[$1]`)
+ .replace(/\[[cC]itation:(\d+)]/g, "[citation]($1)");
};
fetchStream(
response,
@@ -52,27 +55,38 @@ export const parseStreaming = async (
const [sources, rest] = chunks.split(LLM_SPLIT);
if (!sourcesEmitted) {
try {
- onSources(JSON.parse(sources));
+ finalSources = JSON.parse(sources);
} catch (e) {
- onSources([]);
+ finalSources = [];
}
+ onSources(finalSources);
}
sourcesEmitted = true;
if (rest.includes(RELATED_SPLIT)) {
const [md] = rest.split(RELATED_SPLIT);
- markdownParse(md);
+ finalMarkdown = markdownParse(md);
} else {
- markdownParse(rest);
+ finalMarkdown = markdownParse(rest);
}
+ onMarkdown(finalMarkdown);
}
},
() => {
const [_, relates] = chunks.split(RELATED_SPLIT);
try {
- onRelates(JSON.parse(relates));
+ finalRelates = JSON.parse(relates);
} catch (e) {
- onRelates([]);
+ finalRelates = [];
}
+ onRelates(finalRelates);
+ onFinish({
+ markdown: finalMarkdown,
+ sources: finalSources,
+ relates: finalRelates,
+ rid: search_uuid,
+ query,
+ timestamp: new Date().valueOf(),
+ });
},
);
};