Skip to content

Commit

Permalink
Merge pull request #125 from StampyAI/proper-removal
Browse files Browse the repository at this point in the history
proper removal of previous entries
  • Loading branch information
mruwnik committed Nov 5, 2023
2 parents 50860c0 + 0ab399a commit be895a4
Show file tree
Hide file tree
Showing 9 changed files with 185 additions and 178 deletions.
6 changes: 5 additions & 1 deletion api/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,15 @@ def semantic():
@app.route('/chat', methods=['POST'])
@cross_origin()
def chat():
query = request.json.get('query')
query = request.json.get('query', None)
session_id = request.json.get('sessionId')
history = request.json.get('history', [])
settings = request.json.get('settings', {})

if query is None:
query = history[-1].get('content')
history = history[:-1]

def formatter(item):
if isinstance(item, Exception):
item = {'state': 'error', 'error': str(item)}
Expand Down
45 changes: 21 additions & 24 deletions web/src/components/assistant.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,24 @@ import type { Citation, AssistantEntry as AssistantType } from "../types";

export const AssistantEntry: React.FC<{ entry: AssistantType }> = ({
entry,
}) => {
return (
<div className="mt-3 mb-8">
{entry.content.split("\n").map((paragraph, i) => (
<CitationsBlock
key={i}
text={paragraph}
citations={entry.citationsMap}
textRenderer={(t) => <GlossarySpan content={t} />}
/>
))}
<ul className="mt-5">
{
// show citations
Array.from(entry.citationsMap.values()).map((citation) => (
<li key={citation.index}>
<ShowCitation citation={citation} />
</li>
))
}
</ul>
</div>
);
};
}) => (
<div className="mt-3 mb-8">
{entry.content.split("\n").map((paragraph, i) => (
<CitationsBlock
key={i}
text={paragraph}
citations={entry.citationsMap || new Map()}
textRenderer={(t) => <GlossarySpan content={t} />}
/>
))}
<ul className="mt-5">
{entry.citationsMap &&
// show citations
Array.from(entry.citationsMap.values()).map((citation) => (
<li key={citation.index}>
<ShowCitation citation={citation} />
</li>
))}
</ul>
</div>
);
150 changes: 118 additions & 32 deletions web/src/components/chat.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { useState, useEffect } from "react";
import { queryLLM, getStampyContent, runSearch } from "../hooks/useSearch";
import {
queryLLM,
getStampyContent,
EntryRole,
HistoryEntry,
} from "../hooks/useSearch";
import { initialQuestions } from "../settings";

