From e9a9cbbd079bd23329591097bbbbc7c8ac19162b Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 22 Nov 2023 12:16:37 +0000 Subject: [PATCH 1/5] feedback local --- application/api/user/routes.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/application/api/user/routes.py b/application/api/user/routes.py index 4dcbfd4d..2add75df 100644 --- a/application/api/user/routes.py +++ b/application/api/user/routes.py @@ -17,6 +17,7 @@ db = mongo["docsgpt"] conversations_collection = db["conversations"] vectors_collection = db["vectors"] prompts_collection = db["prompts"] +feedback_collection = db["feedback"] user = Blueprint('user', __name__) current_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) @@ -71,19 +72,15 @@ def api_feedback(): answer = data["answer"] feedback = data["feedback"] - print("-" * 5) - print("Question: " + question) - print("Answer: " + answer) - print("Feedback: " + feedback) - print("-" * 5) - response = requests.post( - url="https://86x89umx77.execute-api.eu-west-2.amazonaws.com/docsgpt-feedback", - headers={ - "Content-Type": "application/json; charset=utf-8", - }, - data=json.dumps({"answer": answer, "question": question, "feedback": feedback}), + + feedback_collection.insert_one( + { + "question": question, + "answer": answer, + "feedback": feedback, + } ) - return {"status": http.client.responses.get(response.status_code, "ok")} + return {"status": "ok"} @user.route("/api/delete_by_ids", methods=["get"]) def delete_by_ids(): From b2770f67a17128e812afaadfca5437c3bd8e8d54 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 22 Nov 2023 23:55:41 +0000 Subject: [PATCH 2/5] custom prompts --- application/api/answer/routes.py | 48 ++-- application/api/user/routes.py | 42 ++- frontend/src/Setting.tsx | 271 ++++++++++++------ frontend/src/conversation/conversationApi.ts | 4 + .../src/conversation/conversationSlice.ts | 2 + frontend/src/preferences/preferenceSlice.ts | 4 +- 6 files changed, 244 insertions(+), 127 deletions(-) diff --git a/application/api/answer/routes.py b/application/api/answer/routes.py index e6a8a8b8..19c1b10f 100644 --- a/application/api/answer/routes.py +++ b/application/api/answer/routes.py @@ -25,6 +25,7 @@ mongo = MongoClient(settings.MONGO_URI) db = mongo["docsgpt"] conversations_collection = db["conversations"] vectors_collection = db["vectors"] +prompts_collection = db["prompts"] answer = Blueprint('answer', __name__) if settings.LLM_NAME == "gpt4": @@ -43,10 +44,10 @@ with open(os.path.join(current_dir, "prompts", "chat_reduce_prompt.txt"), "r") a chat_reduce_template = f.read() with open(os.path.join(current_dir, "prompts", "chat_combine_creative.txt"), "r") as f: - chat_reduce_creative = f.read() + chat_combine_creative = f.read() with open(os.path.join(current_dir, "prompts", "chat_combine_strict.txt"), "r") as f: - chat_reduce_strict = f.read() + chat_combine_strict = f.read() api_key_set = settings.API_KEY is not None embeddings_key_set = settings.EMBEDDINGS_KEY is not None @@ -90,23 +91,6 @@ def get_vectorstore(data): return vectorstore -# def get_docsearch(vectorstore, embeddings_key): -# if settings.EMBEDDINGS_NAME == "openai_text-embedding-ada-002": -# if is_azure_configured(): -# os.environ["OPENAI_API_TYPE"] = "azure" -# openai_embeddings = OpenAIEmbeddings(model=settings.AZURE_EMBEDDINGS_DEPLOYMENT_NAME) -# else: -# openai_embeddings = OpenAIEmbeddings(openai_api_key=embeddings_key) -# docsearch = FAISS.load_local(vectorstore, openai_embeddings) -# elif settings.EMBEDDINGS_NAME == "huggingface_sentence-transformers/all-mpnet-base-v2": -# docsearch = FAISS.load_local(vectorstore, HuggingFaceHubEmbeddings()) -# elif settings.EMBEDDINGS_NAME == "huggingface_hkunlp/instructor-large": -# docsearch = FAISS.load_local(vectorstore, HuggingFaceInstructEmbeddings()) -# elif settings.EMBEDDINGS_NAME == "cohere_medium": -# docsearch = FAISS.load_local(vectorstore, CohereEmbeddings(cohere_api_key=embeddings_key)) -# return docsearch - - def is_azure_configured(): return settings.OPENAI_API_BASE and settings.OPENAI_API_VERSION and settings.AZURE_DEPLOYMENT_NAME @@ -115,13 +99,16 @@ def complete_stream(question, docsearch, chat_history, api_key, prompt_id, conve llm = LLMCreator.create_llm(settings.LLM_NAME, api_key=api_key) if prompt_id == 'default': - prompt = chat_reduce_template + prompt = chat_combine_template elif prompt_id == 'creative': - prompt = chat_reduce_creative + prompt = chat_combine_creative elif prompt_id == 'strict': - prompt = chat_reduce_strict + prompt = chat_combine_strict else: - prompt = chat_reduce_template + prompt = prompts_collection.find_one({"_id": ObjectId(prompt_id)})["content"] + import sys + print(prompt_id, file=sys.stderr) + print(prompt, file=sys.stderr) docs = docsearch.search(question, k=2) @@ -253,6 +240,19 @@ def api_answer(): embeddings_key = data["embeddings_key"] else: embeddings_key = settings.EMBEDDINGS_KEY + if 'prompt_id' in data: + prompt_id = data["prompt_id"] + else: + prompt_id = 'default' + + if prompt_id == 'default': + prompt = chat_combine_template + elif prompt_id == 'creative': + prompt = chat_combine_creative + elif prompt_id == 'strict': + prompt = chat_combine_strict + else: + prompt = prompts_collection.find_one({"_id": ObjectId(prompt_id)})["content"] # use try and except to check for exception try: @@ -270,7 +270,7 @@ def api_answer(): docs = docsearch.search(question, k=2) # join all page_content together with a newline docs_together = "\n".join([doc.page_content for doc in docs]) - p_chat_combine = chat_combine_template.replace("{summaries}", docs_together) + p_chat_combine = prompt.replace("{summaries}", docs_together) messages_combine = [{"role": "system", "content": p_chat_combine}] source_log_docs = [] for doc in docs: diff --git a/application/api/user/routes.py b/application/api/user/routes.py index 2add75df..14dec7e9 100644 --- a/application/api/user/routes.py +++ b/application/api/user/routes.py @@ -246,18 +246,21 @@ def check_docs(): @user.route("/api/create_prompt", methods=["POST"]) def create_prompt(): data = request.get_json() - prompt = data["prompt"] + content = data["content"] name = data["name"] + if name == "": + return {"status": "error"} user = "local" # write to mongodb - prompts_collection.insert_one( + resp = prompts_collection.insert_one( { "name": name, - "prompt": prompt, + "content": content, "user": user, } ) - return {"status": "ok"} + new_id = str(resp.inserted_id) + return {"id": new_id, "name": name, "content": content} @user.route("/api/get_prompts", methods=["GET"]) def get_prompts(): @@ -268,32 +271,51 @@ def get_prompts(): list_prompts.append({"id": "creative", "name": "creative", "type": "public"}) list_prompts.append({"id": "precise", "name": "precise", "type": "public"}) for prompt in prompts: - list_prompts.append({"id": str(prompt["_id"]), "name": prompt["name"], type: "private"}) + list_prompts.append({"id": str(prompt["_id"]), "name": prompt["name"], "type": "private"}) return jsonify(list_prompts) @user.route("/api/get_single_prompt", methods=["GET"]) def get_single_prompt(): prompt_id = request.args.get("id") + if prompt_id == 'default': + with open(os.path.join(current_dir, "prompts", "chat_combine_default.txt"), "r") as f: + chat_combine_template = f.read() + return jsonify({"content": chat_combine_template}) + elif prompt_id == 'creative': + with open(os.path.join(current_dir, "prompts", "chat_combine_creative.txt"), "r") as f: + chat_reduce_creative = f.read() + return jsonify({"content": chat_reduce_creative}) + elif prompt_id == 'strict': + with open(os.path.join(current_dir, "prompts", "chat_combine_strict.txt"), "r") as f: + chat_reduce_strict = f.read() + return jsonify({"content": chat_reduce_strict}) + + prompt = prompts_collection.find_one({"_id": ObjectId(prompt_id)}) - return jsonify(prompt['prompt']) + return jsonify({"content": prompt["content"]}) @user.route("/api/delete_prompt", methods=["POST"]) def delete_prompt(): - prompt_id = request.args.get("id") + data = request.get_json() + id = data["id"] prompts_collection.delete_one( { - "_id": ObjectId(prompt_id), + "_id": ObjectId(id), } ) return {"status": "ok"} -@user.route("/api/update_prompt_name", methods=["POST"]) +@user.route("/api/update_prompt", methods=["POST"]) def update_prompt_name(): data = request.get_json() id = data["id"] name = data["name"] - prompts_collection.update_one({"_id": ObjectId(id)},{"$set":{"name":name}}) + content = data["content"] + # check if name is null + if name == "": + return {"status": "error"} + prompts_collection.update_one({"_id": ObjectId(id)},{"$set":{"name":name, "content": content}}) return {"status": "ok"} diff --git a/frontend/src/Setting.tsx b/frontend/src/Setting.tsx index 5f652a84..0927fc18 100644 --- a/frontend/src/Setting.tsx +++ b/frontend/src/Setting.tsx @@ -13,14 +13,10 @@ import { Doc } from './preferences/preferenceApi'; type PromptProps = { prompts: { name: string; id: string; type: string }[]; - selectedPrompt: { name: string; id: string }; - onSelectPrompt: (name: string, id: string) => void; - onAddPrompt: (name: string) => void; - newPromptName: string; - onNewPromptNameChange: (name: string) => void; - isAddPromptModalOpen: boolean; - onToggleAddPromptModal: () => void; - onDeletePrompt: (name: string, id: string) => void; + selectedPrompt: { name: string; id: string; type: string }; + onSelectPrompt: (name: string, id: string, type: string) => void; + setPrompts: (prompts: { name: string; id: string; type: string }[]) => void; + apiHost: string; }; const Setting: React.FC = () => { @@ -32,15 +28,10 @@ const Setting: React.FC = () => { { name: string; id: string; type: string }[] >([]); const selectedPrompt = useSelector(selectPrompt); - const [newPromptName, setNewPromptName] = useState(''); const [isAddPromptModalOpen, setAddPromptModalOpen] = useState(false); const documents = useSelector(selectSourceDocs); const [isAddDocumentModalOpen, setAddDocumentModalOpen] = useState(false); - const [newDocument, setNewDocument] = useState({ - name: '', - vectorDate: '', - vectorLocation: '', - }); + const dispatch = useDispatch(); const apiHost = import.meta.env.VITE_API_HOST || 'https://docsapi.arc53.com'; @@ -50,10 +41,6 @@ const Setting: React.FC = () => { setWidgetScreenshot(screenshot); }; - // Function to toggle the Add Document modal - const toggleAddDocumentModal = () => { - setAddDocumentModalOpen(!isAddDocumentModalOpen); - }; useEffect(() => { const fetchPrompts = async () => { try { @@ -172,19 +159,11 @@ const Setting: React.FC = () => { - dispatch(setPrompt({ name: name, id: id })) + onSelectPrompt={(name, id, type) => + dispatch(setPrompt({ name: name, id: id, type: type })) } - onAddPrompt={addPrompt} - newPromptName={''} - onNewPromptNameChange={function (name: string): void { - throw new Error('Function not implemented.'); - }} - isAddPromptModalOpen={false} - onToggleAddPromptModal={function (): void { - throw new Error('Function not implemented.'); - }} - onDeletePrompt={onDeletePrompt} + setPrompts={setPrompts} + apiHost={apiHost} /> ); case 'Documents': @@ -205,17 +184,6 @@ const Setting: React.FC = () => { return null; } } - - function addPrompt(name: string) { - if (name) { - setNewPromptName(''); - toggleAddPromptModal(); - } - } - - function toggleAddPromptModal() { - setAddPromptModalOpen(!isAddPromptModalOpen); - } }; const General: React.FC = () => { @@ -252,65 +220,196 @@ const Prompts: React.FC = ({ prompts, selectedPrompt, onSelectPrompt, - onAddPrompt, - onDeletePrompt, + setPrompts, + apiHost, }) => { - const [isAddPromptModalOpen, setAddPromptModalOpen] = useState(false); - const [newPromptName, setNewPromptName] = useState(''); - - const openAddPromptModal = () => { - setAddPromptModalOpen(true); + const handleSelectPrompt = ({ + name, + id, + type, + }: { + name: string; + id: string; + type: string; + }) => { + setNewPromptName(name); + onSelectPrompt(name, id, type); }; + const [newPromptName, setNewPromptName] = useState(selectedPrompt.name); + const [newPromptContent, setNewPromptContent] = useState(''); - const closeAddPromptModal = () => { - setAddPromptModalOpen(false); - }; - - const handleSelectPrompt = (name: string) => { - const selected = prompts.find((prompt) => prompt.name === name); - if (selected) { - onSelectPrompt(selected.name, selected.id); + const handleAddPrompt = async () => { + try { + const response = await fetch(`${apiHost}/api/create_prompt`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + name: newPromptName, + content: newPromptContent, + }), + }); + if (!response.ok) { + throw new Error('Failed to add prompt'); + } + const newPrompt = await response.json(); + if (setPrompts) { + setPrompts([...prompts, newPrompt]); + } + onSelectPrompt(newPrompt.name, newPrompt.id, newPrompt.type); + setNewPromptName(newPrompt.name); + } catch (error) { + console.error(error); } }; - const handleDeletePrompt = (name: string) => { - const selected = prompts.find((prompt) => prompt.name === name); - if (selected) { - onDeletePrompt(selected.name, selected.id); - } + const handleDeletePrompt = () => { + setPrompts(prompts.filter((prompt) => prompt.id !== selectedPrompt.id)); + console.log('selectedPrompt.id', selectedPrompt.id); + + fetch(`${apiHost}/api/delete_prompt`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ id: selectedPrompt.id }), + }) + .then((response) => { + if (!response.ok) { + throw new Error('Failed to delete prompt'); + } + // get 1st prompt and set it as selected + if (prompts.length > 0) { + onSelectPrompt(prompts[0].name, prompts[0].id, prompts[0].type); + setNewPromptName(prompts[0].name); + } + }) + .catch((error) => { + console.error(error); + }); + }; + + useEffect(() => { + const fetchPromptContent = async () => { + console.log('fetching prompt content'); + try { + const response = await fetch( + `${apiHost}/api/get_single_prompt?id=${selectedPrompt.id}`, + { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }, + ); + if (!response.ok) { + throw new Error('Failed to fetch prompt content'); + } + const promptContent = await response.json(); + setNewPromptContent(promptContent.content); + } catch (error) { + console.error(error); + } + }; + + fetchPromptContent(); + }, [selectedPrompt]); + + const handleSaveChanges = () => { + fetch(`${apiHost}/api/update_prompt`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + id: selectedPrompt.id, + name: newPromptName, + content: newPromptContent, + }), + }) + .then((response) => { + if (!response.ok) { + throw new Error('Failed to update prompt'); + } + onSelectPrompt(newPromptName, selectedPrompt.id, selectedPrompt.type); + setNewPromptName(newPromptName); + }) + .catch((error) => { + console.error(error); + }); }; return ( - Active Prompt + Active Prompt - {/* + + + Prompt name {' '} + + start by editing name + + setNewPromptName(e.target.value)} + /> + + + + Prompt content + setNewPromptContent(e.target.value)} + placeholder="Active prompt contents" + /> + + + Add New Prompt - */} - {isAddPromptModalOpen && ( - { - onAddPrompt(newPromptName); - closeAddPromptModal(); - }} - onClose={closeAddPromptModal} - /> - )} + + Delete Prompt + + + Save Changes + + ); }; @@ -319,14 +418,10 @@ function DropdownPrompt({ options, selectedValue, onSelect, - showDelete, - onDelete, }: { options: { name: string; id: string; type: string }[]; selectedValue: string; - onSelect: (value: string) => void; - showDelete?: boolean; - onDelete: (value: string) => void; + onSelect: (value: { name: string; id: string; type: string }) => void; }) { const [isOpen, setIsOpen] = useState(false); @@ -356,19 +451,13 @@ function DropdownPrompt({ > { - onSelect(option.name); + onSelect(option); setIsOpen(false); }} className="ml-2 flex-1 overflow-hidden overflow-ellipsis whitespace-nowrap py-3" > {option.name} - {showDelete && option.type === 'private' && ( - onDelete(option.name)} className="p-2"> - {/* Icon or text for delete button */} - Delete - - )} ))} diff --git a/frontend/src/conversation/conversationApi.ts b/frontend/src/conversation/conversationApi.ts index 27e2d5dd..542a3a99 100644 --- a/frontend/src/conversation/conversationApi.ts +++ b/frontend/src/conversation/conversationApi.ts @@ -9,6 +9,7 @@ export function fetchAnswerApi( selectedDocs: Doc, history: Array = [], conversationId: string | null, + promptId: string | null, ): Promise< | { result: any; @@ -62,6 +63,7 @@ export function fetchAnswerApi( history: history, active_docs: docPath, conversation_id: conversationId, + prompt_id: promptId, }), }) .then((response) => { @@ -89,6 +91,7 @@ export function fetchAnswerSteaming( selectedDocs: Doc, history: Array = [], conversationId: string | null, + promptId: string | null, onEvent: (event: MessageEvent) => void, ): Promise { let namePath = selectedDocs.name; @@ -123,6 +126,7 @@ export function fetchAnswerSteaming( active_docs: docPath, history: JSON.stringify(history), conversation_id: conversationId, + prompt_id: promptId, }; fetch(apiHost + '/stream', { diff --git a/frontend/src/conversation/conversationSlice.ts b/frontend/src/conversation/conversationSlice.ts index ffe402b0..ca92092b 100644 --- a/frontend/src/conversation/conversationSlice.ts +++ b/frontend/src/conversation/conversationSlice.ts @@ -25,6 +25,7 @@ export const fetchAnswer = createAsyncThunk( state.preference.selectedDocs!, state.conversation.queries, state.conversation.conversationId, + state.preference.prompt.id, (event) => { const data = JSON.parse(event.data); @@ -81,6 +82,7 @@ export const fetchAnswer = createAsyncThunk( state.preference.selectedDocs!, state.conversation.queries, state.conversation.conversationId, + state.preference.prompt.id, ); if (answer) { let sourcesPrepped = []; diff --git a/frontend/src/preferences/preferenceSlice.ts b/frontend/src/preferences/preferenceSlice.ts index 8b4f2312..beeac465 100644 --- a/frontend/src/preferences/preferenceSlice.ts +++ b/frontend/src/preferences/preferenceSlice.ts @@ -8,7 +8,7 @@ import { RootState } from '../store'; interface Preference { apiKey: string; - prompt: { name: string; id: string }; + prompt: { name: string; id: string; type: string }; selectedDocs: Doc | null; sourceDocs: Doc[] | null; conversations: { name: string; id: string }[] | null; @@ -16,7 +16,7 @@ interface Preference { const initialState: Preference = { apiKey: 'xxx', - prompt: { name: 'default', id: 'default' }, + prompt: { name: 'default', id: 'default', type: 'public' }, selectedDocs: { name: 'default', language: 'default', From d6dcbb63d4d6286437684fffd91997b7c7adf41d Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 22 Nov 2023 23:57:47 +0000 Subject: [PATCH 3/5] fix ruff --- application/api/user/routes.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/application/api/user/routes.py b/application/api/user/routes.py index 14dec7e9..638ff016 100644 --- a/application/api/user/routes.py +++ b/application/api/user/routes.py @@ -1,11 +1,9 @@ import os from flask import Blueprint, request, jsonify import requests -import json from pymongo import MongoClient from bson.objectid import ObjectId from werkzeug.utils import secure_filename -import http.client from application.api.user.tasks import ingest From d7a1be2f3c599c1ae71508c1ebb5cb9aeaba4987 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 23 Nov 2023 00:00:08 +0000 Subject: [PATCH 4/5] fix bug --- frontend/src/store.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/store.ts b/frontend/src/store.ts index 81e7fa55..84225f61 100644 --- a/frontend/src/store.ts +++ b/frontend/src/store.ts @@ -13,7 +13,7 @@ const store = configureStore({ preference: { apiKey: key ?? '', selectedDocs: doc !== null ? JSON.parse(doc) : null, - prompt: { name: 'default', id: 'default' }, + prompt: { name: 'default', id: 'default', type: 'private' }, conversations: null, sourceDocs: [ { From 5bdedacab1253056ea2a95ab88df26589bbd397b Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 23 Nov 2023 00:09:17 +0000 Subject: [PATCH 5/5] fix xss --- application/api/user/routes.py | 3 +-- frontend/src/Setting.tsx | 9 ++++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/application/api/user/routes.py b/application/api/user/routes.py index 638ff016..97e589ed 100644 --- a/application/api/user/routes.py +++ b/application/api/user/routes.py @@ -249,7 +249,6 @@ def create_prompt(): if name == "": return {"status": "error"} user = "local" - # write to mongodb resp = prompts_collection.insert_one( { "name": name, @@ -258,7 +257,7 @@ def create_prompt(): } ) new_id = str(resp.inserted_id) - return {"id": new_id, "name": name, "content": content} + return {"id": new_id} @user.route("/api/get_prompts", methods=["GET"]) def get_prompts(): diff --git a/frontend/src/Setting.tsx b/frontend/src/Setting.tsx index 0927fc18..65756bea 100644 --- a/frontend/src/Setting.tsx +++ b/frontend/src/Setting.tsx @@ -255,10 +255,13 @@ const Prompts: React.FC = ({ } const newPrompt = await response.json(); if (setPrompts) { - setPrompts([...prompts, newPrompt]); + setPrompts([ + ...prompts, + { name: newPromptName, id: newPrompt.id, type: 'private' }, + ]); } - onSelectPrompt(newPrompt.name, newPrompt.id, newPrompt.type); - setNewPromptName(newPrompt.name); + onSelectPrompt(newPromptName, newPrompt.id, newPromptContent); + setNewPromptName(newPromptName); } catch (error) { console.error(error); }
Active Prompt
Prompt name
+ start by editing name +
Prompt content