Merge pull request #1434 from RohittCodes/fix-1231

feat: wrapper modal
This commit is contained in:
Alex
2024-11-20 10:48:30 +00:00
committed by GitHub
8 changed files with 287 additions and 239 deletions

View File

@@ -33,7 +33,6 @@ import {
selectConversations,
selectModalStateDeleteConv,
selectSelectedDocs,
selectSelectedDocsStatus,
selectSourceDocs,
selectPaginatedDocuments,
setConversations,
@@ -86,10 +85,6 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
const [apiKeyModalState, setApiKeyModalState] =
useState<ActiveState>('INACTIVE');
const isSelectedDocsSet = useSelector(selectSelectedDocsStatus);
const [selectedDocsModalState, setSelectedDocsModalState] =
useState<ActiveState>(isSelectedDocsSet ? 'INACTIVE' : 'ACTIVE');
const [uploadModalState, setUploadModalState] =
useState<ActiveState>('INACTIVE');
@@ -493,11 +488,13 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
setModalState={setModalStateDeleteConv}
handleDeleteAllConv={handleDeleteAllConversations}
/>
<Upload
modalState={uploadModalState}
setModalState={setUploadModalState}
isOnboarding={false}
></Upload>
{uploadModalState === 'ACTIVE' && (
<Upload
setModalState={setUploadModalState}
isOnboarding={false}
close={() => setUploadModalState('INACTIVE')}
></Upload>
)}
</>
);
}

View File

