This commit is contained in:
GH Action - Upstream Sync
2024-11-21 01:23:51 +00:00
8 changed files with 287 additions and 239 deletions

View File

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

View File

@@ -1,6 +1,6 @@
import Exit from '../assets/exit.svg';
import { ActiveState } from '../models/misc'; import { ActiveState } from '../models/misc';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import WrapperModal from './WrapperModal';
function ConfirmationModal({ function ConfirmationModal({
message, message,
modalState, modalState,
@@ -20,49 +20,43 @@ function ConfirmationModal({
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<article <>
className={`${ {modalState === 'ACTIVE' && (
modalState === 'ACTIVE' ? 'visible' : 'hidden' <WrapperModal
} fixed top-0 left-0 z-30 h-screen w-screen bg-gray-alpha`} close={() => {
> setModalState('INACTIVE');
<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"> handleCancel && handleCancel();
<div className="relative"> }}
<button >
className="absolute top-3 right-4 m-2 w-3" <div className="relative">
onClick={() => { <div className="p-8">
setModalState('INACTIVE'); <p className="font-base mb-1 w-[90%] text-lg text-jet dark:text-bright-gray">
handleCancel && handleCancel(); {message}
}} </p>
> <div>
<img className="filter dark:invert" src={Exit} /> <div className="mt-6 flex flex-row-reverse gap-1">
</button> <button
<div className="p-8"> onClick={handleSubmit}
<p className="font-base mb-1 w-[90%] text-lg text-jet dark:text-bright-gray"> className="rounded-3xl bg-purple-30 px-5 py-2 text-sm text-white transition-all hover:bg-[#6F3FD1]"
{message} >
</p> {submitLabel}
<div> </button>
<div className="mt-6 flex flex-row-reverse gap-1"> <button
<button onClick={() => {
onClick={handleSubmit} setModalState('INACTIVE');
className="rounded-3xl bg-purple-30 px-5 py-2 text-sm text-white transition-all hover:bg-[#6F3FD1]" handleCancel && handleCancel();
> }}
{submitLabel} 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"
</button> >
<button {cancelLabel ? cancelLabel : t('cancel')}
onClick={() => { </button>
setModalState('INACTIVE'); </div>
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> </div>
</div> </WrapperModal>
</article> )}
</article> </>
); );
} }

View File

@@ -3,11 +3,11 @@ import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import userService from '../api/services/userService'; import userService from '../api/services/userService';
import Exit from '../assets/exit.svg';
import Dropdown from '../components/Dropdown'; import Dropdown from '../components/Dropdown';
import Input from '../components/Input'; import Input from '../components/Input';
import { CreateAPIKeyModalProps, Doc } from '../models/misc'; import { CreateAPIKeyModalProps, Doc } from '../models/misc';
import { selectSourceDocs } from '../preferences/preferenceSlice'; import { selectSourceDocs } from '../preferences/preferenceSlice';
import WrapperModal from './WrapperModal';
const embeddingsName = const embeddingsName =
import.meta.env.VITE_EMBEDDINGS_NAME || import.meta.env.VITE_EMBEDDINGS_NAME ||
@@ -73,91 +73,82 @@ export default function CreateAPIKeyModal({
handleFetchPrompts(); handleFetchPrompts();
}, []); }, []);
return ( 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"> <WrapperModal close={close}>
<div className="relative w-11/12 rounded-2xl bg-white p-10 dark:bg-outer-space sm:w-[512px]"> <div className="mb-6">
<button className="absolute top-3 right-4 m-2 w-3" onClick={close}> <span className="text-xl text-jet dark:text-bright-gray">
<img className="filter dark:invert" src={Exit} /> {t('modals.createAPIKey.label')}
</button> </span>
<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>
</div> </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 Dropdown from '../components/Dropdown';
import { Doc } from '../models/misc'; import { Doc } from '../models/misc';
import Spinner from '../assets/spinner.svg'; 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 apiHost = import.meta.env.VITE_API_HOST || 'https://docsapi.arc53.com';
const embeddingsName = const embeddingsName =
import.meta.env.VITE_EMBEDDINGS_NAME || import.meta.env.VITE_EMBEDDINGS_NAME ||
@@ -19,6 +18,7 @@ const embeddingsName =
type StatusType = 'loading' | 'idle' | 'fetched' | 'failed'; type StatusType = 'loading' | 'idle' | 'fetched' | 'failed';
import conversationService from '../api/services/conversationService'; import conversationService from '../api/services/conversationService';
import WrapperModal from './WrapperModal';
export const ShareConversationModal = ({ export const ShareConversationModal = ({
close, close,
@@ -99,85 +99,78 @@ export const ShareConversationModal = ({
}; };
return ( 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"> <WrapperModal close={close}>
<div className="relative w-11/12 rounded-2xl bg-white p-10 dark:bg-outer-space sm:w-[512px]"> <div className="flex flex-col gap-2">
<button className="absolute top-3 right-4 m-2 w-3" onClick={close}> <h2 className="text-xl font-medium">{t('modals.shareConv.label')}</h2>
<img className="filter dark:invert" src={Exit} /> <p className="text-sm">{t('modals.shareConv.note')}</p>
</button> <div className="flex items-center justify-between">
<div className="flex flex-col gap-2"> <span className="text-lg">{t('modals.shareConv.option')}</span>
<h2 className="text-xl font-medium">{t('modals.shareConv.label')}</h2> <label className=" cursor-pointer select-none items-center">
<p className="text-sm">{t('modals.shareConv.note')}</p> <div className="relative">
<div className="flex items-center justify-between"> <input
<span className="text-lg">{t('modals.shareConv.option')}</span> type="checkbox"
<label className=" cursor-pointer select-none items-center"> checked={allowPrompt}
<div className="relative"> onChange={togglePromptPermission}
<input className="sr-only"
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"
/> />
<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>
)} </label>
<div className="flex items-baseline justify-between gap-2"> </div>
<span className="no-scrollbar w-full overflow-x-auto whitespace-nowrap rounded-full border-2 py-3 px-4"> {allowPrompt && (
{`${domain}/share/${identifier ?? '....'}`} <div className="my-4">
</span> <Dropdown
{status === 'fetched' ? ( placeholder={t('modals.createAPIKey.sourceDoc')}
<button selectedValue={sourcePath}
className="my-1 h-10 w-28 rounded-full border border-solid bg-purple-30 p-2 text-sm text-white hover:bg-[#6F3FD1]" onSelect={(selection: { label: string; value: string }) =>
onClick={() => handleCopyKey(`${domain}/share/${identifier}`)} setSourcePath(selection)
> }
{isCopied options={extractDocPaths(sourceDocs ?? [])}
? t('modals.saveKey.copied') size="w-full"
: t('modals.saveKey.copy')} rounded="xl"
</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 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> </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"> <div className="w-full h-full bg-transparent flex flex-col items-center justify-center p-8">
{/* Your Upload component */} {/* Your Upload component */}
<Upload <Upload
modalState={modalState}
setModalState={setModalState} setModalState={setModalState}
isOnboarding={isOnboarding} isOnboarding={isOnboarding}
close={() => setModalState('INACTIVE')}
/> />
</div> </div>
</div> </div>

View File

@@ -4,7 +4,6 @@ import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import userService from '../api/services/userService'; import userService from '../api/services/userService';
import Exit from '../assets/exit.svg';
import ArrowLeft from '../assets/arrow-left.svg'; import ArrowLeft from '../assets/arrow-left.svg';
import FileUpload from '../assets/file_upload.svg'; import FileUpload from '../assets/file_upload.svg';
import WebsiteCollect from '../assets/website_collect.svg'; import WebsiteCollect from '../assets/website_collect.svg';
@@ -17,15 +16,16 @@ import {
setSourceDocs, setSourceDocs,
selectSourceDocs, selectSourceDocs,
} from '../preferences/preferenceSlice'; } from '../preferences/preferenceSlice';
import WrapperModal from '../modals/WrapperModal';
function Upload({ function Upload({
modalState,
setModalState, setModalState,
isOnboarding, isOnboarding,
close,
}: { }: {
modalState: ActiveState;
setModalState: (state: ActiveState) => void; setModalState: (state: ActiveState) => void;
isOnboarding: boolean; isOnboarding: boolean;
close: () => void;
}) { }) {
const [docName, setDocName] = useState(''); const [docName, setDocName] = useState('');
const [urlName, setUrlName] = useState(''); const [urlName, setUrlName] = useState('');
@@ -603,7 +603,30 @@ function Upload({
) : ( ) : (
<button <button
onClick={uploadRemote} 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')} {t('modals.uploadDoc.train')}
</button> </button>
@@ -629,28 +652,18 @@ function Upload({
} }
return ( return (
<article <WrapperModal
className={`${ isPerformingTask={progress !== undefined && progress.percentage < 100}
modalState === 'ACTIVE' ? 'visible' : 'hidden' close={() => {
} absolute z-30 bg-gray-alpha flex items-center justify-center h-[calc(100vh-4rem)] md:h-screen w-full`} 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"> {view}
{!isOnboarding && !progress && ( </WrapperModal>
<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>
); );
} }