Added programatic dot pattern instead of background image
This commit is contained in:
parent
391a53af76
commit
004c955fba
5 changed files with 353 additions and 178 deletions
|
|
@ -10,9 +10,13 @@
|
|||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"iconLibrary": "lucide",
|
||||
"aliases": {
|
||||
"components": "components",
|
||||
"utils": "lib/utils",
|
||||
"ui": "components/ui"
|
||||
},
|
||||
"registries": {
|
||||
"@magicui": "https://magicui.design/r/{name}.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
158
frontend/components/ui/dot-pattern.tsx
Normal file
158
frontend/components/ui/dot-pattern.tsx
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
"use client";
|
||||
|
||||
import { motion } from "motion/react";
|
||||
import type React from "react";
|
||||
import { useEffect, useId, useRef, useState } from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
/**
|
||||
* DotPattern Component Props
|
||||
*
|
||||
* @param {number} [width=16] - The horizontal spacing between dots
|
||||
* @param {number} [height=16] - The vertical spacing between dots
|
||||
* @param {number} [x=0] - The x-offset of the entire pattern
|
||||
* @param {number} [y=0] - The y-offset of the entire pattern
|
||||
* @param {number} [cx=1] - The x-offset of individual dots
|
||||
* @param {number} [cy=1] - The y-offset of individual dots
|
||||
* @param {number} [cr=1] - The radius of each dot
|
||||
* @param {string} [className] - Additional CSS classes to apply to the SVG container
|
||||
* @param {boolean} [glow=false] - Whether dots should have a glowing animation effect
|
||||
*/
|
||||
interface DotPatternProps extends React.SVGProps<SVGSVGElement> {
|
||||
width?: number;
|
||||
height?: number;
|
||||
x?: number;
|
||||
y?: number;
|
||||
cx?: number;
|
||||
cy?: number;
|
||||
cr?: number;
|
||||
className?: string;
|
||||
glow?: boolean;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* DotPattern Component
|
||||
*
|
||||
* A React component that creates an animated or static dot pattern background using SVG.
|
||||
* The pattern automatically adjusts to fill its container and can optionally display glowing dots.
|
||||
*
|
||||
* @component
|
||||
*
|
||||
* @see DotPatternProps for the props interface.
|
||||
*
|
||||
* @example
|
||||
* // Basic usage
|
||||
* <DotPattern />
|
||||
*
|
||||
* // With glowing effect and custom spacing
|
||||
* <DotPattern
|
||||
* width={20}
|
||||
* height={20}
|
||||
* glow={true}
|
||||
* className="opacity-50"
|
||||
* />
|
||||
*
|
||||
* @notes
|
||||
* - The component is client-side only ("use client")
|
||||
* - Automatically responds to container size changes
|
||||
* - When glow is enabled, dots will animate with random delays and durations
|
||||
* - Uses Motion for animations
|
||||
* - Dots color can be controlled via the text color utility classes
|
||||
*/
|
||||
|
||||
export function DotPattern({
|
||||
width = 16,
|
||||
height = 16,
|
||||
x = 0,
|
||||
y = 0,
|
||||
cx = 1,
|
||||
cy = 1,
|
||||
cr = 1,
|
||||
className,
|
||||
glow = false,
|
||||
...props
|
||||
}: DotPatternProps) {
|
||||
const id = useId();
|
||||
const containerRef = useRef<SVGSVGElement>(null);
|
||||
const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
|
||||
|
||||
useEffect(() => {
|
||||
const updateDimensions = () => {
|
||||
if (containerRef.current) {
|
||||
const { width, height } = containerRef.current.getBoundingClientRect();
|
||||
setDimensions({ width, height });
|
||||
}
|
||||
};
|
||||
|
||||
updateDimensions();
|
||||
window.addEventListener("resize", updateDimensions);
|
||||
return () => window.removeEventListener("resize", updateDimensions);
|
||||
}, []);
|
||||
|
||||
const dots = Array.from(
|
||||
{
|
||||
length:
|
||||
Math.ceil(dimensions.width / width) *
|
||||
Math.ceil(dimensions.height / height),
|
||||
},
|
||||
(_, i) => {
|
||||
const col = i % Math.ceil(dimensions.width / width);
|
||||
const row = Math.floor(i / Math.ceil(dimensions.width / width));
|
||||
return {
|
||||
x: col * width + cx,
|
||||
y: row * height + cy,
|
||||
delay: Math.random() * 5,
|
||||
duration: Math.random() * 3 + 2,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
return (
|
||||
<svg
|
||||
ref={containerRef}
|
||||
aria-hidden="true"
|
||||
className={cn(
|
||||
"pointer-events-none absolute inset-0 h-full w-full text-neutral-400/80",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<defs>
|
||||
<radialGradient id={`${id}-gradient`}>
|
||||
<stop offset="0%" stopColor="currentColor" stopOpacity="1" />
|
||||
<stop offset="100%" stopColor="currentColor" stopOpacity="0" />
|
||||
</radialGradient>
|
||||
</defs>
|
||||
{dots.map((dot, index) => (
|
||||
<motion.circle
|
||||
key={`${dot.x}-${dot.y}`}
|
||||
cx={dot.x}
|
||||
cy={dot.y}
|
||||
r={cr}
|
||||
fill={glow ? `url(#${id}-gradient)` : "currentColor"}
|
||||
initial={glow ? { opacity: 0.4, scale: 1 } : {}}
|
||||
animate={
|
||||
glow
|
||||
? {
|
||||
opacity: [0.4, 1, 0.4],
|
||||
scale: [1, 1.5, 1],
|
||||
}
|
||||
: {}
|
||||
}
|
||||
transition={
|
||||
glow
|
||||
? {
|
||||
duration: dot.duration,
|
||||
repeat: Infinity,
|
||||
repeatType: "reverse",
|
||||
delay: dot.delay,
|
||||
ease: "easeInOut",
|
||||
}
|
||||
: {}
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 269 KiB |
|
|
@ -6,7 +6,9 @@ import { Suspense, useEffect } from "react";
|
|||
import GoogleLogo from "@/components/logo/google-logo";
|
||||
import Logo from "@/components/logo/logo";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { DotPattern } from "@/components/ui/dot-pattern";
|
||||
import { useAuth } from "@/contexts/auth-context";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useGetSettingsQuery } from "../api/queries/useGetSettingsQuery";
|
||||
|
||||
function LoginPageContent() {
|
||||
|
|
@ -53,15 +55,19 @@ function LoginPageContent() {
|
|||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="min-h-dvh relative flex gap-4 flex-col items-center justify-center bg-background p-4"
|
||||
style={{
|
||||
backgroundImage: "url('/images/background.png')",
|
||||
backgroundSize: "cover",
|
||||
backgroundPosition: "center",
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-col items-center justify-center gap-4">
|
||||
<div className="min-h-dvh relative flex gap-4 flex-col items-center justify-center bg-background p-4">
|
||||
<DotPattern
|
||||
width={24}
|
||||
height={24}
|
||||
cx={1}
|
||||
cy={1}
|
||||
cr={1}
|
||||
className={cn(
|
||||
"[mask-image:linear-gradient(to_bottom,white,transparent,transparent)]",
|
||||
"text-input/70",
|
||||
)}
|
||||
/>
|
||||
<div className="flex flex-col items-center justify-center gap-4 z-10">
|
||||
<Logo className="fill-primary" width={32} height={28} />
|
||||
<h1 className="text-2xl font-medium font-chivo">Welcome to OpenRAG</h1>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
|
|
@ -72,7 +78,7 @@ function LoginPageContent() {
|
|||
Continue with Google
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex items-center justify-center gap-2 absolute bottom-6 text-xs text-muted-foreground">
|
||||
<div className="flex items-center justify-center gap-2 absolute bottom-6 text-xs text-muted-foreground z-10">
|
||||
<p className="text-accent-emerald-foreground">Systems Operational</p>•
|
||||
<p>Privacy Policy</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ import { useRouter } from "next/navigation";
|
|||
import { Suspense, useEffect, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
type OnboardingVariables,
|
||||
useOnboardingMutation,
|
||||
type OnboardingVariables,
|
||||
useOnboardingMutation,
|
||||
} from "@/app/api/mutations/useOnboardingMutation";
|
||||
import IBMLogo from "@/components/logo/ibm-logo";
|
||||
import OllamaLogo from "@/components/logo/ollama-logo";
|
||||
|
|
@ -13,198 +13,205 @@ import OpenAILogo from "@/components/logo/openai-logo";
|
|||
import { ProtectedRoute } from "@/components/protected-route";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
Card,
|
||||
CardContent,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
} from "@/components/ui/card";
|
||||
import { DotPattern } from "@/components/ui/dot-pattern";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useGetSettingsQuery } from "../api/queries/useGetSettingsQuery";
|
||||
import { IBMOnboarding } from "./components/ibm-onboarding";
|
||||
import { OllamaOnboarding } from "./components/ollama-onboarding";
|
||||
import { OpenAIOnboarding } from "./components/openai-onboarding";
|
||||
|
||||
function OnboardingPage() {
|
||||
const { data: settingsDb, isLoading: isSettingsLoading } =
|
||||
useGetSettingsQuery();
|
||||
const { data: settingsDb, isLoading: isSettingsLoading } =
|
||||
useGetSettingsQuery();
|
||||
|
||||
const redirect = "/";
|
||||
const redirect = "/";
|
||||
|
||||
const router = useRouter();
|
||||
const router = useRouter();
|
||||
|
||||
// Redirect if already authenticated or in no-auth mode
|
||||
useEffect(() => {
|
||||
if (!isSettingsLoading && settingsDb && settingsDb.edited) {
|
||||
router.push(redirect);
|
||||
}
|
||||
}, [isSettingsLoading, settingsDb, router]);
|
||||
// Redirect if already authenticated or in no-auth mode
|
||||
useEffect(() => {
|
||||
if (!isSettingsLoading && settingsDb && settingsDb.edited) {
|
||||
router.push(redirect);
|
||||
}
|
||||
}, [isSettingsLoading, settingsDb, router]);
|
||||
|
||||
const [modelProvider, setModelProvider] = useState<string>("openai");
|
||||
const [modelProvider, setModelProvider] = useState<string>("openai");
|
||||
|
||||
const [sampleDataset, setSampleDataset] = useState<boolean>(true);
|
||||
const [sampleDataset, setSampleDataset] = useState<boolean>(true);
|
||||
|
||||
const handleSetModelProvider = (provider: string) => {
|
||||
setModelProvider(provider);
|
||||
setSettings({
|
||||
model_provider: provider,
|
||||
embedding_model: "",
|
||||
llm_model: "",
|
||||
});
|
||||
};
|
||||
const handleSetModelProvider = (provider: string) => {
|
||||
setModelProvider(provider);
|
||||
setSettings({
|
||||
model_provider: provider,
|
||||
embedding_model: "",
|
||||
llm_model: "",
|
||||
});
|
||||
};
|
||||
|
||||
const [settings, setSettings] = useState<OnboardingVariables>({
|
||||
model_provider: modelProvider,
|
||||
embedding_model: "",
|
||||
llm_model: "",
|
||||
});
|
||||
const [settings, setSettings] = useState<OnboardingVariables>({
|
||||
model_provider: modelProvider,
|
||||
embedding_model: "",
|
||||
llm_model: "",
|
||||
});
|
||||
|
||||
// Mutations
|
||||
const onboardingMutation = useOnboardingMutation({
|
||||
onSuccess: (data) => {
|
||||
toast.success("Onboarding completed successfully!");
|
||||
console.log("Onboarding completed successfully", data);
|
||||
router.push(redirect);
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error("Failed to complete onboarding", {
|
||||
description: error.message,
|
||||
});
|
||||
},
|
||||
});
|
||||
// Mutations
|
||||
const onboardingMutation = useOnboardingMutation({
|
||||
onSuccess: (data) => {
|
||||
toast.success("Onboarding completed successfully!");
|
||||
console.log("Onboarding completed successfully", data);
|
||||
router.push(redirect);
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error("Failed to complete onboarding", {
|
||||
description: error.message,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const handleComplete = () => {
|
||||
if (
|
||||
!settings.model_provider ||
|
||||
!settings.llm_model ||
|
||||
!settings.embedding_model
|
||||
) {
|
||||
toast.error("Please complete all required fields");
|
||||
return;
|
||||
}
|
||||
const handleComplete = () => {
|
||||
if (
|
||||
!settings.model_provider ||
|
||||
!settings.llm_model ||
|
||||
!settings.embedding_model
|
||||
) {
|
||||
toast.error("Please complete all required fields");
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepare onboarding data
|
||||
const onboardingData: OnboardingVariables = {
|
||||
model_provider: settings.model_provider,
|
||||
llm_model: settings.llm_model,
|
||||
embedding_model: settings.embedding_model,
|
||||
sample_data: sampleDataset,
|
||||
};
|
||||
// Prepare onboarding data
|
||||
const onboardingData: OnboardingVariables = {
|
||||
model_provider: settings.model_provider,
|
||||
llm_model: settings.llm_model,
|
||||
embedding_model: settings.embedding_model,
|
||||
sample_data: sampleDataset,
|
||||
};
|
||||
|
||||
// Add API key if available
|
||||
if (settings.api_key) {
|
||||
onboardingData.api_key = settings.api_key;
|
||||
}
|
||||
// Add API key if available
|
||||
if (settings.api_key) {
|
||||
onboardingData.api_key = settings.api_key;
|
||||
}
|
||||
|
||||
// Add endpoint if available
|
||||
if (settings.endpoint) {
|
||||
onboardingData.endpoint = settings.endpoint;
|
||||
}
|
||||
// Add endpoint if available
|
||||
if (settings.endpoint) {
|
||||
onboardingData.endpoint = settings.endpoint;
|
||||
}
|
||||
|
||||
// Add project_id if available
|
||||
if (settings.project_id) {
|
||||
onboardingData.project_id = settings.project_id;
|
||||
}
|
||||
// Add project_id if available
|
||||
if (settings.project_id) {
|
||||
onboardingData.project_id = settings.project_id;
|
||||
}
|
||||
|
||||
onboardingMutation.mutate(onboardingData);
|
||||
};
|
||||
onboardingMutation.mutate(onboardingData);
|
||||
};
|
||||
|
||||
const isComplete = !!settings.llm_model && !!settings.embedding_model;
|
||||
const isComplete = !!settings.llm_model && !!settings.embedding_model;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="min-h-dvh w-full flex gap-5 flex-col items-center justify-center bg-background p-4"
|
||||
style={{
|
||||
backgroundImage: "url('/images/background.png')",
|
||||
backgroundSize: "cover",
|
||||
backgroundPosition: "center",
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-col items-center gap-5 min-h-[550px] w-full">
|
||||
<div className="flex flex-col items-center justify-center gap-4">
|
||||
<h1 className="text-2xl font-medium font-chivo">
|
||||
Configure your models
|
||||
</h1>
|
||||
<p className="text-sm text-muted-foreground">[description of task]</p>
|
||||
</div>
|
||||
<Card className="w-full max-w-[580px]">
|
||||
<Tabs
|
||||
defaultValue={modelProvider}
|
||||
onValueChange={handleSetModelProvider}
|
||||
>
|
||||
<CardHeader>
|
||||
<TabsList>
|
||||
<TabsTrigger value="openai">
|
||||
<OpenAILogo className="w-4 h-4" />
|
||||
OpenAI
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="watsonx">
|
||||
<IBMLogo className="w-4 h-4" />
|
||||
IBM
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="ollama">
|
||||
<OllamaLogo className="w-4 h-4" />
|
||||
Ollama
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<TabsContent value="openai">
|
||||
<OpenAIOnboarding
|
||||
setSettings={setSettings}
|
||||
sampleDataset={sampleDataset}
|
||||
setSampleDataset={setSampleDataset}
|
||||
/>
|
||||
</TabsContent>
|
||||
<TabsContent value="watsonx">
|
||||
<IBMOnboarding
|
||||
setSettings={setSettings}
|
||||
sampleDataset={sampleDataset}
|
||||
setSampleDataset={setSampleDataset}
|
||||
/>
|
||||
</TabsContent>
|
||||
<TabsContent value="ollama">
|
||||
<OllamaOnboarding
|
||||
setSettings={setSettings}
|
||||
sampleDataset={sampleDataset}
|
||||
setSampleDataset={setSampleDataset}
|
||||
/>
|
||||
</TabsContent>
|
||||
</CardContent>
|
||||
</Tabs>
|
||||
<CardFooter className="flex justify-end">
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={handleComplete}
|
||||
disabled={!isComplete}
|
||||
loading={onboardingMutation.isPending}
|
||||
>
|
||||
Complete
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{!isComplete ? "Please fill in all required fields" : ""}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="min-h-dvh w-full flex gap-5 flex-col items-center justify-center bg-background relative p-4">
|
||||
<DotPattern
|
||||
width={24}
|
||||
height={24}
|
||||
cx={1}
|
||||
cy={1}
|
||||
cr={1}
|
||||
className={cn(
|
||||
"[mask-image:linear-gradient(to_bottom,white,transparent,transparent)]",
|
||||
"text-input/70",
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="flex flex-col items-center gap-5 min-h-[550px] w-full z-10">
|
||||
<div className="flex flex-col items-center justify-center gap-4">
|
||||
<h1 className="text-2xl font-medium font-chivo">
|
||||
Configure your models
|
||||
</h1>
|
||||
<p className="text-sm text-muted-foreground">[description of task]</p>
|
||||
</div>
|
||||
<Card className="w-full max-w-[580px]">
|
||||
<Tabs
|
||||
defaultValue={modelProvider}
|
||||
onValueChange={handleSetModelProvider}
|
||||
>
|
||||
<CardHeader>
|
||||
<TabsList>
|
||||
<TabsTrigger value="openai">
|
||||
<OpenAILogo className="w-4 h-4" />
|
||||
OpenAI
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="watsonx">
|
||||
<IBMLogo className="w-4 h-4" />
|
||||
IBM
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="ollama">
|
||||
<OllamaLogo className="w-4 h-4" />
|
||||
Ollama
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<TabsContent value="openai">
|
||||
<OpenAIOnboarding
|
||||
setSettings={setSettings}
|
||||
sampleDataset={sampleDataset}
|
||||
setSampleDataset={setSampleDataset}
|
||||
/>
|
||||
</TabsContent>
|
||||
<TabsContent value="watsonx">
|
||||
<IBMOnboarding
|
||||
setSettings={setSettings}
|
||||
sampleDataset={sampleDataset}
|
||||
setSampleDataset={setSampleDataset}
|
||||
/>
|
||||
</TabsContent>
|
||||
<TabsContent value="ollama">
|
||||
<OllamaOnboarding
|
||||
setSettings={setSettings}
|
||||
sampleDataset={sampleDataset}
|
||||
setSampleDataset={setSampleDataset}
|
||||
/>
|
||||
</TabsContent>
|
||||
</CardContent>
|
||||
</Tabs>
|
||||
<CardFooter className="flex justify-end">
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={handleComplete}
|
||||
disabled={!isComplete}
|
||||
loading={onboardingMutation.isPending}
|
||||
>
|
||||
Complete
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{!isComplete ? "Please fill in all required fields" : ""}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function ProtectedOnboardingPage() {
|
||||
return (
|
||||
<ProtectedRoute>
|
||||
<Suspense fallback={<div>Loading onboarding...</div>}>
|
||||
<OnboardingPage />
|
||||
</Suspense>
|
||||
</ProtectedRoute>
|
||||
);
|
||||
return (
|
||||
<ProtectedRoute>
|
||||
<Suspense fallback={<div>Loading onboarding...</div>}>
|
||||
<OnboardingPage />
|
||||
</Suspense>
|
||||
</ProtectedRoute>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue