conflict fixing
@@ -32,7 +32,10 @@ function MainLayout() {
|
||||
}
|
||||
|
||||
export default function App() {
|
||||
useDarkTheme();
|
||||
const [, , componentMounted] = useDarkTheme();
|
||||
if (!componentMounted) {
|
||||
return <div />;
|
||||
}
|
||||
return (
|
||||
<div className="h-full relative overflow-auto">
|
||||
<Routes>
|
||||
|
||||
@@ -11,7 +11,6 @@ import Discord from './assets/discord.svg';
|
||||
import Expand from './assets/expand.svg';
|
||||
import Github from './assets/github.svg';
|
||||
import Hamburger from './assets/hamburger.svg';
|
||||
import Info from './assets/info.svg';
|
||||
import SettingGear from './assets/settingGear.svg';
|
||||
import Twitter from './assets/TwitterX.svg';
|
||||
import UploadIcon from './assets/upload.svg';
|
||||
@@ -41,6 +40,7 @@ import {
|
||||
setSourceDocs,
|
||||
} from './preferences/preferenceSlice';
|
||||
import Upload from './upload/Upload';
|
||||
import Help from './components/Help';
|
||||
|
||||
interface NavigationProps {
|
||||
navOpen: boolean;
|
||||
@@ -275,7 +275,10 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
|
||||
{t('newChat')}
|
||||
</p>
|
||||
</NavLink>
|
||||
<div className="mb-auto h-[78vh] overflow-y-auto overflow-x-hidden dark:text-white">
|
||||
<div
|
||||
id="conversationsMainDiv"
|
||||
className="mb-auto h-[78vh] overflow-y-auto overflow-x-hidden dark:text-white"
|
||||
>
|
||||
{conversations && conversations.length > 0 ? (
|
||||
<div>
|
||||
<div className=" my-auto mx-4 mt-2 flex h-6 items-center justify-between gap-4 rounded-3xl">
|
||||
@@ -304,7 +307,6 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex h-auto flex-col justify-end text-eerie-black dark:text-white">
|
||||
<div className="flex flex-col-reverse border-b-[1px] dark:border-b-purple-taupe">
|
||||
<div className="relative my-4 mx-4 flex gap-2">
|
||||
@@ -359,68 +361,51 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
|
||||
</p>
|
||||
</NavLink>
|
||||
</div>
|
||||
<div className="flex justify-between gap-2 border-b-[1.5px] py-2 dark:border-b-purple-taupe">
|
||||
<NavLink
|
||||
onClick={() => {
|
||||
if (isMobile) {
|
||||
setNavOpen(!navOpen);
|
||||
}
|
||||
resetConversation();
|
||||
}}
|
||||
to="/about"
|
||||
className={({ isActive }) =>
|
||||
`my-auto mx-4 flex h-9 cursor-pointer gap-4 rounded-3xl hover:bg-gray-100 dark:hover:bg-[#28292E] ${
|
||||
isActive ? 'bg-gray-3000 dark:bg-[#28292E]' : ''
|
||||
}`
|
||||
}
|
||||
>
|
||||
<img
|
||||
src={Info}
|
||||
alt="icon"
|
||||
className="ml-2 w-5 filter dark:invert"
|
||||
/>
|
||||
<p className="my-auto pr-1 text-sm">{t('about')}</p>
|
||||
</NavLink>
|
||||
<div className="flex items-center justify-evenly gap-1 px-1">
|
||||
<NavLink
|
||||
target="_blank"
|
||||
to={'https://discord.gg/WHJdfbQDR4'}
|
||||
className={
|
||||
'rounded-full hover:bg-gray-100 dark:hover:bg-[#28292E]'
|
||||
}
|
||||
>
|
||||
<img
|
||||
src={Discord}
|
||||
alt="discord"
|
||||
className="m-2 w-6 self-center filter dark:invert"
|
||||
/>
|
||||
</NavLink>
|
||||
<NavLink
|
||||
target="_blank"
|
||||
to={'https://twitter.com/docsgptai'}
|
||||
className={
|
||||
'rounded-full hover:bg-gray-100 dark:hover:bg-[#28292E]'
|
||||
}
|
||||
>
|
||||
<img
|
||||
src={Twitter}
|
||||
alt="x"
|
||||
className="m-2 w-5 self-center filter dark:invert"
|
||||
/>
|
||||
</NavLink>
|
||||
<NavLink
|
||||
target="_blank"
|
||||
to={'https://github.com/arc53/docsgpt'}
|
||||
className={
|
||||
'rounded-full hover:bg-gray-100 dark:hover:bg-[#28292E]'
|
||||
}
|
||||
>
|
||||
<img
|
||||
src={Github}
|
||||
alt="github"
|
||||
className="m-2 w-6 self-center filter dark:invert"
|
||||
/>
|
||||
</NavLink>
|
||||
<div className="flex flex-col justify-end text-eerie-black dark:text-white">
|
||||
<div className="flex justify-between items-center px-1 py-1">
|
||||
<Help />
|
||||
|
||||
<div className="flex items-center gap-1">
|
||||
<NavLink
|
||||
target="_blank"
|
||||
to={'https://discord.gg/WHJdfbQDR4'}
|
||||
className={
|
||||
'rounded-full hover:bg-gray-100 dark:hover:bg-[#28292E]'
|
||||
}
|
||||
>
|
||||
<img
|
||||
src={Discord}
|
||||
alt="discord"
|
||||
className="m-2 w-6 self-center filter dark:invert"
|
||||
/>
|
||||
</NavLink>
|
||||
<NavLink
|
||||
target="_blank"
|
||||
to={'https://twitter.com/docsgptai'}
|
||||
className={
|
||||
'rounded-full hover:bg-gray-100 dark:hover:bg-[#28292E]'
|
||||
}
|
||||
>
|
||||
<img
|
||||
src={Twitter}
|
||||
alt="x"
|
||||
className="m-2 w-5 self-center filter dark:invert"
|
||||
/>
|
||||
</NavLink>
|
||||
<NavLink
|
||||
target="_blank"
|
||||
to={'https://github.com/arc53/docsgpt'}
|
||||
className={
|
||||
'rounded-full hover:bg-gray-100 dark:hover:bg-[#28292E]'
|
||||
}
|
||||
>
|
||||
<img
|
||||
src={Github}
|
||||
alt="github"
|
||||
className="m-2 w-6 self-center filter dark:invert"
|
||||
/>
|
||||
</NavLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -450,6 +435,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
|
||||
<Upload
|
||||
modalState={uploadModalState}
|
||||
setModalState={setUploadModalState}
|
||||
isOnboarding={false}
|
||||
></Upload>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="100" height="100" viewBox="0,0,256,256">
|
||||
<g transform="translate(-19.2,-19.2) scale(1.15,1.15)"><g fill="#949494" fill-rule="nonzero" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" font-family="none" font-weight="none" font-size="none" text-anchor="none" style="mix-blend-mode: normal"><g transform="scale(10.66667,10.66667)"><path d="M13.172,2h-7.172c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2v-11.172c0,-0.53 -0.211,-1.039 -0.586,-1.414l-4.828,-4.828c-0.375,-0.375 -0.884,-0.586 -1.414,-0.586zM15,18h-6c-0.552,0 -1,-0.448 -1,-1v0c0,-0.552 0.448,-1 1,-1h6c0.552,0 1,0.448 1,1v0c0,0.552 -0.448,1 -1,1zM15,14h-6c-0.552,0 -1,-0.448 -1,-1v0c0,-0.552 0.448,-1 1,-1h6c0.552,0 1,0.448 1,1v0c0,0.552 -0.448,1 -1,1zM13,9v-5.5l5.5,5.5z"></path></g></g></g>
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16.25 8.64258V16.25C16.25 16.7473 16.0525 17.2242 15.7008 17.5758C15.3492 17.9275 14.8723 18.125 14.375 18.125H5.625C5.12772 18.125 4.65081 17.9275 4.29917 17.5758C3.94754 17.2242 3.75 16.7473 3.75 16.25V3.75C3.75 3.25272 3.94754 2.77581 4.29917 2.42417C4.65081 2.07254 5.12772 1.875 5.625 1.875H9.48242C9.81383 1.87505 10.1316 2.0067 10.366 2.24102L15.884 7.75898C16.1183 7.99335 16.2499 8.31117 16.25 8.64258Z" stroke="#ECECF1" stroke-width="1.11111" stroke-linejoin="round"/>
|
||||
<path d="M10 2.1875V6.875C10 7.20652 10.1317 7.52446 10.3661 7.75888C10.6005 7.9933 10.9185 8.125 11.25 8.125H15.9375M6.875 11.25H13.125M6.875 14.375H13.125" stroke="#ECECF1" stroke-width="1.11111" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 933 B After Width: | Height: | Size: 839 B |
@@ -1,3 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="100" height="100" viewBox="0,0,256,256">
|
||||
<g transform="translate(-19.2,-19.2) scale(1.15,1.15)"><g fill="black" fill-opacity="0.54" fill-rule="nonzero" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" font-family="none" font-weight="none" font-size="none" text-anchor="none" style="mix-blend-mode: normal"><g transform="scale(10.66667,10.66667)"><path d="M13.172,2h-7.172c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2v-11.172c0,-0.53 -0.211,-1.039 -0.586,-1.414l-4.828,-4.828c-0.375,-0.375 -0.884,-0.586 -1.414,-0.586zM15,18h-6c-0.552,0 -1,-0.448 -1,-1v0c0,-0.552 0.448,-1 1,-1h6c0.552,0 1,0.448 1,1v0c0,0.552 -0.448,1 -1,1zM15,14h-6c-0.552,0 -1,-0.448 -1,-1v0c0,-0.552 0.448,-1 1,-1h6c0.552,0 1,0.448 1,1v0c0,0.552 -0.448,1 -1,1zM13,9v-5.5l5.5,5.5z"></path></g></g></g>
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16.25 8.64258V16.25C16.25 16.7473 16.0525 17.2242 15.7008 17.5758C15.3492 17.9275 14.8723 18.125 14.375 18.125H5.625C5.12772 18.125 4.65081 17.9275 4.29917 17.5758C3.94754 17.2242 3.75 16.7473 3.75 16.25V3.75C3.75 3.25272 3.94754 2.77581 4.29917 2.42417C4.65081 2.07254 5.12772 1.875 5.625 1.875H9.48242C9.81383 1.87505 10.1316 2.0067 10.366 2.24102L15.884 7.75898C16.1183 7.99335 16.2499 8.31117 16.25 8.64258Z" stroke="#6E6E6E" stroke-width="1.11111" stroke-linejoin="round"/>
|
||||
<path d="M10 2.1875V6.875C10 7.20652 10.1317 7.52446 10.3661 7.75888C10.6005 7.9933 10.9185 8.125 11.25 8.125H15.9375M6.875 11.25H13.125M6.875 14.375H13.125" stroke="#6E6E6E" stroke-width="1.11111" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 951 B After Width: | Height: | Size: 839 B |
3
frontend/src/assets/envelope-dark.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.04867 15.49H15.1775C16.5339 15.49 17.3172 14.7067 17.3172 13.1548V4.83755C17.3172 3.29309 16.5265 2.50977 14.9515 2.50977H2.82302C1.47463 2.50977 0.683594 3.28569 0.683594 4.83755V13.1545C0.683594 14.7138 1.48138 15.49 3.04867 15.49ZM8.10441 9.3803L2.41609 3.76816C2.58163 3.70034 2.77738 3.66273 3.01106 3.66273H14.9971C15.2305 3.66273 15.434 3.70034 15.6072 3.78327L9.92692 9.38062C9.60291 9.7043 9.31652 9.84766 9.01534 9.84766C8.71384 9.84766 8.42841 9.70398 8.10441 9.3803ZM1.83559 13.1548V4.76234L6.16717 9.01162L1.84331 13.2824C1.83592 13.2448 1.83559 13.1998 1.83559 13.1548ZM16.1646 4.84494V13.2599L11.8629 9.0113L16.1649 4.78516L16.1646 4.84494ZM3.01106 14.3377C2.79249 14.3377 2.61184 14.3075 2.4537 14.2397L6.95852 9.78723L7.44838 10.2694C7.97552 10.7891 8.48017 11.0077 9.01534 11.0077C9.54249 11.0077 10.0548 10.7891 10.5823 10.2694L11.0718 9.78723L15.5696 14.2319C15.4111 14.3075 15.2154 14.3374 14.9968 14.3374L3.01106 14.3377Z" fill="#ECECF1"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
3
frontend/src/assets/envelope.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.04867 15.49H15.1775C16.5339 15.49 17.3172 14.7067 17.3172 13.1548V4.83755C17.3172 3.29309 16.5265 2.50977 14.9515 2.50977H2.82302C1.47463 2.50977 0.683594 3.28569 0.683594 4.83755V13.1545C0.683594 14.7138 1.48138 15.49 3.04867 15.49ZM8.10441 9.3803L2.41609 3.76816C2.58163 3.70034 2.77738 3.66273 3.01106 3.66273H14.9971C15.2305 3.66273 15.434 3.70034 15.6072 3.78327L9.92692 9.38062C9.60291 9.7043 9.31652 9.84766 9.01534 9.84766C8.71384 9.84766 8.42841 9.70398 8.10441 9.3803ZM1.83559 13.1548V4.76234L6.16717 9.01162L1.84331 13.2824C1.83592 13.2448 1.83559 13.1998 1.83559 13.1548ZM16.1646 4.84494V13.2599L11.8629 9.0113L16.1649 4.78516L16.1646 4.84494ZM3.01106 14.3377C2.79249 14.3377 2.61184 14.3075 2.4537 14.2397L6.95852 9.78723L7.44838 10.2694C7.97552 10.7891 8.48017 11.0077 9.01534 11.0077C9.54249 11.0077 10.0548 10.7891 10.5823 10.2694L11.0718 9.78723L15.5696 14.2319C15.4111 14.3075 15.2154 14.3374 14.9968 14.3374L3.01106 14.3377Z" fill="#6F6F6F"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
3
frontend/src/assets/file_upload.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="28" height="34" viewBox="0 0 28 34" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10 26.0003H18C19.1 26.0003 20 25.1003 20 24.0003V14.0003H23.18C24.96 14.0003 25.86 11.8403 24.6 10.5803L15.42 1.40032C15.235 1.21491 15.0152 1.06782 14.7732 0.967453C14.5313 0.86709 14.2719 0.81543 14.01 0.81543C13.7481 0.81543 13.4887 0.86709 13.2468 0.967453C13.0048 1.06782 12.785 1.21491 12.6 1.40032L3.42 10.5803C2.16 11.8403 3.04 14.0003 4.82 14.0003H8V24.0003C8 25.1003 8.9 26.0003 10 26.0003ZM2 30.0003H26C27.1 30.0003 28 30.9003 28 32.0003C28 33.1003 27.1 34.0003 26 34.0003H2C0.9 34.0003 0 33.1003 0 32.0003C0 30.9003 0.9 30.0003 2 30.0003Z" fill="#828282"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 681 B |
6
frontend/src/assets/website_collect.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg width="58" height="49" viewBox="0 0 58 49" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M33.0002 45.3337H6.3335C5.27263 45.3337 4.25521 44.9122 3.50507 44.1621C2.75492 43.4119 2.3335 42.3945 2.3335 41.3337V6.66699C2.3335 5.60613 2.75492 4.58871 3.50507 3.83856C4.25521 3.08842 5.27263 2.66699 6.3335 2.66699H51.6668C52.7277 2.66699 53.7451 3.08842 54.4953 3.83856C55.2454 4.58871 55.6668 5.60613 55.6668 6.66699V24.0003M42.3335 40.0003L49.0002 46.667M49.0002 46.667L55.6668 40.0003M49.0002 46.667V30.667" stroke="#626262" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M2.3335 6.66699C2.3335 5.60613 2.75492 4.58871 3.50507 3.83856C4.25521 3.08842 5.27263 2.66699 6.3335 2.66699H51.6668C52.7277 2.66699 53.7451 3.08842 54.4953 3.83856C55.2454 4.58871 55.6668 5.60613 55.6668 6.66699V18.667H2.3335V6.66699Z" stroke="#626262" stroke-width="4"/>
|
||||
<path d="M7.66667 10.6673C7.66667 9.19456 8.86057 8.00065 10.3333 8.00065C11.8061 8.00065 13 9.19456 13 10.6673C13 12.1401 11.8061 13.334 10.3333 13.334C8.86057 13.334 7.66667 12.1401 7.66667 10.6673Z" fill="#626262"/>
|
||||
<path d="M15.6667 10.6673C15.6667 9.19456 16.8606 8.00065 18.3333 8.00065C19.8061 8.00065 21 9.19456 21 10.6673C21 12.1401 19.8061 13.334 18.3333 13.334C16.8606 13.334 15.6667 12.1401 15.6667 10.6673Z" fill="#626262"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
80
frontend/src/components/Help.tsx
Normal file
@@ -0,0 +1,80 @@
|
||||
import { useState, useRef, useEffect } from 'react';
|
||||
import Info from '../assets/info.svg';
|
||||
import PageIcon from '../assets/documentation.svg';
|
||||
import EmailIcon from '../assets/envelope.svg';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
const Help = () => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const dropdownRef = useRef<HTMLDivElement | null>(null);
|
||||
const buttonRef = useRef<HTMLButtonElement | null>(null);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const toggleDropdown = () => {
|
||||
setIsOpen((prev) => !prev);
|
||||
};
|
||||
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (
|
||||
dropdownRef.current &&
|
||||
!dropdownRef.current.contains(event.target as Node) &&
|
||||
buttonRef.current &&
|
||||
!buttonRef.current.contains(event.target as Node)
|
||||
) {
|
||||
setIsOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="relative inline-block text-sm" ref={dropdownRef}>
|
||||
<button
|
||||
ref={buttonRef}
|
||||
onClick={toggleDropdown}
|
||||
className="my-auto mx-4 w-full flex items-center h-9 gap-4 rounded-3xl hover:bg-gray-100 dark:hover:bg-[#28292E]"
|
||||
>
|
||||
<img src={Info} alt="info" className="ml-1 w-5 filter dark:invert" />
|
||||
{t('help')}
|
||||
</button>
|
||||
{isOpen && (
|
||||
<div
|
||||
className={`absolute translate-x-4 -translate-y-28 z-10 w-48 shadow-lg bg-white dark:bg-[#444654] rounded-xl`}
|
||||
>
|
||||
<a
|
||||
href="https://docs.docsgpt.cloud/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-start gap-4 px-4 py-2 text-black dark:text-white hover:bg-bright-gray dark:hover:bg-[#545561] rounded-t-xl"
|
||||
>
|
||||
<img
|
||||
src={PageIcon}
|
||||
alt="Documentation"
|
||||
className="filter dark:invert"
|
||||
width={20}
|
||||
/>
|
||||
{t('documentation')}
|
||||
</a>
|
||||
<a
|
||||
href="mailto:contact@arc53.com"
|
||||
className="flex items-start gap-4 px-4 py-2 text-black dark:text-white hover:bg-bright-gray dark:hover:bg-[#545561] rounded-b-xl"
|
||||
>
|
||||
<img
|
||||
src={EmailIcon}
|
||||
alt="Email Us"
|
||||
className="filter dark:invert p-0.5"
|
||||
width={20}
|
||||
/>
|
||||
{t('emailUs')}
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Help;
|
||||
@@ -4,10 +4,11 @@ const RetryIcon = (props: SVGProps<SVGSVGElement>) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlSpace="preserve"
|
||||
width={16}
|
||||
height={16}
|
||||
width={props.width ? props.width : 16}
|
||||
height={props.height ? props.height : 16}
|
||||
fill={props.fill}
|
||||
stroke={props.stroke}
|
||||
stroke={props.stroke ? props.stroke : 'none'}
|
||||
strokeWidth={props.strokeWidth ? props.strokeWidth : 10}
|
||||
viewBox="0 0 383.748 383.748"
|
||||
{...props}
|
||||
>
|
||||
|
||||
@@ -71,9 +71,9 @@ const SettingsBar = ({ setActiveTab, activeTab }: SettingsBarProps) => {
|
||||
<button
|
||||
key={index}
|
||||
onClick={() => setActiveTab(tab)}
|
||||
className={`snap-start h-9 rounded-3xl px-4 font-bold ${
|
||||
className={`snap-start h-9 rounded-3xl px-4 font-bold hover:text-neutral-600 dark:hover:text-white/60 ${
|
||||
activeTab === tab
|
||||
? 'bg-purple-3000 text-purple-30 dark:bg-dark-charcoal'
|
||||
? 'bg-neutral-100 text-neutral-600 dark:bg-dark-charcoal dark:text-white/60'
|
||||
: 'text-gray-6000'
|
||||
}`}
|
||||
>
|
||||
|
||||
@@ -10,7 +10,7 @@ import SpinnerDark from '../assets/spinner-dark.svg';
|
||||
import Spinner from '../assets/spinner.svg';
|
||||
import RetryIcon from '../components/RetryIcon';
|
||||
import Hero from '../Hero';
|
||||
import { useDarkTheme } from '../hooks';
|
||||
import { useDarkTheme, useMediaQuery } from '../hooks';
|
||||
import { ShareConversationModal } from '../modals/ShareConversationModal';
|
||||
import { selectConversationId } from '../preferences/preferenceSlice';
|
||||
import { AppDispatch } from '../store';
|
||||
@@ -39,6 +39,7 @@ export default function Conversation() {
|
||||
const [lastQueryReturnedErr, setLastQueryReturnedErr] = useState(false);
|
||||
const [isShareModalOpen, setShareModalState] = useState<boolean>(false);
|
||||
const { t } = useTranslation();
|
||||
const { isMobile } = useMediaQuery();
|
||||
|
||||
const handleUserInterruption = () => {
|
||||
if (!eventInterrupt && status === 'loading') setEventInterrupt(true);
|
||||
@@ -139,7 +140,7 @@ export default function Conversation() {
|
||||
} else if (query.error) {
|
||||
const retryBtn = (
|
||||
<button
|
||||
className="flex items-center justify-center gap-3 self-center rounded-full border border-silver py-3 px-5 text-lg text-gray-500 transition-colors delay-100 hover:border-gray-500 disabled:cursor-not-allowed dark:text-bright-gray"
|
||||
className="flex items-center justify-center gap-3 self-center rounded-full py-3 px-5 text-lg text-gray-500 transition-colors delay-100 hover:border-gray-500 disabled:cursor-not-allowed dark:text-bright-gray"
|
||||
disabled={status === 'loading'}
|
||||
onClick={() => {
|
||||
handleQuestion({
|
||||
@@ -149,10 +150,12 @@ export default function Conversation() {
|
||||
}}
|
||||
>
|
||||
<RetryIcon
|
||||
width={isMobile ? 12 : 12} // change the width and height according to device size if necessary
|
||||
height={isMobile ? 12 : 12}
|
||||
fill={isDarkTheme ? 'rgb(236 236 241)' : 'rgb(107 114 120)'}
|
||||
stroke={isDarkTheme ? 'rgb(236 236 241)' : 'rgb(107 114 120)'}
|
||||
strokeWidth={10}
|
||||
/>
|
||||
Retry
|
||||
</button>
|
||||
);
|
||||
responseView = (
|
||||
|
||||
@@ -8,7 +8,6 @@ import remarkMath from 'remark-math';
|
||||
import rehypeKatex from 'rehype-katex';
|
||||
import 'katex/dist/katex.min.css';
|
||||
|
||||
import Alert from '../assets/alert.svg';
|
||||
import DocsGPT3 from '../assets/cute_docsgpt3.svg';
|
||||
import Dislike from '../assets/dislike.svg?react';
|
||||
import Document from '../assets/document.svg';
|
||||
@@ -238,14 +237,6 @@ const ConversationBubble = forwardRef<
|
||||
: 'flex-col rounded-3xl'
|
||||
}`}
|
||||
>
|
||||
{type === 'ERROR' && (
|
||||
<>
|
||||
<img src={Alert} alt="alert" className="mr-2 inline" />
|
||||
<div className="absolute right-0 lg:-right-32 top-1/2 translate-y-full lg:-translate-y-1/2">
|
||||
{retryBtn}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<ReactMarkdown
|
||||
className="whitespace-pre-wrap break-normal leading-normal"
|
||||
remarkPlugins={[remarkGfm, remarkMath]}
|
||||
@@ -339,12 +330,17 @@ const ConversationBubble = forwardRef<
|
||||
<div className="my-2 ml-2 flex justify-start">
|
||||
<div
|
||||
className={`relative mr-5 block items-center justify-center lg:invisible
|
||||
${type !== 'ERROR' ? 'group-hover:lg:visible' : ''}`}
|
||||
${type !== 'ERROR' ? 'group-hover:lg:visible' : 'hidden'}`}
|
||||
>
|
||||
<div>
|
||||
<CopyButton text={message} />
|
||||
</div>
|
||||
</div>
|
||||
{type === 'ERROR' && (
|
||||
<div className="relative mr-5 block items-center justify-center">
|
||||
<div>{retryBtn}</div>
|
||||
</div>
|
||||
)}
|
||||
{handleFeedback && (
|
||||
<>
|
||||
<div
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import { SyntheticEvent, useEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
SyntheticEvent,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
useCallback,
|
||||
} from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import Edit from '../assets/edit.svg';
|
||||
import Exit from '../assets/exit.svg';
|
||||
import Message from '../assets/message.svg';
|
||||
import MessageDark from '../assets/message-dark.svg';
|
||||
import { useDarkTheme } from '../hooks';
|
||||
import ConfirmationModal from '../modals/ConfirmationModal';
|
||||
import CheckMark2 from '../assets/checkMark2.svg';
|
||||
@@ -77,6 +81,36 @@ export default function ConversationTile({
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const preventScroll = useCallback((event: WheelEvent | TouchEvent) => {
|
||||
event.preventDefault();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const conversationsMainDiv = document.getElementById(
|
||||
'conversationsMainDiv',
|
||||
);
|
||||
|
||||
if (conversationsMainDiv) {
|
||||
if (isOpen) {
|
||||
conversationsMainDiv.addEventListener('wheel', preventScroll, {
|
||||
passive: false,
|
||||
});
|
||||
conversationsMainDiv.addEventListener('touchmove', preventScroll, {
|
||||
passive: false,
|
||||
});
|
||||
} else {
|
||||
conversationsMainDiv.removeEventListener('wheel', preventScroll);
|
||||
conversationsMainDiv.removeEventListener('touchmove', preventScroll);
|
||||
}
|
||||
|
||||
return () => {
|
||||
conversationsMainDiv.removeEventListener('wheel', preventScroll);
|
||||
conversationsMainDiv.removeEventListener('touchmove', preventScroll);
|
||||
};
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
function onClear() {
|
||||
setConversationsName(conversation.name);
|
||||
setIsEdit(false);
|
||||
@@ -96,17 +130,13 @@ export default function ConversationTile({
|
||||
conversationId !== conversation.id &&
|
||||
selectConversation(conversation.id);
|
||||
}}
|
||||
className={`my-auto mx-4 mt-4 flex h-9 cursor-pointer items-center justify-between gap-4 rounded-3xl hover:bg-gray-100 dark:hover:bg-[#28292E] ${
|
||||
className={`my-auto mx-4 mt-4 flex h-9 cursor-pointer items-center justify-between pl-4 gap-4 rounded-3xl hover:bg-gray-100 dark:hover:bg-[#28292E] ${
|
||||
conversationId === conversation.id || isOpen || isHovered
|
||||
? 'bg-gray-100 dark:bg-[#28292E]'
|
||||
: ''
|
||||
}`}
|
||||
>
|
||||
<div className={`flex w-10/12 gap-4`}>
|
||||
<img
|
||||
src={isDarkTheme ? MessageDark : Message}
|
||||
className="ml-4 w-5 dark:text-white"
|
||||
/>
|
||||
{isEdit ? (
|
||||
<input
|
||||
autoFocus
|
||||
@@ -153,7 +183,7 @@ export default function ConversationTile({
|
||||
<button
|
||||
onClick={(event: SyntheticEvent) => {
|
||||
event.stopPropagation();
|
||||
setOpen(true);
|
||||
setOpen(!isOpen);
|
||||
}}
|
||||
className="mr-2 flex w-4 justify-center"
|
||||
>
|
||||
|
||||
@@ -38,9 +38,11 @@ export function handleFetchAnswer(
|
||||
prompt_id: promptId,
|
||||
chunks: chunks,
|
||||
token_limit: token_limit,
|
||||
isNoneDoc: selectedDocs === null,
|
||||
};
|
||||
if (selectedDocs && 'id' in selectedDocs)
|
||||
if (selectedDocs && 'id' in selectedDocs) {
|
||||
payload.active_docs = selectedDocs.id as string;
|
||||
}
|
||||
payload.retriever = selectedDocs?.retriever as string;
|
||||
return conversationService
|
||||
.answer(payload, signal)
|
||||
@@ -84,26 +86,16 @@ export function handleFetchAnswerSteaming(
|
||||
prompt_id: promptId,
|
||||
chunks: chunks,
|
||||
token_limit: token_limit,
|
||||
isNoneDoc: selectedDocs === null,
|
||||
};
|
||||
if (selectedDocs && 'id' in selectedDocs)
|
||||
if (selectedDocs && 'id' in selectedDocs) {
|
||||
payload.active_docs = selectedDocs.id as string;
|
||||
}
|
||||
payload.retriever = selectedDocs?.retriever as string;
|
||||
|
||||
return new Promise<Answer>((resolve, reject) => {
|
||||
conversationService
|
||||
.answerStream(
|
||||
{
|
||||
question: question,
|
||||
active_docs: selectedDocs?.id as string,
|
||||
history: JSON.stringify(history),
|
||||
conversation_id: conversationId,
|
||||
prompt_id: promptId,
|
||||
chunks: chunks,
|
||||
token_limit: token_limit,
|
||||
isNoneDoc: selectedDocs === null,
|
||||
},
|
||||
signal,
|
||||
)
|
||||
.answerStream(payload, signal)
|
||||
.then((response) => {
|
||||
if (!response.body) throw Error('No response body');
|
||||
|
||||
@@ -169,20 +161,13 @@ export function handleSearch(
|
||||
conversation_id: conversation_id,
|
||||
chunks: chunks,
|
||||
token_limit: token_limit,
|
||||
isNoneDoc: selectedDocs === null,
|
||||
};
|
||||
if (selectedDocs && 'id' in selectedDocs)
|
||||
payload.active_docs = selectedDocs.id as string;
|
||||
payload.retriever = selectedDocs?.retriever as string;
|
||||
return conversationService
|
||||
.search({
|
||||
question: question,
|
||||
active_docs: selectedDocs?.id as string,
|
||||
conversation_id,
|
||||
history,
|
||||
chunks: chunks,
|
||||
token_limit: token_limit,
|
||||
isNoneDoc: selectedDocs === null,
|
||||
})
|
||||
.search(payload)
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
return data;
|
||||
|
||||
@@ -40,4 +40,5 @@ export interface RetrievalPayload {
|
||||
prompt_id?: string | null;
|
||||
chunks: string;
|
||||
token_limit: number;
|
||||
isNoneDoc: boolean;
|
||||
}
|
||||
|
||||
@@ -82,6 +82,7 @@ export function useDarkTheme() {
|
||||
};
|
||||
|
||||
const [isDarkTheme, setIsDarkTheme] = useState<boolean>(getInitialTheme());
|
||||
const [componentMounted, setComponentMounted] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
@@ -102,11 +103,12 @@ export function useDarkTheme() {
|
||||
} else {
|
||||
document.body?.classList.remove('dark');
|
||||
}
|
||||
setComponentMounted(true);
|
||||
}, [isDarkTheme]);
|
||||
|
||||
const toggleTheme = () => {
|
||||
setIsDarkTheme(!isDarkTheme);
|
||||
};
|
||||
|
||||
return [isDarkTheme, toggleTheme] as const;
|
||||
return [isDarkTheme, toggleTheme, componentMounted] as const;
|
||||
}
|
||||
|
||||
@@ -7,9 +7,12 @@
|
||||
"about": "About",
|
||||
"inputPlaceholder": "Type your message here...",
|
||||
"tagline": "DocsGPT uses GenAI, please review critical information using sources.",
|
||||
"sourceDocs": "Source Docs",
|
||||
"sourceDocs": "Source",
|
||||
"none": "None",
|
||||
"cancel": "Cancel",
|
||||
"help":"Help",
|
||||
"emailUs":"Email us",
|
||||
"documentation":"documentation",
|
||||
"demo": [
|
||||
{
|
||||
"header": "Learn about DocsGPT",
|
||||
@@ -75,8 +78,12 @@
|
||||
"modals": {
|
||||
"uploadDoc": {
|
||||
"label": "Upload New Documentation",
|
||||
"file": "From File",
|
||||
"remote": "Remote",
|
||||
"select": "Choose how to upload your document to DocsGPT",
|
||||
"file": "Upload from device",
|
||||
"back": "Back",
|
||||
"wait": "Please wait ...",
|
||||
"remote": "Collect from a website",
|
||||
"start": "Start Chatting",
|
||||
"name": "Name",
|
||||
"choose": "Choose Files",
|
||||
"info": "Please upload .pdf, .txt, .rst, .csv, .xlsx, .docx, .md, .zip limited to 25mb",
|
||||
|
||||
@@ -7,9 +7,12 @@
|
||||
"about": "Acerca de",
|
||||
"inputPlaceholder": "Escribe tu mensaje aquí...",
|
||||
"tagline": "DocsGPT utiliza GenAI, por favor revisa información crítica utilizando fuentes.",
|
||||
"sourceDocs": "Documentos Fuente",
|
||||
"sourceDocs": "Fuente",
|
||||
"none": "Nada",
|
||||
"cancel": "Cancelar",
|
||||
"help":"Asistencia",
|
||||
"emailUs": "Envíanos un correo",
|
||||
"documentation": "documentación",
|
||||
"demo": [
|
||||
{
|
||||
"header": "Aprende sobre DocsGPT",
|
||||
@@ -75,8 +78,12 @@
|
||||
"modals": {
|
||||
"uploadDoc": {
|
||||
"label": "Subir Nueva Documentación",
|
||||
"file": "Desde Archivo",
|
||||
"remote": "Remota",
|
||||
"select": "Elija cómo cargar su documento en DocsGPT",
|
||||
"file": "Subir desde el dispositivo",
|
||||
"back": "Atrás",
|
||||
"wait": "Espere por favor ...",
|
||||
"remote": "Recoger desde un sitio web",
|
||||
"start": "Empezar a chatear",
|
||||
"name": "Nombre",
|
||||
"choose": "Seleccionar Archivos",
|
||||
"info": "Por favor, suba archivos .pdf, .txt, .rst, .docx, .md, .zip limitados a 25 MB",
|
||||
|
||||
@@ -6,6 +6,7 @@ import en from './en.json'; //English
|
||||
import es from './es.json'; //Spanish
|
||||
import jp from './jp.json'; //Japanese
|
||||
import zh from './zh.json'; //Mandarin
|
||||
import zhTW from './zh-TW.json'; //Traditional Chinese
|
||||
|
||||
i18n
|
||||
.use(LanguageDetector)
|
||||
@@ -24,6 +25,9 @@ i18n
|
||||
zh: {
|
||||
translation: zh,
|
||||
},
|
||||
'zh-TW': {
|
||||
translation: zhTW,
|
||||
},
|
||||
},
|
||||
fallbackLng: 'en',
|
||||
detection: {
|
||||
|
||||
@@ -7,9 +7,12 @@
|
||||
"about": "について",
|
||||
"inputPlaceholder": "ここにメッセージを入力してください...",
|
||||
"tagline": "DocsGPTはGenAIを使用しています。重要な情報はソースで確認してください。",
|
||||
"sourceDocs": "ソースドキュメント",
|
||||
"sourceDocs": "ソース",
|
||||
"none": "なし",
|
||||
"cancel": "キャンセル",
|
||||
"help":"ヘルプ",
|
||||
"emailUs": "メールを送る",
|
||||
"documentation": "ドキュメント",
|
||||
"demo": [
|
||||
{
|
||||
"header": "DocsGPTについて学ぶ",
|
||||
@@ -75,8 +78,12 @@
|
||||
"modals": {
|
||||
"uploadDoc": {
|
||||
"label": "新規書類のアップロード",
|
||||
"file": "ファイルから",
|
||||
"remote": "リモート",
|
||||
"select": "ドキュメントを DocsGPT にアップロードする方法を選択します",
|
||||
"file": "デバイスからアップロード",
|
||||
"back": "戻る",
|
||||
"wait": "お待ちください ...",
|
||||
"remote": "ウェブサイトから収集する",
|
||||
"start": "チャットを開始する",
|
||||
"name": "名前",
|
||||
"choose": "ファイルを選択",
|
||||
"info": ".pdf, .txt, .rst, .docx, .md, .zipファイルを25MBまでアップロードしてください",
|
||||
|
||||
133
frontend/src/locale/zh-TW.json
Normal file
@@ -0,0 +1,133 @@
|
||||
{
|
||||
"language": "繁體中文(臺灣)",
|
||||
"chat": "對話",
|
||||
"chats": "對話",
|
||||
"newChat": "新對話",
|
||||
"myPlan": "我的方案",
|
||||
"about": "關於",
|
||||
"inputPlaceholder": "在此輸入您的訊息...",
|
||||
"tagline": "DocsGPT 使用生成式 AI,請使用原始資料來源審閱重要資訊。",
|
||||
"sourceDocs": "原始文件",
|
||||
"none": "無",
|
||||
"cancel": "取消",
|
||||
"help":"聯繫支援",
|
||||
"emailUs": "寄送電子郵件給我們",
|
||||
"documentation": "文件",
|
||||
"demo": [
|
||||
{
|
||||
"header": "了解 DocsGPT",
|
||||
"query": "什麼是 DocsGPT?"
|
||||
},
|
||||
{
|
||||
"header": "摘要文件",
|
||||
"query": "摘要目前的內容"
|
||||
},
|
||||
{
|
||||
"header": "撰寫程式碼",
|
||||
"query": "為 /api/answer 撰寫 API 請求程式碼"
|
||||
},
|
||||
{
|
||||
"header": "學習輔助",
|
||||
"query": "為此內容撰寫可能的問題"
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"label": "設定",
|
||||
"general": {
|
||||
"label": "一般",
|
||||
"selectTheme": "選擇主題",
|
||||
"light": "淺色",
|
||||
"dark": "深色",
|
||||
"selectLanguage": "選擇語言",
|
||||
"chunks": "每次查詢處理的區塊數",
|
||||
"prompt": "使用中的提示",
|
||||
"deleteAllLabel": "刪除所有對話",
|
||||
"deleteAllBtn": "全部刪除",
|
||||
"addNew": "新增",
|
||||
"convHistory": "對話歷史記錄",
|
||||
"none": "無",
|
||||
"low": "低",
|
||||
"medium": "中",
|
||||
"high": "高",
|
||||
"unlimited": "無限制",
|
||||
"default": "預設"
|
||||
},
|
||||
"documents": {
|
||||
"label": "文件",
|
||||
"name": "文件名稱",
|
||||
"date": "向量日期",
|
||||
"type": "類型",
|
||||
"tokenUsage": "Token 使用量"
|
||||
},
|
||||
"apiKeys": {
|
||||
"label": "API 金鑰",
|
||||
"name": "名稱",
|
||||
"key": "API 金鑰",
|
||||
"sourceDoc": "來源文件",
|
||||
"createNew": "新增"
|
||||
},
|
||||
"analytics": {
|
||||
"label": "分析"
|
||||
},
|
||||
"logs": {
|
||||
"label": "日誌"
|
||||
}
|
||||
},
|
||||
"modals": {
|
||||
"uploadDoc": {
|
||||
"label": "上傳新文件",
|
||||
"file": "從檔案",
|
||||
"remote": "遠端",
|
||||
"name": "名稱",
|
||||
"choose": "選擇檔案",
|
||||
"info": "請上傳 .pdf, .txt, .rst, .docx, .md, .zip 檔案,大小限制為 25MB",
|
||||
"uploadedFiles": "已上傳的檔案",
|
||||
"cancel": "取消",
|
||||
"train": "訓練",
|
||||
"link": "連結",
|
||||
"urlLink": "URL 連結",
|
||||
"reddit": {
|
||||
"id": "用戶端 ID",
|
||||
"secret": "用戶端金鑰",
|
||||
"agent": "使用者代理(User-Agent)",
|
||||
"searchQueries": "搜尋查詢",
|
||||
"numberOfPosts": "貼文數量"
|
||||
}
|
||||
},
|
||||
"createAPIKey": {
|
||||
"label": "建立新的 API 金鑰",
|
||||
"apiKeyName": "API 金鑰名稱",
|
||||
"chunks": "每次查詢處理的區塊數",
|
||||
"prompt": "選擇使用中的提示",
|
||||
"sourceDoc": "來源文件",
|
||||
"create": "建立"
|
||||
},
|
||||
"saveKey": {
|
||||
"note": "請儲存您的金鑰",
|
||||
"disclaimer": "這是唯一一次顯示您的金鑰。",
|
||||
"copy": "複製",
|
||||
"copied": "已複製",
|
||||
"confirm": "我已儲存金鑰"
|
||||
},
|
||||
"deleteConv": {
|
||||
"confirm": "您確定要刪除所有對話嗎?",
|
||||
"delete": "刪除"
|
||||
},
|
||||
"shareConv": {
|
||||
"label": "建立公開頁面以分享",
|
||||
"note": "來源文件、個人資訊和後續對話將保持私密",
|
||||
"create": "建立"
|
||||
}
|
||||
},
|
||||
"sharedConv": {
|
||||
"subtitle": "使用以下工具建立",
|
||||
"button": "開始使用 DocsGPT",
|
||||
"meta": "DocsGPT 使用生成式 AI,請使用原始資料來源審閱重要資訊。"
|
||||
},
|
||||
"convTile": {
|
||||
"share": "分享",
|
||||
"delete": "刪除",
|
||||
"rename": "重新命名",
|
||||
"deleteWarning": "您確定要刪除這個對話嗎?"
|
||||
}
|
||||
}
|
||||
@@ -7,9 +7,12 @@
|
||||
"about": "关于",
|
||||
"inputPlaceholder": "在这里输入您的消息...",
|
||||
"tagline": "DocsGPT 使用 GenAI, 请使用来源审核关键信息.",
|
||||
"sourceDocs": "来源文档",
|
||||
"sourceDocs": "源",
|
||||
"none": "无",
|
||||
"cancel": "取消",
|
||||
"help":"联系支持",
|
||||
"emailUs": "给我们发邮件",
|
||||
"documentation": "文档",
|
||||
"demo": [
|
||||
{
|
||||
"header": "了解 DocsGPT",
|
||||
@@ -75,8 +78,12 @@
|
||||
"modals": {
|
||||
"uploadDoc": {
|
||||
"label": "上传新文档资料",
|
||||
"file": "从文件",
|
||||
"remote": "远程",
|
||||
"select": "选择如何将文档上传到 DocsGPT",
|
||||
"file": "从设备上传",
|
||||
"back": "后退",
|
||||
"wait": "请稍等 ...",
|
||||
"remote": "从网站收集",
|
||||
"start": "开始聊天",
|
||||
"name": "名称",
|
||||
"choose": "选择文件",
|
||||
"info": "请上传 .pdf, .txt, .rst, .docx, .md, .zip 文件,限 25MB",
|
||||
|
||||
@@ -6,7 +6,7 @@ import Trash from '../assets/trash.svg';
|
||||
import CreateAPIKeyModal from '../modals/CreateAPIKeyModal';
|
||||
import SaveAPIKeyModal from '../modals/SaveAPIKeyModal';
|
||||
import { APIKeyData } from './types';
|
||||
import SkeletonLoader from '../utils/loader';
|
||||
import SkeletonLoader from '../components/SkeletonLoader';
|
||||
|
||||
export default function APIKeys() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -17,7 +17,7 @@ import { formatDate } from '../utils/dateTimeUtils';
|
||||
import { APIKeyData } from './types';
|
||||
|
||||
import type { ChartData } from 'chart.js';
|
||||
import SkeletonLoader from '../utils/loader';
|
||||
import SkeletonLoader from '../components/SkeletonLoader';
|
||||
|
||||
ChartJS.register(
|
||||
CategoryScale,
|
||||
|
||||
@@ -7,7 +7,7 @@ import userService from '../api/services/userService';
|
||||
import SyncIcon from '../assets/sync.svg';
|
||||
import Trash from '../assets/trash.svg';
|
||||
import DropdownMenu from '../components/DropdownMenu';
|
||||
import SkeletonLoader from '../utils/loader';
|
||||
import SkeletonLoader from '../components/SkeletonLoader';
|
||||
import { Doc, DocumentsProps } from '../models/misc';
|
||||
import { getDocs } from '../preferences/preferenceApi';
|
||||
import { setSourceDocs } from '../preferences/preferenceSlice';
|
||||
|
||||
@@ -3,7 +3,7 @@ import React, { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import userService from '../api/services/userService';
|
||||
import ChevronRight from '../assets/chevron-right.svg';
|
||||
import Dropdown from '../components/Dropdown';
|
||||
import SkeletonLoader from '../utils/loader';
|
||||
import SkeletonLoader from '../components/SkeletonLoader';
|
||||
import { APIKeyData, LogData } from './types';
|
||||
import CoppyButton from '../components/CopyButton';
|
||||
|
||||
|
||||
@@ -4,6 +4,10 @@ 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';
|
||||
import Dropdown from '../components/Dropdown';
|
||||
import Input from '../components/Input';
|
||||
import { ActiveState, Doc } from '../models/misc';
|
||||
@@ -17,9 +21,11 @@ import {
|
||||
function Upload({
|
||||
modalState,
|
||||
setModalState,
|
||||
isOnboarding,
|
||||
}: {
|
||||
modalState: ActiveState;
|
||||
setModalState: (state: ActiveState) => void;
|
||||
isOnboarding: boolean;
|
||||
}) {
|
||||
const [docName, setDocName] = useState('');
|
||||
const [urlName, setUrlName] = useState('');
|
||||
@@ -32,7 +38,7 @@ function Upload({
|
||||
search_queries: [''],
|
||||
number_posts: 10,
|
||||
});
|
||||
const [activeTab, setActiveTab] = useState<string>('file');
|
||||
const [activeTab, setActiveTab] = useState<string | null>(null);
|
||||
const [files, setfiles] = useState<File[]>([]);
|
||||
const [progress, setProgress] = useState<{
|
||||
type: 'UPLOAD' | 'TRAINING';
|
||||
@@ -66,18 +72,24 @@ function Upload({
|
||||
|
||||
function ProgressBar({ progressPercent }: { progressPercent: number }) {
|
||||
return (
|
||||
<div className="my-5 w-[50%]">
|
||||
<div
|
||||
className={`h-8 overflow-hidden rounded-xl border border-purple-30 text-xs text-bright-gray outline-none `}
|
||||
>
|
||||
<div className="flex items-center justify-center h-full w-full my-8">
|
||||
<div className="relative w-32 h-32 rounded-full">
|
||||
<div className="absolute inset-0 rounded-full shadow-[0_0_10px_2px_rgba(0,0,0,0.3)_inset] dark:shadow-[0_0_10px_2px_rgba(0,0,0,0.3)_inset]"></div>
|
||||
<div
|
||||
className={`h-full border-none text-xl w-${
|
||||
progress || 0
|
||||
}% flex items-center justify-center bg-purple-30 outline-none transition-all`}
|
||||
style={{ width: `${progressPercent || 0}%` }}
|
||||
>
|
||||
{progressPercent >= 5 && `${progressPercent}%`}
|
||||
className={`absolute inset-0 rounded-full ${progressPercent === 100 ? 'shadow-xl shadow-lime-300/50 dark:shadow-lime-300/50 bg-gradient-to-r from-white to-gray-400 dark:bg-gradient-to-br dark:from-gray-500 dark:to-gray-300' : 'shadow-[0_2px_0_#FF3D00_inset] dark:shadow-[0_2px_0_#FF3D00_inset]'}`}
|
||||
style={{
|
||||
animation: `${progressPercent === 100 ? 'none' : 'rotate 2s linear infinite'}`,
|
||||
}}
|
||||
></div>
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<span className="text-2xl font-bold">{progressPercent}%</span>
|
||||
</div>
|
||||
<style>
|
||||
{`@keyframes rotate {
|
||||
0% { transform: rotate(0deg); }
|
||||
100%{ transform: rotate(360deg); }
|
||||
}`}
|
||||
</style>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -87,20 +99,47 @@ function Upload({
|
||||
title,
|
||||
isCancellable = false,
|
||||
isFailed = false,
|
||||
isTraining = false,
|
||||
}: {
|
||||
title: string;
|
||||
isCancellable?: boolean;
|
||||
isFailed?: boolean;
|
||||
isTraining?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<div className="mt-5 flex flex-col items-center gap-2 text-gray-2000 dark:text-bright-gray">
|
||||
<p className="text-gra text-xl tracking-[0.15px]">{title}...</p>
|
||||
<p className="text-gra text-xl tracking-[0.15px]">
|
||||
{isTraining &&
|
||||
(progress?.percentage === 100 ? 'Training completed' : title)}
|
||||
{!isTraining && title}
|
||||
</p>
|
||||
<p className="text-sm">This may take several minutes</p>
|
||||
<p className={`ml-5 text-xl text-red-400 ${isFailed ? '' : 'hidden'}`}>
|
||||
Over the token limit, please consider uploading smaller document
|
||||
</p>
|
||||
{/* <p className="mt-10 text-2xl">{progress?.percentage || 0}%</p> */}
|
||||
<ProgressBar progressPercent={progress?.percentage as number} />
|
||||
<ProgressBar progressPercent={progress?.percentage || 0} />
|
||||
{isTraining &&
|
||||
(progress?.percentage === 100 ? (
|
||||
<button
|
||||
onClick={() => {
|
||||
setDocName('');
|
||||
setfiles([]);
|
||||
setProgress(undefined);
|
||||
setModalState('INACTIVE');
|
||||
}}
|
||||
className="cursor-pointer rounded-3xl text-sm h-[42px] px-[28px] py-[6px] bg-[#7D54D1] text-white hover:bg-[#6F3FD1] shadow-lg"
|
||||
>
|
||||
{t('modals.uploadDoc.start')}
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
className="ml-2 cursor-pointer rounded-3xl text-sm h-[42px] px-[28px] py-[6px] bg-[#7D54D14D] text-white shadow-lg"
|
||||
disabled
|
||||
>
|
||||
{t('modals.uploadDoc.wait')}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -191,6 +230,7 @@ function Upload({
|
||||
title="Training is in progress"
|
||||
isCancellable={progress?.percentage === 100}
|
||||
isFailed={progress?.failed === true}
|
||||
isTraining={true}
|
||||
></Progress>
|
||||
);
|
||||
}
|
||||
@@ -305,32 +345,39 @@ function Upload({
|
||||
view = <TrainingProgress></TrainingProgress>;
|
||||
} else {
|
||||
view = (
|
||||
<>
|
||||
<p className="text-xl text-jet dark:text-bright-gray">
|
||||
<div className="flex flex-col gap-4 w-full">
|
||||
<p className="text-xl text-jet dark:text-bright-gray text-center font-semibold">
|
||||
{t('modals.uploadDoc.label')}
|
||||
</p>
|
||||
<div>
|
||||
<button
|
||||
onClick={() => setActiveTab('file')}
|
||||
className={`${
|
||||
activeTab === 'file'
|
||||
? 'bg-soap text-purple-30 dark:bg-independence dark:text-purple-400'
|
||||
: 'text-sonic-silver hover:text-purple-30'
|
||||
} mr-4 rounded-full px-[20px] py-[5px] text-sm font-semibold`}
|
||||
>
|
||||
{t('modals.uploadDoc.file')}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('remote')}
|
||||
className={`${
|
||||
activeTab === 'remote'
|
||||
? 'bg-soap text-purple-30 dark:bg-independence dark:text-purple-400'
|
||||
: 'text-sonic-silver hover:text-purple-30'
|
||||
} mr-4 rounded-full px-[20px] py-[5px] text-sm font-semibold`}
|
||||
>
|
||||
{t('modals.uploadDoc.remote')}
|
||||
</button>
|
||||
</div>
|
||||
{!activeTab && (
|
||||
<div>
|
||||
<p className="text-gray-6000 dark:text-light-gray text-sm text-center">
|
||||
{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="rounded-3xl text-md font-medium border flex flex-col items-center justify-center hover:shadow-lg dark:hover:shadow-lg p-8 dark:active:shadow-lg gap-4 bg-white dark:bg-outer-space dark:text-light-gray hover:bg-gray-100 dark:hover:bg-[#767183]/50 hover:text-purple-30 dark:hover:text-gray-30 border-purple-30 dark:border-gray-700 h-40 w-40 md:w-52 md:h-52"
|
||||
>
|
||||
<img
|
||||
src={FileUpload}
|
||||
className="w-8 h-8 mr-2 dark:filter dark:invert"
|
||||
/>
|
||||
{t('modals.uploadDoc.file')}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('remote')}
|
||||
className="rounded-3xl text-md font-medium border flex flex-col items-center justify-center hover:shadow-lg dark:hover:shadow-lg p-8 dark:active:shadow-lg gap-4 bg-white dark:bg-outer-space dark:text-light-gray hover:bg-gray-100 dark:hover:bg-[#767183]/50 hover:text-purple-30 dark:hover:text-gray-30 border-purple-30 dark:border-gray-700 h-40 w-40 md:w-52 md:h-52"
|
||||
>
|
||||
<img
|
||||
src={WebsiteCollect}
|
||||
className="w-8 h-8 mr-2 dark:filter dark:invert"
|
||||
/>
|
||||
{t('modals.uploadDoc.remote')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === 'file' && (
|
||||
<>
|
||||
@@ -519,43 +566,48 @@ function Upload({
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="flex flex-row-reverse">
|
||||
{activeTab === 'file' ? (
|
||||
{activeTab && (
|
||||
<div className="flex w-full justify-between flex-row-reverse">
|
||||
{activeTab === 'file' ? (
|
||||
<button
|
||||
onClick={uploadFile}
|
||||
className={`ml-2 cursor-pointer rounded-3xl bg-purple-30 text-sm text-white ${
|
||||
files.length > 0 && docName.trim().length > 0
|
||||
? 'hover:bg-[#6F3FD1]'
|
||||
: 'bg-opacity-75 text-opacity-80'
|
||||
} py-2 px-6`}
|
||||
disabled={
|
||||
(files.length === 0 || docName.trim().length === 0) &&
|
||||
activeTab === 'file'
|
||||
}
|
||||
>
|
||||
{t('modals.uploadDoc.train')}
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
onClick={uploadRemote}
|
||||
className={`ml-2 cursor-pointer rounded-3xl bg-purple-30 py-2 px-6 text-sm text-white hover:bg-[#6F3FD1]`}
|
||||
>
|
||||
{t('modals.uploadDoc.train')}
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={uploadFile}
|
||||
className={`ml-2 cursor-pointer rounded-3xl bg-purple-30 text-sm text-white ${
|
||||
files.length > 0 && docName.trim().length > 0
|
||||
? 'hover:bg-[#6F3FD1]'
|
||||
: 'bg-opacity-75 text-opacity-80'
|
||||
} py-2 px-6`}
|
||||
disabled={
|
||||
(files.length === 0 || docName.trim().length === 0) &&
|
||||
activeTab === 'file'
|
||||
}
|
||||
onClick={() => {
|
||||
setDocName('');
|
||||
setfiles([]);
|
||||
setActiveTab(null);
|
||||
}}
|
||||
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 flex items-center gap-1"
|
||||
>
|
||||
{t('modals.uploadDoc.train')}
|
||||
<img
|
||||
src={ArrowLeft}
|
||||
className="w-[10px] h-[10px] dark:filter dark:invert"
|
||||
/>
|
||||
{t('modals.uploadDoc.back')}
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
onClick={uploadRemote}
|
||||
className={`ml-2 cursor-pointer rounded-3xl bg-purple-30 py-2 px-6 text-sm text-white hover:bg-[#6F3FD1]`}
|
||||
>
|
||||
{t('modals.uploadDoc.train')}
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={() => {
|
||||
setDocName('');
|
||||
setfiles([]);
|
||||
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.uploadDoc.cancel')}
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -563,9 +615,22 @@ function Upload({
|
||||
<article
|
||||
className={`${
|
||||
modalState === 'ACTIVE' ? 'visible' : 'hidden'
|
||||
} absolute z-30 h-screen w-screen bg-gray-alpha`}
|
||||
} absolute z-30 bg-gray-alpha flex items-center justify-center h-[calc(100vh-4rem)] md:h-screen w-full`}
|
||||
>
|
||||
<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">
|
||||
<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>
|
||||
|
||||