diff --git a/application/api/answer/routes.py b/application/api/answer/routes.py index bd1fa21f..b122eac1 100644 --- a/application/api/answer/routes.py +++ b/application/api/answer/routes.py @@ -207,7 +207,9 @@ def stream(): prompt_id = data["prompt_id"] else: prompt_id = 'default' - if 'chunks' in data: + if 'selectedDocs' in data and data['selectedDocs'] is None: + chunks = 0 + elif 'chunks' in data: chunks = int(data["chunks"]) else: chunks = 2 diff --git a/docs/pages/Developing/API-docs.md b/docs/pages/Developing/API-docs.md index ee43d848..c5bd8970 100644 --- a/docs/pages/Developing/API-docs.md +++ b/docs/pages/Developing/API-docs.md @@ -227,3 +227,119 @@ JSON response indicating the status of the operation: ```json { "status": "ok" } ``` + +### 7. /api/get_api_keys +**Description:** + +The endpoint retrieves a list of API keys for the user. + +**Request:** + +**Method**: `GET` + +**Sample JavaScript Fetch Request:** +```js +// get_api_keys (GET http://127.0.0.1:5000/api/get_api_keys) +fetch("http://localhost:5001/api/get_api_keys", { + "method": "GET", + "headers": { + "Content-Type": "application/json; charset=utf-8" + }, +}) +.then((res) => res.text()) +.then(console.log.bind(console)) + +``` +**Response:** + +JSON response with a list of created API keys: + +```json +[ + { + "id": "string", + "name": "string", + "key": "string", + "source": "string" + }, + ... + ] +``` + +### 8. /api/create_api_key + +**Description:** + +Create a new API key for the user. + +**Request:** + +**Method**: `POST` + +**Headers**: Content-Type should be set to `application/json; charset=utf-8` + +**Request Body**: JSON object with the following fields: +* `name` — A name for the API key. +* `source` — The source documents that will be used. + +Here is a JavaScript Fetch Request example: +```js +// create_api_key (POST http://127.0.0.1:5000/api/create_api_key) +fetch("http://127.0.0.1:5000/api/create_api_key", { + "method": "POST", + "headers": { + "Content-Type": "application/json; charset=utf-8" + }, + "body": JSON.stringify({"name":"Example Key Name","source":"Example Source"}) +}) +.then((res) => res.json()) +.then(console.log.bind(console)) +``` + +**Response** + +In response, you will get a JSON document containing the `id` and `key`: +```json +{ + "id": "string", + "key": "string" +} +``` + +### 9. /api/delete_api_key + +**Description:** + +Delete an API key for the user. + +**Request:** + +**Method**: `POST` + +**Headers**: Content-Type should be set to `application/json; charset=utf-8` + +**Request Body**: JSON object with the field: +* `id` — The unique identifier of the API key to be deleted. + +Here is a JavaScript Fetch Request example: +```js +// delete_api_key (POST http://127.0.0.1:5000/api/delete_api_key) +fetch("http://127.0.0.1:5000/api/delete_api_key", { + "method": "POST", + "headers": { + "Content-Type": "application/json; charset=utf-8" + }, + "body": JSON.stringify({"id":"API_KEY_ID"}) +}) +.then((res) => res.json()) +.then(console.log.bind(console)) +``` + +**Response:** + +In response, you will get a JSON document indicating the status of the operation: +```json +{ + "status": "ok" +} +``` \ No newline at end of file diff --git a/docs/pages/_app.mdx b/docs/pages/_app.mdx index 51f4539f..992283cc 100644 --- a/docs/pages/_app.mdx +++ b/docs/pages/_app.mdx @@ -4,7 +4,7 @@ export default function MyApp({ Component, pageProps }) { return ( <> - + ) } \ No newline at end of file diff --git a/extensions/react-widget/src/components/DocsGPTWidget.tsx b/extensions/react-widget/src/components/DocsGPTWidget.tsx index 45cd2285..54e1beea 100644 --- a/extensions/react-widget/src/components/DocsGPTWidget.tsx +++ b/extensions/react-widget/src/components/DocsGPTWidget.tsx @@ -285,7 +285,7 @@ const Hero = ({ title, description }: { title: string, description: string }) => export const DocsGPTWidget = ({ apiHost = 'https://gptcloud.arc53.com', selectDocs = 'default', - apiKey = 'docsgpt-public', + apiKey = '82962c9a-aa77-4152-94e5-a4f84fd44c6a', avatar = 'https://d3dg1063dc54p9.cloudfront.net/cute-docsgpt.png', title = 'Get AI assistance', description = 'DocsGPT\'s AI Chatbot is here to help', diff --git a/frontend/src/Setting.tsx b/frontend/src/Setting.tsx index 46c64cad..71b351d1 100644 --- a/frontend/src/Setting.tsx +++ b/frontend/src/Setting.tsx @@ -15,23 +15,24 @@ import { import { Doc } from './preferences/preferenceApi'; import { useDarkTheme } from './hooks'; import Dropdown from './components/Dropdown'; +import { ActiveState } from './models/misc'; +import PromptsModal from './preferences/PromptsModal'; +type PromptProps = { + prompts: { name: string; id: string; type: string }[]; + 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 apiHost = import.meta.env.VITE_API_HOST || 'https://docsapi.arc53.com'; - const embeddingsName = import.meta.env.VITE_EMBEDDINGS_NAME || 'huggingface_sentence-transformers/all-mpnet-base-v2'; -const Setting: React.FC = () => { - const tabs = ['General', 'Prompts', 'Documents', 'API Keys']; - //const tabs = ['General', 'Prompts', 'Documents', 'Widgets']; +const Setting: React.FC = () => { + const tabs = ['General', 'Documents', 'API Keys']; const [activeTab, setActiveTab] = useState('General'); - const [prompts, setPrompts] = useState< - { name: string; id: string; type: string }[] - >([]); - const selectedPrompt = useSelector(selectPrompt); - const [isAddPromptModalOpen, setAddPromptModalOpen] = useState(false); const documents = useSelector(selectSourceDocs); - const [isAddDocumentModalOpen, setAddDocumentModalOpen] = useState(false); const dispatch = useDispatch(); @@ -41,43 +42,6 @@ const Setting: React.FC = () => { setWidgetScreenshot(screenshot); }; - useEffect(() => { - const fetchPrompts = async () => { - try { - const response = await fetch(`${apiHost}/api/get_prompts`); - if (!response.ok) { - throw new Error('Failed to fetch prompts'); - } - const promptsData = await response.json(); - setPrompts(promptsData); - } catch (error) { - console.error(error); - } - }; - fetchPrompts(); - }, []); - - const onDeletePrompt = (name: string, id: string) => { - setPrompts(prompts.filter((prompt) => prompt.id !== id)); - - fetch(`${apiHost}/api/delete_prompt`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - // send id in body only - body: JSON.stringify({ id: id }), - }) - .then((response) => { - if (!response.ok) { - throw new Error('Failed to delete prompt'); - } - }) - .catch((error) => { - console.error(error); - }); - }; - const handleDeleteClick = (index: number, doc: Doc) => { const docPath = 'indexes/' + 'local' + '/' + doc.name; @@ -156,17 +120,6 @@ const Setting: React.FC = () => { switch (activeTab) { case 'General': return ; - case 'Prompts': - return ( - - dispatch(setPrompt({ name: name, id: id, type: type })) - } - setPrompts={setPrompts} - /> - ); case 'Documents': return ( { const themes = ['Light', 'Dark']; const languages = ['English']; const chunks = ['0', '2', '4', '6', '8', '10']; + const [prompts, setPrompts] = useState< + { name: string; id: string; type: string }[] + >([]); const selectedChunks = useSelector(selectChunks); const [isDarkTheme, toggleTheme] = useDarkTheme(); const [selectedTheme, setSelectedTheme] = useState( @@ -200,6 +156,24 @@ const General: React.FC = () => { ); const dispatch = useDispatch(); const [selectedLanguage, setSelectedLanguage] = useState(languages[0]); + const selectedPrompt = useSelector(selectPrompt); + const apiHost = import.meta.env.VITE_API_HOST || 'https://docsapi.arc53.com'; + + useEffect(() => { + const fetchPrompts = async () => { + try { + const response = await fetch(`${apiHost}/api/get_prompts`); + if (!response.ok) { + throw new Error('Failed to fetch prompts'); + } + const promptsData = await response.json(); + setPrompts(promptsData); + } catch (error) { + console.error(error); + } + }; + fetchPrompts(); + }, []); return (
@@ -212,6 +186,8 @@ const General: React.FC = () => { setSelectedTheme(option); option !== selectedTheme && toggleTheme(); }} + size="w-56" + rounded="3xl" />
@@ -223,9 +199,11 @@ const General: React.FC = () => { options={languages} selectedValue={selectedLanguage} onSelect={setSelectedLanguage} + size="w-56" + rounded="3xl" />
-
+

Chunks processed per query

@@ -234,6 +212,19 @@ const General: React.FC = () => { options={chunks} selectedValue={selectedChunks} onSelect={(value: string) => dispatch(setChunks(value))} + size="w-56" + rounded="3xl" + /> +
+
+ + dispatch(setPrompt({ name: name, id: id, type: type })) + } + setPrompts={setPrompts} + apiHost={apiHost} />
@@ -241,12 +232,6 @@ const General: React.FC = () => { }; export default Setting; -type PromptProps = { - prompts: { name: string; id: string; type: string }[]; - selectedPrompt: { name: string; id: string; type: string }; - onSelectPrompt: (name: string, id: string, type: string) => void; - setPrompts: (prompts: { name: string; id: string; type: string }[]) => void; -}; const Prompts: React.FC = ({ prompts, @@ -263,11 +248,20 @@ const Prompts: React.FC = ({ id: string; type: string; }) => { - setNewPromptName(name); + setEditPromptName(name); onSelectPrompt(name, id, type); }; - const [newPromptName, setNewPromptName] = useState(selectedPrompt.name); + const [newPromptName, setNewPromptName] = useState(''); const [newPromptContent, setNewPromptContent] = useState(''); + const [editPromptName, setEditPromptName] = useState(''); + const [editPromptContent, setEditPromptContent] = useState(''); + const [currentPromptEdit, setCurrentPromptEdit] = useState({ + id: '', + name: '', + type: '', + }); + const [modalType, setModalType] = useState<'ADD' | 'EDIT'>('ADD'); + const [modalState, setModalState] = useState('INACTIVE'); const handleAddPrompt = async () => { try { @@ -291,6 +285,7 @@ const Prompts: React.FC = ({ { name: newPromptName, id: newPrompt.id, type: 'private' }, ]); } + setModalState('INACTIVE'); onSelectPrompt(newPromptName, newPrompt.id, newPromptContent); setNewPromptName(newPromptName); } catch (error) { @@ -298,16 +293,14 @@ const Prompts: React.FC = ({ } }; - const handleDeletePrompt = () => { - setPrompts(prompts.filter((prompt) => prompt.id !== selectedPrompt.id)); - console.log('selectedPrompt.id', selectedPrompt.id); - + const handleDeletePrompt = (id: string) => { + setPrompts(prompts.filter((prompt) => prompt.id !== id)); fetch(`${apiHost}/api/delete_prompt`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, - body: JSON.stringify({ id: selectedPrompt.id }), + body: JSON.stringify({ id: id }), }) .then((response) => { if (!response.ok) { @@ -316,7 +309,6 @@ const Prompts: React.FC = ({ // 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) => { @@ -324,50 +316,65 @@ const Prompts: React.FC = ({ }); }; - 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', - }, + const fetchPromptContent = async (id: string) => { + console.log('fetching prompt content'); + try { + const response = await fetch( + `${apiHost}/api/get_single_prompt?id=${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); + }, + ); + if (!response.ok) { + throw new Error('Failed to fetch prompt content'); } - }; + const promptContent = await response.json(); + setEditPromptContent(promptContent.content); + } catch (error) { + console.error(error); + } + }; - fetchPromptContent(); - }, [selectedPrompt]); - - const handleSaveChanges = () => { + const handleSaveChanges = (id: string, type: string) => { fetch(`${apiHost}/api/update_prompt`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ - id: selectedPrompt.id, - name: newPromptName, - content: newPromptContent, + id: id, + name: editPromptName, + content: editPromptContent, }), }) .then((response) => { if (!response.ok) { throw new Error('Failed to update prompt'); } - onSelectPrompt(newPromptName, selectedPrompt.id, selectedPrompt.type); - setNewPromptName(newPromptName); + if (setPrompts) { + const existingPromptIndex = prompts.findIndex( + (prompt) => prompt.id === id, + ); + if (existingPromptIndex === -1) { + setPrompts([ + ...prompts, + { name: editPromptName, id: id, type: type }, + ]); + } else { + const updatedPrompts = [...prompts]; + updatedPrompts[existingPromptIndex] = { + name: editPromptName, + id: id, + type: type, + }; + setPrompts(updatedPrompts); + } + } + setModalState('INACTIVE'); + onSelectPrompt(editPromptName, id, type); }) .catch((error) => { console.error(error); @@ -375,76 +382,65 @@ const Prompts: React.FC = ({ }; return ( -
-
-

Active Prompt

- + <> +
+
+
+

Active Prompt

+ { + setModalType('EDIT'); + setEditPromptName(name); + fetchPromptContent(id); + setCurrentPromptEdit({ id: id, name: name, type: type }); + setModalState('ACTIVE'); + }} + onDelete={handleDeletePrompt} + /> +
+ +
- -
-

Prompt name

{' '} -

- start by editing name -

- setNewPromptName(e.target.value)} - /> -
- -
-

Prompt content

- +
+
+ + +
+
+ ); +} + +function EditPrompt({ + setModalState, + handleEditPrompt, + editPromptName, + setEditPromptName, + editPromptContent, + setEditPromptContent, + currentPromptEdit, +}: { + setModalState: (state: ActiveState) => void; + handleEditPrompt?: (id: string, type: string) => void; + editPromptName: string; + setEditPromptName: (name: string) => void; + editPromptContent: string; + setEditPromptContent: (content: string) => void; + currentPromptEdit: { name: string; id: string; type: string }; +}) { + return ( +
+

Edit Prompt

+

+ Edit your custom prompt and save it to DocsGPT +

+
+ setEditPromptName(e.target.value)} + > +
+ + Prompt Name + +
+
+ + Prompt Text + +
+ +
+
+ + +
+
+ ); +} + +export default function PromptsModal({ + modalState, + setModalState, + type, + newPromptName, + setNewPromptName, + newPromptContent, + setNewPromptContent, + editPromptName, + setEditPromptName, + editPromptContent, + setEditPromptContent, + currentPromptEdit, + handleAddPrompt, + handleEditPrompt, +}: { + modalState: ActiveState; + setModalState: (state: ActiveState) => void; + type: 'ADD' | 'EDIT'; + newPromptName: string; + setNewPromptName: (name: string) => void; + newPromptContent: string; + setNewPromptContent: (content: string) => void; + editPromptName: string; + setEditPromptName: (name: string) => void; + editPromptContent: string; + setEditPromptContent: (content: string) => void; + currentPromptEdit: { name: string; id: string; type: string }; + handleAddPrompt?: () => void; + handleEditPrompt?: (id: string, type: string) => void; +}) { + let view; + + if (type === 'ADD') { + view = ( + + ); + } else if (type === 'EDIT') { + view = ( + + ); + } else { + view = <>; + } + return ( +
+
+ {view} +
+
+ ); +} diff --git a/frontend/src/upload/Upload.tsx b/frontend/src/upload/Upload.tsx index b2144427..39c2a093 100644 --- a/frontend/src/upload/Upload.tsx +++ b/frontend/src/upload/Upload.tsx @@ -308,6 +308,8 @@ export default function Upload({ onSelect={(value: { label: string; value: string }) => setUrlType(value) } + size="w-full" + rounded="3xl" /> {urlType.label !== 'Reddit' ? ( <>