@@ -1,6 +1,6 @@
import Exit from '../assets/exit.svg';
import { ActiveState } from '../models/misc';
import { useTranslation } from 'react-i18next';
import WrapperModal from './WrapperModal';
function ConfirmationModal({
message,
modalState,
@@ -20,49 +20,43 @@ function ConfirmationModal({
}) {
const { t } = useTranslation();
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-[35vh] flex w-[90vw] max-w-lg flex-col gap-4 rounded-2xl bg-white shadow-lg dark:bg-outer-space">
<div className="relative">
<button
className="absolute top-3 right-4 m-2 w-3"
onClick={() => {
setModalState('INACTIVE');
handleCancel && handleCancel();
}}
>
<img className="filter dark:invert" src={Exit} />
</button>
<div className="p-8">
<p className="font-base mb-1 w-[90%] text-lg text-jet dark:text-bright-gray">
{message}
</p>
<div>
<div className="mt-6 flex flex-row-reverse gap-1">
<button
onClick={handleSubmit}
className="rounded-3xl bg-purple-30 px-5 py-2 text-sm text-white transition-all hover:bg-[#6F3FD1]"
>
{submitLabel}
</button>
<button
onClick={() => {
setModalState('INACTIVE');
handleCancel && handleCancel();
}}
className="cursor-pointer rounded-3xl px-5 py-2 text-sm font-medium hover:bg-gray-100 dark:bg-transparent dark:text-light-gray dark:hover:bg-[#767183]/50"
>
{cancelLabel ? cancelLabel : t('cancel')}
</button>
<>
{modalState === 'ACTIVE' && (
<WrapperModal
close={() => {
setModalState('INACTIVE');
handleCancel && handleCancel();
}}
>
<div className="relative">
<div className="p-8">
<p className="font-base mb-1 w-[90%] text-lg text-jet dark:text-bright-gray">
{message}
</p>
<div>
<div className="mt-6 flex flex-row-reverse gap-1">
<button
onClick={handleSubmit}
className="rounded-3xl bg-purple-30 px-5 py-2 text-sm text-white transition-all hover:bg-[#6F3FD1]"
>
{submitLabel}
</button>
<button
onClick={() => {
setModalState('INACTIVE');
handleCancel && handleCancel();
}}
className="cursor-pointer rounded-3xl px-5 py-2 text-sm font-medium hover:bg-gray-100 dark:bg-transparent dark:text-light-gray dark:hover:bg-[#767183]/50"
>
{cancelLabel ? cancelLabel : t('cancel')}
</button>
</div>
</div>
</div>
</div>
</div>
</article>
</article>
</WrapperModal>
)}
</>
);
}

View File

@@ -3,11 +3,11 @@ import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import userService from '../api/services/userService';
import Exit from '../assets/exit.svg';
import Dropdown from '../components/Dropdown';
import Input from '../components/Input';
import { CreateAPIKeyModalProps, Doc } from '../models/misc';
import { selectSourceDocs } from '../preferences/preferenceSlice';
import WrapperModal from './WrapperModal';
const embeddingsName =
import.meta.env.VITE_EMBEDDINGS_NAME ||
@@ -73,91 +73,82 @@ export default function CreateAPIKeyModal({
handleFetchPrompts();
}, []);
return (
<div className="fixed top-0 left-0 z-30 flex h-screen w-screen items-center justify-center bg-gray-alpha bg-opacity-50">
<div className="relative w-11/12 rounded-2xl bg-white p-10 dark:bg-outer-space sm:w-[512px]">
<button className="absolute top-3 right-4 m-2 w-3" onClick={close}>
<img className="filter dark:invert" src={Exit} />
</button>
<div className="mb-6">
<span className="text-xl text-jet dark:text-bright-gray">
{t('modals.createAPIKey.label')}
</span>
</div>
<div className="relative mt-5 mb-4">
<span className="absolute left-2 -top-2 bg-white px-2 text-xs text-gray-4000 dark:bg-outer-space dark:text-silver">
{t('modals.createAPIKey.apiKeyName')}
</span>
<Input
type="text"
className="rounded-md"
value={APIKeyName}
onChange={(e) => setAPIKeyName(e.target.value)}
></Input>
</div>
<div className="my-4">
<Dropdown
placeholder={t('modals.createAPIKey.sourceDoc')}
selectedValue={sourcePath ? sourcePath.name : null}
onSelect={(selection: {
name: string;
id: string;
type: string;
}) => {
setSourcePath(selection);
}}
options={extractDocPaths()}
size="w-full"
rounded="xl"
border="border"
/>
</div>
<div className="my-4">
<Dropdown
options={activePrompts}
selectedValue={prompt ? prompt.name : null}
placeholder={t('modals.createAPIKey.prompt')}
onSelect={(value: { name: string; id: string; type: string }) =>
setPrompt(value)
}
size="w-full"
border="border"
/>
</div>
<div className="my-4">
<p className="mb-2 ml-2 font-semibold text-jet dark:text-bright-gray">
{t('modals.createAPIKey.chunks')}
</p>
<Dropdown
options={chunkOptions}
selectedValue={chunk}
onSelect={(value: string) => setChunk(value)}
size="w-full"
border="border"
/>
</div>
<button
disabled={!sourcePath || APIKeyName.length === 0 || !prompt}
onClick={() => {
if (sourcePath && prompt) {
const payload: any = {
name: APIKeyName,
prompt_id: prompt.id,
chunks: chunk,
};
if (sourcePath.type === 'default') {
payload.retriever = sourcePath.id;
}
if (sourcePath.type === 'local') {
payload.source = sourcePath.id;
}
createAPIKey(payload);
}
}}
className="float-right mt-4 rounded-full bg-purple-30 px-5 py-2 text-sm text-white hover:bg-[#6F3FD1] disabled:opacity-50"
>
{t('modals.createAPIKey.create')}
</button>
<WrapperModal close={close}>
<div className="mb-6">
<span className="text-xl text-jet dark:text-bright-gray">
{t('modals.createAPIKey.label')}
</span>
</div>
</div>
<div className="relative mt-5 mb-4">
<span className="absolute left-2 -top-2 bg-white px-2 text-xs text-gray-4000 dark:bg-outer-space dark:text-silver">
{t('modals.createAPIKey.apiKeyName')}
</span>
<Input
type="text"
className="rounded-md"
value={APIKeyName}
onChange={(e) => setAPIKeyName(e.target.value)}
></Input>
</div>
<div className="my-4">
<Dropdown
placeholder={t('modals.createAPIKey.sourceDoc')}
selectedValue={sourcePath ? sourcePath.name : null}
onSelect={(selection: { name: string; id: string; type: string }) => {
setSourcePath(selection);
}}
options={extractDocPaths()}
size="w-full"
rounded="xl"
border="border"
/>
</div>
<div className="my-4">
<Dropdown
options={activePrompts}
selectedValue={prompt ? prompt.name : null}
placeholder={t('modals.createAPIKey.prompt')}
onSelect={(value: { name: string; id: string; type: string }) =>
setPrompt(value)
}
size="w-full"
border="border"
/>
</div>
<div className="my-4">
<p className="mb-2 ml-2 font-semibold text-jet dark:text-bright-gray">
{t('modals.createAPIKey.chunks')}
</p>
<Dropdown
options={chunkOptions}
selectedValue={chunk}
onSelect={(value: string) => setChunk(value)}
size="w-full"
border="border"
/>
</div>
<button
disabled={!sourcePath || APIKeyName.length === 0 || !prompt}
onClick={() => {
if (sourcePath && prompt) {
const payload: any = {
name: APIKeyName,
prompt_id: prompt.id,
chunks: chunk,
};
if (sourcePath.type === 'default') {
payload.retriever = sourcePath.id;
}
if (sourcePath.type === 'local') {
payload.source = sourcePath.id;
}
createAPIKey(payload);
}
}}
className="float-right mt-4 rounded-full bg-purple-30 px-5 py-2 text-sm text-white hover:bg-[#6F3FD1] disabled:opacity-50"
>
{t('modals.createAPIKey.create')}
</button>
</WrapperModal>
);
}

