mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-09 15:52:40 +02:00
refactor: enhance onboarding experience
- Replaced the completion step component with a more streamlined onboarding page that includes action cards for managing teams, adding sources, and starting chats. - Updated the search space form to make the description field optional and improved validation messages. - Adjusted the onboarding logic to support auto-configuration of LLM roles and improved loading states. - Removed unused imports and components to clean up the codebase.
This commit is contained in:
parent
a0bdcf366f
commit
9c858a520d
11 changed files with 715 additions and 428 deletions
|
|
@ -1,208 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import {
|
||||
ArrowRight,
|
||||
Bot,
|
||||
Brain,
|
||||
CheckCircle,
|
||||
FileText,
|
||||
MessageSquare,
|
||||
Sparkles,
|
||||
UserPlus,
|
||||
Users,
|
||||
Zap,
|
||||
} from "lucide-react";
|
||||
import { motion } from "motion/react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { useGlobalLLMConfigs, useLLMConfigs, useLLMPreferences } from "@/hooks/use-llm-configs";
|
||||
|
||||
interface CompletionStepProps {
|
||||
searchSpaceId: number;
|
||||
}
|
||||
|
||||
export function CompletionStep({ searchSpaceId }: CompletionStepProps) {
|
||||
const router = useRouter();
|
||||
const { llmConfigs } = useLLMConfigs(searchSpaceId);
|
||||
const { globalConfigs } = useGlobalLLMConfigs();
|
||||
const { preferences } = useLLMPreferences(searchSpaceId);
|
||||
|
||||
// Combine global and user-specific configs
|
||||
const allConfigs = [...globalConfigs, ...llmConfigs];
|
||||
|
||||
const assignedConfigs = {
|
||||
long_context: allConfigs.find((c) => c.id === preferences.long_context_llm_id),
|
||||
fast: allConfigs.find((c) => c.id === preferences.fast_llm_id),
|
||||
strategic: allConfigs.find((c) => c.id === preferences.strategic_llm_id),
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
{/* Next Steps - What would you like to do? */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.6 }}
|
||||
className="space-y-4"
|
||||
>
|
||||
<div className="text-center">
|
||||
<h3 className="text-xl font-semibold mb-2">What would you like to do next?</h3>
|
||||
<p className="text-muted-foreground">Choose an option to continue</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
{/* Manage Team Card */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.9, type: "spring", stiffness: 300, damping: 25 }}
|
||||
>
|
||||
<Card className="h-full border-2 hover:border-emerald-500/50 transition-all duration-300 hover:shadow-xl hover:shadow-emerald-500/10 cursor-pointer group relative overflow-hidden">
|
||||
<div className="absolute top-0 right-0 w-32 h-32 bg-gradient-to-br from-emerald-500/10 to-transparent rounded-full blur-2xl -mr-16 -mt-16 group-hover:scale-150 transition-transform duration-500" />
|
||||
<CardHeader className="relative">
|
||||
<div className="w-12 h-12 bg-gradient-to-br from-emerald-500/20 to-emerald-600/10 rounded-xl flex items-center justify-center mb-3 group-hover:scale-110 group-hover:rotate-3 transition-all duration-300 ring-1 ring-emerald-500/20">
|
||||
<Users className="w-6 h-6 text-emerald-600 dark:text-emerald-400" />
|
||||
</div>
|
||||
<CardTitle className="text-lg">Manage Team</CardTitle>
|
||||
<CardDescription>
|
||||
Invite team members and collaborate on your search space
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4 relative">
|
||||
<div className="space-y-2 text-sm text-muted-foreground">
|
||||
<div className="flex items-center gap-2">
|
||||
<UserPlus className="w-4 h-4 text-emerald-500" />
|
||||
<span>Invite team members</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<CheckCircle className="w-4 h-4 text-emerald-500" />
|
||||
<span>Assign roles & permissions</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<CheckCircle className="w-4 h-4 text-emerald-500" />
|
||||
<span>Collaborate together</span>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
className="w-full bg-emerald-600 hover:bg-emerald-700 text-white group-hover:shadow-lg group-hover:shadow-emerald-500/25 transition-all duration-300"
|
||||
onClick={() => router.push(`/dashboard/${searchSpaceId}/team`)}
|
||||
>
|
||||
Manage Team
|
||||
<ArrowRight className="w-4 h-4 ml-2 group-hover:translate-x-1 transition-transform" />
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
{/* Add Sources Card */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.7, type: "spring", stiffness: 300, damping: 25 }}
|
||||
>
|
||||
<Card className="h-full border-2 hover:border-blue-500/50 transition-all duration-300 hover:shadow-xl hover:shadow-blue-500/10 cursor-pointer group relative overflow-hidden">
|
||||
<div className="absolute top-0 right-0 w-32 h-32 bg-gradient-to-br from-blue-500/10 to-transparent rounded-full blur-2xl -mr-16 -mt-16 group-hover:scale-150 transition-transform duration-500" />
|
||||
<CardHeader className="relative">
|
||||
<div className="w-12 h-12 bg-gradient-to-br from-blue-500/20 to-blue-600/10 rounded-xl flex items-center justify-center mb-3 group-hover:scale-110 group-hover:rotate-3 transition-all duration-300 ring-1 ring-blue-500/20">
|
||||
<FileText className="w-6 h-6 text-blue-600 dark:text-blue-400" />
|
||||
</div>
|
||||
<CardTitle className="text-lg">Add Sources</CardTitle>
|
||||
<CardDescription>
|
||||
Connect your data sources to start building your knowledge base
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4 relative">
|
||||
<div className="space-y-2 text-sm text-muted-foreground">
|
||||
<div className="flex items-center gap-2">
|
||||
<CheckCircle className="w-4 h-4 text-emerald-500" />
|
||||
<span>Connect documents and files</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<CheckCircle className="w-4 h-4 text-emerald-500" />
|
||||
<span>Import from various sources</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<CheckCircle className="w-4 h-4 text-emerald-500" />
|
||||
<span>Build your knowledge base</span>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
className="w-full bg-blue-600 hover:bg-blue-700 text-white group-hover:shadow-lg group-hover:shadow-blue-500/25 transition-all duration-300"
|
||||
onClick={() => router.push(`/dashboard/${searchSpaceId}/sources/add`)}
|
||||
>
|
||||
Add Sources
|
||||
<ArrowRight className="w-4 h-4 ml-2 group-hover:translate-x-1 transition-transform" />
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
{/* Start Chatting Card */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.8, type: "spring", stiffness: 300, damping: 25 }}
|
||||
>
|
||||
<Card className="h-full border-2 hover:border-purple-500/50 transition-all duration-300 hover:shadow-xl hover:shadow-purple-500/10 cursor-pointer group relative overflow-hidden">
|
||||
<div className="absolute top-0 right-0 w-32 h-32 bg-gradient-to-br from-purple-500/10 to-transparent rounded-full blur-2xl -mr-16 -mt-16 group-hover:scale-150 transition-transform duration-500" />
|
||||
<CardHeader className="relative">
|
||||
<div className="w-12 h-12 bg-gradient-to-br from-purple-500/20 to-purple-600/10 rounded-xl flex items-center justify-center mb-3 group-hover:scale-110 group-hover:rotate-3 transition-all duration-300 ring-1 ring-purple-500/20">
|
||||
<MessageSquare className="w-6 h-6 text-purple-600 dark:text-purple-400" />
|
||||
</div>
|
||||
<CardTitle className="text-lg">Start Chatting</CardTitle>
|
||||
<CardDescription>
|
||||
Jump right into the AI researcher and start asking questions
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4 relative">
|
||||
<div className="space-y-2 text-sm text-muted-foreground">
|
||||
<div className="flex items-center gap-2">
|
||||
<CheckCircle className="w-4 h-4 text-emerald-500" />
|
||||
<span>AI-powered conversations</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<CheckCircle className="w-4 h-4 text-emerald-500" />
|
||||
<span>Research and explore topics</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<CheckCircle className="w-4 h-4 text-emerald-500" />
|
||||
<span>Get instant insights</span>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
className="w-full bg-purple-600 hover:bg-purple-700 text-white group-hover:shadow-lg group-hover:shadow-purple-500/25 transition-all duration-300"
|
||||
onClick={() => router.push(`/dashboard/${searchSpaceId}/researcher`)}
|
||||
>
|
||||
Start Chatting
|
||||
<ArrowRight className="w-4 h-4 ml-2 group-hover:translate-x-1 transition-transform" />
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* Quick Stats */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.9 }}
|
||||
className="flex flex-wrap justify-center gap-2 pt-4"
|
||||
>
|
||||
<Badge variant="secondary">
|
||||
✓ {allConfigs.length} LLM provider{allConfigs.length > 1 ? "s" : ""} available
|
||||
</Badge>
|
||||
{globalConfigs.length > 0 && (
|
||||
<Badge variant="secondary">✓ {globalConfigs.length} Global config(s)</Badge>
|
||||
)}
|
||||
{llmConfigs.length > 0 && (
|
||||
<Badge variant="secondary">✓ {llmConfigs.length} Custom config(s)</Badge>
|
||||
)}
|
||||
<Badge variant="secondary">✓ All roles assigned</Badge>
|
||||
<Badge variant="secondary">✓ Ready to use</Badge>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
8
surfsense_web/components/onboard/index.ts
Normal file
8
surfsense_web/components/onboard/index.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
export { OnboardActionCard } from "./onboard-action-card";
|
||||
export { OnboardAdvancedSettings } from "./onboard-advanced-settings";
|
||||
export { OnboardHeader } from "./onboard-header";
|
||||
export { OnboardLLMSetup } from "./onboard-llm-setup";
|
||||
export { OnboardLoading } from "./onboard-loading";
|
||||
export { OnboardStats } from "./onboard-stats";
|
||||
export { SetupLLMStep } from "./setup-llm-step";
|
||||
export { SetupPromptStep } from "./setup-prompt-step";
|
||||
114
surfsense_web/components/onboard/onboard-action-card.tsx
Normal file
114
surfsense_web/components/onboard/onboard-action-card.tsx
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
"use client";
|
||||
|
||||
import { ArrowRight, CheckCircle, type LucideIcon } from "lucide-react";
|
||||
import { motion } from "motion/react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface OnboardActionCardProps {
|
||||
title: string;
|
||||
description: string;
|
||||
icon: LucideIcon;
|
||||
features: string[];
|
||||
buttonText: string;
|
||||
onClick: () => void;
|
||||
colorScheme: "emerald" | "blue" | "violet";
|
||||
delay?: number;
|
||||
}
|
||||
|
||||
const colorSchemes = {
|
||||
emerald: {
|
||||
iconBg: "bg-emerald-500/10 dark:bg-emerald-500/20",
|
||||
iconRing: "ring-emerald-500/20 dark:ring-emerald-500/30",
|
||||
iconColor: "text-emerald-600 dark:text-emerald-400",
|
||||
checkColor: "text-emerald-500",
|
||||
buttonBg: "bg-emerald-600 hover:bg-emerald-500",
|
||||
hoverBorder: "hover:border-emerald-500/50",
|
||||
},
|
||||
blue: {
|
||||
iconBg: "bg-blue-500/10 dark:bg-blue-500/20",
|
||||
iconRing: "ring-blue-500/20 dark:ring-blue-500/30",
|
||||
iconColor: "text-blue-600 dark:text-blue-400",
|
||||
checkColor: "text-blue-500",
|
||||
buttonBg: "bg-blue-600 hover:bg-blue-500",
|
||||
hoverBorder: "hover:border-blue-500/50",
|
||||
},
|
||||
violet: {
|
||||
iconBg: "bg-violet-500/10 dark:bg-violet-500/20",
|
||||
iconRing: "ring-violet-500/20 dark:ring-violet-500/30",
|
||||
iconColor: "text-violet-600 dark:text-violet-400",
|
||||
checkColor: "text-violet-500",
|
||||
buttonBg: "bg-violet-600 hover:bg-violet-500",
|
||||
hoverBorder: "hover:border-violet-500/50",
|
||||
},
|
||||
};
|
||||
|
||||
export function OnboardActionCard({
|
||||
title,
|
||||
description,
|
||||
icon: Icon,
|
||||
features,
|
||||
buttonText,
|
||||
onClick,
|
||||
colorScheme,
|
||||
delay = 0,
|
||||
}: OnboardActionCardProps) {
|
||||
const colors = colorSchemes[colorScheme];
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay, type: "spring", stiffness: 200 }}
|
||||
whileHover={{ y: -6, transition: { duration: 0.2 } }}
|
||||
>
|
||||
<Card
|
||||
className={cn(
|
||||
"h-full cursor-pointer group relative overflow-hidden transition-all duration-300",
|
||||
"border bg-card hover:shadow-lg",
|
||||
colors.hoverBorder
|
||||
)}
|
||||
onClick={onClick}
|
||||
>
|
||||
<CardHeader className="relative pb-4">
|
||||
<motion.div
|
||||
className={cn(
|
||||
"w-14 h-14 rounded-2xl flex items-center justify-center mb-4 ring-1 transition-all duration-300",
|
||||
colors.iconBg,
|
||||
colors.iconRing,
|
||||
"group-hover:scale-110"
|
||||
)}
|
||||
whileHover={{ rotate: [0, -5, 5, 0] }}
|
||||
transition={{ duration: 0.5 }}
|
||||
>
|
||||
<Icon className={cn("w-7 h-7", colors.iconColor)} />
|
||||
</motion.div>
|
||||
<CardTitle className="text-xl">{title}</CardTitle>
|
||||
<CardDescription>{description}</CardDescription>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="relative space-y-4">
|
||||
<div className="space-y-2.5 text-sm text-muted-foreground">
|
||||
{features.map((feature, index) => (
|
||||
<div key={index} className="flex items-center gap-2.5">
|
||||
<CheckCircle className={cn("w-4 h-4", colors.checkColor)} />
|
||||
<span>{feature}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<Button
|
||||
className={cn(
|
||||
"w-full text-white border-0 transition-all duration-300",
|
||||
colors.buttonBg
|
||||
)}
|
||||
>
|
||||
{buttonText}
|
||||
<ArrowRight className="w-4 h-4 ml-2 group-hover:translate-x-1 transition-transform" />
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
144
surfsense_web/components/onboard/onboard-advanced-settings.tsx
Normal file
144
surfsense_web/components/onboard/onboard-advanced-settings.tsx
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
"use client";
|
||||
|
||||
import { ChevronDown, MessageSquare, Settings2 } from "lucide-react";
|
||||
import { AnimatePresence, motion } from "motion/react";
|
||||
import { SetupLLMStep } from "@/components/onboard/setup-llm-step";
|
||||
import { SetupPromptStep } from "@/components/onboard/setup-prompt-step";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface OnboardAdvancedSettingsProps {
|
||||
searchSpaceId: number;
|
||||
showLLMSettings: boolean;
|
||||
setShowLLMSettings: (show: boolean) => void;
|
||||
showPromptSettings: boolean;
|
||||
setShowPromptSettings: (show: boolean) => void;
|
||||
onConfigCreated: () => void;
|
||||
onConfigDeleted: () => void;
|
||||
onPreferencesUpdated: () => Promise<void>;
|
||||
}
|
||||
|
||||
export function OnboardAdvancedSettings({
|
||||
searchSpaceId,
|
||||
showLLMSettings,
|
||||
setShowLLMSettings,
|
||||
showPromptSettings,
|
||||
setShowPromptSettings,
|
||||
onConfigCreated,
|
||||
onConfigDeleted,
|
||||
onPreferencesUpdated,
|
||||
}: OnboardAdvancedSettingsProps) {
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 1 }}
|
||||
className="space-y-4"
|
||||
>
|
||||
{/* LLM Configuration */}
|
||||
<Collapsible open={showLLMSettings} onOpenChange={setShowLLMSettings}>
|
||||
<CollapsibleTrigger asChild>
|
||||
<Card className="hover:bg-muted/50 transition-colors cursor-pointer">
|
||||
<CardContent className="py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-2 rounded-xl bg-fuchsia-500/10 dark:bg-fuchsia-500/20 border border-fuchsia-500/20">
|
||||
<Settings2 className="w-5 h-5 text-fuchsia-600 dark:text-fuchsia-400" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-semibold">LLM Configuration</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Customize AI models and role assignments
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<motion.div
|
||||
animate={{ rotate: showLLMSettings ? 180 : 0 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
<ChevronDown className="w-5 h-5 text-muted-foreground" />
|
||||
</motion.div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</CollapsibleTrigger>
|
||||
|
||||
<CollapsibleContent>
|
||||
<AnimatePresence>
|
||||
{showLLMSettings && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, height: 0 }}
|
||||
animate={{ opacity: 1, height: "auto" }}
|
||||
exit={{ opacity: 0, height: 0 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
<Card className="mt-2">
|
||||
<CardContent className="pt-6">
|
||||
<SetupLLMStep
|
||||
searchSpaceId={searchSpaceId}
|
||||
onConfigCreated={onConfigCreated}
|
||||
onConfigDeleted={onConfigDeleted}
|
||||
onPreferencesUpdated={onPreferencesUpdated}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
|
||||
{/* Prompt Configuration */}
|
||||
<Collapsible open={showPromptSettings} onOpenChange={setShowPromptSettings}>
|
||||
<CollapsibleTrigger asChild>
|
||||
<Card className="hover:bg-muted/50 transition-colors cursor-pointer">
|
||||
<CardContent className="py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-2 rounded-xl bg-cyan-500/10 dark:bg-cyan-500/20 border border-cyan-500/20">
|
||||
<MessageSquare className="w-5 h-5 text-cyan-600 dark:text-cyan-400" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-semibold">AI Response Settings</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Configure citations and custom instructions (Optional)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<motion.div
|
||||
animate={{ rotate: showPromptSettings ? 180 : 0 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
>
|
||||
<ChevronDown className="w-5 h-5 text-muted-foreground" />
|
||||
</motion.div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</CollapsibleTrigger>
|
||||
|
||||
<CollapsibleContent>
|
||||
<AnimatePresence>
|
||||
{showPromptSettings && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, height: 0 }}
|
||||
animate={{ opacity: 1, height: "auto" }}
|
||||
exit={{ opacity: 0, height: 0 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
>
|
||||
<Card className="mt-2">
|
||||
<CardContent className="pt-6">
|
||||
<SetupPromptStep
|
||||
searchSpaceId={searchSpaceId}
|
||||
onComplete={() => setShowPromptSettings(false)}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
56
surfsense_web/components/onboard/onboard-header.tsx
Normal file
56
surfsense_web/components/onboard/onboard-header.tsx
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
"use client";
|
||||
|
||||
import { CheckCircle } from "lucide-react";
|
||||
import { motion } from "motion/react";
|
||||
import { Logo } from "@/components/Logo";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
|
||||
interface OnboardHeaderProps {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
isReady?: boolean;
|
||||
}
|
||||
|
||||
export function OnboardHeader({ title, subtitle, isReady }: OnboardHeaderProps) {
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5, delay: 0.1 }}
|
||||
className="text-center mb-10"
|
||||
>
|
||||
<motion.div
|
||||
initial={{ scale: 0 }}
|
||||
animate={{ scale: 1 }}
|
||||
transition={{ type: "spring", stiffness: 200, delay: 0.2 }}
|
||||
className="inline-flex items-center justify-center mb-6"
|
||||
>
|
||||
<Logo className="w-20 h-20 rounded-2xl shadow-lg" />
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.3 }}
|
||||
className="space-y-2"
|
||||
>
|
||||
<h1 className="text-4xl md:text-5xl font-bold text-foreground">{title}</h1>
|
||||
<p className="text-muted-foreground text-lg md:text-xl max-w-2xl mx-auto">{subtitle}</p>
|
||||
</motion.div>
|
||||
|
||||
{isReady && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.8 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ delay: 0.4, type: "spring" }}
|
||||
className="mt-4"
|
||||
>
|
||||
<Badge className="px-4 py-2 text-sm bg-emerald-500/10 border-emerald-500/30 text-emerald-600 dark:text-emerald-400">
|
||||
<CheckCircle className="w-4 h-4 mr-2" />
|
||||
AI Configuration Complete
|
||||
</Badge>
|
||||
</motion.div>
|
||||
)}
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
93
surfsense_web/components/onboard/onboard-llm-setup.tsx
Normal file
93
surfsense_web/components/onboard/onboard-llm-setup.tsx
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
"use client";
|
||||
|
||||
import { Bot } from "lucide-react";
|
||||
import { motion } from "motion/react";
|
||||
import { Logo } from "@/components/Logo";
|
||||
import { SetupLLMStep } from "@/components/onboard/setup-llm-step";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
|
||||
interface OnboardLLMSetupProps {
|
||||
searchSpaceId: number;
|
||||
title: string;
|
||||
configTitle: string;
|
||||
configDescription: string;
|
||||
onConfigCreated: () => void;
|
||||
onConfigDeleted: () => void;
|
||||
onPreferencesUpdated: () => Promise<void>;
|
||||
}
|
||||
|
||||
export function OnboardLLMSetup({
|
||||
searchSpaceId,
|
||||
title,
|
||||
configTitle,
|
||||
configDescription,
|
||||
onConfigCreated,
|
||||
onConfigDeleted,
|
||||
onPreferencesUpdated,
|
||||
}: OnboardLLMSetupProps) {
|
||||
return (
|
||||
<div className="min-h-screen bg-background flex items-center justify-center p-4">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
className="w-full max-w-4xl"
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="text-center mb-8">
|
||||
<motion.div
|
||||
initial={{ scale: 0 }}
|
||||
animate={{ scale: 1 }}
|
||||
transition={{ type: "spring", stiffness: 200, delay: 0.1 }}
|
||||
className="inline-flex items-center justify-center mb-6"
|
||||
>
|
||||
<Logo className="w-16 h-16 rounded-2xl shadow-lg" />
|
||||
</motion.div>
|
||||
<motion.h1
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.2 }}
|
||||
className="text-4xl font-bold text-foreground mb-3"
|
||||
>
|
||||
{title}
|
||||
</motion.h1>
|
||||
<motion.p
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.3 }}
|
||||
className="text-muted-foreground text-lg"
|
||||
>
|
||||
Configure your AI model to get started
|
||||
</motion.p>
|
||||
</div>
|
||||
|
||||
{/* LLM Setup Card */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.4 }}
|
||||
>
|
||||
<Card className="shadow-lg">
|
||||
<CardHeader className="text-center border-b pb-6">
|
||||
<div className="flex items-center justify-center gap-3 mb-2">
|
||||
<div className="p-2 rounded-xl bg-primary/10 border border-primary/20">
|
||||
<Bot className="w-6 h-6 text-primary" />
|
||||
</div>
|
||||
<CardTitle className="text-2xl">{configTitle}</CardTitle>
|
||||
</div>
|
||||
<CardDescription>{configDescription}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-6">
|
||||
<SetupLLMStep
|
||||
searchSpaceId={searchSpaceId}
|
||||
onConfigCreated={onConfigCreated}
|
||||
onConfigDeleted={onConfigDeleted}
|
||||
onPreferencesUpdated={onPreferencesUpdated}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
47
surfsense_web/components/onboard/onboard-loading.tsx
Normal file
47
surfsense_web/components/onboard/onboard-loading.tsx
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
"use client";
|
||||
|
||||
import { Wand2 } from "lucide-react";
|
||||
import { motion } from "motion/react";
|
||||
|
||||
interface OnboardLoadingProps {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
}
|
||||
|
||||
export function OnboardLoading({ title, subtitle }: OnboardLoadingProps) {
|
||||
return (
|
||||
<div className="min-h-screen bg-background flex items-center justify-center p-4">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.9 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
className="text-center"
|
||||
>
|
||||
<div className="relative mb-8 flex justify-center">
|
||||
<motion.div
|
||||
animate={{ rotate: 360 }}
|
||||
transition={{ duration: 2, repeat: Infinity, ease: "linear" }}
|
||||
>
|
||||
<Wand2 className="w-16 h-16 text-primary" />
|
||||
</motion.div>
|
||||
</div>
|
||||
<h2 className="text-2xl font-bold text-foreground mb-2">{title}</h2>
|
||||
<p className="text-muted-foreground">{subtitle}</p>
|
||||
<div className="mt-6 flex justify-center gap-1.5">
|
||||
{[0, 1, 2].map((i) => (
|
||||
<motion.div
|
||||
key={i}
|
||||
className="w-2 h-2 rounded-full bg-primary"
|
||||
animate={{ scale: [1, 1.5, 1], opacity: [0.5, 1, 0.5] }}
|
||||
transition={{
|
||||
duration: 1,
|
||||
repeat: Infinity,
|
||||
delay: i * 0.2,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
38
surfsense_web/components/onboard/onboard-stats.tsx
Normal file
38
surfsense_web/components/onboard/onboard-stats.tsx
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
"use client";
|
||||
|
||||
import { Bot, Brain, Sparkles } from "lucide-react";
|
||||
import { motion } from "motion/react";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
|
||||
interface OnboardStatsProps {
|
||||
globalConfigsCount: number;
|
||||
userConfigsCount: number;
|
||||
}
|
||||
|
||||
export function OnboardStats({ globalConfigsCount, userConfigsCount }: OnboardStatsProps) {
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.5 }}
|
||||
className="flex flex-wrap justify-center gap-3 mb-10"
|
||||
>
|
||||
{globalConfigsCount > 0 && (
|
||||
<Badge variant="secondary" className="px-3 py-1.5">
|
||||
<Sparkles className="w-3 h-3 mr-1.5 text-violet-500" />
|
||||
{globalConfigsCount} Global Model{globalConfigsCount > 1 ? "s" : ""}
|
||||
</Badge>
|
||||
)}
|
||||
{userConfigsCount > 0 && (
|
||||
<Badge variant="secondary" className="px-3 py-1.5">
|
||||
<Bot className="w-3 h-3 mr-1.5 text-blue-500" />
|
||||
{userConfigsCount} Custom Config{userConfigsCount > 1 ? "s" : ""}
|
||||
</Badge>
|
||||
)}
|
||||
<Badge variant="secondary" className="px-3 py-1.5">
|
||||
<Brain className="w-3 h-3 mr-1.5 text-fuchsia-500" />
|
||||
All Roles Assigned
|
||||
</Badge>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue