From 8804b39362825cad738fb15f1a37b8e302ecdbb3 Mon Sep 17 00:00:00 2001 From: Nuno Campos Date: Mon, 29 Jan 2024 10:17:42 -0800 Subject: [PATCH 1/6] Render documents in UI --- backend/app/retrieval.py | 14 +- frontend/src/components/Config.tsx | 211 ++++++++++++------------- frontend/src/components/Document.tsx | 96 +++++++++++ frontend/src/components/FileUpload.tsx | 5 +- frontend/src/components/Message.tsx | 20 ++- frontend/src/hooks/useChatList.ts | 5 +- 6 files changed, 231 insertions(+), 120 deletions(-) create mode 100644 frontend/src/components/Document.tsx diff --git a/backend/app/retrieval.py b/backend/app/retrieval.py index 42dd6093..df127f27 100644 --- a/backend/app/retrieval.py +++ b/backend/app/retrieval.py @@ -67,7 +67,7 @@ def _get_messages(messages): ] + chat_history @chain - def get_search_query(messages): + async def get_search_query(messages): convo = [] for m in messages: if isinstance(m, AIMessage): @@ -76,11 +76,11 @@ def get_search_query(messages): if isinstance(m, HumanMessage): convo.append(f"Human: {m.content}") conversation = "\n".join(convo) - prompt = search_prompt.invoke({"conversation": conversation}) - response = llm.invoke(prompt) + prompt = await search_prompt.ainvoke({"conversation": conversation}) + response = await llm.ainvoke(prompt) return response.content - def invoke_retrieval(messages): + async def invoke_retrieval(messages): if len(messages) == 1: human_input = messages[-1].content return AIMessage( @@ -93,7 +93,7 @@ def invoke_retrieval(messages): }, ) else: - search_query = get_search_query.invoke(messages) + search_query = await get_search_query.ainvoke(messages) return AIMessage( content="", additional_kwargs={ @@ -104,10 +104,10 @@ def invoke_retrieval(messages): }, ) - def retrieve(messages): + async def retrieve(messages): params = messages[-1].additional_kwargs["function_call"] query = json.loads(params["arguments"])["query"] - response = retriever.invoke(query) + response = await retriever.ainvoke(query) msg = LiberalFunctionMessage(name="retrieval", content=response) return msg diff --git a/frontend/src/components/Config.tsx b/frontend/src/components/Config.tsx index d1dad7c5..d06efadc 100644 --- a/frontend/src/components/Config.tsx +++ b/frontend/src/components/Config.tsx @@ -377,7 +377,19 @@ export function Config(props: { ); return ( -
+
{ + e.preventDefault(); + e.stopPropagation(); + const form = e.target as HTMLFormElement; + const key = form.key.value; + if (!key) return; + setInflight(true); + await props.saveConfig(key, values!, files, isPublic); + setInflight(false); + }} + > {settings} {typeField && ( )} - { - e.preventDefault(); - e.stopPropagation(); - const form = e.target as HTMLFormElement; - const key = form.key.value; - if (!key) return; - setInflight(true); - await props.saveConfig(key, values!, files, isPublic); - setInflight(false); - }} - > - {!props.config && typeSpec?.files && ( - + {!props.config && typeSpec?.files && ( + + )} +
- {orderBy( - Object.entries( - props.configSchema?.properties.configurable.properties ?? {} - ), - ([key]) => ORDER.indexOf(last(key.split("/"))!) - ).map(([key, value]) => { - const title = value.title; - if (key.split("/")[0].includes("==")) { - const [parentKey, parentValue] = key.split("/")[0].split("=="); - if (values?.configurable?.[parentKey] !== parentValue) { - return null; - } - } else { - return null; - } - if ( - last(key.split("/")) === "retrieval_description" && - !files.length - ) { + > + {orderBy( + Object.entries( + props.configSchema?.properties.configurable.properties ?? {} + ), + ([key]) => ORDER.indexOf(last(key.split("/"))!) + ).map(([key, value]) => { + const title = value.title; + if (key.split("/")[0].includes("==")) { + const [parentKey, parentValue] = key.split("/")[0].split("=="); + if (values?.configurable?.[parentKey] !== parentValue) { return null; } - if (value.type === "string" && value.enum) { - return ( - - setValues({ - ...values, - configurable: { ...values!.configurable, [key]: value }, - }) - } - readonly={readonly} - /> - ); - } else if (value.type === "string") { - return ( - - setValues({ - ...values, - configurable: { ...values!.configurable, [key]: value }, - }) - } - readonly={readonly} - /> - ); - } else if ( - value.type === "array" && - value.items?.type === "string" && - value.items?.enum - ) { - return ( - - setValues({ - ...values, - configurable: { ...values!.configurable, [key]: value }, - }) - } - readonly={readonly} - descriptions={TOOL_DESCRIPTIONS} - /> - ); - } - })} -
- -
+ } else { + return null; + } + if ( + last(key.split("/")) === "retrieval_description" && + !files.length + ) { + return null; + } + if (value.type === "string" && value.enum) { + return ( + + setValues({ + ...values, + configurable: { ...values!.configurable, [key]: value }, + }) + } + readonly={readonly} + /> + ); + } else if (value.type === "string") { + return ( + + setValues({ + ...values, + configurable: { ...values!.configurable, [key]: value }, + }) + } + readonly={readonly} + /> + ); + } else if ( + value.type === "array" && + value.items?.type === "string" && + value.items?.enum + ) { + return ( + + setValues({ + ...values, + configurable: { ...values!.configurable, [key]: value }, + }) + } + readonly={readonly} + descriptions={TOOL_DESCRIPTIONS} + /> + ); + } + })} + + ); } diff --git a/frontend/src/components/Document.tsx b/frontend/src/components/Document.tsx new file mode 100644 index 00000000..4bb1b4c1 --- /dev/null +++ b/frontend/src/components/Document.tsx @@ -0,0 +1,96 @@ +import { useMemo, useState } from "react"; +import { cn } from "../utils/cn"; + +export interface PageDocument { + page_content: string; + metadata: Record; +} + +function PageDocument(props: { document: PageDocument; className?: string }) { + const [open, setOpen] = useState(false); + + const metadata = useMemo(() => { + return Object.keys(props.document.metadata) + .sort((a, b) => { + const aValue = JSON.stringify(props.document.metadata[a]); + const bValue = JSON.stringify(props.document.metadata[b]); + + const aLines = aValue.split("\n"); + const bLines = bValue.split("\n"); + + if (aLines.length !== bLines.length) { + return aLines.length - bLines.length; + } + + return aValue.length - bValue.length; + }) + .map((key) => { + const value = props.document.metadata[key]; + return { + key, + value: + typeof value === "string" || typeof value === "number" + ? `${value}` + : JSON.stringify(value), + }; + }); + }, [props.document.metadata]); + + if (!open) { + return ( + + ); + } + + return ( + + ); +} + +export function DocumentList(props: { documents: PageDocument[] }) { + return ( +
+
+ {props.documents.map((document, idx) => ( + + ))} +
+
+ ); +} diff --git a/frontend/src/components/FileUpload.tsx b/frontend/src/components/FileUpload.tsx index 6bbac49d..e7276893 100644 --- a/frontend/src/components/FileUpload.tsx +++ b/frontend/src/components/FileUpload.tsx @@ -45,6 +45,7 @@ export function FileUploadDropzone(props: { state: DropzoneState; files: File[]; setFiles: React.Dispatch>; + className?: string; }) { const { getRootProps, getInputProps, fileRejections } = props.state; @@ -74,7 +75,7 @@ export function FileUploadDropzone(props: { ); return ( -
+