View File

@@ -10,7 +10,6 @@ import {
import Dropdown from '../components/Dropdown';
import { Doc } from '../models/misc';
import Spinner from '../assets/spinner.svg';
import Exit from '../assets/exit.svg';
const apiHost = import.meta.env.VITE_API_HOST || 'https://docsapi.arc53.com';
const embeddingsName =
import.meta.env.VITE_EMBEDDINGS_NAME ||
@@ -19,6 +18,7 @@ const embeddingsName =
type StatusType = 'loading' | 'idle' | 'fetched' | 'failed';
import conversationService from '../api/services/conversationService';
import WrapperModal from './WrapperModal';
export const ShareConversationModal = ({
close,
@@ -99,85 +99,78 @@ export const ShareConversationModal = ({
};
return (
<div className="fixed top-0 left-0 z-40 flex h-screen w-screen cursor-default items-center justify-center bg-gray-alpha bg-opacity-50 text-chinese-black dark:text-silver">
<div className="relative w-11/12 rounded-2xl bg-white p-10 dark:bg-outer-space sm:w-[512px]">
<button className="absolute top-3 right-4 m-2 w-3" onClick={close}>
<img className="filter dark:invert" src={Exit} />
</button>
<div className="flex flex-col gap-2">
<h2 className="text-xl font-medium">{t('modals.shareConv.label')}</h2>
<p className="text-sm">{t('modals.shareConv.note')}</p>
<div className="flex items-center justify-between">
<span className="text-lg">{t('modals.shareConv.option')}</span>
<label className=" cursor-pointer select-none items-center">
<div className="relative">
<input
type="checkbox"
checked={allowPrompt}
onChange={togglePromptPermission}
className="sr-only"
/>
<div
className={`box block h-8 w-14 rounded-full border border-purple-30 ${
allowPrompt
? 'bg-purple-30 dark:bg-purple-30'
: 'dark:bg-transparent'
}`}
></div>
<div
className={`absolute left-1 top-1 flex h-6 w-6 items-center justify-center rounded-full transition ${
allowPrompt ? 'translate-x-full bg-silver' : 'bg-purple-30'
}`}
></div>
</div>
</label>
</div>
{allowPrompt && (
<div className="my-4">
<Dropdown
placeholder={t('modals.createAPIKey.sourceDoc')}
selectedValue={sourcePath}
onSelect={(selection: { label: string; value: string }) =>
setSourcePath(selection)
}
options={extractDocPaths(sourceDocs ?? [])}
size="w-full"
rounded="xl"
<WrapperModal close={close}>
<div className="flex flex-col gap-2">
<h2 className="text-xl font-medium">{t('modals.shareConv.label')}</h2>
<p className="text-sm">{t('modals.shareConv.note')}</p>
<div className="flex items-center justify-between">
<span className="text-lg">{t('modals.shareConv.option')}</span>
<label className=" cursor-pointer select-none items-center">
<div className="relative">
<input
type="checkbox"
checked={allowPrompt}
onChange={togglePromptPermission}
className="sr-only"
/>
<div
className={`box block h-8 w-14 rounded-full border border-purple-30 ${
allowPrompt
? 'bg-purple-30 dark:bg-purple-30'
: 'dark:bg-transparent'
}`}
></div>
<div
className={`absolute left-1 top-1 flex h-6 w-6 items-center justify-center rounded-full transition ${
allowPrompt ? 'translate-x-full bg-silver' : 'bg-purple-30'
}`}
></div>
</div>
)}
<div className="flex items-baseline justify-between gap-2">
<span className="no-scrollbar w-full overflow-x-auto whitespace-nowrap rounded-full border-2 py-3 px-4">
{`${domain}/share/${identifier ?? '....'}`}
</span>
{status === 'fetched' ? (
<button
className="my-1 h-10 w-28 rounded-full border border-solid bg-purple-30 p-2 text-sm text-white hover:bg-[#6F3FD1]"
onClick={() => handleCopyKey(`${domain}/share/${identifier}`)}
>
{isCopied
? t('modals.saveKey.copied')
: t('modals.saveKey.copy')}
</button>
) : (
<button
className="my-1 flex h-10 w-28 items-center justify-evenly rounded-full border border-solid bg-purple-30 p-2 text-center text-sm font-normal text-white hover:bg-[#6F3FD1]"
onClick={() => {
shareCoversationPublicly(allowPrompt);
}}
>
{t('modals.shareConv.create')}
{status === 'loading' && (
<img
src={Spinner}
className="inline animate-spin cursor-pointer bg-transparent filter dark:invert"
></img>
)}
</button>
)}
</label>
</div>
{allowPrompt && (
<div className="my-4">
<Dropdown
placeholder={t('modals.createAPIKey.sourceDoc')}
selectedValue={sourcePath}
onSelect={(selection: { label: string; value: string }) =>
setSourcePath(selection)
}
options={extractDocPaths(sourceDocs ?? [])}
size="w-full"
rounded="xl"
/>
</div>
)}
<div className="flex items-baseline justify-between gap-2">
<span className="no-scrollbar w-full overflow-x-auto whitespace-nowrap rounded-full border-2 py-3 px-4">
{`${domain}/share/${identifier ?? '....'}`}
</span>
{status === 'fetched' ? (
<button
className="my-1 h-10 w-28 rounded-full border border-solid bg-purple-30 p-2 text-sm text-white hover:bg-[#6F3FD1]"
onClick={() => handleCopyKey(`${domain}/share/${identifier}`)}
>
{isCopied ? t('modals.saveKey.copied') : t('modals.saveKey.copy')}
</button>
) : (
<button
className="my-1 flex h-10 w-28 items-center justify-evenly rounded-full border border-solid bg-purple-30 p-2 text-center text-sm font-normal text-white hover:bg-[#6F3FD1]"
onClick={() => {
shareCoversationPublicly(allowPrompt);
}}
>
{t('modals.shareConv.create')}
{status === 'loading' && (
<img
src={Spinner}
className="inline animate-spin cursor-pointer bg-transparent filter dark:invert"
></img>
)}
</button>
)}
</div>
</div>
</div>
</WrapperModal>
);
};

View File

@@ -0,0 +1,55 @@
import React, { useEffect, useRef } from 'react';
import { WrapperModalProps } from './types';
import Exit from '../assets/exit.svg';
const WrapperModal: React.FC<WrapperModalProps> = ({
children,
close,
isPerformingTask,
}) => {
const modalRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (isPerformingTask) return;
const handleClickOutside = (event: MouseEvent) => {
if (
modalRef.current &&
!modalRef.current.contains(event.target as Node)
) {
close();
}
};
const handleEscapePress = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
close();
}
};
document.addEventListener('mousedown', handleClickOutside);
document.addEventListener('keydown', handleEscapePress);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
document.removeEventListener('keydown', handleEscapePress);
};
}, [close]);
return (
<div className="fixed top-0 left-0 z-30 flex h-screen w-screen items-center justify-center bg-gray-alpha bg-opacity-50">
<div
ref={modalRef}
className="relative w-11/12 rounded-2xl bg-white p-10 dark:bg-outer-space sm:w-[512px]"
>
{!isPerformingTask && (
<button className="absolute top-3 right-4 m-2 w-3" onClick={close}>
<img className="filter dark:invert" src={Exit} />
</button>
)}
{children}
</div>
</div>
);
};
export default WrapperModal;

View File

@@ -0,0 +1,5 @@
export type WrapperModalProps = {
children?: React.ReactNode;
isPerformingTask?: boolean;
close: () => void;
};

View File

@@ -255,9 +255,9 @@ const Documents: React.FC<DocumentsProps> = ({
<div className="w-full h-full bg-transparent flex flex-col items-center justify-center p-8">
{/* Your Upload component */}
<Upload
modalState={modalState}
setModalState={setModalState}
isOnboarding={isOnboarding}
close={() => setModalState('INACTIVE')}
/>
</div>
</div>

View File

@@ -4,7 +4,6 @@ import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import userService from '../api/services/userService';
import Exit from '../assets/exit.svg';
import ArrowLeft from '../assets/arrow-left.svg';
import FileUpload from '../assets/file_upload.svg';
import WebsiteCollect from '../assets/website_collect.svg';
@@ -17,15 +16,16 @@ import {
setSourceDocs,
selectSourceDocs,
} from '../preferences/preferenceSlice';
import WrapperModal from '../modals/WrapperModal';
function Upload({
modalState,
setModalState,
isOnboarding,
close,
}: {
modalState: ActiveState;
setModalState: (state: ActiveState) => void;
isOnboarding: boolean;
close: () => void;
}) {
const [docName, setDocName] = useState('');
const [urlName, setUrlName] = useState('');
@@ -600,7 +600,30 @@ function Upload({
) : (
<button
onClick={uploadRemote}
className={`ml-2 cursor-pointer rounded-3xl bg-purple-30 py-2 px-6 text-sm text-white hover:bg-[#6F3FD1]`}
className={`ml-2 cursor-pointer rounded-3xl bg-purple-30 py-2 px-6 text-sm text-white hover:bg-[#6F3FD1] ${
urlName.trim().length === 0 ||
url.trim().length === 0 ||
(urlType.label === 'Reddit' &&
(redditData.client_id.length === 0 ||
redditData.client_secret.length === 0 ||
redditData.user_agent.length === 0 ||
redditData.search_queries.length === 0 ||
redditData.number_posts === 0)) ||
(urlType.label === 'GitHub' && repoUrl.trim().length === 0)
? 'bg-opacity-80 text-opacity-80'
: ''
}`}
disabled={
urlName.trim().length === 0 ||
url.trim().length === 0 ||
(urlType.label === 'Reddit' &&
(redditData.client_id.length === 0 ||
redditData.client_secret.length === 0 ||
redditData.user_agent.length === 0 ||
redditData.search_queries.length === 0 ||
redditData.number_posts === 0)) ||
(urlType.label === 'GitHub' && repoUrl.trim().length === 0)
}
>
{t('modals.uploadDoc.train')}
</button>
@@ -626,28 +649,18 @@ function Upload({
}
return (
<article
className={`${
modalState === 'ACTIVE' ? 'visible' : 'hidden'
} absolute z-30 bg-gray-alpha flex items-center justify-center h-[calc(100vh-4rem)] md:h-screen w-full`}
<WrapperModal
isPerformingTask={progress !== undefined && progress.percentage < 100}
close={() => {
close();
setDocName('');
setfiles([]);
setModalState('INACTIVE');
setActiveTab(null);
}}
>
<article className="relative mx-auto flex w-[90vw] max-w-lg flex-col gap-4 rounded-lg bg-white p-6 shadow-lg dark:bg-outer-space h-fit-content">
{!isOnboarding && !progress && (
<button
className="absolute top-4 right-4 m-1 w-3"
onClick={() => {
setDocName('');
setfiles([]);
setModalState('INACTIVE');
setActiveTab(null);
}}
>
<img className="filter dark:invert" src={Exit} />
</button>
)}
{view}
</article>
</article>
{view}
</WrapperModal>
);
}