fix: fix loading states, flickering and positioning on onboarding (#323)
* Removed background from dog * Changed loading providers to be on tab * Fixed case on add a document button * fixed dog icon flickering * Changed size of animated processing icon on tab trigger * fixed max height on onboarding * Fixed animated steps size * Changed skip overview design * disable autocomplete
This commit is contained in:
parent
9d55ecd717
commit
d63fcb3843
11 changed files with 330 additions and 301 deletions
|
|
@ -7,29 +7,29 @@ const DogIcon = ({ disabled = false, stroke, ...props }: DogIconProps) => {
|
||||||
|
|
||||||
// CSS for the stepped animation states
|
// CSS for the stepped animation states
|
||||||
const animationCSS = `
|
const animationCSS = `
|
||||||
.state1 { animation: showState1 600ms infinite; }
|
.state1 { animation: showDogState1 600ms infinite; }
|
||||||
.state2 { animation: showState2 600ms infinite; }
|
.state2 { animation: showDogState2 600ms infinite; }
|
||||||
.state3 { animation: showState3 600ms infinite; }
|
.state3 { animation: showDogState3 600ms infinite; }
|
||||||
.state4 { animation: showState4 600ms infinite; }
|
.state4 { animation: showDogState4 600ms infinite; }
|
||||||
|
|
||||||
@keyframes showState1 {
|
@keyframes showDogState1 {
|
||||||
0%, 24.99% { opacity: 1; }
|
0%, 24.99% { opacity: 1; }
|
||||||
25%, 100% { opacity: 0; }
|
25%, 100% { opacity: 0; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes showState2 {
|
@keyframes showDogState2 {
|
||||||
0%, 24.99% { opacity: 0; }
|
0%, 24.99% { opacity: 0; }
|
||||||
25%, 49.99% { opacity: 1; }
|
25%, 49.99% { opacity: 1; }
|
||||||
50%, 100% { opacity: 0; }
|
50%, 100% { opacity: 0; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes showState3 {
|
@keyframes showDogState3 {
|
||||||
0%, 49.99% { opacity: 0; }
|
0%, 49.99% { opacity: 0; }
|
||||||
50%, 74.99% { opacity: 1; }
|
50%, 74.99% { opacity: 1; }
|
||||||
75%, 100% { opacity: 0; }
|
75%, 100% { opacity: 0; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes showState4 {
|
@keyframes showDogState4 {
|
||||||
0%, 74.99% { opacity: 0; }
|
0%, 74.99% { opacity: 0; }
|
||||||
75%, 100% { opacity: 1; }
|
75%, 100% { opacity: 1; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ export function AssistantMessage({
|
||||||
>
|
>
|
||||||
<Message
|
<Message
|
||||||
icon={
|
icon={
|
||||||
<div className="w-8 h-8 rounded-lg bg-accent/20 flex items-center justify-center flex-shrink-0 select-none">
|
<div className="w-8 h-8 flex items-center justify-center flex-shrink-0 select-none">
|
||||||
<DogIcon
|
<DogIcon
|
||||||
className="h-6 w-6 transition-colors duration-300"
|
className="h-6 w-6 transition-colors duration-300"
|
||||||
disabled={isCompleted || isInactive}
|
disabled={isCompleted || isInactive}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { ArrowRight, Check, Funnel, Loader2, Plus } from "lucide-react";
|
import { ArrowRight, Check, Funnel, Loader2, Plus } from "lucide-react";
|
||||||
import { forwardRef, useImperativeHandle, useRef } from "react";
|
import { forwardRef, useImperativeHandle, useRef, useState } from "react";
|
||||||
import TextareaAutosize from "react-textarea-autosize";
|
import TextareaAutosize from "react-textarea-autosize";
|
||||||
import type { FilterColor } from "@/components/filter-icon-popover";
|
import type { FilterColor } from "@/components/filter-icon-popover";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
@ -9,7 +9,6 @@ import {
|
||||||
PopoverContent,
|
PopoverContent,
|
||||||
} from "@/components/ui/popover";
|
} from "@/components/ui/popover";
|
||||||
import type { KnowledgeFilterData } from "../types";
|
import type { KnowledgeFilterData } from "../types";
|
||||||
import { useState } from "react";
|
|
||||||
import { FilePreview } from "./file-preview";
|
import { FilePreview } from "./file-preview";
|
||||||
import { SelectedKnowledgeFilter } from "./selected-knowledge-filter";
|
import { SelectedKnowledgeFilter } from "./selected-knowledge-filter";
|
||||||
|
|
||||||
|
|
@ -107,13 +106,17 @@ export const ChatInput = forwardRef<ChatInputHandle, ChatInputProps>(
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Main Input Container - flex-row or flex-col based on textarea height */}
|
{/* Main Input Container - flex-row or flex-col based on textarea height */}
|
||||||
<div className={`relative flex w-full gap-2 ${
|
<div
|
||||||
textareaHeight > 40 ? 'flex-col' : 'flex-row items-center'
|
className={`relative flex w-full gap-2 ${
|
||||||
}`}>
|
textareaHeight > 40 ? "flex-col" : "flex-row items-center"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
{/* Filter + Textarea Section */}
|
{/* Filter + Textarea Section */}
|
||||||
<div className={`flex items-center gap-2 ${textareaHeight > 40 ? 'w-full' : 'flex-1'}`}>
|
<div
|
||||||
{textareaHeight <= 40 && (
|
className={`flex items-center gap-2 ${textareaHeight > 40 ? "w-full" : "flex-1"}`}
|
||||||
selectedFilter ? (
|
>
|
||||||
|
{textareaHeight <= 40 &&
|
||||||
|
(selectedFilter ? (
|
||||||
<SelectedKnowledgeFilter
|
<SelectedKnowledgeFilter
|
||||||
selectedFilter={selectedFilter}
|
selectedFilter={selectedFilter}
|
||||||
parsedFilterData={parsedFilterData}
|
parsedFilterData={parsedFilterData}
|
||||||
|
|
@ -136,8 +139,7 @@ export const ChatInput = forwardRef<ChatInputHandle, ChatInputProps>(
|
||||||
>
|
>
|
||||||
<Funnel className="h-4 w-4" />
|
<Funnel className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
)
|
))}
|
||||||
)}
|
|
||||||
<div
|
<div
|
||||||
className="relative flex-1"
|
className="relative flex-1"
|
||||||
style={{ height: `${textareaHeight}px` }}
|
style={{ height: `${textareaHeight}px` }}
|
||||||
|
|
@ -149,6 +151,7 @@ export const ChatInput = forwardRef<ChatInputHandle, ChatInputProps>(
|
||||||
onKeyDown={onKeyDown}
|
onKeyDown={onKeyDown}
|
||||||
onHeightChange={(height) => setTextareaHeight(height)}
|
onHeightChange={(height) => setTextareaHeight(height)}
|
||||||
maxRows={7}
|
maxRows={7}
|
||||||
|
autoComplete="off"
|
||||||
minRows={1}
|
minRows={1}
|
||||||
placeholder="Ask a question..."
|
placeholder="Ask a question..."
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
|
|
@ -159,9 +162,11 @@ export const ChatInput = forwardRef<ChatInputHandle, ChatInputProps>(
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Action Buttons Section */}
|
{/* Action Buttons Section */}
|
||||||
<div className={`flex items-center gap-2 ${textareaHeight > 40 ? 'justify-between w-full' : ''}`}>
|
<div
|
||||||
{textareaHeight > 40 && (
|
className={`flex items-center gap-2 ${textareaHeight > 40 ? "justify-between w-full" : ""}`}
|
||||||
selectedFilter ? (
|
>
|
||||||
|
{textareaHeight > 40 &&
|
||||||
|
(selectedFilter ? (
|
||||||
<SelectedKnowledgeFilter
|
<SelectedKnowledgeFilter
|
||||||
selectedFilter={selectedFilter}
|
selectedFilter={selectedFilter}
|
||||||
parsedFilterData={parsedFilterData}
|
parsedFilterData={parsedFilterData}
|
||||||
|
|
@ -184,8 +189,7 @@ export const ChatInput = forwardRef<ChatInputHandle, ChatInputProps>(
|
||||||
>
|
>
|
||||||
<Funnel className="h-4 w-4" />
|
<Funnel className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
)
|
))}
|
||||||
)}
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,7 @@ export function AnimatedProviderSteps({
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"transition-all duration-150 relative",
|
"transition-all duration-150 relative",
|
||||||
isDone ? "w-3.5 h-3.5" : "w-3.5 h-2.5",
|
isDone ? "w-3.5 h-3.5" : "w-6 h-6",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<CheckIcon
|
<CheckIcon
|
||||||
|
|
@ -110,7 +110,7 @@ export function AnimatedProviderSteps({
|
||||||
transition={{ duration: 0.4, ease: "easeInOut" }}
|
transition={{ duration: 0.4, ease: "easeInOut" }}
|
||||||
className="flex items-center gap-4 overflow-y-hidden relative h-6"
|
className="flex items-center gap-4 overflow-y-hidden relative h-6"
|
||||||
>
|
>
|
||||||
<div className="w-px h-6 bg-border ml-2" />
|
<div className="w-px h-6 bg-border ml-3" />
|
||||||
<div className="relative h-5 w-full">
|
<div className="relative h-5 w-full">
|
||||||
<AnimatePresence mode="sync" initial={false}>
|
<AnimatePresence mode="sync" initial={false}>
|
||||||
<motion.span
|
<motion.span
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import { AnimatedProviderSteps } from "./animated-provider-steps";
|
||||||
import { IBMOnboarding } from "./ibm-onboarding";
|
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";
|
||||||
|
import { TabTrigger } from "./tab-trigger";
|
||||||
|
|
||||||
interface OnboardingCardProps {
|
interface OnboardingCardProps {
|
||||||
onComplete: () => void;
|
onComplete: () => void;
|
||||||
|
|
@ -191,6 +192,10 @@ const OnboardingCard = ({
|
||||||
>
|
>
|
||||||
<TabsList className="mb-4">
|
<TabsList className="mb-4">
|
||||||
<TabsTrigger value="openai">
|
<TabsTrigger value="openai">
|
||||||
|
<TabTrigger
|
||||||
|
selected={modelProvider === "openai"}
|
||||||
|
isLoading={isLoadingModels}
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex items-center justify-center gap-2 w-8 h-8 rounded-md",
|
"flex items-center justify-center gap-2 w-8 h-8 rounded-md",
|
||||||
|
|
@ -207,12 +212,19 @@ const OnboardingCard = ({
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
OpenAI
|
OpenAI
|
||||||
|
</TabTrigger>
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger value="watsonx">
|
<TabsTrigger value="watsonx">
|
||||||
|
<TabTrigger
|
||||||
|
selected={modelProvider === "watsonx"}
|
||||||
|
isLoading={isLoadingModels}
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex items-center justify-center gap-2 w-8 h-8 rounded-md",
|
"flex items-center justify-center gap-2 w-8 h-8 rounded-md",
|
||||||
modelProvider === "watsonx" ? "bg-[#1063FE]" : "bg-muted",
|
modelProvider === "watsonx"
|
||||||
|
? "bg-[#1063FE]"
|
||||||
|
: "bg-muted",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<IBMLogo
|
<IBMLogo
|
||||||
|
|
@ -225,8 +237,13 @@ const OnboardingCard = ({
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
IBM watsonx.ai
|
IBM watsonx.ai
|
||||||
|
</TabTrigger>
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger value="ollama">
|
<TabsTrigger value="ollama">
|
||||||
|
<TabTrigger
|
||||||
|
selected={modelProvider === "ollama"}
|
||||||
|
isLoading={isLoadingModels}
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex items-center justify-center gap-2 w-8 h-8 rounded-md",
|
"flex items-center justify-center gap-2 w-8 h-8 rounded-md",
|
||||||
|
|
@ -243,32 +260,9 @@ const OnboardingCard = ({
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
Ollama
|
Ollama
|
||||||
|
</TabTrigger>
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
<AnimatePresence>
|
|
||||||
{isLoadingModels && (
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0, height: 0 }}
|
|
||||||
animate={{ opacity: 1, height: "auto" }}
|
|
||||||
exit={{ opacity: 0, height: 0 }}
|
|
||||||
transition={{ duration: 0.1, ease: "easeInOut" }}
|
|
||||||
className="overflow-hidden"
|
|
||||||
>
|
|
||||||
<div className="py-3">
|
|
||||||
<AnimatedProviderSteps
|
|
||||||
currentStep={loadingStep}
|
|
||||||
isCompleted={false}
|
|
||||||
setCurrentStep={setLoadingStep}
|
|
||||||
steps={[
|
|
||||||
"Connecting to the provider",
|
|
||||||
"Fetching language models",
|
|
||||||
"Fetching embedding models",
|
|
||||||
]}
|
|
||||||
storageKey="model-loading-steps"
|
|
||||||
/></div>
|
|
||||||
</motion.div>
|
|
||||||
)}
|
|
||||||
</AnimatePresence>
|
|
||||||
<TabsContent value="openai">
|
<TabsContent value="openai">
|
||||||
<OpenAIOnboarding
|
<OpenAIOnboarding
|
||||||
setSettings={setSettings}
|
setSettings={setSettings}
|
||||||
|
|
@ -295,8 +289,6 @@ const OnboardingCard = ({
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<div>
|
<div>
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ export function OnboardingStep({
|
||||||
<div className="w-8 h-8 rounded-lg flex-shrink-0" />
|
<div className="w-8 h-8 rounded-lg flex-shrink-0" />
|
||||||
) : (
|
) : (
|
||||||
icon || (
|
icon || (
|
||||||
<div className="w-8 h-8 rounded-lg bg-accent/20 flex items-center justify-center flex-shrink-0 select-none">
|
<div className="w-8 h-8 flex items-center justify-center flex-shrink-0 select-none">
|
||||||
<DogIcon
|
<DogIcon
|
||||||
className="h-6 w-6 text-accent-foreground transition-colors duration-300"
|
className="h-6 w-6 text-accent-foreground transition-colors duration-300"
|
||||||
disabled={isCompleted}
|
disabled={isCompleted}
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ const OnboardingUpload = ({ onComplete }: OnboardingUploadProps) => {
|
||||||
onClick={handleUploadClick}
|
onClick={handleUploadClick}
|
||||||
disabled={isUploading}
|
disabled={isUploading}
|
||||||
>
|
>
|
||||||
{isUploading ? "Uploading..." : "Add a Document"}
|
<div>{isUploading ? "Uploading..." : "Add a document"}</div>
|
||||||
</Button>
|
</Button>
|
||||||
<input
|
<input
|
||||||
ref={fileInputRef}
|
ref={fileInputRef}
|
||||||
|
|
|
||||||
|
|
@ -30,12 +30,12 @@ export function ProgressBar({ currentStep, totalSteps, onSkip }: ProgressBarProp
|
||||||
<div className="flex-1 flex justify-end">
|
<div className="flex-1 flex justify-end">
|
||||||
{currentStep > 0 && onSkip && (
|
{currentStep > 0 && onSkip && (
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="link"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={onSkip}
|
onClick={onSkip}
|
||||||
className="flex items-center gap-2 text-xs text-muted-foreground"
|
className="flex items-center gap-2 text-mmd !text-placeholder-foreground hover:!text-foreground hover:!no-underline"
|
||||||
>
|
>
|
||||||
Skip onboarding
|
Skip overview
|
||||||
<ArrowRight className="w-4 h-4" />
|
<ArrowRight className="w-4 h-4" />
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
33
frontend/src/app/onboarding/components/tab-trigger.tsx
Normal file
33
frontend/src/app/onboarding/components/tab-trigger.tsx
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
import AnimatedProcessingIcon from "@/components/ui/animated-processing-icon";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
export function TabTrigger({
|
||||||
|
children,
|
||||||
|
selected,
|
||||||
|
isLoading,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
selected: boolean;
|
||||||
|
isLoading: boolean;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col relative items-start justify-between gap-4 h-full w-full">
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex absolute items-center justify-center h-full w-full transition-opacity duration-200",
|
||||||
|
isLoading && selected ? "opacity-100" : "opacity-0",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<AnimatedProcessingIcon className="text-current shrink-0 h-10 w-10" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex flex-col items-start justify-between gap-4 h-full w-full transition-opacity duration-200",
|
||||||
|
isLoading && selected ? "opacity-0" : "opacity-100",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -156,7 +156,7 @@ export function ChatRenderer({
|
||||||
}}
|
}}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex h-full w-full max-w-full max-h-full items-center justify-center overflow-hidden",
|
"flex h-full w-full max-w-full max-h-full items-center justify-center overflow-hidden",
|
||||||
!showLayout && "absolute",
|
!showLayout && "absolute max-h-[calc(100vh-190px)]",
|
||||||
showLayout && !isOnChatPage && "bg-background",
|
showLayout && !isOnChatPage && "bg-background",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|
@ -199,7 +199,7 @@ export function ChatRenderer({
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
animate={{ opacity: showLayout ? 0 : 1, y: showLayout ? 20 : 0 }}
|
animate={{ opacity: showLayout ? 0 : 1, y: showLayout ? 20 : 0 }}
|
||||||
transition={{ duration: ANIMATION_DURATION, ease: "easeOut" }}
|
transition={{ duration: ANIMATION_DURATION, ease: "easeOut" }}
|
||||||
className={cn("absolute bottom-10 left-0 right-0")}
|
className={cn("absolute bottom-6 left-0 right-0")}
|
||||||
>
|
>
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
currentStep={currentStep}
|
currentStep={currentStep}
|
||||||
|
|
|
||||||
|
|
@ -51,8 +51,8 @@ const AnimatedProcessingIcon = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
width="16"
|
width="24"
|
||||||
height="16"
|
height="24"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
fill="none"
|
fill="none"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue