diff --git a/docusaurus.config.ts b/docusaurus.config.ts index ec3b239f..3a3b4784 100644 --- a/docusaurus.config.ts +++ b/docusaurus.config.ts @@ -168,21 +168,15 @@ const config: Config = { className: "navbar__dropdown--products", items: [ { - to: "/one-ai", - label: "ONE AI", + to: "/", + label: "ONE WARE AI", className: "dropdown__link--highlight", + activeBaseRegex: "^/$", }, { - to: "/docs/one-ai/getting-started/quick-start-guide", - label: "Get Started", - }, - { - to: "/docs/one-ai/demos/overview", - label: "Demo Projects", - }, - { - to: "/docs/one-ai/supported-vendors", - label: "Supported Hardware", + to: "/one-ai", + label: "LucidNet", + className: "dropdown__link--highlight", }, { to: "/studio", diff --git a/i18n/de/code.json b/i18n/de/code.json index cff76b24..7b5b437f 100644 --- a/i18n/de/code.json +++ b/i18n/de/code.json @@ -2124,5 +2124,14 @@ }, "pricing.opensource.subtitle": { "message": "Kostenlose Credits für nicht-kommerzielle Projekte" + }, + "homepage.showcase.cta.title": { + "message": "Praxisbenchmarks, maßgeschneiderte Architekturen & One-Click-Deployment" + }, + "homepage.showcase.cta.description": { + "message": "Erfahre, wie ONE AI YOLO in direkten Vergleichen übertrifft — und deploye auf FPGAs, GPUs, CPUs und mehr." + }, + "homepage.showcase.cta.link": { + "message": "ONE AI entdecken" } } diff --git a/src/components/AiProcessSlider/index.tsx b/src/components/AiProcessSlider/index.tsx new file mode 100644 index 00000000..ad3a24ec --- /dev/null +++ b/src/components/AiProcessSlider/index.tsx @@ -0,0 +1,162 @@ +import React, { useState, useEffect, useRef, useCallback } from "react"; +import Translate from "@docusaurus/Translate"; + +const steps = [ + { + title: Capture and Label, + src: require("@site/static/img/ai/Capture.png").default, + alt: "Capture", + description: ( + + Capture just a few images, label them - ONE AI takes care of the rest. + ONE AI requires only a small dataset to deliver a fully functional AI + model. Its adaptive architecture automatically scales with your data. + + ), + }, + { + title: Guide and Select, + src: require("@site/static/img/ai/Pre.png").default, + alt: "Hardware", + description: ( + + Use our intuitive and visual process to teach the AI what is important, + where to generalize and what to predict. You can specify your exact + hardware and performance requirements and then let ONE AI create the + perfect model for your needs. + + ), + }, + { + title: Predict and Train, + src: require("@site/static/img/ai/Train.png").default, + alt: "Simulation", + description: ( + + After you start training, ONE AI will automatically generate a custom + neural network for your hardware and application. The AI then trains on + your data, but only learns what is important. This ensures highest + performance and accuracy. + + ), + }, + { + title: Test and Deploy, + src: require("@site/static/img/ai/Export.png").default, + alt: "Extensible", + description: ( + + While training and testing, the AI already behaves like on your target + hardware. No matter if you are using an FPGA, Microcontroller, GPU, CPU + or TPU. If you are satisfied with the results, you can export the AI as + cross-platform executable, universal HDL code, C++ project or + ONNX/TF/TF-Lite Model. + + ), + }, +]; + +export default function AiProcessSlider() { + const [active, setActive] = useState(0); + const [visible, setVisible] = useState(true); + const pendingRef = useRef(null); + const timerRef = useRef | null>(null); + + const transitionTo = useCallback((idx: number) => { + if (idx === active && pendingRef.current === null) return; + setVisible(false); + pendingRef.current = idx; + }, [active]); + + useEffect(() => { + if (!visible && pendingRef.current !== null) { + const timeout = setTimeout(() => { + setActive(pendingRef.current!); + pendingRef.current = null; + setVisible(true); + }, 300); + return () => clearTimeout(timeout); + } + }, [visible]); + + useEffect(() => { + timerRef.current = setInterval(() => { + setVisible(false); + pendingRef.current = null; + setTimeout(() => { + setActive((a) => { + const next = (a + 1) % steps.length; + return next; + }); + setVisible(true); + }, 300); + }, 20000); + return () => { if (timerRef.current) clearInterval(timerRef.current); }; + }, []); + + const step = steps[active]; + + return ( +
+
+
+

+ + The Entire AI Development Process Automated in One Tool + +

+ +
+
+ {step.alt} +
+ +
+
+

+ {step.title} +

+

+ {step.description} +

+
+ +
+ {steps.map((_, idx) => ( +
+
+
+
+
+
+ ); +} diff --git a/src/components/ArchitectureSection/ArchFlowComponents.tsx b/src/components/ArchitectureSection/ArchFlowComponents.tsx new file mode 100644 index 00000000..75c8b2ba --- /dev/null +++ b/src/components/ArchitectureSection/ArchFlowComponents.tsx @@ -0,0 +1,160 @@ +import React from "react"; +import type { LayerBlock, MainPathSegment, ResidualBlock } from "./types"; +import { isResidualBlock } from "./types"; + +export function LayerCard({ block }: { block: LayerBlock }) { + return ( +
+ + {block.label} + +
+ ); +} + +export function CompactLayerCard({ block }: { block: LayerBlock }) { + return ; +} + +export function ArrowDown() { + return ( +
+
+ + + +
+ ); +} + +export function MiniMergeIndicator() { + return ( +
+
+
+ + + +
+
+
+ ); +} + +export function MiniForkIndicator() { + return ( +
+
+
+
+
+ + + +
+
+
+
+ ); +} + +export function LinearArchFlow({ layers }: { layers: LayerBlock[] }) { + return ( +
+ {layers.map((block, idx) => ( + + + {idx < layers.length - 1 && } + + ))} +
+ ); +} + +export function SequentialFlow({ layers }: { layers: LayerBlock[] }) { + return ( + <> + {layers.map((block, idx) => ( + +
+ {idx < layers.length - 1 && } +
+ ))} + + ); +} + +export function CompactSequentialFlow({ layers }: { layers: LayerBlock[] }) { + return ( + <> + {layers.map((block, idx) => ( + +
+ {idx < layers.length - 1 && } +
+ ))} + + ); +} + +export function ResidualBlockFlow({ block }: { block: ResidualBlock }) { + const isIdentitySkip = block.skipBranch.length === 0; + + return ( +
+ + +
+
+ +
+
+ {isIdentitySkip ? ( +
+ identity +
+
+ ) : ( + <> + +
+
+
+ + )} +
+
+ + + +
+
+ ); +} + +export function MainPathFlow({ segments }: { segments: MainPathSegment[] }) { + return ( +
+ {segments.map((segment, segIdx) => ( + + {segIdx > 0 && } + {isResidualBlock(segment) ? ( + + ) : ( + + )} + + ))} +
+ ); +} diff --git a/src/components/ArchitectureSection/ArchModelSelector.tsx b/src/components/ArchitectureSection/ArchModelSelector.tsx new file mode 100644 index 00000000..38027701 --- /dev/null +++ b/src/components/ArchitectureSection/ArchModelSelector.tsx @@ -0,0 +1,67 @@ +import React, { useEffect, useRef, useState } from "react"; +import Translate from "@docusaurus/Translate"; +import { archModels } from "./data"; + +export function ArchModelSelector({ models, activeModel, onSelect }: { + models: typeof archModels; + activeModel: number; + onSelect: (i: number) => void; +}) { + const containerRef = useRef(null); + const buttonRefs = useRef<(HTMLButtonElement | null)[]>([]); + const [indicator, setIndicator] = useState({ left: 0, width: 0 }); + + useEffect(() => { + const btn = buttonRefs.current[activeModel]; + const container = containerRef.current; + if (btn && container) { + const containerRect = container.getBoundingClientRect(); + const btnRect = btn.getBoundingClientRect(); + setIndicator({ + left: btnRect.left - containerRect.left, + width: btnRect.width, + }); + } + }, [activeModel]); + + return ( +
+
0 ? 1 : 0, + backgroundColor: "color-mix(in srgb, var(--ifm-color-primary) 12%, transparent)", + borderColor: "color-mix(in srgb, var(--ifm-color-primary) 35%, transparent)", + }} + /> + + {models.map((m, index) => { + const isActive = index === activeModel; + const Icon = m.icon; + return ( + + ); + })} +
+ ); +} diff --git a/src/components/ArchitectureSection/ArchitectureViewer.tsx b/src/components/ArchitectureSection/ArchitectureViewer.tsx new file mode 100644 index 00000000..a02e6d18 --- /dev/null +++ b/src/components/ArchitectureSection/ArchitectureViewer.tsx @@ -0,0 +1,144 @@ +import React, { useEffect, useRef, useState } from "react"; +import { createPortal } from "react-dom"; + +function ArchViewerControls({ scale, zoomIn, zoomOut, onExpand }: { scale: number; zoomIn: () => void; zoomOut: () => void; onExpand: () => void }) { + return ( +
+ + + +
+ ); +} + +export function ArchitectureViewer({ children, onExpand }: { children: React.ReactNode; onExpand: () => void }) { + const [scale, setScale] = useState(0.75); + const contentRef = useRef(null); + const [contentHeight, setContentHeight] = useState(0); + const zoomIn = () => setScale(s => Math.min(+(s + 0.1).toFixed(1), 2)); + const zoomOut = () => setScale(s => Math.max(+(s - 0.1).toFixed(1), 0.3)); + + useEffect(() => { + const el = contentRef.current; + if (!el) return; + const ro = new ResizeObserver((entries) => { + for (const entry of entries) { + setContentHeight(entry.contentRect.height); + } + }); + ro.observe(el); + return () => ro.disconnect(); + }, []); + + return ( +
+
+
+
+ {children} +
+
+
+ +
+ ); +} + +export function ArchitectureModal({ children, onClose }: { children: React.ReactNode; onClose: () => void }) { + const [scale, setScale] = useState(0.75); + const contentRef = useRef(null); + const [contentHeight, setContentHeight] = useState(0); + const zoomIn = () => setScale(s => Math.min(+(s + 0.1).toFixed(1), 2)); + const zoomOut = () => setScale(s => Math.max(+(s - 0.1).toFixed(1), 0.3)); + + useEffect(() => { + const el = contentRef.current; + if (!el) return; + const ro = new ResizeObserver((entries) => { + for (const entry of entries) { + setContentHeight(entry.contentRect.height); + } + }); + ro.observe(el); + return () => ro.disconnect(); + }, []); + + useEffect(() => { + const handleEsc = (e: KeyboardEvent) => { if (e.key === "Escape") onClose(); }; + const scrollY = window.scrollY; + document.addEventListener("keydown", handleEsc); + document.body.style.position = "fixed"; + document.body.style.top = `-${scrollY}px`; + document.body.style.left = "0"; + document.body.style.right = "0"; + document.body.style.overflow = "hidden"; + return () => { + document.removeEventListener("keydown", handleEsc); + document.body.style.position = ""; + document.body.style.top = ""; + document.body.style.left = ""; + document.body.style.right = ""; + document.body.style.overflow = ""; + window.scrollTo(0, scrollY); + }; + }, [onClose]); + + return createPortal( +
+
+
e.stopPropagation()} + > +
+
+
+ {children} +
+
+
+
+ + + +
+
+
, + document.body + ); +} diff --git a/src/components/ArchitectureSection/data.ts b/src/components/ArchitectureSection/data.ts new file mode 100644 index 00000000..3d2ac5ac --- /dev/null +++ b/src/components/ArchitectureSection/data.ts @@ -0,0 +1,89 @@ +import { TbGridPattern, TbArrowsSplit } from "react-icons/tb"; +import type { LayerBlock, MainPathSegment } from "./types"; + +export const linearArch: LayerBlock[] = [ + { label: "Input" }, + { label: "Quantization" }, + { label: "Convolution", trainable: true }, + { label: "Max Pooling" }, + { label: "Convolution", trainable: true }, + { label: "Max Pooling" }, + { label: "Convolution", trainable: true }, + { label: "Max Pooling" }, + { label: "Flatten" }, + { label: "Dense", trainable: true }, +]; + +export const multiInputShared: LayerBlock = { + label: "Input", +}; + +export const multiInputMainSegments: MainPathSegment[] = [ + [ + { label: "Reshape" }, + { label: "Convolution", trainable: true }, + { label: "Batch Norm", trainable: true }, + { label: "ReLU" }, + { label: "Max Pooling" }, + ], + { + mainBranch: [ + { label: "Convolution", trainable: true }, + { label: "Batch Norm", trainable: true }, + { label: "ReLU" }, + { label: "Convolution", trainable: true }, + { label: "Batch Norm", trainable: true }, + ], + skipBranch: [ + { label: "Convolution", trainable: true }, + ], + merge: { label: "Add" }, + }, + [ + { label: "ReLU" }, + ], + { + mainBranch: [ + { label: "Convolution", trainable: true }, + { label: "Batch Norm", trainable: true }, + { label: "ReLU" }, + { label: "Convolution", trainable: true }, + { label: "Batch Norm", trainable: true }, + ], + skipBranch: [], + merge: { label: "Add" }, + }, + [ + { label: "ReLU" }, + ], +]; + +export const multiInputBranch: LayerBlock[] = [ + { label: "Split" }, + { label: "Squeeze" }, + { label: "Squeeze" }, + { label: "Subtract" }, + { label: "Avg Pooling" }, + { label: "Convolution", trainable: true }, + { label: "Batch Norm", trainable: true }, + { label: "ReLU" }, + { label: "Convolution", trainable: true }, + { label: "Batch Norm", trainable: true }, + { label: "ReLU" }, + { label: "Avg Pooling" }, +]; + +export const multiInputMerged: LayerBlock[] = [ + { label: "Concatenate" }, + { label: "Convolution", trainable: true }, + { label: "Convolution", trainable: true }, + { label: "Batch Norm", trainable: true }, + { label: "ReLU" }, + { label: "Convolution", trainable: true }, + { label: "Reshape" }, +]; + +export const archModels = [ + { label: "Image Classification", labelId: "oneai.architecture.linear.label", image: "/img/ai/one_ai_plugin/benefits/3.webp", icon: TbGridPattern }, + { label: "Multi-Input", labelId: "oneai.architecture.multi.label", image: "/img/ai/one_ai_plugin/benefits/4.webp", icon: TbArrowsSplit }, +]; diff --git a/src/components/ArchitectureSection/index.tsx b/src/components/ArchitectureSection/index.tsx new file mode 100644 index 00000000..439f11a9 --- /dev/null +++ b/src/components/ArchitectureSection/index.tsx @@ -0,0 +1,152 @@ +import React, { useState } from "react"; +import Translate from "@docusaurus/Translate"; +import useBaseUrl from "@docusaurus/useBaseUrl"; +import HeroBackground from "../HeroBackground"; +import { LayerCard, ArrowDown, LinearArchFlow, MainPathFlow } from "./ArchFlowComponents"; +import { ArchitectureViewer, ArchitectureModal } from "./ArchitectureViewer"; +import { ArchModelSelector } from "./ArchModelSelector"; +import { + archModels, + linearArch, + multiInputShared, + multiInputMainSegments, + multiInputBranch, + multiInputMerged, +} from "./data"; + +export default function ArchitectureSection() { + const [activeModel, setActiveModel] = useState(0); + const [modalOpen, setModalOpen] = useState(false); + + const models = archModels; + + const currentModel = models[activeModel]; + + const viewerContent = ( +
+ {activeModel === 0 ? ( + + ) : ( + <> +
+ +
+
+
+
+
+
+ + + +
+
+
+
+
+
+ Main Path + +
+
+ In-Connect Branch + {multiInputBranch.map((block, idx) => ( + +
+ {idx < multiInputBranch.length - 1 && } +
+ ))} +
+
+
+
+
+
+
+
+ + + +
+
+
+
+ {multiInputMerged.map((block, idx) => ( + +
+ + {idx < multiInputMerged.length - 1 && } +
+
+ ))} +
+ + )} +
+ ); + + return ( + +
+
+

+ + Example AI Architectures + +

+

+ + Custom neural networks generated by ONE AI — optimized for your specific application + +

+
+
+ +
+
+
+
+ +
+ {currentModel.label} +
+
+
+ setModalOpen(true)}> + {viewerContent} + +
+
+
+
+ + {modalOpen && ( + setModalOpen(false)}> + {viewerContent} + + )} + + +
+ ); +} diff --git a/src/components/ArchitectureSection/types.ts b/src/components/ArchitectureSection/types.ts new file mode 100644 index 00000000..b325c893 --- /dev/null +++ b/src/components/ArchitectureSection/types.ts @@ -0,0 +1,16 @@ +export interface LayerBlock { + label: string; + trainable?: boolean; +} + +export interface ResidualBlock { + mainBranch: LayerBlock[]; + skipBranch: LayerBlock[]; + merge: LayerBlock; +} + +export type MainPathSegment = LayerBlock[] | ResidualBlock; + +export function isResidualBlock(segment: MainPathSegment): segment is ResidualBlock { + return !Array.isArray(segment) && 'mainBranch' in segment; +} diff --git a/src/components/BenefitsSection/index.tsx b/src/components/BenefitsSection/index.tsx new file mode 100644 index 00000000..4f5e623a --- /dev/null +++ b/src/components/BenefitsSection/index.tsx @@ -0,0 +1,231 @@ +import React, { useState } from "react"; +import Translate from "@docusaurus/Translate"; +import useBaseUrl from "@docusaurus/useBaseUrl"; + +export default function BenefitsSection() { + const [activeIndex, setActiveIndex] = useState(null); + + const benefits = [ + { + title: "Use Smaller Datasets", + titleId: "oneai.benefits.smaller.title", + description: "With ONE AI, you are rewarded for having a smaller dataset. This results in a leaner and faster AI model. Smaller datasets prevent memorization and help the AI focus on the actual application. Even with as few as 20 images, ONE AI can often create highly effective models and significantly reduce development time.", + descriptionId: "oneai.benefits.smaller.description" + }, + { + title: "Do Less Manual Labeling", + titleId: "oneai.benefits.labeling.title", + description: "Because ONE AI achieves great results with smaller datasets, expanding your dataset becomes much faster. The first model can automatically label new data, while ONE AI adapts the model to the growing dataset. This saves a lot of manual work.", + descriptionId: "oneai.benefits.labeling.description" + }, + { + title: "No Model Selection or Parameter Tuning", + titleId: "oneai.benefits.tuning.title", + description: "ONE AI automatically builds the best possible model for your specific use case. You do not need to search for the right foundation model, test endless parameters, or manually fine-tune your AI. ONE AI takes care of everything.", + descriptionId: "oneai.benefits.tuning.description" + }, + { + title: "Create AI with Multiple Input Images", + titleId: "oneai.benefits.multiple.title", + description: "Many applications benefit from comparing multiple images or using reference images to improve accuracy. With ONE AI, you can easily provide multiple input images, and the system automatically adjusts the AI architecture to make use of this context.", + descriptionId: "oneai.benefits.multiple.description" + }, + { + title: "Deploy AI on Any Hardware or Platform", + titleId: "oneai.benefits.export.title", + description: "The ONE AI Capture Tool can be used directly as a ready-to-use interface on any operating system. You can also export your AI as a platform-independent model, C++ project, executable, or HDL code for FPGAs. This saves a lot of integration time.", + descriptionId: "oneai.benefits.export.description" + }, + { + title: "Get AI for Your Performance Needs", + titleId: "oneai.benefits.performance.title", + description: "ONE AI automatically considers your target hardware when creating models. It ensures that your desired frame rate and resource usage are always met. You do not need to waste time on trial and error since ONE AI optimizes performance for you.", + descriptionId: "oneai.benefits.performance.description" + }, + { + title: "No Coding Required", + titleId: "oneai.benefits.coding.title", + description: "All AI creation features are fully integrated into ONE AI, so no programming is required. You simply provide your application knowledge, and ONE AI handles the complex parts. In the end, you get fully working software and AI models ready to use or integrate.", + descriptionId: "oneai.benefits.coding.description" + }, + { + title: "Get Better Results in Less Time", + titleId: "oneai.benefits.results.title", + description: "In most cases, AI models predicted by ONE AI in just 0.7 seconds are more reliable and accurate than manually developed ones. Traditional AI development rarely makes sense anymore, especially since ONE AI can handle very specific requirements that used to require manual engineering.", + descriptionId: "oneai.benefits.results.description" + } + ]; + + const toggleAccordion = (index: number) => { + setActiveIndex(activeIndex === index ? null : index); + }; + + const hotspots = [ + [ + { x: 35, y: 30, targetIndex: 1 }, + { x: 6, y: 33, targetIndex: 2 }, + { x: 90, y: 8, targetIndex: 3 }, + { x: 9, y: 13, targetIndex: 4 }, + { x: 11, y: 28, targetIndex: 5 }, + { x: 6, y: 23, targetIndex: 6 }, + ], + [ + { x: 64, y: 27, targetIndex: 1 }, + { x: 6, y: 33, targetIndex: 2 }, + { x: 9, y: 13, targetIndex: 4 }, + { x: 11, y: 28, targetIndex: 5 }, + { x: 6, y: 23, targetIndex: 6 }, + { x: 87, y: 19, targetIndex: 0 }, + { x: 90, y: 8, targetIndex: 3 }, + ], + [ + { x: 6, y: 8, targetIndex: 0 }, + { x: 70, y: 50, targetIndex: 2 }, + { x: 55, y: 21, targetIndex: 7 }, + { x: 11, y: 13, targetIndex: 4 }, + { x: 11, y: 28, targetIndex: 5 }, + { x: 6, y: 23, targetIndex: 6 }, + ], + [ + { x: 90, y: 8, targetIndex: 0 }, + { x: 35, y: 30, targetIndex: 1 }, + { x: 6, y: 33, targetIndex: 2 }, + { x: 9, y: 13, targetIndex: 4 }, + { x: 11, y: 28, targetIndex: 5 }, + { x: 6, y: 23, targetIndex: 6 }, + ], + [ + { x: 6, y: 8, targetIndex: 0 }, + { x: 4, y: 33, targetIndex: 2 }, + { x: 84, y: 16, targetIndex: 4 }, + { x: 8, y: 28, targetIndex: 5 }, + { x: 4, y: 23, targetIndex: 6 }, + ], + [ + { x: 6, y: 8, targetIndex: 0 }, + { x: 6, y: 33, targetIndex: 2 }, + { x: 9, y: 13, targetIndex: 4 }, + { x: 25, y: 55, targetIndex: 5 }, + { x: 6, y: 23, targetIndex: 6 }, + ], + [ + { x: 6, y: 8, targetIndex: 0 }, + { x: 6, y: 33, targetIndex: 2 }, + { x: 9, y: 13, targetIndex: 4 }, + { x: 11, y: 28, targetIndex: 5 }, + { x: 30, y: 25, targetIndex: 6 }, + ], + [ + { x: 49, y: 21, targetIndex: 2 }, + { x: 9, y: 13, targetIndex: 4 }, + { x: 11, y: 28, targetIndex: 5 }, + { x: 6, y: 23, targetIndex: 6 }, + ] + ]; + + return ( +
+
+
+
+
+ {benefits.map((benefit, index) => { + const isOpen = activeIndex === index; + return ( +
toggleAccordion(index)} + className={`px-3 cursor-pointer transition-all duration-300 ease-in-out ${ + isOpen + ? 'bg-[var(--ifm-color-primary)]' + : 'bg-white/50 hover:bg-white dark:bg-[#2a2a2a] dark:hover:bg-[#333]' + }`} + > +
+ + + {benefit.title} + + + + + +
+ +
+

+ + {benefit.description} + +

+
+
+ ); + })} +
+ +
+
+ ONE AI Benefit Illustration { + e.currentTarget.src = '/img/ai/Capture.png'; + }} + /> + + {hotspots[activeIndex !== null ? activeIndex : 0]?.map((hotspot, idx) => ( +
toggleAccordion(hotspot.targetIndex)} + > +
+
+
+ +
+ + {benefits[hotspot.targetIndex].title} + +
+
+ ))} +
+
+
+
+
+
+ ); +} diff --git a/src/components/ChatHero/SignUpModal.tsx b/src/components/ChatHero/SignUpModal.tsx new file mode 100644 index 00000000..e85e33d3 --- /dev/null +++ b/src/components/ChatHero/SignUpModal.tsx @@ -0,0 +1,116 @@ +import React, { useEffect } from "react"; +import Translate from "@docusaurus/Translate"; +import { useColorMode } from "@docusaurus/theme-common"; + +export type ModalTrigger = "attach" | "send"; + +interface SignUpModalProps { + isOpen: boolean; + onClose: () => void; + trigger: ModalTrigger; +} + +export default function SignUpModal({ isOpen, onClose, trigger }: SignUpModalProps) { + const { colorMode } = useColorMode(); + const isDarkMode = colorMode === "dark"; + + useEffect(() => { + if (isOpen) { + document.body.style.overflow = "hidden"; + } else { + document.body.style.overflow = ""; + } + return () => { + document.body.style.overflow = ""; + }; + }, [isOpen]); + + useEffect(() => { + const handleEsc = (e: KeyboardEvent) => { + if (e.key === "Escape") { + onClose(); + } + }; + if (isOpen) { + window.addEventListener("keydown", handleEsc); + } + return () => window.removeEventListener("keydown", handleEsc); + }, [isOpen, onClose]); + + if (!isOpen) return null; + + return ( +
+
e.stopPropagation()} + > +
+ {trigger === "attach" ? ( + <> +

+ Sign in to upload files +

+

+ + Support for up to 10 files of any file type + +

+ + ) : ( + <> +

+ Sign in to create your AI +

+

+ + Get started with your custom AI model + +

+ + )} +
+ +
+ + +
+
+ + +
+ ); +} diff --git a/src/components/ChatHero/index.tsx b/src/components/ChatHero/index.tsx new file mode 100644 index 00000000..45bbf155 --- /dev/null +++ b/src/components/ChatHero/index.tsx @@ -0,0 +1,263 @@ +import React, { useState, useEffect } from "react"; +import Translate from "@docusaurus/Translate"; +import { useColorMode } from "@docusaurus/theme-common"; +import SignUpModal, { ModalTrigger } from "./SignUpModal"; +import HeroBackground from "../HeroBackground"; + +const STORAGE_KEY = "chathero_prompt"; +const STORAGE_EXPIRY_DAYS = 90; + +function getStoredPrompt(): string { + if (typeof window === "undefined") return ""; + try { + const stored = localStorage.getItem(STORAGE_KEY); + if (!stored) return ""; + const { value, expiry } = JSON.parse(stored); + if (Date.now() > expiry) { + localStorage.removeItem(STORAGE_KEY); + return ""; + } + return value || ""; + } catch { + return ""; + } +} + +function savePrompt(value: string) { + if (typeof window === "undefined") return; + try { + const expiry = Date.now() + STORAGE_EXPIRY_DAYS * 24 * 60 * 60 * 1000; + localStorage.setItem(STORAGE_KEY, JSON.stringify({ value, expiry })); + } catch {} +} + +export default function ChatHero() { + const { colorMode } = useColorMode(); + const isDarkMode = colorMode === "dark"; + const [isModalOpen, setIsModalOpen] = useState(false); + const [modalTrigger, setModalTrigger] = useState("send"); + const [prompt, setPrompt] = useState(""); + + useEffect(() => { + setPrompt(getStoredPrompt()); + }, []); + + const handlePromptChange = (e: React.ChangeEvent) => { + const value = e.target.value; + setPrompt(value); + savePrompt(value); + }; + + const handleAttachClick = (e: React.MouseEvent) => { + e.stopPropagation(); + setModalTrigger("attach"); + setIsModalOpen(true); + }; + + const handleSendClick = (e: React.MouseEvent) => { + e.stopPropagation(); + setModalTrigger("send"); + setIsModalOpen(true); + }; + + const scrollToSection = (id: string) => { + const element = document.getElementById(id); + if (element) { + const navbarHeight = 60; + const elementPosition = element.getBoundingClientRect().top + window.scrollY; + window.scrollTo({ + top: elementPosition - navbarHeight, + behavior: "smooth", + }); + } + }; + + return ( + +
+
+

+ Create your Custom AI +

+ +

+ + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore. + +

+ +
+
+
+