extract chat component

This commit is contained in:
Daniel O'Connell
2023-10-02 18:53:33 +02:00
parent 60173a17a0
commit b4a072cc8e
4 changed files with 159 additions and 151 deletions
+151
View File
@@ -0,0 +1,151 @@
import { useState, useEffect } from "react";
import { queryLLM, getStampyContent, runSearch } from "../hooks/useSearch";
import type {
CurrentSearch,
Citation,
Entry,
AssistantEntry as AssistantEntryType,
Mode,
Followup,
} from "../types";
import { SearchBox } from "../components/searchbox";
import { AssistantEntry } from "../components/assistant";
import { Entry as EntryTag } from "../components/entry";
const MAX_FOLLOWUPS = 4;
type State =
| {
state: "idle";
}
| {
state: "loading";
phase: "semantic" | "prompt" | "llm";
citations: Citation[];
}
| {
state: "streaming";
response: AssistantEntryType;
};
// smooth-scroll to the bottom of the window if we're already less than 30% a screen away
// note: finicky interaction with "smooth" - maybe fix later.
function scroll30() {
if (
document.documentElement.scrollHeight - window.scrollY >
window.innerHeight * 1.3
)
return;
window.scrollTo({ top: document.body.scrollHeight, behavior: "smooth" });
}
const Chat = ({ sessionId, mode }: { sessionId: string; mode: Mode }) => {
const [entries, setEntries] = useState<Entry[]>([]);
const [current, setCurrent] = useState<CurrentSearch>();
const [citations, setCitations] = useState<Citation[]>([]);
const updateCurrent = (current: CurrentSearch) => {
setCurrent(current);
if (current?.phase === "streaming") {
scroll30();
}
};
const updateCitations = (
allCitations: Citation[],
current?: CurrentSearch
) => {
if (!current) return;
const entryCitations = Array.from(current.citationsMap.values());
if (!entryCitations.some((c) => !c.index)) {
// All of the entries citations have indexes, so there weren't any changes since the last check
return;
}
// Get a mapping of all known citations, so as to reuse them if they appear again
const citationsMapping = Object.fromEntries(
allCitations.map((c) => [c.title + c.url, c.index])
);
entryCitations.forEach((c) => {
const hash = c.title + c.url;
const index = citationsMapping[hash];
if (!index) {
c.index = allCitations.length + 1;
allCitations.push(c);
} else {
c.index = index;
}
});
setCitations(allCitations);
setCurrent(current);
};
const search = async (
query: string,
query_source: "search" | "followups",
disable: () => void,
enable: (f_set: Followup[] | ((fs: Followup[]) => Followup[])) => void
) => {
// clear the query box, append to entries
const userEntry: Entry = {
role: "user",
content: query_source === "search" ? query : query.split("\n", 2)[1]!,
};
setEntries((prev) => [...prev, userEntry]);
disable();
const { result, followups } = await runSearch(
query,
query_source,
mode,
entries,
updateCurrent,
sessionId
);
setCurrent(undefined);
setEntries((prev) => [...prev, result]);
enable(followups || []);
scroll30();
};
var last_entry = <></>;
switch (current?.phase) {
case "semantic":
last_entry = <p>Loading: Performing semantic search...</p>;
break;
case "prompt":
last_entry = <p>Loading: Creating prompt...</p>;
break;
case "llm":
last_entry = <p>Loading: Waiting for LLM...</p>;
break;
case "streaming":
updateCitations(citations, current);
last_entry = <AssistantEntry entry={current} />;
break;
case "followups":
last_entry = (
<>
<AssistantEntry entry={current} />
<p>Checking for followups...</p>
</>
);
break;
}
return (
<ul>
{entries.map((entry, i) => (
<EntryTag entry={entry} key={i} />
))}
<SearchBox search={search} />
{last_entry}
</ul>
);
};
export default Chat;
+1 -1
View File
@@ -1,4 +1,4 @@
export type Mode = "rookie" | "concise" | "default";
import type { Mode } from "../types";
export const Controls = ({
mode,
+5 -150
View File
@@ -1,61 +1,17 @@
import { type NextPage } from "next";
import { useState, useEffect } from "react";
import Link from "next/link";
import Image from "next/image";
import Page from "../components/page";
import { API_URL } from "../settings";
import { queryLLM, getStampyContent, runSearch } from "../hooks/useSearch";
import type {
CurrentSearch,
Citation,
Entry,
UserEntry,
AssistantEntry as AssistantEntryType,
ErrorMessage,
StampyMessage,
Followup,
} from "../types";
import { SearchBox } from "../components/searchbox";
import { GlossarySpan } from "../components/glossary";
import { Controls, Mode } from "../components/controls";
import { AssistantEntry } from "../components/assistant";
import { Entry as EntryTag } from "../components/entry";
import type { Mode } from "../types";
import Page from "../components/page";
import Chat from "../components/chat";
import { Controls } from "../components/controls";
const MAX_FOLLOWUPS = 4;
type State =
| {
state: "idle";
}
| {
state: "loading";
phase: "semantic" | "prompt" | "llm";
citations: Citation[];
}
| {
state: "streaming";
response: AssistantEntryType;
};
// smooth-scroll to the bottom of the window if we're already less than 30% a screen away
// note: finicky interaction with "smooth" - maybe fix later.
function scroll30() {
if (
document.documentElement.scrollHeight - window.scrollY >
window.innerHeight * 1.3
)
return;
window.scrollTo({ top: document.body.scrollHeight, behavior: "smooth" });
}
const Home: NextPage = () => {
const [entries, setEntries] = useState<Entry[]>([]);
const [current, setCurrent] = useState<CurrentSearch>();
const [sessionId, setSessionId] = useState("");
const [citations, setCitations] = useState<Citation[]>([]);
// [state, ready to save to localstorage]
const [mode, setMode] = useState<[Mode, boolean]>(["default", false]);
// store mode in localstorage
@@ -70,100 +26,6 @@ const Home: NextPage = () => {
setSessionId(crypto.randomUUID());
}, []);
const updateCurrent = (current: CurrentSearch) => {
setCurrent(current);
if (current?.phase === "streaming") {
scroll30();
}
};
const updateCitations = (
allCitations: Citation[],
current: CurrentSearch
) => {
if (!current) return;
const entryCitations = Array.from(
current.citationsMap.values()
) as Citation[];
if (!entryCitations.some((c) => !c.index)) {
// All of the entries citations have indexes, so there weren't any changes since the last check
return;
}
// Get a mapping of all known citations, so as to reuse them if they appear again
const citationsMapping = Object.fromEntries(
allCitations.map((c) => [c.title + c.url, c.index])
);
entryCitations.forEach((c) => {
const hash = c.title + c.url;
const index = citationsMapping[hash];
if (index !== undefined) {
c.index = index;
} else {
c.index = allCitations.length + 1;
allCitations.push(c);
}
});
setCitations(allCitations);
setCurrent(current);
};
const search = async (
query: string,
query_source: "search" | "followups",
disable: () => void,
enable: (f_set: Followup[] | ((fs: Followup[]) => Followup[])) => void
) => {
// clear the query box, append to entries
const userEntry: Entry = {
role: "user",
content: query_source === "search" ? query : query.split("\n", 2)[1]!,
};
setEntries((prev) => [...prev, userEntry]);
disable();
const { result, followups } = await runSearch(
query,
query_source,
mode[0],
entries,
updateCurrent,
sessionId
);
setCurrent(undefined);
setEntries((prev) => [...prev, result]);
enable(followups || []);
scroll30();
};
var last_entry = <></>;
switch (current?.phase) {
case "semantic":
last_entry = <p>Loading: Performing semantic search...</p>;
break;
case "prompt":
last_entry = <p>Loading: Creating prompt...</p>;
break;
case "llm":
last_entry = <p>Loading: Waiting for LLM...</p>;
break;
case "streaming":
updateCitations(citations, current);
last_entry = <AssistantEntry entry={current} />;
break;
case "followups":
last_entry = (
<>
<AssistantEntry entry={current} />
<p>Checking for followups...</p>
</>
);
break;
}
return (
<Page page="index">
<Controls mode={mode} setMode={setMode} />
@@ -176,14 +38,7 @@ const Home: NextPage = () => {
welcomed.
</h2>
<ul>
{entries.map((entry, i) => (
<EntryTag entry={entry} key={i} />
))}
<SearchBox search={search} />
{last_entry}
</ul>
<Chat sessionId={sessionId} mode={mode[0]} />
</Page>
);
};
+2
View File
@@ -42,3 +42,5 @@ export type SearchResult = {
result: Entry;
};
export type CurrentSearch = (AssistantEntry & { phase?: string }) | undefined;
export type Mode = "rookie" | "concise" | "default";