diff --git a/README.md b/README.md index 9f84994..88a88bf 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Edison: The Creative Electronics Companion +![Banner](./docs/banner.png) + ## Project Overview Welcome to Edison: The Creative Electronics Companion! This project is designed to assist users in creating exciting projects with various electronic parts. By utilizing advanced Language Models (LLMs) and the Gemini API, our system identifies electronic components from images and provides users with detailed, step-by-step project tutorials. Additionally, LlamaIndex is employed for effective data management and retrieval, ensuring an interactive and seamless user experience. diff --git a/client/package-lock.json b/client/package-lock.json index 61ba87f..1d5d63a 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -25,7 +25,7 @@ "axios": "^1.7.2", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", - "framer-motion": "^11.3.21", + "framer-motion": "^11.3.24", "html-react-parser": "^5.1.12", "html2canvas": "^1.4.1", "html2pdf.js": "^0.10.2", @@ -36,6 +36,7 @@ "pdfjs-dist": "^3.11.174", "prism-react-renderer": "^2.3.1", "react": "^18.3.1", + "react-before-after-slider-component": "^1.1.8", "react-dom": "^18.3.1", "react-markdown": "^9.0.1", "react-scripts": "5.0.1", @@ -43,7 +44,7 @@ "react-zoom-pan-pinch": "^3.6.1", "rehype-raw": "^7.0.0", "remark-gfm": "^4.0.0", - "tailwind-merge": "^2.5.0", + "tailwind-merge": "^2.5.1", "tailwindcss-animate": "^1.0.7", "typescript": "^4.9.5", "web-vitals": "^2.1.4" @@ -6866,7 +6867,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "license": "MIT", "engines": { "node": ">=6" } @@ -9636,10 +9636,9 @@ } }, "node_modules/framer-motion": { - "version": "11.3.21", - "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.3.21.tgz", - "integrity": "sha512-D+hfIsvzV8eL/iycld4K+tKlg2Q2LdwnrcBEohtGw3cG1AIuNYATbT5RUqIM1ndsAk+EfGhoSGf0UaiFodc5Tw==", - "license": "MIT", + "version": "11.3.24", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.3.24.tgz", + "integrity": "sha512-kl0YI7HwAtyV0VOAWuU/rXoOS8+z5qSkMN6rZS+a9oe6fIha6SC3vjJN6u/hBpvjrg5MQNdSnqnjYxm0WYTX9g==", "dependencies": { "tslib": "^2.4.0" }, @@ -19549,6 +19548,15 @@ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" }, + "node_modules/react-before-after-slider-component": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/react-before-after-slider-component/-/react-before-after-slider-component-1.1.8.tgz", + "integrity": "sha512-KcY231f68+7bF0Zkfat55jvgNSSCB5TkBtm1HhLeb336jtQ0hYKkdq6VwrleNrfeVdUD2v+E7DzgNJYc6dsY3Q==", + "peerDependencies": { + "react": ">=17.0.2", + "react-dom": ">=17.0.2" + } + }, "node_modules/react-dev-utils": { "version": "12.0.1", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz", @@ -21666,10 +21674,9 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" }, "node_modules/tailwind-merge": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.0.tgz", - "integrity": "sha512-a6Q/isR5XAo9IR7Hjh80BQDkn8PG9ONJpSO/U3vGzdKyKG125lPHNXdiPfeQ5X0EOG0qKlS/0qnxdBYkLlD6tA==", - "license": "MIT", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.1.tgz", + "integrity": "sha512-1zKDdExKvNltulO+J0x/Rqv40xQn78FHsEQVn3rxt8e4HdebRIT6o6zGeLYlGuxd3Efue9Y69qsp8vKwEhuEeg==", "funding": { "type": "github", "url": "https://github.com/sponsors/dcastil" diff --git a/client/package.json b/client/package.json index 62da23e..1670fb8 100644 --- a/client/package.json +++ b/client/package.json @@ -20,7 +20,7 @@ "axios": "^1.7.2", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", - "framer-motion": "^11.3.21", + "framer-motion": "^11.3.24", "html-react-parser": "^5.1.12", "html2canvas": "^1.4.1", "html2pdf.js": "^0.10.2", @@ -31,6 +31,7 @@ "pdfjs-dist": "^3.11.174", "prism-react-renderer": "^2.3.1", "react": "^18.3.1", + "react-before-after-slider-component": "^1.1.8", "react-dom": "^18.3.1", "react-markdown": "^9.0.1", "react-scripts": "5.0.1", @@ -38,7 +39,7 @@ "react-zoom-pan-pinch": "^3.6.1", "rehype-raw": "^7.0.0", "remark-gfm": "^4.0.0", - "tailwind-merge": "^2.5.0", + "tailwind-merge": "^2.5.1", "tailwindcss-animate": "^1.0.7", "typescript": "^4.9.5", "web-vitals": "^2.1.4" diff --git a/client/public/Edison.png b/client/public/Edison.png new file mode 100644 index 0000000..6f7ff82 Binary files /dev/null and b/client/public/Edison.png differ diff --git a/client/public/background.jpg b/client/public/background.jpg new file mode 100644 index 0000000..306c9a6 Binary files /dev/null and b/client/public/background.jpg differ diff --git a/client/public/favicon.ico b/client/public/favicon.ico index a11777c..6f7ff82 100644 Binary files a/client/public/favicon.ico and b/client/public/favicon.ico differ diff --git a/client/public/index.html b/client/public/index.html index b66314b..ea2a68b 100644 --- a/client/public/index.html +++ b/client/public/index.html @@ -1,21 +1,39 @@ - - - - - - - - - - - TechBlueprint - - - -
- - + diff --git a/client/public/sample_image.png b/client/public/sample_image.png new file mode 100644 index 0000000..9b20002 Binary files /dev/null and b/client/public/sample_image.png differ diff --git a/client/src/App.tsx b/client/src/App.tsx index 1c86569..a4b9943 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,67 +1,219 @@ -// App.tsx -import React from "react"; +import React from 'react'; import { - AppBar, - Toolbar, - Container, - CssBaseline, - Typography, - Box, - Button, - IconButton, -} from "@mui/material"; -import MenuIcon from "@mui/icons-material/Menu"; -import { ThemeProvider, createTheme } from "@mui/material/styles"; -import UploadPage from "./pages/UploadPage"; + AppBar, + Toolbar, + CssBaseline, + Typography, + Box, + IconButton, + Container, +} from '@mui/material'; +import MenuIcon from '@mui/icons-material/Menu'; +import { ThemeProvider, createTheme, styled } from '@mui/material/styles'; +import UploadPage from './pages/UploadPage'; +import { motion } from 'framer-motion'; +import logo from './assets/logoz.png'; +import { Background } from './pages/components/Background'; +import { BoxesCore } from './ui/background-boxes'; +import { BackgroundBoxes } from './pages/components/Background2'; +import { Background3 } from './pages/components/Background3'; +import { TextGenerateEffect } from './ui/text-generate-effect'; const theme = createTheme({ - palette: { - primary: { - main: "#1976d2", - }, - background: { - default: "#f5f5f5", - }, - }, - typography: { - h1: { - fontSize: "2.5rem", - fontWeight: 700, - color: "#333", - }, - h6: { - fontSize: "1.25rem", - fontWeight: 500, - }, - }, + palette: { + mode: 'dark', // Set the theme to dark mode + primary: { + main: '#bb86fc', + }, + background: { + default: '#0d0d0d', // Adjusted to a more consistent black + paper: '#1c1c1c', // Slightly lighter black for components + }, + text: { + primary: '#ffffff', + secondary: '#b0b0b0', + }, + }, + typography: { + fontFamily: [ + '"Bricolage Grotesque"', + '-apple-system', + 'BlinkMacSystemFont', + '"Segoe UI"', + 'Roboto', + '"Helvetica Neue"', + 'Arial', + 'sans-serif', + '"Apple Color Emoji"', + '"Segoe UI Emoji"', + '"Segoe UI Symbol"', + ].join(','), + h1: { + fontSize: '3rem', + fontWeight: 800, + color: '#ffffff', + letterSpacing: '0.05rem', + marginBottom: '20px', + }, + h6: { + fontSize: '1.2rem', + fontWeight: 500, + color: '#b0b0b0', + }, + }, + // components: { + // MuiAppBar: { + // styleOverrides: { + // root: { + // backgroundColor: '#1c1c1c', + // boxShadow: 'none', + // borderBottom: '1px solid #333', + // }, + // }, + // }, + // }, +}); + +const CustomContainer = styled(Box)({ + padding: '40px', + backgroundColor: '#1c1c1caa', // Consistent with the app's overall theme + borderRadius: '16px', + boxShadow: '0 10px 30px rgba(0, 0, 0, 0.5)', + maxWidth: '900px', + width: '100%', + margin: 'auto', + backdropFilter: 'blur(10px)', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', }); const App: React.FC = () => { - return ( - - - - - - + return ( + + + {/* */} + + + + {/* + + + + + + + + + + + + + + + + - TechBlueprint - - - - - - Welcome to TechBlueprint - - Upload your electronic component images to get project suggestions - and step-by-step guides. - - - - - - ); + Edison + */} + logo + + + + + + + + + + + Snap. Upload. Innovate. Let our AI analyze your Raspberry Pi + components and conjure up personalized project ideas with detailed + PDF guides. + + + + + + + ); }; export default App; diff --git a/client/src/assets/AI Logo.png b/client/src/assets/AI Logo.png new file mode 100644 index 0000000..04cdd06 Binary files /dev/null and b/client/src/assets/AI Logo.png differ diff --git a/client/src/assets/Bot.webp b/client/src/assets/Bot.webp new file mode 100644 index 0000000..a000ff2 Binary files /dev/null and b/client/src/assets/Bot.webp differ diff --git a/client/src/assets/EDISON.png b/client/src/assets/EDISON.png new file mode 100644 index 0000000..6f7ff82 Binary files /dev/null and b/client/src/assets/EDISON.png differ diff --git a/client/src/assets/Raspberry_Pi_4_Model_B_-_Side.jpg b/client/src/assets/Raspberry_Pi_4_Model_B_-_Side.jpg new file mode 100644 index 0000000..c9b6125 Binary files /dev/null and b/client/src/assets/Raspberry_Pi_4_Model_B_-_Side.jpg differ diff --git a/client/src/assets/cover.png b/client/src/assets/cover.png new file mode 100644 index 0000000..0fd0b9d Binary files /dev/null and b/client/src/assets/cover.png differ diff --git a/client/src/assets/darkLogo.png b/client/src/assets/darkLogo.png new file mode 100644 index 0000000..e8be800 Binary files /dev/null and b/client/src/assets/darkLogo.png differ diff --git a/client/src/assets/darkLogoOld.png b/client/src/assets/darkLogoOld.png new file mode 100644 index 0000000..04cdd06 Binary files /dev/null and b/client/src/assets/darkLogoOld.png differ diff --git a/client/src/assets/logo.png b/client/src/assets/logo.png new file mode 100644 index 0000000..10af2f8 Binary files /dev/null and b/client/src/assets/logo.png differ diff --git a/client/src/assets/logoz.png b/client/src/assets/logoz.png new file mode 100644 index 0000000..7904124 Binary files /dev/null and b/client/src/assets/logoz.png differ diff --git a/client/src/assets/raspberrypi_kit.png b/client/src/assets/raspberrypi_kit.png new file mode 100644 index 0000000..69a486e Binary files /dev/null and b/client/src/assets/raspberrypi_kit.png differ diff --git a/client/src/assets/raspberrypi_robot.jpg b/client/src/assets/raspberrypi_robot.jpg new file mode 100644 index 0000000..0867d38 Binary files /dev/null and b/client/src/assets/raspberrypi_robot.jpg differ diff --git a/client/src/components/ui/hover-border-gradient.tsx b/client/src/components/ui/hover-border-gradient.tsx new file mode 100644 index 0000000..3676cbc --- /dev/null +++ b/client/src/components/ui/hover-border-gradient.tsx @@ -0,0 +1,98 @@ +import React, { useState, useEffect, useRef } from "react"; + +import { motion } from "framer-motion"; +import { cn } from "../../lib/utils"; + +type Direction = "TOP" | "LEFT" | "BOTTOM" | "RIGHT"; + +export function HoverBorderGradient({ + children, + containerClassName, + className, + as: Tag = "button", + duration = 1, + clockwise = true, + ...props +}: React.PropsWithChildren< + { + as?: React.ElementType; + containerClassName?: string; + className?: string; + duration?: number; + clockwise?: boolean; + } & React.HTMLAttributes +>) { + const [hovered, setHovered] = useState(false); + const [direction, setDirection] = useState("TOP"); + + const rotateDirection = (currentDirection: Direction): Direction => { + const directions: Direction[] = ["TOP", "LEFT", "BOTTOM", "RIGHT"]; + const currentIndex = directions.indexOf(currentDirection); + const nextIndex = clockwise + ? (currentIndex - 1 + directions.length) % directions.length + : (currentIndex + 1) % directions.length; + return directions[nextIndex]; + }; + + const movingMap: Record = { + TOP: "radial-gradient(20.7% 50% at 50% 0%, hsl(0, 0%, 100%) 0%, rgba(255, 255, 255, 0) 100%)", + LEFT: "radial-gradient(16.6% 43.1% at 0% 50%, hsl(0, 0%, 100%) 0%, rgba(255, 255, 255, 0) 100%)", + BOTTOM: + "radial-gradient(20.7% 50% at 50% 100%, hsl(0, 0%, 100%) 0%, rgba(255, 255, 255, 0) 100%)", + RIGHT: + "radial-gradient(16.2% 41.199999999999996% at 100% 50%, hsl(0, 0%, 100%) 0%, rgba(255, 255, 255, 0) 100%)", + }; + + const highlight = + "radial-gradient(75% 181.15942028985506% at 50% 50%, #3275F8 0%, rgba(255, 255, 255, 0) 100%)"; + + useEffect(() => { + if (!hovered) { + const interval = setInterval(() => { + setDirection((prevState) => rotateDirection(prevState)); + }, duration * 1000); + return () => clearInterval(interval); + } + }, [hovered]); + return ( + ) => { + setHovered(true); + }} + onMouseLeave={() => setHovered(false)} + className={cn( + "relative flex rounded-full border content-center bg-black/20 hover:bg-black/10 transition duration-500 dark:bg-white/20 items-center flex-col flex-nowrap gap-10 h-min justify-center overflow-visible p-px decoration-clone w-fit", + containerClassName + )} + {...props} + > +
+ {children} +
+ +
+ + ); +} diff --git a/client/src/index.css b/client/src/index.css index 4af727d..6e97945 100644 --- a/client/src/index.css +++ b/client/src/index.css @@ -10,65 +10,14 @@ body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } -/* -.title { - font-size: 1.5rem; - font-weight: bold; - margin-top: 1rem; - margin-bottom: 1rem; -} - -.subtitle { - font-size: 1.25rem; - font-weight: bold; - margin-top: 1rem; - margin-bottom: 1rem; -} - -.paragraph { - font-size: 1rem; - margin-top: 0.5rem; - margin-bottom: 0.5rem; -} - -.code-block-container { - position: relative; - margin: 1rem 0; -} -.code-block { - background-color: #f5f5f5; - border: 1px solid #ddd; - border-radius: 4px; - padding: 1rem; - font-family: "Courier New", Courier, monospace; - overflow-x: auto; - white-space: pre-wrap; - word-wrap: break-word; -} - -.inline-code { - background-color: #f5f5f5; - border: 1px solid #ddd; - border-radius: 4px; - padding: 0.2rem 0.4rem; - font-family: "Courier New", Courier, monospace; -} - -.list-item { - margin-bottom: 0.5rem; -} - -h1, -h2, -h3, -h4, -h5, -h6 { - margin-top: 1rem; - margin-bottom: 0.5rem; -} */ .page-break { page-break-before: always; break-before: always; } +.react-before-slider__image { + height: 100% !important; + width: 100% !important; + object-fit: cover !important; + object-position: center center !important; /* Adjusts the image to show the center */ +} diff --git a/client/src/pages/UploadPage.tsx b/client/src/pages/UploadPage.tsx index ff61444..f7d6cda 100644 --- a/client/src/pages/UploadPage.tsx +++ b/client/src/pages/UploadPage.tsx @@ -1,18 +1,13 @@ import React, { useState } from "react"; -import { - Container, - Card, - CardContent, - Box, - CircularProgress, - IconButton, -} from "@mui/material"; +import { Box, CircularProgress, IconButton, Divider } from "@mui/material"; import StopIcon from "@mui/icons-material/Stop"; import { analyzeImage, getProjectDetails } from "../services/api"; import FileUpload from "./components/FileUpload"; import ComponentList from "./components/ComponentList"; import ProjectIdeasList from "./components/ProjectIdeasList"; import ProjectTutorial from "./components/ProjectTutorial"; +import { motion } from "framer-motion"; +import robot from "../assets/Bot.webp"; const UploadPage: React.FC = () => { const [file, setFile] = useState(null); @@ -20,8 +15,8 @@ const UploadPage: React.FC = () => { const [projectIdeas, setProjectIdeas] = useState([]); const [selectedProject, setSelectedProject] = useState(null); const [tutorial, setTutorial] = useState(""); - const [loading, setLoading] = useState(false); // For image analysis and get project details - const [ideasLoading, setIdeasLoading] = useState(false); // For refreshing project ideas + const [loading, setLoading] = useState(false); + const [ideasLoading, setIdeasLoading] = useState(false); const [abortController, setAbortController] = useState(null); @@ -44,26 +39,22 @@ const UploadPage: React.FC = () => { setLoading(false); } }; + const handleGetProjectDetails = async () => { if (file && selectedProject !== null) { - setLoading(true); // Only affect the loading for get project details + setLoading(true); setTutorial(""); const controller = new AbortController(); setAbortController(controller); - let projectOverviewCompleted = false; // Flag to determine when to handle the project title - try { await getProjectDetails( file, selectedProject, (data) => { if (data.project_overview) { - // Handle the project overview first setTutorial((prev) => prev + data.project_overview); - projectOverviewCompleted = true; // Set flag to true after overview } else if (data.section && data.content) { - // Handle the regular section content setTutorial((prev) => prev + data.content); } }, @@ -76,19 +67,19 @@ const UploadPage: React.FC = () => { console.error("Error during streaming:", error); } } - setLoading(false); // End the loading state for get project details + setLoading(false); } }; const handleRefreshIdeas = async () => { if (file) { - setIdeasLoading(true); // Start loading for refresh + setIdeasLoading(true); const data = await analyzeImage(file); console.log("Refreshed Project Ideas: ", data); const projectIdeasData = data.project_ideas || []; setProjectIdeas(projectIdeasData); - setIdeasLoading(false); // Stop loading after refresh + setIdeasLoading(false); } }; @@ -99,51 +90,79 @@ const UploadPage: React.FC = () => { }; return ( - - - + + + - - {components.length > 0 && } - {projectIdeas.length > 0 && ( - - )} - - - {tutorial && } - + {components.length > 0 && } + + {projectIdeas.length > 0 && ( + + )} + + {tutorial && } + {loading && ( - - + + )} {loading && ( )} - - - + + + + {/* Right side: Before and After Image */} + robot { + // (e.currentTarget as HTMLElement).style.transform = "scale(1.05)"; + // }} + // onMouseOut={(e) => { + // (e.currentTarget as HTMLElement).style.transform = "scale(1)"; + // }} + /> + + ); }; diff --git a/client/src/pages/components/Background.tsx b/client/src/pages/components/Background.tsx new file mode 100644 index 0000000..7bc91d2 --- /dev/null +++ b/client/src/pages/components/Background.tsx @@ -0,0 +1,11 @@ +import styles from './background.module.css'; + +export const Background = () => { + return ( +
+
+
+
+
+ ); +}; diff --git a/client/src/pages/components/Background2.tsx b/client/src/pages/components/Background2.tsx new file mode 100644 index 0000000..62973aa --- /dev/null +++ b/client/src/pages/components/Background2.tsx @@ -0,0 +1,10 @@ +import { Boxes } from "../../ui/background-boxes"; + +export function BackgroundBoxes() { + return ( +
+ {/*
*/} + +
+ ); +} diff --git a/client/src/pages/components/Background3.tsx b/client/src/pages/components/Background3.tsx new file mode 100644 index 0000000..1b69ac8 --- /dev/null +++ b/client/src/pages/components/Background3.tsx @@ -0,0 +1,7 @@ +export const Background3 = () => { + return ( +
+ background +
+ ) +} \ No newline at end of file diff --git a/client/src/pages/components/ComponentList.tsx b/client/src/pages/components/ComponentList.tsx index 8183c6b..6f124a2 100644 --- a/client/src/pages/components/ComponentList.tsx +++ b/client/src/pages/components/ComponentList.tsx @@ -1,13 +1,6 @@ import React from "react"; -import { - Card, - CardContent, - Typography, - List, - ListItem, - ListItemText, - -} from "@mui/material"; +import { Box, Typography, List, ListItem, ListItemText } from "@mui/material"; +import { motion } from "framer-motion"; interface ComponentListProps { components: string[]; @@ -15,46 +8,72 @@ interface ComponentListProps { const ComponentList: React.FC = ({ components }) => { return ( - - - - Components - - - {components.map((component, index) => ( + + Components + + + {components.map((component, index) => ( + - ))} - - - + + ))} + + ); }; diff --git a/client/src/pages/components/FileUpload.tsx b/client/src/pages/components/FileUpload.tsx index 35ae2e2..f9f2d8a 100644 --- a/client/src/pages/components/FileUpload.tsx +++ b/client/src/pages/components/FileUpload.tsx @@ -1,5 +1,12 @@ +// myfile.tsx import React, { useState, useEffect } from "react"; -import { Box, Button } from "@mui/material"; +import { Box, Typography } from "@mui/material"; +import { motion } from "framer-motion"; +import CloudUploadIcon from "@mui/icons-material/CloudUpload"; +import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined"; +import components1 from "../../assets/raspberrypi_kit.png"; +import components2 from "../../assets/Raspberry_Pi_4_Model_B_-_Side.jpg"; +import { HoverBorderGradient } from "../../components/ui/hover-border-gradient"; interface FileUploadProps { onFileChange: (file: File | null) => void; @@ -25,68 +32,208 @@ const FileUpload: React.FC = ({ const objectUrl = URL.createObjectURL(file); setPreview(objectUrl); - // Cleanup the object URL return () => URL.revokeObjectURL(objectUrl); }, [file]); + const handleSampleImageClick = async (imageSrc: string) => { + try { + const response = await fetch(imageSrc); + const blob = await response.blob(); + + const file = new File([blob], "sample-image.png", { type: blob.type }); + onFileChange(file); + } catch (error) { + console.error("Error uploading sample image:", error); + } + }; + return (
- -