amaniquery-frontend / src /components /chat /ThinkingIndicator.tsx
Benaah's picture
Upload folder using huggingface_hub
6a059d3 verified
Raw
History Blame Contribute Delete
6.13 kB
"use client"
import { useState, useEffect } from "react"
import { cn } from "@/lib/utils"
import { Brain, ChevronDown, ChevronRight, Check, Clock, Search, Lightbulb } from "lucide-react"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
interface ThinkingStep {
id: string
title: string
status: "pending" | "active" | "completed"
duration?: number
details?: string
}
interface ThinkingIndicatorProps {
isActive: boolean
steps?: ThinkingStep[]
currentStep?: number
className?: string
onToggle?: (expanded: boolean) => void
defaultExpanded?: boolean
}
const defaultThinkingSteps: ThinkingStep[] = [
{ id: "1", title: "Understanding your question", status: "pending" },
{ id: "2", title: "Searching relevant information", status: "pending" },
{ id: "3", title: "Analyzing sources", status: "pending" },
{ id: "4", title: "Formulating response", status: "pending" }
]
export function ThinkingIndicator({
isActive,
steps = defaultThinkingSteps,
currentStep = 0,
className,
onToggle,
defaultExpanded = false
}: ThinkingIndicatorProps) {
const [isExpanded, setIsExpanded] = useState(defaultExpanded)
const [activeSteps, setActiveSteps] = useState(steps)
useEffect(() => {
if (isActive) {
const timer = setInterval(() => {
setActiveSteps(prev => {
const newSteps = [...prev]
const currentActiveIndex = newSteps.findIndex(step => step.status === "active")
if (currentActiveIndex >= 0 && currentActiveIndex < newSteps.length - 1) {
newSteps[currentActiveIndex].status = "completed"
newSteps[currentActiveIndex + 1].status = "active"
} else if (currentActiveIndex === -1 && newSteps.length > 0) {
newSteps[0].status = "active"
}
return newSteps
})
}, 2000)
return () => clearInterval(timer)
}
}, [isActive])
useEffect(() => {
if (!isActive) {
setActiveSteps(steps.map(step => ({ ...step, status: "pending" })))
}
}, [isActive, steps])
const handleToggle = () => {
const newExpanded = !isExpanded
setIsExpanded(newExpanded)
onToggle?.(newExpanded)
}
const getStepIcon = (status: string) => {
switch (status) {
case "completed":
return <Check className="w-4 h-4 text-green-600" />
case "active":
return <div className="w-4 h-4 border-2 border-primary border-t-transparent rounded-full animate-spin" />
default:
return <div className="w-4 h-4 rounded-full border-2 border-muted-foreground/30" />
}
}
if (!isActive) return null
return (
<Card className={cn("border-primary/20 bg-primary/5", className)}>
<CardHeader
className="cursor-pointer hover:bg-primary/10 transition-colors py-3"
onClick={handleToggle}
>
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<Brain className="w-5 h-5 text-primary animate-pulse" />
<CardTitle className="text-base font-medium">Thinking</CardTitle>
{isExpanded ? (
<ChevronDown className="w-4 h-4 text-muted-foreground" />
) : (
<ChevronRight className="w-4 h-4 text-muted-foreground" />
)}
</div>
<div className="flex items-center gap-2 text-xs text-muted-foreground">
<Clock className="w-3 h-3" />
<span>Analyzing...</span>
</div>
</div>
</CardHeader>
{isExpanded && (
<CardContent className="pt-0 pb-4">
<div className="space-y-3">
{activeSteps.map((step, index) => (
<div
key={step.id}
className={cn(
"flex items-center gap-3 p-2 rounded-lg transition-all",
step.status === "active" && "bg-primary/10",
step.status === "completed" && "opacity-75"
)}
>
{getStepIcon(step.status)}
<div className="flex-1">
<div className={cn(
"text-sm font-medium",
step.status === "active" ? "text-foreground" : "text-muted-foreground"
)}>
{step.title}
</div>
{step.details && step.status === "active" && (
<div className="text-xs text-muted-foreground mt-1">
{step.details}
</div>
)}
</div>
{step.status === "active" && (
<Search className="w-4 h-4 text-primary animate-pulse" />
)}
{step.status === "completed" && step.duration && (
<span className="text-xs text-muted-foreground">
{step.duration}ms
</span>
)}
</div>
))}
</div>
</CardContent>
)}
</Card>
)
}
interface CompactThinkingIndicatorProps {
isActive: boolean
className?: string
}
export function CompactThinkingIndicator({ isActive, className }: CompactThinkingIndicatorProps) {
if (!isActive) return null
return (
<div className={cn(
"inline-flex items-center gap-2 px-3 py-1.5 rounded-full bg-primary/10 text-primary text-sm",
className
)}>
<Brain className="w-4 h-4 animate-pulse" />
<span>Thinking...</span>
<div className="flex gap-1">
<div className="w-1.5 h-1.5 bg-primary rounded-full animate-bounce" style={{ animationDelay: '0ms' }} />
<div className="w-1.5 h-1.5 bg-primary rounded-full animate-bounce" style={{ animationDelay: '150ms' }} />
<div className="w-1.5 h-1.5 bg-primary rounded-full animate-bounce" style={{ animationDelay: '300ms' }} />
</div>
</div>
)
}