import type {
CurrentSearch,
Expand All @@ -8,6 +14,7 @@ import type {
AssistantEntry as AssistantEntryType,
LLMSettings,
Followup,
SearchResult,
} from "../types";
import useCitations from "../hooks/useCitations";
import { SearchBox } from "../components/searchbox";
Expand Down Expand Up @@ -41,6 +48,9 @@ function scroll30() {
window.scrollTo({ top: document.body.scrollHeight, behavior: "smooth" });
}

const randomQuestion = () =>
initialQuestions[Math.floor(Math.random() * initialQuestions.length)] || "";

export const ChatResponse = ({
current,
defaultElem,
Expand Down Expand Up @@ -73,6 +83,22 @@ export const ChatResponse = ({
}
};

const makeHistory = (query: string, entries: Entry[]): HistoryEntry[] => {
const getRole = (entry: Entry): EntryRole => {
if (entry.deleted) return "deleted";
if (entry.role === "stampy") return "assistant";
return entry.role;
};

const history = entries
.filter((entry) => entry.role !== "error")
.map((entry) => ({
role: getRole(entry),
content: entry.content.trim(),
}));
return [...history, { role: "user", content: query }];
};

type ChatParams = {
sessionId: string;
settings: LLMSettings;
Expand All @@ -82,7 +108,11 @@ type ChatParams = {

const Chat = ({ sessionId, settings, onQuery, onNewEntry }: ChatParams) => {
const [entries, setEntries] = useState<Entry[]>([]);

const [query, setQuery] = useState(() => randomQuestion());
const [current, setCurrent] = useState<CurrentSearch>();
const [followups, setFollowups] = useState<Followup[]>([]);
const [controller, setController] = useState(() => new AbortController());
const { citations, setEntryCitations } = useCitations();

const updateCurrent = (current: CurrentSearch) => {
Expand All @@ -94,48 +124,79 @@ const Chat = ({ sessionId, settings, onQuery, onNewEntry }: ChatParams) => {
}
};

const addEntry = (entry: Entry) => {
const addResult = (query: string, { result, followups }: SearchResult) => {
const userEntry = { role: "user", content: query };
setEntries((prev) => {
const entries = [...prev, entry];
const entries = [...prev, userEntry, result] as Entry[];
if (onNewEntry) {
onNewEntry(entries);
}
return entries;
});
setFollowups(followups || []);
setQuery("");
scroll30();
};

const search = async (
query: string,
query_source: "search" | "followups",
enable: (f_set: Followup[] | ((fs: Followup[]) => Followup[])) => void,
controller: AbortController
) => {
// clear the query box, append to entries
const userEntry: Entry = {
role: "user",
content: query_source === "search" ? query : query.split("\n", 2)[1]!,
const abortable =
(f: any) =>
(...args: any) => {
controller.abort();
const newController = new AbortController();
setController(newController);
return f(newController, ...args);
};

const { result, followups } = await runSearch(
query,
query_source,
const search = async (controller: AbortController, query: string) => {
// clear the query box, append to entries
setFollowups([]);

const history = makeHistory(query, entries);

const result = await queryLLM(
settings,
entries,
history,
updateCurrent,
sessionId,
controller
);
if (result.content !== "aborted") {
addEntry(userEntry);
addEntry(result);
enable(followups || []);
scroll30();
} else {
enable([]);

if (result.result.content !== "aborted") {
addResult(query, result);
}
setCurrent(undefined);
};

const fetchFollowup = async (
controller: AbortController,
followup: Followup
) => {
setCurrent({ role: "assistant", content: "", phase: "started" });
const result = await getStampyContent(followup.pageid, controller);
if (!controller.signal.aborted) {
addResult(followup.text, result);
}
setCurrent(undefined);
};

const deleteEntry = (i: number) => {
const entry = entries[i];
if (entry === undefined) {
return;
} else if (
i === entries.length - 1 &&
["assistant", "stampy"].includes(entry.role)
) {
const prev = entries[i - 1];
if (prev !== undefined) setQuery(prev.content);
setEntries(entries.slice(0, i - 1));
setFollowups([]);
} else {
entry.deleted = true;
setEntries([...entries]);
}
};

return (
<ul className="flex-auto">
{entries.map(
Expand All @@ -145,20 +206,24 @@ const Chat = ({ sessionId, settings, onQuery, onNewEntry }: ChatParams) => {
<EntryTag entry={entry} />
<span
className="delete-item absolute right-5 hidden cursor-pointer group-hover:block"
onClick={() => {
const entry = entries[i];
if (entry !== undefined) {
entry.deleted = true;
setEntries([...entries]);
}
}}
onClick={() => deleteEntry(i)}
>
</span>
</li>
)
)}
<SearchBox search={search} onQuery={onQuery} />

<Followups followups={followups} onClick={abortable(fetchFollowup)} />
<SearchBox
search={abortable(search)}
query={query}
onQuery={(v: string) => {
setQuery(v);
onQuery && onQuery(v);
}}
abortSearch={() => controller.abort()}
/>
<ChatResponse
current={current}
defaultElem={
Expand All @@ -170,3 +235,24 @@ const Chat = ({ sessionId, settings, onQuery, onNewEntry }: ChatParams) => {
};

export default Chat;

const Followups = ({
followups,
onClick,
}: {
followups: Followup[];
onClick: (f: Followup) => void;
}) => (
<div className="mt-1 flex flex-col items-end">
{followups.map((followup: Followup, i: number) => (
<li key={i}>
<button
className="my-1 border border-gray-300 px-1"
onClick={() => onClick(followup)}
>
<span> {followup.text} </span>
</button>
</li>
))}
</div>
);
Loading

0 comments on commit be895a4

Please sign in to comment.