diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 7b6f11d61..496be3389 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -20,6 +20,7 @@
"react-dropzone": "^14.3.5",
"react-helmet": "^6.1.0",
"react-i18next": "^15.0.2",
+ "react-icons": "^5.3.0",
"react-markdown": "^9.0.1",
"react-redux": "^8.0.5",
"react-router-dom": "^6.8.1",
@@ -7885,6 +7886,14 @@
}
}
},
+ "node_modules/react-icons": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.3.0.tgz",
+ "integrity": "sha512-DnUk8aFbTyQPSkCfF8dbX6kQjXA9DktMeJqfjrg6cK9vwQVMxmcA3BfP4QoiztVmEHtwlTgLFsPuH2NskKT6eg==",
+ "peerDependencies": {
+ "react": "*"
+ }
+ },
"node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index 868a72ae5..f45d99272 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -31,6 +31,7 @@
"react-helmet": "^6.1.0",
"react-dropzone": "^14.3.5",
"react-i18next": "^15.0.2",
+ "react-icons": "^5.3.0",
"react-markdown": "^9.0.1",
"react-redux": "^8.0.5",
"react-router-dom": "^6.8.1",
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index ba0a4bd7c..9dd9ceb93 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -10,6 +10,7 @@ import './locale/i18n';
import { Outlet } from 'react-router-dom';
import { SharedConversation } from './conversation/SharedConversation';
import { useDarkTheme } from './hooks';
+import Onboarding from './components/Onboarding';
function MainLayout() {
const { isMobile } = useMediaQuery();
@@ -33,20 +34,26 @@ function MainLayout() {
export default function App() {
const [, , componentMounted] = useDarkTheme();
+ const [isOnboarding] = useState(false);
+
if (!componentMounted) {
return
;
}
return (
-
- }>
- } />
- } />
- } />
-
- } />
- } />
-
+ {isOnboarding ? (
+
+ ) : (
+
+ }>
+ } />
+ } />
+ } />
+
+ } />
+ } />
+
+ )}
);
}
diff --git a/frontend/src/assets/Back.svg b/frontend/src/assets/Back.svg
new file mode 100644
index 000000000..779962abd
--- /dev/null
+++ b/frontend/src/assets/Back.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/frontend/src/assets/Logo.svg b/frontend/src/assets/Logo.svg
new file mode 100644
index 000000000..4d6fb7bf7
--- /dev/null
+++ b/frontend/src/assets/Logo.svg
@@ -0,0 +1,9 @@
+
diff --git a/frontend/src/assets/bg.svg b/frontend/src/assets/bg.svg
new file mode 100644
index 000000000..6d530e156
--- /dev/null
+++ b/frontend/src/assets/bg.svg
@@ -0,0 +1,14 @@
+
diff --git a/frontend/src/assets/globe.svg b/frontend/src/assets/globe.svg
new file mode 100644
index 000000000..28c54e667
--- /dev/null
+++ b/frontend/src/assets/globe.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/frontend/src/assets/profie.png b/frontend/src/assets/profie.png
new file mode 100644
index 000000000..20809b182
Binary files /dev/null and b/frontend/src/assets/profie.png differ
diff --git a/frontend/src/components/CollectFromWebsiteForm.tsx b/frontend/src/components/CollectFromWebsiteForm.tsx
new file mode 100644
index 000000000..c7a1427c4
--- /dev/null
+++ b/frontend/src/components/CollectFromWebsiteForm.tsx
@@ -0,0 +1,131 @@
+import React, { useState } from 'react';
+import { FiLoader } from 'react-icons/fi';
+
+const CollectFromWebsiteForm: React.FC = () => {
+ const [isFetching, setIsFetching] = useState(false);
+ const [formData, setFormData] = useState({
+ name: '',
+ link: '',
+ });
+ const [selectedOption, setSelectedOption] = useState('From URL');
+
+ const handleChange = (e: React.ChangeEvent) => {
+ const { name, value } = e.target;
+ setFormData({
+ ...formData,
+ [name]: value,
+ });
+ };
+
+ const handleLinkPaste = (e: React.ChangeEvent) => {
+ const { value } = e.target;
+ setFormData({
+ ...formData,
+ link: value,
+ });
+
+ // Simulate fetching animation for link paste
+ setIsFetching(true);
+ setTimeout(() => {
+ setIsFetching(false);
+ // Handle link fetching logic here (API call, etc.)
+ }, 2000);
+ };
+
+ const handleSelectChange = (e: React.ChangeEvent) => {
+ setSelectedOption(e.target.value);
+ };
+
+ return (
+
+
+ Collect from a website
+
+
+
+ {/* Custom styled dropdown */}
+
+
+ {/* Custom dropdown arrow */}
+
+
+
+ {/* Name input */}
+
+
+
+
+
+ {/* Link input */}
+
+
+
+
+
+ {/* Fetching status */}
+ {/* Fetching status */}
+
+
+ {isFetching ? (
+ <>
+
+ Fetching
+
+
+ >
+ ) : (
+ <>
+
Fetching
+
+ >
+ )}
+
+
+
+
+ );
+};
+
+export default CollectFromWebsiteForm;
diff --git a/frontend/src/components/Onboarding.tsx b/frontend/src/components/Onboarding.tsx
new file mode 100644
index 000000000..e2de287b7
--- /dev/null
+++ b/frontend/src/components/Onboarding.tsx
@@ -0,0 +1,392 @@
+'use client';
+
+import { useState } from 'react';
+import Globe from '../assets/globe.svg';
+import Profile from '../assets/profie.png';
+import Logo from '../assets/Logo.svg';
+import L2C1 from '../assets/file_upload.svg';
+import L2C2 from '../assets/website_collect.svg';
+import Back from '../assets/Back.svg';
+import UploadFromDeviceForm from './UploadFromDeviceForm';
+import CollectFromWebsiteForm from './CollectFromWebsiteForm';
+import { useDarkTheme } from '../hooks';
+
+interface LevelIndicatorProps {
+ currentLevel: number;
+ indicatorLevel: number;
+}
+
+const LevelIndicator: React.FC = ({
+ currentLevel,
+ indicatorLevel,
+}) => {
+ const isActive = currentLevel === indicatorLevel;
+ const isPast = currentLevel > indicatorLevel;
+
+ return (
+
+ );
+};
+
+export default function Onboarding() {
+ const [language] = useState('EN');
+ const [level, setLevel] = useState(1);
+ const [selectedCard, setSelectedCard] = useState(null);
+ const [isLoading, setIsLoading] = useState(false);
+ const [progress, setProgress] = useState(0);
+ const [isFinalPage, setIsFinalPage] = useState(false);
+ const [isTrainingComplete, setIsTrainingComplete] = useState(false);
+ const [isDarkTheme, toggleTheme] = useDarkTheme();
+
+ const handleLevelUp = () => {
+ if (level < 3) {
+ setLevel((prevLevel) => prevLevel + 1);
+ }
+ };
+
+ const handleLevelDown = () => {
+ if (level > 1) {
+ setLevel((prevLevel) => prevLevel - 1);
+ }
+ setSelectedCard(null);
+ };
+
+ const handleCardSelect = (card: number) => {
+ setSelectedCard(card);
+ };
+
+ const handleFinalButtonClick = () => {
+ setIsLoading(true);
+ setIsFinalPage(true);
+
+ const updateProgress = (current: number) => {
+ if (current >= 100) {
+ setProgress(100);
+ setIsLoading(false);
+ setIsTrainingComplete(true);
+ return;
+ }
+ setProgress(current);
+ setTimeout(() => updateProgress(current + 10), 400);
+ };
+
+ updateProgress(0);
+ };
+
+ const getGradientForLevel = (level: number): string => {
+ switch (level) {
+ case 1:
+ return `relative bg-gradient-to-br from-green-200 via-white to-white dark:from-[#222327] dark:to-black`; // Light greenish tone
+ case 2:
+ return 'bg-gradient-to-br from-pink-300 via-white to-white dark:from-[#222327] dark:to-black'; // Light pink tone
+ case 3:
+ return `bg-gradient-to-br ${isTrainingComplete == true ? 'from-green-200 ' : 'from-orange-100 '} via-white to-white dark:from-[#222327] dark:to-black`; // Light orange tone
+ default:
+ return 'bg-white dark:bg-gray-900'; // Default light and dark backgrounds
+ }
+ };
+
+ const renderContentForLevel = (level: number) => {
+ if (isFinalPage) {
+ return (
+
+
+ {isTrainingComplete == true ? (
+
+
+ Training Complete !
+
+
+ ) : (
+ <>
+ {' '}
+ {/* Heading */}
+
+ Training is in progress...
+
+ {/* Subheading */}
+
+ This may take several minutes
+
+ >
+ )}
+
+
+
+ {/* New Progress Bar */}
+
+
+
+
+
+ {progress}%
+
+
+
+
+ {/* Keyframes for rotation animation */}
+
+
+
+
+ {!isLoading && (
+
+ )}
+ {isLoading && (
+
+ )}
+
+ );
+ }
+
+ switch (level) {
+ case 1:
+ return (
+ <>
+
+
+
+ Welcome to DocsGPT
+
+
+ Your technical documentation assistant.
+
+
+ >
+ );
+ case 2:
+ return (
+
+ {/* Logo */}
+
+
+ {/* Heading */}
+
+ Upload from device or from web?
+
+
+ {/* Subheading */}
+
+ You can choose how to add your first document to DocsGPT
+
+
+ {/* Card Container */}
+
+ {/* Card 1 */}
+
+
handleCardSelect(1)}
+ >
+
+
+
+
+
+ Upload from device
+
+
+
+ {/* Card 2 */}
+
+
handleCardSelect(2)}
+ >
+
+
+
+
+
+ Collect from a website
+
+
+
+
+ );
+ case 3:
+ return (
+
+
+
+ {/* Heading */}
+
+ Upload new document
+
+ {selectedCard === 1 ? (
+
+ ) : (
+
+ )}
+ {isLoading && (
+
+ )}
+
+ );
+ default:
+ return null;
+ }
+ };
+
+ return (
+
+
+ {/* alternate background */}
+ {/*
+
+
*/}
+
+
+ {/* Left Section */}
+
+ {/* Left Section */}
+
+
{language}
+
+
+
+
+ {/* Profile Section */}
+
+
+
+
+
+
+ {/* Main content section */}
+
+ {renderContentForLevel(level)}
+
+ {!isFinalPage && (
+ <>
+
+
+ {[1, 2, 3].map((num) => (
+
+ ))}
+
+
+
+
+ {level === 3 && !isLoading && (
+
+ )}
+
+
+ >
+ )}
+
+
+ );
+}
diff --git a/frontend/src/components/UploadFromDeviceForm.tsx b/frontend/src/components/UploadFromDeviceForm.tsx
new file mode 100644
index 000000000..da08328a1
--- /dev/null
+++ b/frontend/src/components/UploadFromDeviceForm.tsx
@@ -0,0 +1,106 @@
+import { useState } from 'react';
+
+interface UploadFromDeviceForm {
+ language: string;
+ level: number;
+ selectedCard: number | null;
+ isLoading: boolean;
+ progress: number;
+ isFinalPage: boolean;
+}
+
+export default function UploadFromDeviceForm() {
+ const [uploadedFiles, setUploadedFiles] = useState([]);
+ const [isLoading, setIsLoading] = useState(false);
+
+ const handleFileUpload = async (e: React.ChangeEvent) => {
+ const file = e.target.files ? e.target.files[0] : null;
+ if (file && file.size <= 25 * 1024 * 1024) {
+ setIsLoading(true);
+ await new Promise((resolve) => setTimeout(resolve, 2000));
+ setUploadedFiles([...uploadedFiles, file.name]);
+ setIsLoading(false);
+ } else {
+ alert('File size exceeds 25MB or invalid file format');
+ }
+ };
+
+ return (
+
+
+ Upload from device
+
+
+ {/* Name Input with better styling */}
+
+
+
+
+
+ {/* File Upload Button */}
+
+
+
+
+ {/* File type information */}
+
+
+ Please upload .pdf, .txt, .rst, .docx, .md, .zip limited to 25mb
+
+
+
+
+ {/* Uploaded Files Section */}
+
+
+
+ Uploaded Files
+
+
+ {isLoading ? (
+ <>
+
+ Fetching
+
+
+ >
+ ) : (
+ <>
+
Fetching
+
+ >
+ )}
+
+
+
+ {!isLoading && uploadedFiles.length === 0 ? (
+
None
+ ) : (
+
+ {uploadedFiles.map((file, index) => (
+ -
+ {file}
+
+ ))}
+
+ )}
+
+
+ );
+}
diff --git a/frontend/src/components/a.tsx b/frontend/src/components/a.tsx
new file mode 100644
index 000000000..e2de287b7
--- /dev/null
+++ b/frontend/src/components/a.tsx
@@ -0,0 +1,392 @@
+'use client';
+
+import { useState } from 'react';
+import Globe from '../assets/globe.svg';
+import Profile from '../assets/profie.png';
+import Logo from '../assets/Logo.svg';
+import L2C1 from '../assets/file_upload.svg';
+import L2C2 from '../assets/website_collect.svg';
+import Back from '../assets/Back.svg';
+import UploadFromDeviceForm from './UploadFromDeviceForm';
+import CollectFromWebsiteForm from './CollectFromWebsiteForm';
+import { useDarkTheme } from '../hooks';
+
+interface LevelIndicatorProps {
+ currentLevel: number;
+ indicatorLevel: number;
+}
+
+const LevelIndicator: React.FC = ({
+ currentLevel,
+ indicatorLevel,
+}) => {
+ const isActive = currentLevel === indicatorLevel;
+ const isPast = currentLevel > indicatorLevel;
+
+ return (
+
+ );
+};
+
+export default function Onboarding() {
+ const [language] = useState('EN');
+ const [level, setLevel] = useState(1);
+ const [selectedCard, setSelectedCard] = useState(null);
+ const [isLoading, setIsLoading] = useState(false);
+ const [progress, setProgress] = useState(0);
+ const [isFinalPage, setIsFinalPage] = useState(false);
+ const [isTrainingComplete, setIsTrainingComplete] = useState(false);
+ const [isDarkTheme, toggleTheme] = useDarkTheme();
+
+ const handleLevelUp = () => {
+ if (level < 3) {
+ setLevel((prevLevel) => prevLevel + 1);
+ }
+ };
+
+ const handleLevelDown = () => {
+ if (level > 1) {
+ setLevel((prevLevel) => prevLevel - 1);
+ }
+ setSelectedCard(null);
+ };
+
+ const handleCardSelect = (card: number) => {
+ setSelectedCard(card);
+ };
+
+ const handleFinalButtonClick = () => {
+ setIsLoading(true);
+ setIsFinalPage(true);
+
+ const updateProgress = (current: number) => {
+ if (current >= 100) {
+ setProgress(100);
+ setIsLoading(false);
+ setIsTrainingComplete(true);
+ return;
+ }
+ setProgress(current);
+ setTimeout(() => updateProgress(current + 10), 400);
+ };
+
+ updateProgress(0);
+ };
+
+ const getGradientForLevel = (level: number): string => {
+ switch (level) {
+ case 1:
+ return `relative bg-gradient-to-br from-green-200 via-white to-white dark:from-[#222327] dark:to-black`; // Light greenish tone
+ case 2:
+ return 'bg-gradient-to-br from-pink-300 via-white to-white dark:from-[#222327] dark:to-black'; // Light pink tone
+ case 3:
+ return `bg-gradient-to-br ${isTrainingComplete == true ? 'from-green-200 ' : 'from-orange-100 '} via-white to-white dark:from-[#222327] dark:to-black`; // Light orange tone
+ default:
+ return 'bg-white dark:bg-gray-900'; // Default light and dark backgrounds
+ }
+ };
+
+ const renderContentForLevel = (level: number) => {
+ if (isFinalPage) {
+ return (
+
+
+ {isTrainingComplete == true ? (
+
+
+ Training Complete !
+
+
+ ) : (
+ <>
+ {' '}
+ {/* Heading */}
+
+ Training is in progress...
+
+ {/* Subheading */}
+
+ This may take several minutes
+
+ >
+ )}
+
+
+
+ {/* New Progress Bar */}
+
+
+
+
+
+ {progress}%
+
+
+
+
+ {/* Keyframes for rotation animation */}
+
+
+
+
+ {!isLoading && (
+
+ )}
+ {isLoading && (
+
+ )}
+
+ );
+ }
+
+ switch (level) {
+ case 1:
+ return (
+ <>
+
+
+
+ Welcome to DocsGPT
+
+
+ Your technical documentation assistant.
+
+
+ >
+ );
+ case 2:
+ return (
+
+ {/* Logo */}
+
+
+ {/* Heading */}
+
+ Upload from device or from web?
+
+
+ {/* Subheading */}
+
+ You can choose how to add your first document to DocsGPT
+
+
+ {/* Card Container */}
+
+ {/* Card 1 */}
+
+
handleCardSelect(1)}
+ >
+
+
+
+
+
+ Upload from device
+
+
+
+ {/* Card 2 */}
+
+
handleCardSelect(2)}
+ >
+
+
+
+
+
+ Collect from a website
+
+
+
+
+ );
+ case 3:
+ return (
+
+
+
+ {/* Heading */}
+
+ Upload new document
+
+ {selectedCard === 1 ? (
+
+ ) : (
+
+ )}
+ {isLoading && (
+
+ )}
+
+ );
+ default:
+ return null;
+ }
+ };
+
+ return (
+
+
+ {/* alternate background */}
+ {/*
+
+
*/}
+
+
+ {/* Left Section */}
+
+ {/* Left Section */}
+
+
{language}
+
+
+
+
+ {/* Profile Section */}
+
+
+
+
+
+
+ {/* Main content section */}
+
+ {renderContentForLevel(level)}
+
+ {!isFinalPage && (
+ <>
+
+
+ {[1, 2, 3].map((num) => (
+
+ ))}
+
+
+
+
+ {level === 3 && !isLoading && (
+
+ )}
+
+
+ >
+ )}
+
+
+ );
+}
diff --git a/frontend/src/index.css b/frontend/src/index.css
index 91f1403dd..c8d292903 100644
--- a/frontend/src/index.css
+++ b/frontend/src/index.css
@@ -544,3 +544,22 @@ input:-webkit-autofill:focus {
transform: translateY(0);
}
}
+
+.irregular-circle {
+ clip-path: ellipse(50% 48% at 50% 50%);
+}
+
+
+@keyframes fadeInSlideUp {
+ 0% {
+ opacity: 0;
+ transform: translateY(20px);
+ }
+ 100% {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+.animate-fadeInSlideUp {
+ animation: fadeInSlideUp 1s forwards;
+}
\ No newline at end of file
diff --git a/frontend/tailwind.config.cjs b/frontend/tailwind.config.cjs
index 938eaceee..6e33e6739 100644
--- a/frontend/tailwind.config.cjs
+++ b/frontend/tailwind.config.cjs
@@ -50,6 +50,12 @@ module.exports = {
'philippine-yellow':'#FFC700',
'bright-gray':'#EBEBEB'
},
+ boxShadow: {
+ 'all-sides': '0 4px 8px rgba(0, 0, 0, 0.05), 0 -4px 8px rgba(0, 0, 0, 0.05), 4px 0 8px rgba(0, 0, 0, 0.05), -4px 0 8px rgba(0, 0, 0, 0.05)',
+ 'all-sides-hover': '0 8px 16px rgba(0, 0, 0, 0.09), 0 -8px 16px rgba(0, 0, 0, 0.09), 8px 0 16px rgba(0, 0, 0, 0.09), -8px 0 16px rgba(0, 0, 0, 0.09)',
+ // 'all-sides-dark': '0 8px 16px rgba(255, 255, 255, 0.1), 0 -8px 16px rgba(255, 255, 255, 0.1), 8px 0 16px rgba(255, 255, 255, 0.1), -8px 0 16px rgba(255, 255, 255, 0.1)',
+ // 'all-sides-dark-hover': '0 8px 16px rgba(255, 255, 255, 0.2), 0 -8px 16px rgba(255, 255, 255, 0.2), 8px 0 16px rgba(255, 255, 255, 0.2), -8px 0 16px rgba(255, 255, 255, 0.2)',
+ },
},
},
plugins: [],