mirror of
https://github.com/arc53/DocsGPT.git
synced 2025-11-29 08:33:20 +00:00
Merge pull request #1641 from ManishMadan2882/main
Refactor: Apply base modal for UI consitency
This commit is contained in:
@@ -37,7 +37,7 @@ export default function Hero({
|
||||
<Fragment key={key}>
|
||||
<button
|
||||
onClick={() => handleQuestion({ question: demo.query })}
|
||||
className="w-full rounded-full border border-silver px-6 py-4 text-left hover:border-gray-4000 dark:hover:border-gray-3000 xl:min-w-[24vw] bg-white dark:bg-raisin-black focus:outline-none focus:ring-2 focus:ring-purple-taupe"
|
||||
className="w-full rounded-full border border-silver px-6 py-4 text-left hover:border-gray-4000 dark:hover:border-gray-3000 xl:min-w-[24vw] bg-white dark:bg-raisin-black focus:outline-none"
|
||||
>
|
||||
<p className="mb-1 font-semibold text-black-1000 dark:text-bright-gray">
|
||||
{demo.header}
|
||||
|
||||
@@ -119,7 +119,7 @@ function Dropdown({
|
||||
{options.map((option: any, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="hover:eerie-black flex cursor-pointer items-center justify-between hover:bg-gray-100 dark:hover:bg-purple-taupe"
|
||||
className="hover:eerie-black flex cursor-pointer items-center justify-between hover:bg-gray-100 dark:hover:bg-[#545561]"
|
||||
>
|
||||
<span
|
||||
onClick={() => {
|
||||
|
||||
@@ -47,7 +47,7 @@ const Input = ({
|
||||
</input>
|
||||
{label && (
|
||||
<div className="absolute -top-2 left-2">
|
||||
<span className="bg-white px-2 text-xs text-gray-4000 dark:bg-outer-space dark:text-silver flex items-center">
|
||||
<span className="bg-white px-2 text-xs text-gray-4000 dark:bg-[#26272E] dark:text-silver flex items-center">
|
||||
{label}
|
||||
{required && (
|
||||
<span className="text-[#D30000] dark:text-[#D42626] ml-0.5">
|
||||
|
||||
@@ -83,7 +83,7 @@ function SourceDropdown({
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className="flex cursor-pointer items-center justify-between hover:bg-gray-100 dark:text-bright-gray dark:hover:bg-purple-taupe"
|
||||
className="flex cursor-pointer items-center justify-between hover:bg-gray-100 dark:text-bright-gray dark:hover:bg-[#545561]"
|
||||
onClick={() => {
|
||||
dispatch(setSelectedDocs(option));
|
||||
setIsDocsListOpen(false);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import Exit from '../assets/exit.svg';
|
||||
import WrapperModal from './WrapperModal';
|
||||
import Input from '../components/Input';
|
||||
import { ActiveState } from '../models/misc';
|
||||
|
||||
@@ -35,67 +35,53 @@ export default function AddActionModal({
|
||||
setModalState('INACTIVE');
|
||||
};
|
||||
|
||||
// Only render when modal is active
|
||||
if (modalState !== 'ACTIVE') return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`${
|
||||
modalState === 'ACTIVE' ? 'visible' : 'hidden'
|
||||
} fixed top-0 left-0 z-30 h-screen w-screen bg-gray-alpha flex items-center justify-center`}
|
||||
<WrapperModal
|
||||
close={() => setModalState('INACTIVE')}
|
||||
className="sm:w-[512px]"
|
||||
>
|
||||
<article className="flex w-11/12 sm:w-[512px] flex-col gap-4 rounded-2xl bg-white shadow-lg dark:bg-[#26272E]">
|
||||
<div className="relative">
|
||||
<button
|
||||
className="absolute top-3 right-4 m-2 w-3"
|
||||
onClick={() => {
|
||||
setModalState('INACTIVE');
|
||||
}}
|
||||
>
|
||||
<img className="filter dark:invert" src={Exit} />
|
||||
</button>
|
||||
<div className="p-6">
|
||||
<h2 className="font-semibold text-xl text-jet dark:text-bright-gray px-3">
|
||||
New Action
|
||||
</h2>
|
||||
<div className="mt-6 relative px-3">
|
||||
<span className="z-10 absolute left-5 -top-2 bg-white px-2 text-xs text-gray-4000 dark:bg-[#26272E] dark:text-silver">
|
||||
Action Name
|
||||
</span>
|
||||
<Input
|
||||
type="text"
|
||||
value={actionName}
|
||||
onChange={(e) => setActionName(e.target.value)}
|
||||
borderVariant="thin"
|
||||
placeholder={'Enter name'}
|
||||
/>
|
||||
<p className="mt-1 text-gray-500 text-xs">
|
||||
Use only letters, numbers, underscores, and hyphens (e.g.,
|
||||
`get_user_data`, `send-report`).
|
||||
</p>
|
||||
{functionNameError && (
|
||||
<p className="mt-1 text-red-500 text-xs">
|
||||
Invalid function name format. Use only letters, numbers,
|
||||
underscores, and hyphens.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-8 flex flex-row-reverse gap-1 px-3">
|
||||
<button
|
||||
onClick={handleAddAction}
|
||||
className="rounded-3xl bg-purple-30 px-5 py-2 text-sm text-white transition-all hover:bg-[#6F3FD1]"
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
setModalState('INACTIVE');
|
||||
}}
|
||||
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"
|
||||
>
|
||||
{t('modals.configTool.closeButton')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="font-semibold text-xl text-jet dark:text-bright-gray px-3">
|
||||
New Action
|
||||
</h2>
|
||||
<div className="mt-6 px-3">
|
||||
<Input
|
||||
type="text"
|
||||
value={actionName}
|
||||
onChange={(e) => setActionName(e.target.value)}
|
||||
borderVariant="thin"
|
||||
placeholder="Enter name"
|
||||
label="Action Name"
|
||||
/>
|
||||
<p className="mt-1 text-gray-500 text-xs">
|
||||
Use only letters, numbers, underscores, and hyphens (e.g.,
|
||||
`get_user_data`, `send-report`).
|
||||
</p>
|
||||
{functionNameError && (
|
||||
<p className="mt-1 text-red-500 text-xs">
|
||||
Invalid function name format. Use only letters, numbers,
|
||||
underscores, and hyphens.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
<div className="mt-8 flex flex-row-reverse gap-1 px-3">
|
||||
<button
|
||||
onClick={handleAddAction}
|
||||
className="rounded-3xl bg-purple-30 px-5 py-2 text-sm text-white transition-all hover:bg-[#6F3FD1]"
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setModalState('INACTIVE')}
|
||||
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"
|
||||
>
|
||||
{t('modals.configTool.closeButton')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</WrapperModal>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@ import React, { useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import userService from '../api/services/userService';
|
||||
import Exit from '../assets/exit.svg';
|
||||
import { useOutsideAlerter } from '../hooks';
|
||||
import { ActiveState } from '../models/misc';
|
||||
import ConfigToolModal from './ConfigToolModal';
|
||||
import { AvailableToolType } from './types';
|
||||
import WrapperComponent from './WrapperModal';
|
||||
|
||||
export default function AddToolModal({
|
||||
message,
|
||||
@@ -88,28 +88,12 @@ export default function AddToolModal({
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={`${
|
||||
modalState === 'ACTIVE' ? 'visible' : 'hidden'
|
||||
} fixed top-0 left-0 z-30 h-screen w-screen bg-gray-alpha flex items-center justify-center`}
|
||||
>
|
||||
<article
|
||||
ref={modalRef}
|
||||
className="flex h-[85vh] w-[90vw] md:w-[75vw] flex-col gap-4 rounded-2xl bg-[#FBFBFB] shadow-lg dark:bg-[#26272E]"
|
||||
{modalState === 'ACTIVE' && (
|
||||
<WrapperComponent
|
||||
close={() => setModalState('INACTIVE')}
|
||||
className="h-[85vh] w-[90vw] md:w-[75vw]"
|
||||
>
|
||||
<div className="relative">
|
||||
<button
|
||||
className="absolute top-3 right-4 m-2 w-3"
|
||||
onClick={() => {
|
||||
setModalState('INACTIVE');
|
||||
}}
|
||||
>
|
||||
<img
|
||||
className="filter dark:invert"
|
||||
src={Exit}
|
||||
alt={t('cancel')}
|
||||
/>
|
||||
</button>
|
||||
<div className="flex flex-col gap-4 h-full">
|
||||
<div className="p-6">
|
||||
<h2 className="font-semibold text-xl text-jet dark:text-bright-gray px-3">
|
||||
{t('settings.tools.selectToolSetup')}
|
||||
@@ -153,8 +137,8 @@ export default function AddToolModal({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</WrapperComponent>
|
||||
)}
|
||||
<ConfigToolModal
|
||||
modalState={configModalState}
|
||||
setModalState={setConfigModalState}
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import Exit from '../assets/exit.svg';
|
||||
import WrapperModal from './WrapperModal';
|
||||
import Input from '../components/Input';
|
||||
import { ActiveState } from '../models/misc';
|
||||
import { AvailableToolType } from './types';
|
||||
import userService from '../api/services/userService';
|
||||
|
||||
interface ConfigToolModalProps {
|
||||
modalState: ActiveState;
|
||||
setModalState: (state: ActiveState) => void;
|
||||
tool: AvailableToolType | null;
|
||||
getUserTools: () => void;
|
||||
}
|
||||
|
||||
export default function ConfigToolModal({
|
||||
modalState,
|
||||
setModalState,
|
||||
tool,
|
||||
getUserTools,
|
||||
}: {
|
||||
modalState: ActiveState;
|
||||
setModalState: (state: ActiveState) => void;
|
||||
tool: AvailableToolType | null;
|
||||
getUserTools: () => void;
|
||||
}) {
|
||||
}: ConfigToolModalProps) {
|
||||
const { t } = useTranslation();
|
||||
const [authKey, setAuthKey] = React.useState<string>('');
|
||||
|
||||
@@ -36,63 +38,47 @@ export default function ConfigToolModal({
|
||||
getUserTools();
|
||||
});
|
||||
};
|
||||
|
||||
// Only render when modal is active
|
||||
if (modalState !== 'ACTIVE') return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`${
|
||||
modalState === 'ACTIVE' ? 'visible' : 'hidden'
|
||||
} fixed top-0 left-0 z-30 h-screen w-screen bg-gray-alpha flex items-center justify-center`}
|
||||
>
|
||||
<article className="flex w-11/12 sm:w-[512px] flex-col gap-4 rounded-2xl bg-white shadow-lg dark:bg-[#26272E]">
|
||||
<div className="relative">
|
||||
<button
|
||||
className="absolute top-3 right-4 m-2 w-3"
|
||||
onClick={() => {
|
||||
setModalState('INACTIVE');
|
||||
}}
|
||||
>
|
||||
<img className="filter dark:invert" src={Exit} />
|
||||
</button>
|
||||
<div className="p-6">
|
||||
<h2 className="font-semibold text-xl text-jet dark:text-bright-gray px-3">
|
||||
{t('modals.configTool.title')}
|
||||
</h2>
|
||||
<p className="mt-5 text-sm text-gray-600 dark:text-gray-400 px-3">
|
||||
{t('modals.configTool.type')}:{' '}
|
||||
<span className="font-semibold">{tool?.name} </span>
|
||||
</p>
|
||||
<div className="mt-6 relative px-3">
|
||||
<span className="absolute left-5 -top-2 bg-white px-2 text-xs text-gray-4000 dark:bg-[#26272E] dark:text-silver">
|
||||
{t('modals.configTool.apiKeyLabel')}
|
||||
</span>
|
||||
<Input
|
||||
type="text"
|
||||
value={authKey}
|
||||
onChange={(e) => setAuthKey(e.target.value)}
|
||||
borderVariant="thin"
|
||||
placeholder={t('modals.configTool.apiKeyPlaceholder')}
|
||||
></Input>
|
||||
</div>
|
||||
<div className="mt-8 flex flex-row-reverse gap-1 px-3">
|
||||
<button
|
||||
onClick={() => {
|
||||
handleAddTool(tool as AvailableToolType);
|
||||
}}
|
||||
className="rounded-3xl bg-purple-30 px-5 py-2 text-sm text-white transition-all hover:bg-[#6F3FD1]"
|
||||
>
|
||||
{t('modals.configTool.addButton')}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
setModalState('INACTIVE');
|
||||
}}
|
||||
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"
|
||||
>
|
||||
{t('modals.configTool.closeButton')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<WrapperModal close={() => setModalState('INACTIVE')}>
|
||||
<div>
|
||||
<h2 className="font-semibold text-xl text-jet dark:text-bright-gray px-3">
|
||||
{t('modals.configTool.title')}
|
||||
</h2>
|
||||
<p className="mt-5 text-sm text-gray-600 dark:text-gray-400 px-3">
|
||||
{t('modals.configTool.type')}:{' '}
|
||||
<span className="font-semibold">{tool?.name}</span>
|
||||
</p>
|
||||
<div className="mt-6 px-3">
|
||||
<Input
|
||||
type="text"
|
||||
value={authKey}
|
||||
onChange={(e) => setAuthKey(e.target.value)}
|
||||
borderVariant="thin"
|
||||
placeholder={t('modals.configTool.apiKeyPlaceholder')}
|
||||
label={t('modals.configTool.apiKeyLabel')}
|
||||
/>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
<div className="mt-8 flex flex-row-reverse gap-1 px-3">
|
||||
<button
|
||||
onClick={() => {
|
||||
tool && handleAddTool(tool);
|
||||
}}
|
||||
className="rounded-3xl bg-purple-30 px-5 py-2 text-sm text-white transition-all hover:bg-[#6F3FD1]"
|
||||
>
|
||||
{t('modals.configTool.addButton')}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setModalState('INACTIVE')}
|
||||
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"
|
||||
>
|
||||
{t('modals.configTool.closeButton')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</WrapperModal>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -73,21 +73,20 @@ export default function CreateAPIKeyModal({
|
||||
handleFetchPrompts();
|
||||
}, []);
|
||||
return (
|
||||
<WrapperModal close={close}>
|
||||
<WrapperModal close={close} className="p-4">
|
||||
<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}
|
||||
label={t('modals.createAPIKey.apiKeyName')}
|
||||
onChange={(e) => setAPIKeyName(e.target.value)}
|
||||
borderVariant="thin"
|
||||
></Input>
|
||||
</div>
|
||||
<div className="my-4">
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import Exit from '../assets/exit.svg';
|
||||
import { SaveAPIKeyModalProps } from '../models/misc';
|
||||
import WrapperModal from './WrapperModal';
|
||||
|
||||
export default function SaveAPIKeyModal({
|
||||
apiKey,
|
||||
@@ -15,38 +15,37 @@ export default function SaveAPIKeyModal({
|
||||
navigator.clipboard.writeText(apiKey);
|
||||
setIsCopied(true);
|
||||
};
|
||||
|
||||
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-3xl bg-white px-6 py-8 dark:bg-outer-space dark:text-bright-gray sm:w-[512px]">
|
||||
<button className="absolute top-3 right-4 m-2 w-3" onClick={close}>
|
||||
<img className="filter dark:invert" src={Exit} />
|
||||
</button>
|
||||
<h1 className="my-0 text-xl font-medium">
|
||||
{' '}
|
||||
{t('modals.saveKey.note')}
|
||||
</h1>
|
||||
<h3 className="text-sm font-normal text-outer-space">
|
||||
{t('modals.saveKey.disclaimer')}
|
||||
</h3>
|
||||
<div className="flex justify-between py-2">
|
||||
<div>
|
||||
<h2 className="text-base font-semibold">API Key</h2>
|
||||
<span className="text-sm font-normal leading-7 ">{apiKey}</span>
|
||||
</div>
|
||||
<button
|
||||
className="my-1 h-10 w-20 rounded-full border border-solid border-purple-30 p-2 text-sm text-purple-30 hover:bg-purple-30 hover:text-white"
|
||||
onClick={handleCopyKey}
|
||||
>
|
||||
{isCopied ? t('modals.saveKey.copied') : t('modals.saveKey.copy')}
|
||||
</button>
|
||||
<WrapperModal close={close}>
|
||||
<h1 className="my-0 text-xl font-medium text-jet dark:text-bright-gray">
|
||||
{t('modals.saveKey.note')}
|
||||
</h1>
|
||||
<h3 className="text-sm font-normal text-outer-space dark:text-silver">
|
||||
{t('modals.saveKey.disclaimer')}
|
||||
</h3>
|
||||
<div className="flex justify-between py-2">
|
||||
<div>
|
||||
<h2 className="text-base font-semibold text-jet dark:text-bright-gray">
|
||||
API Key
|
||||
</h2>
|
||||
<span className="text-sm font-normal leading-7 text-jet dark:text-bright-gray">
|
||||
{apiKey}
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={close}
|
||||
className="rounded-full bg-philippine-yellow px-4 py-3 font-medium text-black hover:bg-[#E6B91A]"
|
||||
className="my-1 h-10 w-20 rounded-full border border-solid border-purple-30 p-2 text-sm text-purple-30 hover:bg-purple-30 hover:text-white"
|
||||
onClick={handleCopyKey}
|
||||
>
|
||||
{t('modals.saveKey.confirm')}
|
||||
{isCopied ? t('modals.saveKey.copied') : t('modals.saveKey.copy')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={close}
|
||||
className="rounded-full bg-philippine-yellow px-4 py-3 font-medium text-black hover:bg-[#E6B91A]"
|
||||
>
|
||||
{t('modals.saveKey.confirm')}
|
||||
</button>
|
||||
</WrapperModal>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,21 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
|
||||
import Exit from '../assets/exit.svg';
|
||||
import { WrapperModalPropsType } from './types';
|
||||
|
||||
interface WrapperModalPropsType {
|
||||
children: React.ReactNode;
|
||||
close: () => void;
|
||||
isPerformingTask?: boolean;
|
||||
className?: string;
|
||||
contentClassName?: string;
|
||||
}
|
||||
|
||||
export default function WrapperModal({
|
||||
children,
|
||||
close,
|
||||
isPerformingTask,
|
||||
isPerformingTask = false,
|
||||
className = '', // Default width, but can be overridden
|
||||
contentClassName = '', // Default padding, but can be overridden
|
||||
}: WrapperModalPropsType) {
|
||||
const modalRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
@@ -40,17 +49,17 @@ export default function WrapperModal({
|
||||
<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 dark:bg-outer-space sm:w-[512px] p-8"
|
||||
className={`relative w-11/12 sm:w-[512px] p-8 rounded-2xl bg-white dark:bg-[#26272E] ${className}`}
|
||||
>
|
||||
{!isPerformingTask && (
|
||||
<button
|
||||
className="absolute top-3 right-4 m-2 w-3 z-50"
|
||||
onClick={close}
|
||||
>
|
||||
<img className="filter dark:invert" src={Exit} />
|
||||
<img className="filter dark:invert" src={Exit} alt="Close" />
|
||||
</button>
|
||||
)}
|
||||
{children}
|
||||
<div className={`${contentClassName}`}>{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
interface ModalProps {
|
||||
handleSubmit: () => void;
|
||||
isCancellable: boolean;
|
||||
handleCancel?: () => void;
|
||||
render: () => JSX.Element;
|
||||
modalState: string;
|
||||
isError: boolean;
|
||||
errorMessage?: string;
|
||||
textDelete?: boolean;
|
||||
}
|
||||
|
||||
const Modal = (props: ModalProps) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div
|
||||
className={`${
|
||||
props.modalState === 'ACTIVE' ? 'visible' : 'hidden'
|
||||
} absolute z-30 h-screen w-screen bg-gray-alpha`}
|
||||
>
|
||||
{props.render()}
|
||||
<div className=" mx-auto flex w-[90vw] max-w-lg flex-row-reverse rounded-b-lg bg-white pb-5 pr-5 shadow-lg dark:bg-outer-space">
|
||||
<div>
|
||||
<button
|
||||
onClick={() => props.handleSubmit()}
|
||||
className="ml-auto h-10 w-20 rounded-3xl bg-violet-800 text-white transition-all hover:bg-violet-700 dark:text-silver"
|
||||
>
|
||||
{props.textDelete ? 'Delete' : 'Save'}
|
||||
</button>
|
||||
{props.isCancellable && (
|
||||
<button
|
||||
onClick={() => props.handleCancel && props.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"
|
||||
>
|
||||
{t('cancel')}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{props.isError && (
|
||||
<p className="mx-auto mt-2 mr-auto text-sm text-red-500">
|
||||
{props.errorMessage}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Modal;
|
||||
@@ -1,80 +0,0 @@
|
||||
import { useRef, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { ActiveState } from '../models/misc';
|
||||
import { selectApiKey, setApiKey } from './preferenceSlice';
|
||||
import { useMediaQuery, useOutsideAlerter } from './../hooks';
|
||||
import Modal from '../modals';
|
||||
import Input from '../components/Input';
|
||||
|
||||
export default function APIKeyModal({
|
||||
modalState,
|
||||
setModalState,
|
||||
isCancellable = true,
|
||||
}: {
|
||||
modalState: ActiveState;
|
||||
setModalState: (val: ActiveState) => void;
|
||||
isCancellable?: boolean;
|
||||
}) {
|
||||
const dispatch = useDispatch();
|
||||
const apiKey = useSelector(selectApiKey);
|
||||
const [key, setKey] = useState(apiKey);
|
||||
const [isError, setIsError] = useState(false);
|
||||
const modalRef = useRef(null);
|
||||
const { isMobile } = useMediaQuery();
|
||||
|
||||
useOutsideAlerter(modalRef, () => {
|
||||
if (isMobile && modalState === 'ACTIVE') {
|
||||
setModalState('INACTIVE');
|
||||
}
|
||||
}, [modalState]);
|
||||
|
||||
function handleSubmit() {
|
||||
if (key.length <= 1) {
|
||||
setIsError(true);
|
||||
} else {
|
||||
dispatch(setApiKey(key));
|
||||
setModalState('INACTIVE');
|
||||
setIsError(false);
|
||||
}
|
||||
}
|
||||
|
||||
function handleCancel() {
|
||||
setKey(apiKey);
|
||||
setIsError(false);
|
||||
setModalState('INACTIVE');
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
handleCancel={handleCancel}
|
||||
isError={isError}
|
||||
modalState={modalState}
|
||||
isCancellable={isCancellable}
|
||||
handleSubmit={handleSubmit}
|
||||
render={() => {
|
||||
return (
|
||||
<article
|
||||
ref={modalRef}
|
||||
className="mx-auto mt-24 flex w-[90vw] max-w-lg flex-col gap-4 rounded-t-lg bg-white p-6 shadow-lg"
|
||||
>
|
||||
<p className="text-xl text-jet">OpenAI API Key</p>
|
||||
<p className="text-md leading-6 text-gray-500">
|
||||
Before you can start using DocsGPT we need you to provide an API
|
||||
key for llm. Currently, we support only OpenAI but soon many more.
|
||||
You can find it here.
|
||||
</p>
|
||||
<Input
|
||||
type="text"
|
||||
colorVariant="jet"
|
||||
className="h-10 border-b-2 focus:outline-none"
|
||||
value={key}
|
||||
maxLength={100}
|
||||
placeholder="API Key"
|
||||
onChange={(e) => setKey(e.target.value)}
|
||||
></Input>
|
||||
</article>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ActiveState } from '../models/misc';
|
||||
import Exit from '../assets/exit.svg';
|
||||
import Input from '../components/Input';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import WrapperModal from '../modals/WrapperModal';
|
||||
|
||||
function AddPrompt({
|
||||
setModalState,
|
||||
@@ -24,69 +24,52 @@ function AddPrompt({
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<button
|
||||
className="absolute top-3 right-4 m-2 w-3"
|
||||
onClick={() => {
|
||||
setModalState('INACTIVE');
|
||||
setNewPromptName('');
|
||||
setNewPromptContent('');
|
||||
}}
|
||||
aria-label="Close add prompt modal"
|
||||
>
|
||||
<img className="filter dark:invert" src={Exit} alt="Close modal" />
|
||||
</button>
|
||||
<div className="p-8">
|
||||
<p className="mb-1 text-xl text-jet dark:text-bright-gray">
|
||||
{t('modals.prompts.addPrompt')}
|
||||
</p>
|
||||
<p className="mb-7 text-xs text-[#747474] dark:text-[#7F7F82]">
|
||||
{t('modals.prompts.addDescription')}
|
||||
</p>
|
||||
<div>
|
||||
<label htmlFor="new-prompt-name" className="sr-only">
|
||||
Prompt Name
|
||||
</label>
|
||||
<Input
|
||||
placeholder={t('modals.prompts.promptName')}
|
||||
type="text"
|
||||
className="h-10 rounded-lg"
|
||||
value={newPromptName}
|
||||
onChange={(e) => setNewPromptName(e.target.value)}
|
||||
/>
|
||||
<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">
|
||||
{t('modals.prompts.promptName')}
|
||||
</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">
|
||||
{t('modals.prompts.promptText')}
|
||||
</span>
|
||||
</div>
|
||||
<label htmlFor="new-prompt-content" className="sr-only">
|
||||
Prompt Text
|
||||
</label>
|
||||
<textarea
|
||||
id="new-prompt-content"
|
||||
className="h-56 w-full rounded-lg border-2 border-silver px-3 py-2 outline-none dark:border-silver/40 dark:bg-transparent dark:text-white"
|
||||
value={newPromptContent}
|
||||
onChange={(e) => setNewPromptContent(e.target.value)}
|
||||
aria-label="Prompt Text"
|
||||
></textarea>
|
||||
</div>
|
||||
<div className="mt-6 flex flex-row-reverse">
|
||||
<button
|
||||
onClick={handleAddPrompt}
|
||||
className="rounded-3xl bg-purple-30 px-5 py-2 text-sm text-white transition-all hover:opacity-90"
|
||||
disabled={disableSave}
|
||||
title={
|
||||
disableSave && newPromptName ? t('modals.prompts.nameExists') : ''
|
||||
}
|
||||
>
|
||||
{t('modals.prompts.save')}
|
||||
</button>
|
||||
<div>
|
||||
<p className="mb-1 text-xl text-jet dark:text-bright-gray">
|
||||
{t('modals.prompts.addPrompt')}
|
||||
</p>
|
||||
<p className="mb-7 text-xs text-[#747474] dark:text-[#7F7F82]">
|
||||
{t('modals.prompts.addDescription')}
|
||||
</p>
|
||||
<div>
|
||||
<label htmlFor="new-prompt-name" className="sr-only">
|
||||
Prompt Name
|
||||
</label>
|
||||
<Input
|
||||
placeholder={t('modals.prompts.promptName')}
|
||||
type="text"
|
||||
label={t('modals.prompts.promptName')}
|
||||
className="h-10 rounded-lg"
|
||||
value={newPromptName}
|
||||
onChange={(e) => setNewPromptName(e.target.value)}
|
||||
/>
|
||||
<div className="relative top-[7px] left-3">
|
||||
<span className="bg-white px-1 text-xs text-silver dark:bg-[#26272E] dark:text-silver">
|
||||
{t('modals.prompts.promptText')}
|
||||
</span>
|
||||
</div>
|
||||
<label htmlFor="new-prompt-content" className="sr-only">
|
||||
Prompt Text
|
||||
</label>
|
||||
<textarea
|
||||
id="new-prompt-content"
|
||||
className="h-56 w-full rounded-lg border-2 border-silver px-3 py-2 outline-none dark:border-silver/40 dark:bg-transparent dark:text-white"
|
||||
value={newPromptContent}
|
||||
onChange={(e) => setNewPromptContent(e.target.value)}
|
||||
aria-label="Prompt Text"
|
||||
></textarea>
|
||||
</div>
|
||||
<div className="mt-6 flex flex-row-reverse">
|
||||
<button
|
||||
onClick={handleAddPrompt}
|
||||
className="rounded-3xl bg-purple-30 px-5 py-2 text-sm text-white transition-all hover:opacity-90"
|
||||
disabled={disableSave}
|
||||
title={
|
||||
disableSave && newPromptName ? t('modals.prompts.nameExists') : ''
|
||||
}
|
||||
>
|
||||
{t('modals.prompts.save')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -114,16 +97,7 @@ function EditPrompt({
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<button
|
||||
className="absolute top-3 right-4 m-2 w-3"
|
||||
onClick={() => {
|
||||
setModalState('INACTIVE');
|
||||
}}
|
||||
aria-label="Close edit prompt modal"
|
||||
>
|
||||
<img className="filter dark:invert" src={Exit} alt="Close modal" />
|
||||
</button>
|
||||
<div>
|
||||
<div className="p-8">
|
||||
<p className="mb-1 text-xl text-jet dark:text-bright-gray">
|
||||
{t('modals.prompts.editPrompt')}
|
||||
@@ -271,15 +245,19 @@ export default function PromptsModal({
|
||||
} else {
|
||||
view = <></>;
|
||||
}
|
||||
return (
|
||||
<article
|
||||
className={`${
|
||||
modalState === 'ACTIVE' ? 'visible' : 'hidden'
|
||||
} fixed top-0 left-0 z-30 h-screen w-screen bg-gray-alpha`}
|
||||
|
||||
return modalState === 'ACTIVE' ? (
|
||||
<WrapperModal
|
||||
close={() => {
|
||||
setModalState('INACTIVE');
|
||||
if (type === 'ADD') {
|
||||
setNewPromptName('');
|
||||
setNewPromptContent('');
|
||||
}
|
||||
}}
|
||||
className="sm:w-[512px] mt-24"
|
||||
>
|
||||
<article className="mx-auto mt-24 flex w-[90vw] max-w-lg flex-col gap-4 rounded-2xl bg-white shadow-lg dark:bg-outer-space">
|
||||
{view}
|
||||
</article>
|
||||
</article>
|
||||
);
|
||||
{view}
|
||||
</WrapperModal>
|
||||
) : null;
|
||||
}
|
||||
|
||||
@@ -92,8 +92,10 @@ export default function Analytics() {
|
||||
const [loadingMessages, setLoadingMessages] = useLoaderState(true);
|
||||
const [loadingTokens, setLoadingTokens] = useLoaderState(true);
|
||||
const [loadingFeedback, setLoadingFeedback] = useLoaderState(true);
|
||||
const [loadingChatbots, setLoadingChatbots] = useLoaderState(true);
|
||||
|
||||
const fetchChatbots = async () => {
|
||||
setLoadingChatbots(true);
|
||||
try {
|
||||
const response = await userService.getAPIKeys();
|
||||
if (!response.ok) {
|
||||
@@ -103,6 +105,8 @@ export default function Analytics() {
|
||||
setChatbots(chatbots);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
setLoadingChatbots(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -188,37 +192,41 @@ export default function Analytics() {
|
||||
return (
|
||||
<div className="mt-12">
|
||||
<div className="flex flex-col items-start">
|
||||
<div className="flex flex-col gap-3">
|
||||
<p className="font-bold text-jet dark:text-bright-gray">
|
||||
{t('settings.analytics.filterByChatbot')}
|
||||
</p>
|
||||
<Dropdown
|
||||
size="w-[55vw] sm:w-[360px]"
|
||||
options={[
|
||||
...chatbots.map((chatbot) => ({
|
||||
label: chatbot.name,
|
||||
value: chatbot.id,
|
||||
})),
|
||||
{ label: t('settings.analytics.none'), value: '' },
|
||||
]}
|
||||
placeholder={t('settings.analytics.selectChatbot')}
|
||||
onSelect={(chatbot: { label: string; value: string }) => {
|
||||
setSelectedChatbot(
|
||||
chatbots.find((item) => item.id === chatbot.value),
|
||||
);
|
||||
}}
|
||||
selectedValue={
|
||||
(selectedChatbot && {
|
||||
label: selectedChatbot.name,
|
||||
value: selectedChatbot.id,
|
||||
}) ||
|
||||
null
|
||||
}
|
||||
rounded="3xl"
|
||||
border="border"
|
||||
borderColor="gray-700"
|
||||
/>
|
||||
</div>
|
||||
{loadingChatbots ? (
|
||||
<SkeletonLoader component="dropdown" />
|
||||
) : (
|
||||
<div className="flex flex-col gap-3">
|
||||
<p className="font-bold text-jet dark:text-bright-gray">
|
||||
{t('settings.analytics.filterByChatbot')}
|
||||
</p>
|
||||
<Dropdown
|
||||
size="w-[55vw] sm:w-[360px]"
|
||||
options={[
|
||||
...chatbots.map((chatbot) => ({
|
||||
label: chatbot.name,
|
||||
value: chatbot.id,
|
||||
})),
|
||||
{ label: t('settings.analytics.none'), value: '' },
|
||||
]}
|
||||
placeholder={t('settings.analytics.selectChatbot')}
|
||||
onSelect={(chatbot: { label: string; value: string }) => {
|
||||
setSelectedChatbot(
|
||||
chatbots.find((item) => item.id === chatbot.value),
|
||||
);
|
||||
}}
|
||||
selectedValue={
|
||||
(selectedChatbot && {
|
||||
label: selectedChatbot.name,
|
||||
value: selectedChatbot.id,
|
||||
}) ||
|
||||
null
|
||||
}
|
||||
rounded="3xl"
|
||||
border="border"
|
||||
borderColor="gray-700"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Messages Analytics */}
|
||||
<div className="mt-8 w-full flex flex-col [@media(min-width:1080px)]:flex-row gap-3">
|
||||
|
||||
@@ -168,7 +168,7 @@ export default function Prompts({
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
className="mt-[24px] rounded-3xl border border-solid border-purple-700 px-5 py-3 text-purple-700 transition-colors hover:bg-purple-700 hover:text-white dark:border-purple-400 dark:text-purple-400 dark:hover:bg-purple-400 dark:hover:text-white"
|
||||
className="mt-[24px] rounded-3xl border border-solid border-purple-30 px-5 py-3 text-purple-30 transition-colors hover:text-white hover:bg-[#6F3FD1] dark:border-purple-30 dark:text-purple-30 dark:hover:bg-purple-30 dark:hover:text-white"
|
||||
onClick={() => {
|
||||
setModalType('ADD');
|
||||
setModalState('ACTIVE');
|
||||
|
||||
@@ -101,7 +101,7 @@ function Upload({
|
||||
borderVariant="thin"
|
||||
label={field.label}
|
||||
required={isRequired}
|
||||
colorVariant="gray"
|
||||
colorVariant="silver"
|
||||
/>
|
||||
);
|
||||
case 'number':
|
||||
@@ -123,7 +123,7 @@ function Upload({
|
||||
borderVariant="thin"
|
||||
label={field.label}
|
||||
required={isRequired}
|
||||
colorVariant="gray"
|
||||
colorVariant="silver"
|
||||
/>
|
||||
);
|
||||
case 'enum':
|
||||
@@ -572,13 +572,13 @@ function Upload({
|
||||
</p>
|
||||
{!activeTab && (
|
||||
<div>
|
||||
<p className="text-gray-6000 dark:text-bright-gray text-sm text-center font-medium">
|
||||
<p className="dark text-gray-6000 dark:text-bright-gray text-sm text-center font-medium">
|
||||
{t('modals.uploadDoc.select')}
|
||||
</p>
|
||||
<div className="w-full gap-4 h-full p-4 flex flex-col md:flex-row md:gap-4 justify-center items-center">
|
||||
<button
|
||||
onClick={() => setActiveTab('file')}
|
||||
className="opacity-85 hover:opacity-100 rounded-3xl text-sm font-medium border flex flex-col items-center justify-center hover:shadow-purple-30/30 hover:shadow-lg p-8 gap-4 bg-white text-[#777777] dark:bg-outer-space dark:text-[#c3c3c3] hover:border-purple-30 border-[#D7D7D7] h-40 w-40 md:w-52 md:h-52"
|
||||
className="opacity-85 hover:opacity-100 rounded-3xl text-sm font-medium border flex flex-col items-center justify-center hover:shadow-purple-30/30 hover:shadow-lg p-8 gap-4 bg-transparent text-[#777777] dark:bg-transparent dark:text-[#c3c3c3] hover:border-purple-30 border-[#D7D7D7] h-40 w-40 md:w-52 md:h-52"
|
||||
>
|
||||
<img
|
||||
src={FileUpload}
|
||||
@@ -588,7 +588,7 @@ function Upload({
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('remote')}
|
||||
className="opacity-85 hover:opacity-100 rounded-3xl text-sm font-medium border flex flex-col items-center justify-center hover:shadow-purple-30/30 hover:shadow-lg p-8 gap-4 bg-white text-[#777777] dark:bg-outer-space dark:text-[#c3c3c3] hover:border-purple-30 border-[#D7D7D7] h-40 w-40 md:w-52 md:h-52"
|
||||
className="opacity-85 hover:opacity-100 rounded-3xl text-sm font-medium border flex flex-col items-center justify-center hover:shadow-purple-30/30 hover:shadow-lg p-8 gap-4 bg-transparent text-[#777777] dark:bg-transparent dark:text-[#c3c3c3] hover:border-purple-30 border-[#D7D7D7] h-40 w-40 md:w-52 md:h-52"
|
||||
>
|
||||
<img
|
||||
src={WebsiteCollect}
|
||||
@@ -604,18 +604,16 @@ function Upload({
|
||||
<>
|
||||
<Input
|
||||
type="text"
|
||||
colorVariant="gray"
|
||||
colorVariant="silver"
|
||||
value={docName}
|
||||
onChange={(e) => setDocName(e.target.value)}
|
||||
borderVariant="thin"
|
||||
></Input>
|
||||
<div className="relative bottom-12 left-2 mt-[-20px]">
|
||||
<span className="bg-white px-2 text-xs text-gray-4000 dark:bg-outer-space dark:text-silver">
|
||||
{t('modals.uploadDoc.name')}
|
||||
</span>
|
||||
</div>
|
||||
<div {...getRootProps()}>
|
||||
<span className="rounded-3xl border border-purple-30 px-4 py-2 font-medium text-purple-30 hover:cursor-pointer dark:bg-purple-taupe dark:text-silver">
|
||||
placeholder={t('modals.uploadDoc.name')}
|
||||
label={t('modals.uploadDoc.name')}
|
||||
required={true}
|
||||
/>
|
||||
<div className="my-2" {...getRootProps()}>
|
||||
<span className="rounded-3xl bg-transparent px-4 py-2 font-medium text-purple-30 hover:cursor-pointer dark:text-silver border border-[#7F7F82]">
|
||||
<input type="button" {...getInputProps()} />
|
||||
{t('modals.uploadDoc.choose')}
|
||||
</span>
|
||||
@@ -624,7 +622,7 @@ function Upload({
|
||||
{t('modals.uploadDoc.info')}
|
||||
</p>
|
||||
<div className="mt-0 max-w-full">
|
||||
<p className="mb-[14px] font-medium text-eerie-black dark:text-light-gray">
|
||||
<p className="mb-[14px] text-[14px] font-medium text-eerie-black dark:text-light-gray">
|
||||
{t('modals.uploadDoc.uploadedFiles')}
|
||||
</p>
|
||||
<div className="max-w-full overflow-hidden">
|
||||
@@ -638,7 +636,7 @@ function Upload({
|
||||
</p>
|
||||
))}
|
||||
{files.length === 0 && (
|
||||
<p className="text-gray-6000 dark:text-light-gray">
|
||||
<p className="text-[14px] text-gray-6000 dark:text-light-gray">
|
||||
{t('none')}
|
||||
</p>
|
||||
)}
|
||||
@@ -649,7 +647,7 @@ function Upload({
|
||||
{activeTab === 'remote' && (
|
||||
<>
|
||||
<Dropdown
|
||||
border="border-2"
|
||||
border="border"
|
||||
options={urlOptions}
|
||||
selectedValue={
|
||||
urlOptions.find((opt) => opt.value === ingestor.type) || null
|
||||
@@ -664,7 +662,7 @@ function Upload({
|
||||
|
||||
<Input
|
||||
type="text"
|
||||
colorVariant="gray"
|
||||
colorVariant="silver"
|
||||
value={remoteName}
|
||||
onChange={(e) => setRemoteName(e.target.value)}
|
||||
borderVariant="thin"
|
||||
@@ -687,11 +685,11 @@ function Upload({
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<div className="flex justify-between">
|
||||
<div className="flex justify-end gap-4">
|
||||
{activeTab && (
|
||||
<button
|
||||
onClick={() => setActiveTab(null)}
|
||||
className="rounded-3xl border border-purple-30 px-4 py-2 font-medium text-purple-30 hover:cursor-pointer dark:bg-purple-taupe dark:text-silver"
|
||||
className="rounded-3xl bg-transparent px-4 py-2 font-medium text-purple-30 hover:cursor-pointer dark:text-silver text-[14px]"
|
||||
>
|
||||
{t('modals.uploadDoc.back')}
|
||||
</button>
|
||||
@@ -706,7 +704,7 @@ function Upload({
|
||||
}
|
||||
}}
|
||||
disabled={isUploadDisabled()}
|
||||
className={`rounded-3xl px-4 py-2 font-medium ${
|
||||
className={`rounded-3xl px-4 py-2 font-medium text-[14px] ${
|
||||
isUploadDisabled()
|
||||
? 'cursor-not-allowed bg-gray-300 text-gray-500'
|
||||
: 'cursor-pointer bg-purple-30 text-white hover:bg-purple-40'
|
||||
|
||||
Reference in New Issue
Block a user