mirror of
https://github.com/arc53/DocsGPT.git
synced 2025-11-29 16:43:16 +00:00
refactor App, add /shared/id page
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { Routes, Route } from 'react-router-dom';
|
||||
import { ReactElement, useEffect } from 'react';
|
||||
import Navigation from './Navigation';
|
||||
import Conversation from './conversation/Conversation';
|
||||
import About from './About';
|
||||
@@ -8,29 +9,93 @@ import { useMediaQuery } from './hooks';
|
||||
import { useState } from 'react';
|
||||
import Setting from './settings';
|
||||
import './locale/i18n';
|
||||
|
||||
import SharedConversation from './conversation/SharedConversation';
|
||||
import { useDarkTheme } from './hooks';
|
||||
inject();
|
||||
|
||||
export default function App() {
|
||||
function MainLayout({ children }: { children: ReactElement }) {
|
||||
const { isMobile } = useMediaQuery();
|
||||
const [navOpen, setNavOpen] = useState(!isMobile);
|
||||
return (
|
||||
<div className="min-h-full min-w-full dark:bg-raisin-black">
|
||||
<>
|
||||
<Navigation navOpen={navOpen} setNavOpen={setNavOpen} />
|
||||
<div
|
||||
className={`transition-all duration-200 ${
|
||||
className={` h-full dark:bg-raisin-black ${
|
||||
!isMobile
|
||||
? `ml-0 ${!navOpen ? 'md:mx-auto lg:mx-auto' : 'md:ml-72'}`
|
||||
: 'ml-0 md:ml-16'
|
||||
}`}
|
||||
>
|
||||
<Routes>
|
||||
<Route path="/" element={<Conversation />} />
|
||||
<Route path="/about" element={<About />} />
|
||||
<Route path="*" element={<PageNotFound />} />
|
||||
<Route path="/settings" element={<Setting />} />
|
||||
</Routes>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function Layout({ children }: { children: ReactElement }) {
|
||||
return (
|
||||
<>
|
||||
<div className={`h-full dark:bg-raisin-black`}>{children}</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
export default function App() {
|
||||
const [isDarkTheme] = useDarkTheme();
|
||||
useEffect(() => {
|
||||
localStorage.setItem('selectedTheme', isDarkTheme ? 'Dark' : 'Light');
|
||||
if (isDarkTheme) {
|
||||
document
|
||||
.getElementById('root')
|
||||
?.classList.add('dark', 'dark:bg-raisin-black');
|
||||
} else {
|
||||
document.getElementById('root')?.classList.remove('dark');
|
||||
}
|
||||
}, [isDarkTheme]);
|
||||
return (
|
||||
<>
|
||||
<Routes>
|
||||
<Route
|
||||
path="/"
|
||||
element={
|
||||
<MainLayout>
|
||||
<Conversation />
|
||||
</MainLayout>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/about"
|
||||
element={
|
||||
<MainLayout>
|
||||
<About />
|
||||
</MainLayout>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/settings"
|
||||
element={
|
||||
<MainLayout>
|
||||
<Setting />
|
||||
</MainLayout>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/share/:identifier"
|
||||
element={
|
||||
<Layout>
|
||||
<SharedConversation />
|
||||
</Layout>
|
||||
}
|
||||
/>
|
||||
{/* default page */}
|
||||
<Route
|
||||
path="/*"
|
||||
element={
|
||||
<Layout>
|
||||
<PageNotFound />
|
||||
</Layout>
|
||||
}
|
||||
/>
|
||||
</Routes>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
146
frontend/src/conversation/SharedConversation.tsx
Normal file
146
frontend/src/conversation/SharedConversation.tsx
Normal file
@@ -0,0 +1,146 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Query } from './conversationModels';
|
||||
import ConversationBubble from './ConversationBubble';
|
||||
import { Fragment } from 'react';
|
||||
const apiHost = import.meta.env.VITE_API_HOST || 'https://docsapi.arc53.com';
|
||||
const SharedConversation = () => {
|
||||
const params = useParams();
|
||||
const navigate = useNavigate();
|
||||
const { identifier } = params; //identifier is a uuid, not conversationId
|
||||
const [queries, setQueries] = useState<Query[]>([]);
|
||||
const [title, setTitle] = useState('');
|
||||
const [date, setDate] = useState('');
|
||||
|
||||
function formatISODate(isoDateStr: string) {
|
||||
const date = new Date(isoDateStr);
|
||||
|
||||
const monthNames = [
|
||||
'Jan',
|
||||
'Feb',
|
||||
'Mar',
|
||||
'Apr',
|
||||
'May',
|
||||
'June',
|
||||
'July',
|
||||
'Aug',
|
||||
'Sept',
|
||||
'Oct',
|
||||
'Nov',
|
||||
'Dec',
|
||||
];
|
||||
|
||||
const month = monthNames[date.getMonth()];
|
||||
const day = date.getDate();
|
||||
const year = date.getFullYear();
|
||||
|
||||
let hours = date.getHours();
|
||||
const minutes = date.getMinutes();
|
||||
const ampm = hours >= 12 ? 'PM' : 'AM';
|
||||
|
||||
hours = hours % 12;
|
||||
hours = hours ? hours : 12;
|
||||
const minutesStr = minutes < 10 ? '0' + minutes : minutes;
|
||||
const formattedDate = `Published ${month} ${day}, ${year} at ${hours}:${minutesStr} ${ampm}`;
|
||||
return formattedDate;
|
||||
}
|
||||
const fetchQueris = () => {
|
||||
fetch(`${apiHost}/api/shared_conversation/${identifier}`)
|
||||
.then((res) => {
|
||||
if (res.status === 404 || res.status === 400) navigate('/pagenotfound');
|
||||
return res.json();
|
||||
})
|
||||
.then((data) => {
|
||||
if (data.success) {
|
||||
setQueries(data.queries);
|
||||
setTitle(data.title);
|
||||
setDate(formatISODate(data.timestamp));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const prepResponseView = (query: Query, index: number) => {
|
||||
let responseView;
|
||||
if (query.response) {
|
||||
responseView = (
|
||||
<ConversationBubble
|
||||
className={`${index === queries.length - 1 ? 'mb-32' : 'mb-7'}`}
|
||||
key={`${index}ANSWER`}
|
||||
message={query.response}
|
||||
type={'ANSWER'}
|
||||
></ConversationBubble>
|
||||
);
|
||||
} else if (query.error) {
|
||||
responseView = (
|
||||
<ConversationBubble
|
||||
className={`${index === queries.length - 1 ? 'mb-32' : 'mb-7'} `}
|
||||
key={`${index}ERROR`}
|
||||
message={query.error}
|
||||
type="ERROR"
|
||||
></ConversationBubble>
|
||||
);
|
||||
}
|
||||
return responseView;
|
||||
};
|
||||
useEffect(() => {
|
||||
fetchQueris();
|
||||
}, []);
|
||||
return (
|
||||
<div className="">
|
||||
<div className="flex h-screen flex-col items-center justify-between">
|
||||
{queries.length > 0 && (
|
||||
<div className="flex w-full justify-center overflow-auto">
|
||||
<div className="mt-0 w-11/12 md:w-6/12">
|
||||
<div className="w-full border-b pb-2">
|
||||
<h1 className="font-semi-bold text-4xl text-chinese-black dark:text-chinese-silver">
|
||||
{title}
|
||||
</h1>
|
||||
<h2 className="font-semi-bold text-base text-chinese-black dark:text-chinese-silver">
|
||||
Created with{' '}
|
||||
<a href="/" className="text-[#007DFF]">
|
||||
DocsGPT
|
||||
</a>
|
||||
</h2>
|
||||
<h2 className="font-semi-bold text-base text-chinese-black dark:text-chinese-silver">
|
||||
{date}
|
||||
</h2>
|
||||
</div>
|
||||
<div className="pt-2">
|
||||
{queries.map((query, index) => {
|
||||
return (
|
||||
<Fragment key={index}>
|
||||
<ConversationBubble
|
||||
className={'mb-1 last:mb-28 md:mb-7'}
|
||||
key={`${index}QUESTION`}
|
||||
message={query.prompt}
|
||||
type="QUESTION"
|
||||
sources={query.sources}
|
||||
></ConversationBubble>
|
||||
|
||||
{prepResponseView(query, index)}
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className=" flex flex-col items-center gap-4 p-4">
|
||||
<button
|
||||
onClick={() => navigate('/')}
|
||||
className="w-fit rounded-full bg-purple-30 p-4 text-white shadow-xl transition-colors duration-200 hover:bg-purple-taupe"
|
||||
>
|
||||
Get Started with DocsGPT
|
||||
</button>
|
||||
<span className="hidden text-xs text-dark-charcoal dark:text-silver sm:inline">
|
||||
This is a chatbot that uses the GPT-3, Faiss and LangChain to answer
|
||||
questions.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SharedConversation;
|
||||
@@ -77,21 +77,23 @@ export function useDarkTheme() {
|
||||
// Set dark mode based on local storage preference
|
||||
if (savedMode === 'Dark') {
|
||||
setIsDarkTheme(true);
|
||||
document.documentElement.classList.add('dark');
|
||||
document.documentElement.classList.add('dark:bg-raisin-black');
|
||||
document
|
||||
.getElementById('root')
|
||||
?.classList.add('dark', 'dark:bg-raisin-black');
|
||||
} else {
|
||||
// If no preference found, set to default (light mode)
|
||||
setIsDarkTheme(false);
|
||||
document.documentElement.classList.remove('dark');
|
||||
document.getElementById('root')?.classList.remove('dark');
|
||||
}
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
localStorage.setItem('selectedTheme', isDarkTheme ? 'Dark' : 'Light');
|
||||
if (isDarkTheme) {
|
||||
document.documentElement.classList.add('dark');
|
||||
document.documentElement.classList.add('dark:bg-raisin-black');
|
||||
document
|
||||
.getElementById('root')
|
||||
?.classList.add('dark', 'dark:bg-raisin-black');
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
document.getElementById('root')?.classList.remove('dark');
|
||||
}
|
||||
}, [isDarkTheme]);
|
||||
//method to toggle theme
|
||||
|
||||
@@ -57,13 +57,13 @@ export const ShareConversationModal = ({
|
||||
remain private
|
||||
</p>
|
||||
<div className="flex items-baseline justify-between gap-2">
|
||||
<span className="no-scrollbar w-full overflow-x-auto whitespace-nowrap rounded-full border-2 p-3 shadow-inner">{`${domain}/shared/${
|
||||
<span className="no-scrollbar w-full overflow-x-auto whitespace-nowrap rounded-full border-2 p-3 shadow-inner">{`${domain}/share/${
|
||||
identifier ?? '....'
|
||||
}`}</span>
|
||||
{status === 'fetched' ? (
|
||||
<button
|
||||
className="my-1 h-10 w-36 rounded-full border border-solid border-purple-30 p-2 text-sm text-purple-30 hover:bg-purple-30 hover:text-white"
|
||||
onClick={() => handleCopyKey(`${domain}/shared/${identifier}`)}
|
||||
onClick={() => handleCopyKey(`${domain}/share/${identifier}`)}
|
||||
>
|
||||
{isCopied
|
||||
? t('modals.saveKey.copied')
|
||||
|
||||
Reference in New Issue
Block a user