create new onboarding experience
This commit is contained in:
parent
391d2671a0
commit
71edefc710
12 changed files with 349 additions and 85 deletions
|
|
@ -1,6 +1,12 @@
|
||||||
const DogIcon = (props: React.SVGProps<SVGSVGElement>) => {
|
interface DogIconProps extends React.SVGProps<SVGSVGElement> {
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DogIcon = ({ disabled = false, stroke, ...props }: DogIconProps) => {
|
||||||
|
const strokeColor = disabled ? "#71717A" : (stroke || "#0F62FE");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
width="24"
|
width="24"
|
||||||
height="24"
|
height="24"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
|
|
@ -10,7 +16,7 @@ const DogIcon = (props: React.SVGProps<SVGSVGElement>) => {
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M19.9049 23H17.907C17.907 23 15.4096 20.5 16.908 16C17.3753 14.2544 17.3813 12.4181 17.2439 11C17.161 10.1434 17.0256 9.43934 16.908 9C16.7416 8.33333 16.8081 7 18.4065 7C19.5457 7 20.9571 6.92944 21.4034 6.5C22.3268 5.61145 21.9029 4 21.9029 4C21.9029 4 20.9039 3 18.906 3C18.7395 2.33333 17.7072 1 14.9101 1C12.113 1 11.5835 2.16589 10.9143 4C10.4155 5.36686 10.423 6.99637 11.1692 7.71747M14.4106 4C14.2441 5.33333 14.4106 8 11.9132 8C11.5968 8 11.3534 7.89548 11.1692 7.71747M14.9101 23H12.4127M7.91738 23H12.4127M10.4148 15.5C11.5715 16.1667 13.5905 18.6 12.4127 23M3.42204 15C1.02177 18.5 1.64205 23 5.41997 23C5.41997 22 5.71966 19.2 6.91841 16C8.41686 12 11.1692 11.4349 11.1692 7.71747M16.908 4V4.5"
|
d="M19.9049 23H17.907C17.907 23 15.4096 20.5 16.908 16C17.3753 14.2544 17.3813 12.4181 17.2439 11C17.161 10.1434 17.0256 9.43934 16.908 9C16.7416 8.33333 16.8081 7 18.4065 7C19.5457 7 20.9571 6.92944 21.4034 6.5C22.3268 5.61145 21.9029 4 21.9029 4C21.9029 4 20.9039 3 18.906 3C18.7395 2.33333 17.7072 1 14.9101 1C12.113 1 11.5835 2.16589 10.9143 4C10.4155 5.36686 10.423 6.99637 11.1692 7.71747M14.4106 4C14.2441 5.33333 14.4106 8 11.9132 8C11.5968 8 11.3534 7.89548 11.1692 7.71747M14.9101 23H12.4127M7.91738 23H12.4127M10.4148 15.5C11.5715 16.1667 13.5905 18.6 12.4127 23M3.42204 15C1.02177 18.5 1.64205 23 5.41997 23C5.41997 22 5.71966 19.2 6.91841 16C8.41686 12 11.1692 11.4349 11.1692 7.71747M16.908 4V4.5"
|
||||||
stroke="#0F62FE"
|
stroke={strokeColor}
|
||||||
strokeWidth="1.5"
|
strokeWidth="1.5"
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { Bot, GitBranch } from "lucide-react";
|
import { Bot, GitBranch } from "lucide-react";
|
||||||
import { MarkdownRenderer } from "@/components/markdown-renderer";
|
import { MarkdownRenderer } from "@/components/markdown-renderer";
|
||||||
import { FunctionCalls } from "./function-calls";
|
import { FunctionCalls } from "./function-calls";
|
||||||
|
import { Message } from "./message";
|
||||||
import type { FunctionCall } from "../types";
|
import type { FunctionCall } from "../types";
|
||||||
import DogIcon from "@/components/logo/dog-icon";
|
import DogIcon from "@/components/logo/dog-icon";
|
||||||
|
|
||||||
|
|
@ -29,24 +30,14 @@ export function AssistantMessage({
|
||||||
const IconComponent = updatedOnboarding ? DogIcon : Bot;
|
const IconComponent = updatedOnboarding ? DogIcon : Bot;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex gap-3">
|
<Message
|
||||||
<div className="w-8 h-8 rounded-lg bg-accent/20 flex items-center justify-center flex-shrink-0 select-none">
|
icon={
|
||||||
<IconComponent className="h-4 w-4 text-accent-foreground" />
|
<div className="w-8 h-8 rounded-lg bg-accent/20 flex items-center justify-center flex-shrink-0 select-none">
|
||||||
</div>
|
<IconComponent className="h-4 w-4 text-accent-foreground" />
|
||||||
<div className="flex-1 min-w-0">
|
</div>
|
||||||
<FunctionCalls
|
}
|
||||||
functionCalls={functionCalls}
|
actions={
|
||||||
messageIndex={messageIndex}
|
showForkButton && onFork ? (
|
||||||
expandedFunctionCalls={expandedFunctionCalls}
|
|
||||||
onToggle={onToggle}
|
|
||||||
/>
|
|
||||||
<MarkdownRenderer chatMessage={content} />
|
|
||||||
{isStreaming && (
|
|
||||||
<span className="inline-block w-2 h-4 bg-blue-400 ml-1 animate-pulse"></span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{showForkButton && onFork && (
|
|
||||||
<div className="flex-shrink-0 ml-2">
|
|
||||||
<button
|
<button
|
||||||
onClick={onFork}
|
onClick={onFork}
|
||||||
className="opacity-0 group-hover:opacity-100 transition-opacity p-1 hover:bg-accent rounded text-muted-foreground hover:text-foreground"
|
className="opacity-0 group-hover:opacity-100 transition-opacity p-1 hover:bg-accent rounded text-muted-foreground hover:text-foreground"
|
||||||
|
|
@ -54,8 +45,19 @@ export function AssistantMessage({
|
||||||
>
|
>
|
||||||
<GitBranch className="h-3 w-3" />
|
<GitBranch className="h-3 w-3" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
) : undefined
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<FunctionCalls
|
||||||
|
functionCalls={functionCalls}
|
||||||
|
messageIndex={messageIndex}
|
||||||
|
expandedFunctionCalls={expandedFunctionCalls}
|
||||||
|
onToggle={onToggle}
|
||||||
|
/>
|
||||||
|
<MarkdownRenderer chatMessage={content} />
|
||||||
|
{isStreaming && (
|
||||||
|
<span className="inline-block w-2 h-4 bg-blue-400 ml-1 animate-pulse"></span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</Message>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
17
frontend/src/app/chat/components/message.tsx
Normal file
17
frontend/src/app/chat/components/message.tsx
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { ReactNode } from "react";
|
||||||
|
|
||||||
|
interface MessageProps {
|
||||||
|
icon: ReactNode;
|
||||||
|
children: ReactNode;
|
||||||
|
actions?: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Message({ icon, children, actions }: MessageProps) {
|
||||||
|
return (
|
||||||
|
<div className="flex gap-3">
|
||||||
|
{icon}
|
||||||
|
<div className="flex-1 min-w-0">{children}</div>
|
||||||
|
{actions && <div className="flex-shrink-0 ml-2">{actions}</div>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { User } from "lucide-react";
|
import { User } from "lucide-react";
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||||
import { useAuth } from "@/contexts/auth-context";
|
import { useAuth } from "@/contexts/auth-context";
|
||||||
|
import { Message } from "./message";
|
||||||
|
|
||||||
interface UserMessageProps {
|
interface UserMessageProps {
|
||||||
content: string;
|
content: string;
|
||||||
|
|
@ -10,22 +11,23 @@ export function UserMessage({ content }: UserMessageProps) {
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex gap-3">
|
<Message
|
||||||
<Avatar className="w-8 h-8 flex-shrink-0 select-none">
|
icon={
|
||||||
<AvatarImage draggable={false} src={user?.picture} alt={user?.name} />
|
<Avatar className="w-8 h-8 flex-shrink-0 select-none">
|
||||||
<AvatarFallback className="text-sm bg-primary/20 text-primary">
|
<AvatarImage draggable={false} src={user?.picture} alt={user?.name} />
|
||||||
{user?.name ? (
|
<AvatarFallback className="text-sm bg-primary/20 text-primary">
|
||||||
user.name.charAt(0).toUpperCase()
|
{user?.name ? (
|
||||||
) : (
|
user.name.charAt(0).toUpperCase()
|
||||||
<User className="h-4 w-4" />
|
) : (
|
||||||
)}
|
<User className="h-4 w-4" />
|
||||||
</AvatarFallback>
|
)}
|
||||||
</Avatar>
|
</AvatarFallback>
|
||||||
<div className="flex-1">
|
</Avatar>
|
||||||
<p className="text-foreground whitespace-pre-wrap break-words overflow-wrap-anywhere">
|
}
|
||||||
{content}
|
>
|
||||||
</p>
|
<p className="text-foreground whitespace-pre-wrap break-words overflow-wrap-anywhere">
|
||||||
</div>
|
{content}
|
||||||
</div>
|
</p>
|
||||||
|
</Message>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
import { ReactNode, useEffect, useState } from "react";
|
||||||
|
import { motion, AnimatePresence } from "motion/react";
|
||||||
|
import { Message } from "@/app/chat/components/message";
|
||||||
|
import DogIcon from "@/components/logo/dog-icon";
|
||||||
|
|
||||||
|
interface OnboardingStepProps {
|
||||||
|
text: string;
|
||||||
|
children: ReactNode;
|
||||||
|
isVisible: boolean;
|
||||||
|
isCompleted?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function OnboardingStep({ text, children, isVisible, isCompleted = false }: OnboardingStepProps) {
|
||||||
|
const [displayedText, setDisplayedText] = useState("");
|
||||||
|
const [showChildren, setShowChildren] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isVisible) {
|
||||||
|
setDisplayedText("");
|
||||||
|
setShowChildren(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentIndex = 0;
|
||||||
|
setDisplayedText("");
|
||||||
|
setShowChildren(false);
|
||||||
|
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
if (currentIndex < text.length) {
|
||||||
|
setDisplayedText(text.slice(0, currentIndex + 1));
|
||||||
|
currentIndex++;
|
||||||
|
} else {
|
||||||
|
clearInterval(interval);
|
||||||
|
setShowChildren(true);
|
||||||
|
}
|
||||||
|
}, 10); // 10ms per character
|
||||||
|
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, [text, isVisible]);
|
||||||
|
|
||||||
|
if (!isVisible) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.4, ease: "easeOut" }}
|
||||||
|
className={isCompleted ? "opacity-50" : ""}
|
||||||
|
>
|
||||||
|
<Message
|
||||||
|
icon={
|
||||||
|
<div className="w-8 h-8 rounded-lg bg-accent/20 flex items-center justify-center flex-shrink-0 select-none">
|
||||||
|
<DogIcon className="h-6 w-6 text-accent-foreground" disabled={isCompleted} />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<p className={`text-foreground text-sm py-1.5 ${isCompleted ? "text-placeholder-foreground" : ""}`}>
|
||||||
|
{displayedText}
|
||||||
|
{!showChildren && !isCompleted && <span className="inline-block w-1 h-4 bg-primary ml-1 animate-pulse" />}
|
||||||
|
</p>
|
||||||
|
<AnimatePresence>
|
||||||
|
{showChildren && !isCompleted && (
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 10 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
exit={{ opacity: 0, height: 0 }}
|
||||||
|
transition={{ duration: 0.3, ease: "easeOut" }}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
</div>
|
||||||
|
</Message>
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
}
|
||||||
27
frontend/src/app/new-onboarding/components/progress-bar.tsx
Normal file
27
frontend/src/app/new-onboarding/components/progress-bar.tsx
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
interface ProgressBarProps {
|
||||||
|
currentStep: number;
|
||||||
|
totalSteps: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ProgressBar({ currentStep, totalSteps }: ProgressBarProps) {
|
||||||
|
const progressPercentage = ((currentStep + 1) / totalSteps) * 100;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-full">
|
||||||
|
<div className="flex items-center max-w-md mx-auto gap-3">
|
||||||
|
<div className="flex-1 h-2 bg-background rounded-full overflow-hidden">
|
||||||
|
<div
|
||||||
|
className="h-full transition-all duration-300 ease-in-out"
|
||||||
|
style={{
|
||||||
|
width: `${progressPercentage}%`,
|
||||||
|
background: 'linear-gradient(to right, #818CF8, #F472B6)'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span className="text-sm text-muted-foreground whitespace-nowrap">
|
||||||
|
{currentStep + 1}/{totalSteps}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
109
frontend/src/app/new-onboarding/page.tsx
Normal file
109
frontend/src/app/new-onboarding/page.tsx
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Suspense, useState } from "react";
|
||||||
|
import { ProtectedRoute } from "@/components/protected-route";
|
||||||
|
import { DoclingHealthBanner } from "@/components/docling-health-banner";
|
||||||
|
import { DotPattern } from "@/components/ui/dot-pattern";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { OnboardingStep } from "./components/onboarding-step";
|
||||||
|
import { ProgressBar } from "./components/progress-bar";
|
||||||
|
import OnboardingCard from "../onboarding/components/onboarding-card";
|
||||||
|
|
||||||
|
const TOTAL_STEPS = 4;
|
||||||
|
|
||||||
|
function NewOnboardingPage() {
|
||||||
|
const [currentStep, setCurrentStep] = useState(0);
|
||||||
|
|
||||||
|
const handleStepComplete = () => {
|
||||||
|
if (currentStep < TOTAL_STEPS - 1) {
|
||||||
|
setCurrentStep(currentStep + 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-dvh w-full flex gap-5 flex-col items-center justify-center bg-primary-foreground relative p-4">
|
||||||
|
<DoclingHealthBanner className="absolute top-0 left-0 right-0 w-full z-20" />
|
||||||
|
|
||||||
|
{/* Chat-like content area */}
|
||||||
|
<div className="flex flex-col items-center gap-5 w-full max-w-3xl z-10">
|
||||||
|
<div className="w-full h-[872px] bg-background border rounded-lg p-4 shadow-sm overflow-y-auto">
|
||||||
|
<div className="space-y-6">
|
||||||
|
<OnboardingStep
|
||||||
|
isVisible={currentStep >= 0}
|
||||||
|
isCompleted={currentStep > 0}
|
||||||
|
text="Let's get started by setting up your model provider."
|
||||||
|
>
|
||||||
|
<OnboardingCard onComplete={handleStepComplete} />
|
||||||
|
</OnboardingStep>
|
||||||
|
|
||||||
|
<OnboardingStep
|
||||||
|
isVisible={currentStep >= 1}
|
||||||
|
isCompleted={currentStep > 1}
|
||||||
|
text="Step 1: Configure your settings"
|
||||||
|
>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
Let's configure some basic settings for your account.
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
onClick={handleStepComplete}
|
||||||
|
className="px-4 py-2 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90"
|
||||||
|
>
|
||||||
|
Continue
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</OnboardingStep>
|
||||||
|
|
||||||
|
<OnboardingStep
|
||||||
|
isVisible={currentStep >= 2}
|
||||||
|
isCompleted={currentStep > 2}
|
||||||
|
text="Step 2: Connect your model"
|
||||||
|
>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
Choose and connect your preferred AI model provider.
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
onClick={handleStepComplete}
|
||||||
|
className="px-4 py-2 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90"
|
||||||
|
>
|
||||||
|
Continue
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</OnboardingStep>
|
||||||
|
|
||||||
|
<OnboardingStep
|
||||||
|
isVisible={currentStep >= 3}
|
||||||
|
isCompleted={currentStep > 3}
|
||||||
|
text="Step 3: You're all set!"
|
||||||
|
>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
Your account is ready to use. Let's start chatting!
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
onClick={() => window.location.href = "/chat"}
|
||||||
|
className="px-4 py-2 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90"
|
||||||
|
>
|
||||||
|
Go to Chat
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</OnboardingStep>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ProgressBar currentStep={currentStep} totalSteps={TOTAL_STEPS} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ProtectedNewOnboardingPage() {
|
||||||
|
return (
|
||||||
|
<ProtectedRoute>
|
||||||
|
<Suspense fallback={<div>Loading...</div>}>
|
||||||
|
<NewOnboardingPage />
|
||||||
|
</Suspense>
|
||||||
|
</ProtectedRoute>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { LabelWrapper } from "@/components/label-wrapper";
|
import { LabelWrapper } from "@/components/label-wrapper";
|
||||||
import OllamaLogo from "@/components/logo/ollama-logo";
|
|
||||||
import {
|
import {
|
||||||
Accordion,
|
Accordion,
|
||||||
AccordionContent,
|
AccordionContent,
|
||||||
|
|
@ -39,6 +38,8 @@ export function AdvancedOnboarding({
|
||||||
languageModels !== undefined &&
|
languageModels !== undefined &&
|
||||||
languageModel !== undefined &&
|
languageModel !== undefined &&
|
||||||
setLanguageModel !== undefined;
|
setLanguageModel !== undefined;
|
||||||
|
|
||||||
|
const updatedOnboarding = process.env.UPDATED_ONBOARDING === "true";
|
||||||
return (
|
return (
|
||||||
<Accordion type="single" collapsible>
|
<Accordion type="single" collapsible>
|
||||||
<AccordionItem value="item-1">
|
<AccordionItem value="item-1">
|
||||||
|
|
@ -74,18 +75,20 @@ export function AdvancedOnboarding({
|
||||||
/>
|
/>
|
||||||
</LabelWrapper>
|
</LabelWrapper>
|
||||||
)}
|
)}
|
||||||
{(hasLanguageModels || hasEmbeddingModels) && <Separator />}
|
{(hasLanguageModels || hasEmbeddingModels) && !updatedOnboarding && <Separator />}
|
||||||
<LabelWrapper
|
{!updatedOnboarding && (
|
||||||
label="Sample dataset"
|
<LabelWrapper
|
||||||
description="Load sample data to chat with immediately."
|
label="Sample dataset"
|
||||||
id="sample-dataset"
|
description="Load sample data to chat with immediately."
|
||||||
flex
|
id="sample-dataset"
|
||||||
>
|
flex
|
||||||
<Switch
|
>
|
||||||
checked={sampleDataset}
|
<Switch
|
||||||
onCheckedChange={setSampleDataset}
|
checked={sampleDataset}
|
||||||
/>
|
onCheckedChange={setSampleDataset}
|
||||||
</LabelWrapper>
|
/>
|
||||||
|
</LabelWrapper>
|
||||||
|
)}
|
||||||
</AccordionContent>
|
</AccordionContent>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useRouter } from "next/navigation";
|
import { useState } from "react";
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import {
|
import {
|
||||||
type OnboardingVariables,
|
type OnboardingVariables,
|
||||||
useOnboardingMutation,
|
useOnboardingMutation,
|
||||||
} from "@/app/api/mutations/useOnboardingMutation";
|
} from "@/app/api/mutations/useOnboardingMutation";
|
||||||
import { useGetSettingsQuery } from "@/app/api/queries/useGetSettingsQuery";
|
import { useDoclingHealth } from "@/components/docling-health-banner";
|
||||||
import IBMLogo from "@/components/logo/ibm-logo";
|
import IBMLogo from "@/components/logo/ibm-logo";
|
||||||
import OllamaLogo from "@/components/logo/ollama-logo";
|
import OllamaLogo from "@/components/logo/ollama-logo";
|
||||||
import OpenAILogo from "@/components/logo/openai-logo";
|
import OpenAILogo from "@/components/logo/openai-logo";
|
||||||
|
|
@ -28,24 +27,13 @@ import { IBMOnboarding } from "./ibm-onboarding";
|
||||||
import { OllamaOnboarding } from "./ollama-onboarding";
|
import { OllamaOnboarding } from "./ollama-onboarding";
|
||||||
import { OpenAIOnboarding } from "./openai-onboarding";
|
import { OpenAIOnboarding } from "./openai-onboarding";
|
||||||
|
|
||||||
const OnboardingCard = ({
|
interface OnboardingCardProps {
|
||||||
isDoclingHealthy,
|
onComplete: () => void;
|
||||||
}: {
|
}
|
||||||
isDoclingHealthy: boolean;
|
|
||||||
}) => {
|
const OnboardingCard = ({ onComplete }: OnboardingCardProps) => {
|
||||||
const updatedOnboarding = process.env.UPDATED_ONBOARDING === "true";
|
const updatedOnboarding = process.env.UPDATED_ONBOARDING === "true";
|
||||||
const { data: settingsDb, isLoading: isSettingsLoading } =
|
const { isHealthy: isDoclingHealthy } = useDoclingHealth();
|
||||||
useGetSettingsQuery();
|
|
||||||
|
|
||||||
const redirect = "/";
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
// 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");
|
||||||
|
|
@ -71,7 +59,7 @@ const OnboardingCard = ({
|
||||||
const onboardingMutation = useOnboardingMutation({
|
const onboardingMutation = useOnboardingMutation({
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
console.log("Onboarding completed successfully", data);
|
console.log("Onboarding completed successfully", data);
|
||||||
router.push(redirect);
|
onComplete();
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
toast.error("Failed to complete onboarding", {
|
toast.error("Failed to complete onboarding", {
|
||||||
|
|
@ -124,7 +112,7 @@ const OnboardingCard = ({
|
||||||
defaultValue={modelProvider}
|
defaultValue={modelProvider}
|
||||||
onValueChange={handleSetModelProvider}
|
onValueChange={handleSetModelProvider}
|
||||||
>
|
>
|
||||||
<CardHeader>
|
<CardHeader className={`${updatedOnboarding ? "px-0" : ""}`}>
|
||||||
<TabsList>
|
<TabsList>
|
||||||
<TabsTrigger value="openai">
|
<TabsTrigger value="openai">
|
||||||
<OpenAILogo className="w-4 h-4" />
|
<OpenAILogo className="w-4 h-4" />
|
||||||
|
|
@ -140,7 +128,7 @@ const OnboardingCard = ({
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent className={`${updatedOnboarding ? "px-0" : ""}`}>
|
||||||
<TabsContent value="openai">
|
<TabsContent value="openai">
|
||||||
<OpenAIOnboarding
|
<OpenAIOnboarding
|
||||||
setSettings={setSettings}
|
setSettings={setSettings}
|
||||||
|
|
@ -164,7 +152,7 @@ const OnboardingCard = ({
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
<CardFooter className={`flex ${updatedOnboarding ? "" : "justify-end"}`}>
|
<CardFooter className={`flex ${updatedOnboarding ? "px-0" : "justify-end"}`}>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<div>
|
<div>
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,28 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Suspense } from "react";
|
import { Suspense, useEffect } from "react";
|
||||||
import { DoclingHealthBanner, useDoclingHealth } from "@/components/docling-health-banner";
|
import { useRouter } from "next/navigation";
|
||||||
|
import { DoclingHealthBanner } from "@/components/docling-health-banner";
|
||||||
import { ProtectedRoute } from "@/components/protected-route";
|
import { ProtectedRoute } from "@/components/protected-route";
|
||||||
import { DotPattern } from "@/components/ui/dot-pattern";
|
import { DotPattern } from "@/components/ui/dot-pattern";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
import { useGetSettingsQuery } from "@/app/api/queries/useGetSettingsQuery";
|
||||||
import OnboardingCard from "./components/onboarding-card";
|
import OnboardingCard from "./components/onboarding-card";
|
||||||
|
|
||||||
function OnboardingPage() {
|
function LegacyOnboardingPage() {
|
||||||
const { isHealthy: isDoclingHealthy } = useDoclingHealth();
|
const router = useRouter();
|
||||||
|
const { data: settingsDb, isLoading: isSettingsLoading } = useGetSettingsQuery();
|
||||||
|
|
||||||
|
// Redirect if already completed onboarding
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isSettingsLoading && settingsDb && settingsDb.edited) {
|
||||||
|
router.push("/");
|
||||||
|
}
|
||||||
|
}, [isSettingsLoading, settingsDb, router]);
|
||||||
|
|
||||||
|
const handleComplete = () => {
|
||||||
|
router.push("/");
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-dvh w-full flex gap-5 flex-col items-center justify-center bg-background relative p-4">
|
<div className="min-h-dvh w-full flex gap-5 flex-col items-center justify-center bg-background relative p-4">
|
||||||
|
|
@ -32,17 +46,34 @@ function OnboardingPage() {
|
||||||
Connect a model provider
|
Connect a model provider
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<OnboardingCard isDoclingHealthy={isDoclingHealthy} />
|
<OnboardingCard onComplete={handleComplete} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function OnboardingRouter() {
|
||||||
|
const updatedOnboarding = process.env.UPDATED_ONBOARDING === "true";
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (updatedOnboarding) {
|
||||||
|
router.push("/new-onboarding");
|
||||||
|
}
|
||||||
|
}, [updatedOnboarding, router]);
|
||||||
|
|
||||||
|
if (updatedOnboarding) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <LegacyOnboardingPage />;
|
||||||
|
}
|
||||||
|
|
||||||
export default function ProtectedOnboardingPage() {
|
export default function ProtectedOnboardingPage() {
|
||||||
return (
|
return (
|
||||||
<ProtectedRoute>
|
<ProtectedRoute>
|
||||||
<Suspense fallback={<div>Loading onboarding...</div>}>
|
<Suspense fallback={<div>Loading onboarding...</div>}>
|
||||||
<OnboardingPage />
|
<OnboardingRouter />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</ProtectedRoute>
|
</ProtectedRoute>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ export function LayoutWrapper({ children }: { children: React.ReactNode }) {
|
||||||
};
|
};
|
||||||
|
|
||||||
// List of paths that should not show navigation
|
// List of paths that should not show navigation
|
||||||
const authPaths = ["/login", "/auth/callback", "/onboarding"];
|
const authPaths = ["/login", "/auth/callback", "/onboarding", "/new-onboarding"];
|
||||||
const isAuthPage = authPaths.includes(pathname);
|
const isAuthPage = authPaths.includes(pathname);
|
||||||
const isOnKnowledgePage = pathname.startsWith("/knowledge");
|
const isOnKnowledgePage = pathname.startsWith("/knowledge");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,8 @@ export function ProtectedRoute({ children }: ProtectedRouteProps) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isLoading && !isSettingsLoading && !settings.edited) {
|
if (!isLoading && !isSettingsLoading && !settings.edited) {
|
||||||
router.push("/onboarding");
|
const updatedOnboarding = process.env.UPDATED_ONBOARDING === "true";
|
||||||
|
router.push(updatedOnboarding ? "/new-onboarding" : "/onboarding");
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
isLoading,
|
isLoading,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue