Merge pull request #899 from siiddhantt/main

feat: added prompts section under general in settings
feature/api-key-create^2
Alex 2 months ago committed by GitHub
commit 6fd80a5582
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -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 <General />;
case 'Prompts':
return (
<Prompts
prompts={prompts}
selectedPrompt={selectedPrompt}
onSelectPrompt={(name, id, type) =>
dispatch(setPrompt({ name: name, id: id, type: type }))
}
setPrompts={setPrompts}
/>
);
case 'Documents':
return (
<Documents
@ -193,6 +146,9 @@ const General: React.FC = () => {
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 (
<div className="mt-[59px]">
<div className="mb-4">
@ -211,6 +185,8 @@ const General: React.FC = () => {
setSelectedTheme(option);
option !== selectedTheme && toggleTheme();
}}
size="w-56"
rounded="3xl"
/>
</div>
<div className="mb-4">
@ -221,9 +197,11 @@ const General: React.FC = () => {
options={languages}
selectedValue={selectedLanguage}
onSelect={setSelectedLanguage}
size="w-56"
rounded="3xl"
/>
</div>
<div>
<div className="mb-4">
<p className="font-bold text-jet dark:text-bright-gray">
Chunks processed per query
</p>
@ -231,6 +209,19 @@ const General: React.FC = () => {
options={chunks}
selectedValue={selectedChunks}
onSelect={(value: string) => dispatch(setChunks(value))}
size="w-56"
rounded="3xl"
/>
</div>
<div>
<Prompts
prompts={prompts}
selectedPrompt={selectedPrompt}
onSelectPrompt={(name, id, type) =>
dispatch(setPrompt({ name: name, id: id, type: type }))
}
setPrompts={setPrompts}
apiHost={apiHost}
/>
</div>
</div>
@ -238,12 +229,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<PromptProps> = ({
prompts,
@ -260,11 +245,20 @@ const Prompts: React.FC<PromptProps> = ({
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<ActiveState>('INACTIVE');
const handleAddPrompt = async () => {
try {
@ -288,6 +282,7 @@ const Prompts: React.FC<PromptProps> = ({
{ name: newPromptName, id: newPrompt.id, type: 'private' },
]);
}
setModalState('INACTIVE');
onSelectPrompt(newPromptName, newPrompt.id, newPromptContent);
setNewPromptName(newPromptName);
} catch (error) {
@ -295,16 +290,14 @@ const Prompts: React.FC<PromptProps> = ({
}
};
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) {
@ -313,7 +306,6 @@ const Prompts: React.FC<PromptProps> = ({
// 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) => {
@ -321,50 +313,65 @@ const Prompts: React.FC<PromptProps> = ({
});
};
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');
}
};
fetchPromptContent();
}, [selectedPrompt]);
const promptContent = await response.json();
setEditPromptContent(promptContent.content);
} catch (error) {
console.error(error);
}
};
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);
@ -372,76 +379,65 @@ const Prompts: React.FC<PromptProps> = ({
};
return (
<div className="mt-[59px]">
<div className="mb-4">
<p className="font-semibold dark:text-bright-gray">Active Prompt</p>
<Dropdown
options={prompts}
selectedValue={selectedPrompt.name}
onSelect={handleSelectPrompt}
/>
</div>
<div className="mb-4">
<p className="dark:text-bright-gray">Prompt name </p>{' '}
<p className="mb-2 text-xs italic text-eerie-black dark:text-bright-gray">
start by editing name
</p>
<input
type="text"
value={newPromptName}
placeholder="Active Prompt Name"
className="w-full rounded-lg border-2 p-2 dark:border-chinese-silver dark:bg-transparent dark:text-white"
onChange={(e) => setNewPromptName(e.target.value)}
/>
</div>
<div className="mb-4">
<p className="mb-2 dark:text-bright-gray">Prompt content</p>
<textarea
className="h-32 w-full rounded-lg border-2 p-2 dark:border-chinese-silver dark:bg-transparent dark:text-white"
value={newPromptContent}
onChange={(e) => setNewPromptContent(e.target.value)}
placeholder="Active prompt contents"
/>
</div>
<div className="flex justify-between">
<button
className={`rounded-lg bg-green-500 px-4 py-2 font-bold text-white transition-all hover:bg-green-700 ${
newPromptName === selectedPrompt.name
? 'cursor-not-allowed opacity-50'
: ''
}`}
onClick={handleAddPrompt}
disabled={newPromptName === selectedPrompt.name}
>
Add New Prompt
</button>
<button
className={`rounded-lg bg-red-500 px-4 py-2 font-bold text-white transition-all hover:bg-red-700 ${
selectedPrompt.type === 'public'
? 'cursor-not-allowed opacity-50'
: ''
}`}
onClick={handleDeletePrompt}
disabled={selectedPrompt.type === 'public'}
>
Delete Prompt
</button>
<button
className={`rounded-lg bg-blue-500 px-4 py-2 font-bold text-white transition-all hover:bg-blue-700 ${
selectedPrompt.type === 'public'
? 'cursor-not-allowed opacity-50'
: ''
}`}
onClick={handleSaveChanges}
disabled={selectedPrompt.type === 'public'}
>
Save Changes
</button>
<>
<div>
<div className="mb-4 flex flex-row items-center gap-8">
<div>
<p className="font-semibold dark:text-bright-gray">Active Prompt</p>
<Dropdown
options={prompts}
selectedValue={selectedPrompt.name}
onSelect={handleSelectPrompt}
size="w-56"
rounded="3xl"
showEdit
showDelete
onEdit={({
id,
name,
type,
}: {
id: string;
name: string;
type: string;
}) => {
setModalType('EDIT');
setEditPromptName(name);
fetchPromptContent(id);
setCurrentPromptEdit({ id: id, name: name, type: type });
setModalState('ACTIVE');
}}
onDelete={handleDeletePrompt}
/>
</div>
<button
className="mt-[24px] rounded-3xl border-2 border-solid border-purple-30 px-5 py-3 text-purple-30 hover:bg-purple-30 hover:text-white"
onClick={() => {
setModalType('ADD');
setModalState('ACTIVE');
}}
>
Add new
</button>
</div>
</div>
</div>
<PromptsModal
type={modalType}
modalState={modalState}
setModalState={setModalState}
newPromptName={newPromptName}
setNewPromptName={setNewPromptName}
newPromptContent={newPromptContent}
setNewPromptContent={setNewPromptContent}
editPromptName={editPromptName}
setEditPromptName={setEditPromptName}
editPromptContent={editPromptContent}
setEditPromptContent={setEditPromptContent}
currentPromptEdit={currentPromptEdit}
handleAddPrompt={handleAddPrompt}
handleEditPrompt={handleSaveChanges}
/>
</>
);
};
@ -496,10 +492,8 @@ const Documents: React.FC<DocumentsProps> = ({
}) => {
return (
<div className="mt-8">
<div className="flex flex-col overflow-x-auto">
{/* <h2 className="text-xl font-semibold">Documents</h2> */}
<div className="mt-[27px] w-max rounded-xl border dark:border-chinese-silver">
<div className="flex flex-col">
<div className="mt-[27px] w-max overflow-x-auto rounded-xl border dark:border-chinese-silver">
<table className="block w-full table-auto content-center justify-center text-center dark:text-bright-gray">
<thead>
<tr>

@ -1,10 +1,16 @@
import { useState } from 'react';
import Arrow2 from '../assets/dropdown-arrow.svg';
import Edit from '../assets/edit.svg';
import Trash from '../assets/trash.svg';
function Dropdown({
options,
selectedValue,
onSelect,
size = 'w-32',
rounded = 'xl',
showEdit,
onEdit,
showDelete,
onDelete,
placeholder,
@ -18,6 +24,10 @@ function Dropdown({
| ((value: string) => void)
| ((value: { name: string; id: string; type: string }) => void)
| ((value: { label: string; value: string }) => void);
size?: string;
rounded?: 'xl' | '3xl';
showEdit?: boolean;
onEdit?: (value: { name: string; id: string; type: string }) => void;
showDelete?: boolean;
onDelete?: (value: string) => void;
placeholder?: string;
@ -27,26 +37,21 @@ function Dropdown({
const [isOpen, setIsOpen] = useState(false);
return (
<div
className={
className={[
typeof selectedValue === 'string'
? 'relative mt-2 w-32'
: 'relative w-full align-middle'
}
? 'relative mt-2'
: 'relative align-middle',
size,
].join(' ')}
>
<button
onClick={() => setIsOpen(!isOpen)}
className={`flex w-full cursor-pointer items-center justify-between border-2 border-silver bg-white p-3 dark:border-chinese-silver dark:bg-transparent ${
isOpen
? typeof selectedValue === 'string'
? 'rounded-t-xl'
: 'rounded-t-3xl'
: typeof selectedValue === 'string'
? 'rounded-xl'
: 'rounded-3xl'
className={`flex w-full cursor-pointer items-center justify-between border-2 border-silver bg-white px-5 py-3 dark:border-chinese-silver dark:bg-transparent ${
isOpen ? `rounded-t-${rounded}` : `rounded-${rounded}`
}`}
>
{typeof selectedValue === 'string' ? (
<span className="flex-1 overflow-hidden text-ellipsis dark:text-bright-gray">
<span className="overflow-hidden text-ellipsis dark:text-bright-gray">
{selectedValue}
</span>
) : (
@ -71,7 +76,7 @@ function Dropdown({
/>
</button>
{isOpen && (
<div className="absolute left-0 right-0 z-50 -mt-1 max-h-40 overflow-y-auto rounded-b-xl border-2 bg-white shadow-lg dark:border-chinese-silver dark:bg-dark-charcoal">
<div className="absolute left-0 right-0 z-20 -mt-1 max-h-40 overflow-y-auto rounded-b-xl border-2 bg-white shadow-lg dark:border-chinese-silver dark:bg-dark-charcoal">
{options.map((option: any, index) => (
<div
key={index}
@ -90,9 +95,35 @@ function Dropdown({
? option.name
: option.label}
</span>
{showEdit && onEdit && (
<img
src={Edit}
alt="Edit"
className="mr-4 h-4 w-4 cursor-pointer hover:opacity-50"
onClick={() => {
onEdit({
id: option.id,
name: option.name,
type: option.type,
});
setIsOpen(false);
}}
/>
)}
{showDelete && onDelete && (
<button onClick={() => onDelete(option)} className="p-2">
Delete
<button
onClick={() => onDelete(option.id)}
disabled={option.type === 'public'}
>
<img
src={Trash}
alt="Delete"
className={`mr-2 h-4 w-4 cursor-pointer hover:opacity-50 ${
option.type === 'public'
? 'cursor-not-allowed opacity-50'
: ''
}`}
/>
</button>
)}
</div>

@ -0,0 +1,213 @@
import { ActiveState } from '../models/misc';
function AddPrompt({
setModalState,
handleAddPrompt,
newPromptName,
setNewPromptName,
newPromptContent,
setNewPromptContent,
}: {
setModalState: (state: ActiveState) => void;
handleAddPrompt?: () => void;
newPromptName: string;
setNewPromptName: (name: string) => void;
newPromptContent: string;
setNewPromptContent: (content: string) => void;
}) {
return (
<div className="rounded-3xl px-4 py-2">
<p className="mb-1 text-xl text-jet dark:text-bright-gray">Add Prompt</p>
<p className="mb-7 text-xs text-[#747474] dark:text-[#7F7F82]">
Add your custom prompt and save it to DocsGPT
</p>
<div>
<input
placeholder="Prompt Name"
type="text"
className="h-10 w-full rounded-lg border-2 border-silver px-3 outline-none dark:bg-transparent dark:text-silver"
value={newPromptName}
onChange={(e) => setNewPromptName(e.target.value)}
></input>
<div className="relative bottom-12 left-3 mt-[-3.00px]">
<span className="bg-white px-1 text-xs text-silver dark:bg-outer-space dark:text-silver">
Prompt Name
</span>
</div>
<div className="relative top-[7px] left-3">
<span className="bg-white px-1 text-xs text-silver dark:bg-outer-space dark:text-silver">
Prompt Text
</span>
</div>
<textarea
className="h-56 w-full rounded-lg border-2 border-silver px-3 py-2 outline-none dark:bg-transparent dark:text-silver"
value={newPromptContent}
onChange={(e) => setNewPromptContent(e.target.value)}
></textarea>
</div>
<div className="mt-6 flex flex-row-reverse gap-4">
<button
onClick={handleAddPrompt}
className="rounded-3xl bg-purple-30 px-5 py-2 text-white transition-all hover:opacity-90"
>
Save
</button>
<button
onClick={() => {
setModalState('INACTIVE');
}}
className="cursor-pointer font-medium dark:text-light-gray"
>
Cancel
</button>
</div>
</div>
);
}
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 (
<div className="rounded-3xl px-4 py-2">
<p className="mb-1 text-xl text-jet dark:text-bright-gray">Edit Prompt</p>
<p className="mb-7 text-xs text-[#747474] dark:text-[#7F7F82]">
Edit your custom prompt and save it to DocsGPT
</p>
<div>
<input
placeholder="Prompt Name"
type="text"
className="h-10 w-full rounded-lg border-2 border-silver px-3 outline-none dark:bg-transparent dark:text-silver"
value={editPromptName}
onChange={(e) => setEditPromptName(e.target.value)}
></input>
<div className="relative bottom-12 left-3 mt-[-3.00px]">
<span className="bg-white px-1 text-xs text-silver dark:bg-outer-space dark:text-silver">
Prompt Name
</span>
</div>
<div className="relative top-[7px] left-3">
<span className="bg-white px-1 text-xs text-silver dark:bg-outer-space dark:text-silver">
Prompt Text
</span>
</div>
<textarea
className="h-56 w-full rounded-lg border-2 border-silver px-3 py-2 outline-none dark:bg-transparent dark:text-silver"
value={editPromptContent}
onChange={(e) => setEditPromptContent(e.target.value)}
></textarea>
</div>
<div className="mt-6 flex flex-row-reverse gap-4">
<button
className={`rounded-3xl bg-purple-30 px-5 py-2 text-white transition-all ${
currentPromptEdit.type === 'public'
? 'cursor-not-allowed opacity-50'
: 'hover:opacity-90'
}`}
onClick={() => {
handleEditPrompt &&
handleEditPrompt(currentPromptEdit.id, currentPromptEdit.type);
}}
disabled={currentPromptEdit.type === 'public'}
>
Save
</button>
<button
onClick={() => {
setModalState('INACTIVE');
}}
className="cursor-pointer font-medium dark:text-light-gray"
>
Cancel
</button>
</div>
</div>
);
}
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 = (
<AddPrompt
setModalState={setModalState}
handleAddPrompt={handleAddPrompt}
newPromptName={newPromptName}
setNewPromptName={setNewPromptName}
newPromptContent={newPromptContent}
setNewPromptContent={setNewPromptContent}
/>
);
} else if (type === 'EDIT') {
view = (
<EditPrompt
setModalState={setModalState}
handleEditPrompt={handleEditPrompt}
editPromptName={editPromptName}
setEditPromptName={setEditPromptName}
editPromptContent={editPromptContent}
setEditPromptContent={setEditPromptContent}
currentPromptEdit={currentPromptEdit}
/>
);
} else {
view = <></>;
}
return (
<article
className={`${
modalState === 'ACTIVE' ? 'visible' : 'hidden'
} fixed top-0 left-0 z-30 h-screen w-screen bg-gray-alpha`}
>
<article className="mx-auto mt-24 flex w-[90vw] max-w-lg flex-col gap-4 rounded-lg bg-white p-6 shadow-lg dark:bg-outer-space">
{view}
</article>
</article>
);
}

@ -307,6 +307,8 @@ export default function Upload({
onSelect={(value: { label: string; value: string }) =>
setUrlType(value)
}
size="w-full"
rounded="3xl"
/>
{urlType.label !== 'Reddit' ? (
<>

Loading…
Cancel
Save