mirror of
https://github.com/arc53/DocsGPT.git
synced 2025-11-30 00:53:14 +00:00
Merge branch 'main' into changing_discord_github_icons
This commit is contained in:
@@ -2,24 +2,19 @@ import { Routes, Route } from 'react-router-dom';
|
||||
import Navigation from './Navigation';
|
||||
import Conversation from './conversation/Conversation';
|
||||
import About from './About';
|
||||
import { useState } from 'react';
|
||||
import { ActiveState } from './models/misc';
|
||||
import { inject } from '@vercel/analytics';
|
||||
import { useMediaQuery } from './hooks';
|
||||
|
||||
inject();
|
||||
|
||||
export default function App() {
|
||||
//TODO : below media query is disjoint from tailwind. Please wire it together.
|
||||
const [navState, setNavState] = useState<ActiveState>(
|
||||
window.matchMedia('(min-width: 768px)').matches ? 'ACTIVE' : 'INACTIVE',
|
||||
);
|
||||
|
||||
const { isMobile } = useMediaQuery();
|
||||
return (
|
||||
<div className="min-h-full min-w-full">
|
||||
<Navigation navState={navState} setNavState={setNavState} />
|
||||
<Navigation />
|
||||
<div
|
||||
className={`transition-all duration-200 ${
|
||||
navState === 'ACTIVE' ? 'ml-0 md:ml-72 lg:ml-60' : 'ml-0 md:ml-16'
|
||||
!isMobile ? 'ml-0 md:ml-72 lg:ml-60' : 'ml-0 md:ml-16'
|
||||
}`}
|
||||
>
|
||||
<Routes>
|
||||
|
||||
@@ -10,8 +10,8 @@ export default function Hero({ className = '' }: { className?: string }) {
|
||||
</p>
|
||||
<p className="mb-3 text-center leading-6 text-black-1000">
|
||||
Enter a query related to the information in the documentation you
|
||||
selected to receive and we will provide you with the most relevant
|
||||
answers.
|
||||
selected to receive
|
||||
<br /> and we will provide you with the most relevant answers.
|
||||
</p>
|
||||
<p className="mb-3 text-center leading-6 text-black-1000">
|
||||
Start by entering your query in the input field below and we will do the
|
||||
|
||||
@@ -28,23 +28,19 @@ import {
|
||||
setConversation,
|
||||
updateConversationId,
|
||||
} from './conversation/conversationSlice';
|
||||
import { useOutsideAlerter } from './hooks';
|
||||
import { useMediaQuery, useOutsideAlerter } from './hooks';
|
||||
import Upload from './upload/Upload';
|
||||
import { Doc, getConversations } from './preferences/preferenceApi';
|
||||
import SelectDocsModal from './preferences/SelectDocsModal';
|
||||
|
||||
export default function Navigation({
|
||||
navState,
|
||||
setNavState,
|
||||
}: {
|
||||
navState: ActiveState;
|
||||
setNavState: React.Dispatch<React.SetStateAction<ActiveState>>;
|
||||
}) {
|
||||
export default function Navigation() {
|
||||
const dispatch = useDispatch();
|
||||
const docs = useSelector(selectSourceDocs);
|
||||
const selectedDocs = useSelector(selectSelectedDocs);
|
||||
const conversations = useSelector(selectConversations);
|
||||
const conversationId = useSelector(selectConversationId);
|
||||
const { isMobile } = useMediaQuery();
|
||||
const [navOpen, setNavOpen] = useState(!isMobile);
|
||||
|
||||
const [isDocsListOpen, setIsDocsListOpen] = useState(false);
|
||||
|
||||
@@ -126,51 +122,46 @@ export default function Navigation({
|
||||
useOutsideAlerter(
|
||||
navRef,
|
||||
() => {
|
||||
if (
|
||||
window.matchMedia('(max-width: 768px)').matches &&
|
||||
navState === 'ACTIVE' &&
|
||||
apiKeyModalState === 'INACTIVE'
|
||||
) {
|
||||
setNavState('INACTIVE');
|
||||
if (isMobile && navOpen && apiKeyModalState === 'INACTIVE') {
|
||||
setNavOpen(false);
|
||||
setIsDocsListOpen(false);
|
||||
}
|
||||
},
|
||||
[navState, isDocsListOpen, apiKeyModalState],
|
||||
[navOpen, isDocsListOpen, apiKeyModalState],
|
||||
);
|
||||
|
||||
/*
|
||||
Needed to fix bug where if mobile nav was closed and then window was resized to desktop, nav would still be closed but the button to open would be gone, as per #1 on issue #146
|
||||
*/
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('resize', () => {
|
||||
if (window.matchMedia('(min-width: 768px)').matches) {
|
||||
setNavState('ACTIVE');
|
||||
} else {
|
||||
setNavState('INACTIVE');
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
if (isMobile) {
|
||||
setNavOpen(false);
|
||||
return;
|
||||
}
|
||||
setNavOpen(true);
|
||||
}, [isMobile]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
ref={navRef}
|
||||
className={`${
|
||||
navState === 'INACTIVE' && '-ml-96 md:-ml-[14rem]'
|
||||
!navOpen && '-ml-96 md:-ml-[14rem]'
|
||||
} duration-20 fixed z-20 flex h-full w-72 flex-col border-r-2 bg-gray-50 transition-all`}
|
||||
>
|
||||
<div className={'visible h-16 w-full border-b-2 md:hidden'}>
|
||||
<button
|
||||
className="float-right mr-5 mt-5 h-5 w-5"
|
||||
onClick={() =>
|
||||
setNavState(navState === 'ACTIVE' ? 'INACTIVE' : 'ACTIVE')
|
||||
}
|
||||
onClick={() => {
|
||||
setNavOpen(!navOpen);
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={Arrow1}
|
||||
alt="menu toggle"
|
||||
className={`${
|
||||
navState === 'INACTIVE' ? 'rotate-180' : 'rotate-0'
|
||||
!navOpen ? 'rotate-180' : 'rotate-0'
|
||||
} m-auto w-3 transition-all duration-200`}
|
||||
/>
|
||||
</button>
|
||||
@@ -179,7 +170,11 @@ export default function Navigation({
|
||||
to={'/'}
|
||||
onClick={() => {
|
||||
dispatch(setConversation([]));
|
||||
dispatch(updateConversationId({ query: { conversationId: null } }));
|
||||
dispatch(
|
||||
updateConversationId({
|
||||
query: { conversationId: null },
|
||||
}),
|
||||
);
|
||||
}}
|
||||
className={({ isActive }) =>
|
||||
`${
|
||||
@@ -340,7 +335,6 @@ export default function Navigation({
|
||||
<img src={Discord} alt="link" className="ml-2 w-5" />
|
||||
<p className="my-auto text-eerie-black">Visit our Discord</p>
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://github.com/arc53/DocsGPT"
|
||||
target="_blank"
|
||||
@@ -356,7 +350,7 @@ export default function Navigation({
|
||||
<div className="fixed h-16 w-full border-b-2 bg-gray-50 md:hidden">
|
||||
<button
|
||||
className="mt-5 ml-6 h-6 w-6 md:hidden"
|
||||
onClick={() => setNavState('ACTIVE')}
|
||||
onClick={() => setNavOpen(true)}
|
||||
>
|
||||
<img src={Hamburger} alt="menu toggle" className="w-7" />
|
||||
</button>
|
||||
|
||||
@@ -61,6 +61,8 @@ export default function Conversation() {
|
||||
};
|
||||
|
||||
const handleQuestion = (question: string) => {
|
||||
question = question.trim();
|
||||
if (question === '') return;
|
||||
dispatch(addQuery({ prompt: question }));
|
||||
dispatch(fetchAnswer({ question }));
|
||||
};
|
||||
@@ -149,6 +151,7 @@ export default function Conversation() {
|
||||
<div className="flex w-full">
|
||||
<div
|
||||
ref={inputRef}
|
||||
placeholder="Type your message here..."
|
||||
contentEditable
|
||||
onPaste={handlePaste}
|
||||
className={`border-000000 overflow-x-hidden; max-h-24 min-h-[2.6rem] w-full overflow-y-auto whitespace-pre-wrap rounded-xl border bg-white py-2 pl-4 pr-9 leading-7 opacity-100 focus:outline-none`}
|
||||
|
||||
@@ -43,7 +43,7 @@ const ConversationBubble = forwardRef<
|
||||
<div ref={ref} className={`flex flex-row-reverse self-end ${className}`}>
|
||||
<Avatar className="mt-2 text-2xl" avatar="🧑💻"></Avatar>
|
||||
<div className="mr-2 ml-10 flex items-center rounded-3xl bg-blue-1000 p-3.5 text-white">
|
||||
<ReactMarkdown className="whitespace-pre-wrap break-words">
|
||||
<ReactMarkdown className="whitespace-pre-wrap break-all">
|
||||
{message}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import { useEffect, RefObject } from 'react';
|
||||
|
||||
export function useOutsideAlerter<T extends HTMLElement>(
|
||||
ref: RefObject<T>,
|
||||
handler: () => void,
|
||||
additionalDeps: unknown[],
|
||||
) {
|
||||
useEffect(() => {
|
||||
function handleClickOutside(this: Document, event: MouseEvent) {
|
||||
if (ref.current && !ref.current.contains(event.target as Node)) {
|
||||
handler();
|
||||
}
|
||||
}
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
};
|
||||
}, [ref, ...additionalDeps]);
|
||||
}
|
||||
45
frontend/src/hooks/index.ts
Normal file
45
frontend/src/hooks/index.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { useEffect, RefObject, useState } from 'react';
|
||||
|
||||
export function useOutsideAlerter<T extends HTMLElement>(
|
||||
ref: RefObject<T>,
|
||||
handler: () => void,
|
||||
additionalDeps: unknown[],
|
||||
) {
|
||||
useEffect(() => {
|
||||
function handleClickOutside(this: Document, event: MouseEvent) {
|
||||
if (ref.current && !ref.current.contains(event.target as Node)) {
|
||||
handler();
|
||||
}
|
||||
}
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
};
|
||||
}, [ref, ...additionalDeps]);
|
||||
}
|
||||
|
||||
// Use isMobile for checking if the width is in the expected mobile range (less than 768px)
|
||||
// use IsDesktop for effects you explicitly only want when width is wider than 960px.
|
||||
export function useMediaQuery() {
|
||||
const mobileQuery = '(max-width: 768px)';
|
||||
const desktopQuery = '(min-width: 960px)';
|
||||
const [isMobile, setIsMobile] = useState(false);
|
||||
const [isDesktop, setIsDesktop] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const mobileMedia = window.matchMedia(mobileQuery);
|
||||
const desktopMedia = window.matchMedia(desktopQuery);
|
||||
const updateMediaQueries = () => {
|
||||
setIsMobile(mobileMedia.matches);
|
||||
setIsDesktop(desktopMedia.matches);
|
||||
};
|
||||
updateMediaQueries();
|
||||
const listener = () => updateMediaQueries();
|
||||
window.addEventListener('resize', listener);
|
||||
return () => {
|
||||
window.removeEventListener('resize', listener);
|
||||
};
|
||||
}, [mobileQuery, desktopQuery]);
|
||||
|
||||
return { isMobile, isDesktop };
|
||||
}
|
||||
@@ -358,3 +358,9 @@ template {
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
[contentEditable]:empty:before {
|
||||
content: attr(placeholder);
|
||||
color: #9ca3af;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useRef, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { ActiveState } from '../models/misc';
|
||||
import { selectApiKey, setApiKey } from './preferenceSlice';
|
||||
import { useOutsideAlerter } from './../hooks';
|
||||
import { useMediaQuery, useOutsideAlerter } from './../hooks';
|
||||
import Modal from '../Modal';
|
||||
|
||||
export default function APIKeyModal({
|
||||
@@ -19,14 +19,12 @@ export default function APIKeyModal({
|
||||
const [key, setKey] = useState(apiKey);
|
||||
const [isError, setIsError] = useState(false);
|
||||
const modalRef = useRef(null);
|
||||
const { isMobile } = useMediaQuery();
|
||||
|
||||
useOutsideAlerter(
|
||||
modalRef,
|
||||
() => {
|
||||
if (
|
||||
window.matchMedia('(max-width: 768px)').matches &&
|
||||
modalState === 'ACTIVE'
|
||||
) {
|
||||
if (isMobile && modalState === 'ACTIVE') {
|
||||
setModalState('INACTIVE');
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user