Spaces:
Build error
Build error
| /** | |
| * Thinking Process Component | |
| * | |
| * Displays the agent's reasoning path including: | |
| * - Step-by-step reasoning (Claude, o1 style) | |
| * - Tool execution visualization | |
| * - Re-ranking progress | |
| * - HyDE query expansion | |
| * - Knowledge base indicators | |
| */ | |
| "use client" | |
| import { useState } from "react" | |
| import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" | |
| import { Badge } from "@/components/ui/badge" | |
| import { ScrollArea } from "@/components/ui/scroll-area" | |
| import { Progress } from "@/components/ui/progress" | |
| import { | |
| ChevronDown, | |
| ChevronRight, | |
| Brain, | |
| Check, | |
| Clock, | |
| Search, | |
| Lightbulb, | |
| AlertCircle, | |
| TrendingUp, | |
| Eye, | |
| Layers, | |
| Zap, | |
| Database, | |
| RefreshCw, | |
| Filter, | |
| Sparkles, | |
| FileText, | |
| Scale, | |
| Newspaper | |
| } from "lucide-react" | |
| import { cn } from "@/lib/utils" | |
| // Enhanced Types | |
| interface ToolExecution { | |
| name: string | |
| status: 'pending' | 'running' | 'completed' | 'error' | |
| input?: Record<string, unknown> | |
| output?: unknown | |
| duration_ms?: number | |
| } | |
| interface RetrievalStep { | |
| namespace: string | |
| docs_retrieved: number | |
| docs_after_rerank?: number | |
| used_hyde?: boolean | |
| query_expanded?: string | |
| } | |
| interface ThoughtStep { | |
| step: number | |
| action: string | |
| observation: string | |
| reasoning: string | |
| duration_ms?: number | |
| confidence?: number | |
| tool_executions?: ToolExecution[] | |
| retrieval?: RetrievalStep | |
| } | |
| interface ReasoningPath { | |
| query: string | |
| thoughts: ThoughtStep[] | |
| total_duration_ms: number | |
| final_conclusion: string | |
| knowledge_bases_used?: string[] | |
| used_reranking?: boolean | |
| used_hyde?: boolean | |
| model_used?: string | |
| } | |
| interface ThinkingProcessProps { | |
| reasoning: ReasoningPath | string | |
| className?: string | |
| defaultExpanded?: boolean | |
| showEnhancements?: boolean | |
| } | |
| export function ThinkingProcess({ | |
| reasoning, | |
| className, | |
| defaultExpanded = false | |
| }: ThinkingProcessProps) { | |
| const [isExpanded, setIsExpanded] = useState(defaultExpanded) | |
| const [expandedSteps, setExpandedSteps] = useState<Set<number>>(new Set()) | |
| // Handle string reasoning (new format) | |
| if (typeof reasoning === 'string') { | |
| return ( | |
| <Card className={cn("border-2 border-primary/20", className)}> | |
| <CardHeader | |
| className="cursor-pointer hover:bg-accent/50 transition-colors py-3" | |
| onClick={() => setIsExpanded(!isExpanded)} | |
| > | |
| <div className="flex items-center justify-between"> | |
| <div className="flex items-center gap-2"> | |
| <Brain className="h-5 w-5 text-primary" /> | |
| <CardTitle className="text-base">Thinking Process</CardTitle> | |
| </div> | |
| <div className="flex items-center gap-3"> | |
| <Badge variant="secondary" className="font-mono text-xs"> | |
| Analysis | |
| </Badge> | |
| {isExpanded ? ( | |
| <ChevronDown className="h-5 w-5 text-muted-foreground" /> | |
| ) : ( | |
| <ChevronRight className="h-5 w-5 text-muted-foreground" /> | |
| )} | |
| </div> | |
| </div> | |
| </CardHeader> | |
| {isExpanded && ( | |
| <CardContent className="pt-0 pb-4"> | |
| <ScrollArea className="max-h-[400px]"> | |
| <div className="bg-muted/30 p-4 rounded-lg text-sm text-muted-foreground whitespace-pre-wrap font-mono leading-relaxed"> | |
| {reasoning} | |
| </div> | |
| </ScrollArea> | |
| </CardContent> | |
| )} | |
| </Card> | |
| ) | |
| } | |
| // Handle structured reasoning (legacy/future format) | |
| const toggleStep = (step: number) => { | |
| const newExpanded = new Set(expandedSteps) | |
| if (newExpanded.has(step)) { | |
| newExpanded.delete(step) | |
| } else { | |
| newExpanded.add(step) | |
| } | |
| setExpandedSteps(newExpanded) | |
| } | |
| const toggleAll = () => { | |
| if (isExpanded) { | |
| setIsExpanded(false) | |
| setExpandedSteps(new Set()) | |
| } else { | |
| setIsExpanded(true) | |
| setExpandedSteps(new Set(reasoning.thoughts.map(t => t.step))) | |
| } | |
| } | |
| return ( | |
| <Card className={cn("border-2 border-primary/20", className)}> | |
| <CardHeader | |
| className="cursor-pointer hover:bg-accent/50 transition-colors" | |
| onClick={toggleAll} | |
| > | |
| <div className="flex items-center justify-between"> | |
| <div className="flex items-center gap-2"> | |
| <Brain className="h-5 w-5 text-primary" /> | |
| <CardTitle className="text-lg">Thinking Process</CardTitle> | |
| </div> | |
| <div className="flex items-center gap-3"> | |
| <Badge variant="secondary" className="font-mono"> | |
| {reasoning.thoughts.length} steps | |
| </Badge> | |
| <Badge variant="outline" className="font-mono"> | |
| {reasoning.total_duration_ms}ms | |
| </Badge> | |
| {isExpanded ? ( | |
| <ChevronDown className="h-5 w-5 text-muted-foreground" /> | |
| ) : ( | |
| <ChevronRight className="h-5 w-5 text-muted-foreground" /> | |
| )} | |
| </div> | |
| </div> | |
| <CardDescription className="mt-2"> | |
| Step-by-step reasoning for: <span className="font-medium">{reasoning.query}</span> | |
| </CardDescription> | |
| </CardHeader> | |
| {isExpanded && ( | |
| <CardContent className="pt-0"> | |
| <ScrollArea className="max-h-[600px]"> | |
| <div className="space-y-3 relative"> | |
| {/* Timeline line */} | |
| <div className="absolute left-[17px] top-4 bottom-4 w-[2px] bg-border" /> | |
| {reasoning.thoughts.map((thought, idx) => ( | |
| <ThoughtStepCard | |
| key={thought.step} | |
| thought={thought} | |
| isExpanded={expandedSteps.has(thought.step)} | |
| onToggle={() => toggleStep(thought.step)} | |
| isLast={idx === reasoning.thoughts.length - 1} | |
| /> | |
| ))} | |
| {/* Final Conclusion */} | |
| <div className="ml-12 p-4 rounded-lg bg-primary/5 border-2 border-primary/20"> | |
| <div className="flex items-center gap-2 mb-2"> | |
| <Check className="h-5 w-5 text-green-500" /> | |
| <h4 className="font-semibold">Conclusion</h4> | |
| </div> | |
| <p className="text-sm text-muted-foreground">{reasoning.final_conclusion}</p> | |
| </div> | |
| </div> | |
| </ScrollArea> | |
| </CardContent> | |
| )} | |
| </Card> | |
| ) | |
| } | |
| // Individual Thought Step Card | |
| function ThoughtStepCard({ | |
| thought, | |
| isExpanded, | |
| onToggle, | |
| isLast | |
| }: { | |
| thought: ThoughtStep | |
| isExpanded: boolean | |
| onToggle: () => void | |
| isLast: boolean | |
| }) { | |
| const getActionIcon = (action: string) => { | |
| const actionLower = action.toLowerCase() | |
| if (actionLower.includes("search") || actionLower.includes("retriev")) { | |
| return <Search className="h-4 w-4" /> | |
| } | |
| if (actionLower.includes("analyz") || actionLower.includes("check")) { | |
| return <Eye className="h-4 w-4" /> | |
| } | |
| if (actionLower.includes("reason") || actionLower.includes("think")) { | |
| return <Lightbulb className="h-4 w-4" /> | |
| } | |
| if (actionLower.includes("synthesiz") || actionLower.includes("combin")) { | |
| return <Layers className="h-4 w-4" /> | |
| } | |
| return <TrendingUp className="h-4 w-4" /> | |
| } | |
| return ( | |
| <div className="relative"> | |
| {/* Step number bubble */} | |
| <div className="absolute left-0 top-3 w-[34px] h-[34px] rounded-full bg-primary flex items-center justify-center text-white font-bold text-sm z-10"> | |
| {thought.step} | |
| </div> | |
| {/* Step card */} | |
| <div className="ml-12 relative"> | |
| <div | |
| className={cn( | |
| "border rounded-lg transition-all cursor-pointer", | |
| isExpanded ? "bg-accent/50 border-primary/30" : "bg-card hover:bg-accent/30" | |
| )} | |
| onClick={onToggle} | |
| > | |
| {/* Header */} | |
| <div className="p-3 flex items-center justify-between"> | |
| <div className="flex items-center gap-2 flex-1"> | |
| {getActionIcon(thought.action)} | |
| <span className="font-medium text-sm">{thought.action}</span> | |
| </div> | |
| <div className="flex items-center gap-2"> | |
| {thought.duration_ms && ( | |
| <Badge variant="outline" className="text-xs font-mono"> | |
| <Clock className="h-3 w-3 mr-1" /> | |
| {thought.duration_ms}ms | |
| </Badge> | |
| )} | |
| {thought.confidence !== undefined && ( | |
| <Badge | |
| variant={thought.confidence >= 0.8 ? "default" : "secondary"} | |
| className="text-xs font-mono" | |
| > | |
| {(thought.confidence * 100).toFixed(0)}% | |
| </Badge> | |
| )} | |
| {isExpanded ? ( | |
| <ChevronDown className="h-4 w-4 text-muted-foreground" /> | |
| ) : ( | |
| <ChevronRight className="h-4 w-4 text-muted-foreground" /> | |
| )} | |
| </div> | |
| </div> | |
| {/* Expanded content */} | |
| {isExpanded && ( | |
| <div className="px-3 pb-3 space-y-3 border-t"> | |
| {/* Observation */} | |
| <div className="pt-3"> | |
| <div className="flex items-center gap-2 mb-1"> | |
| <Eye className="h-3.5 w-3.5 text-muted-foreground" /> | |
| <span className="text-xs font-semibold text-muted-foreground uppercase tracking-wide"> | |
| Observation | |
| </span> | |
| </div> | |
| <p className="text-sm ml-5">{thought.observation}</p> | |
| </div> | |
| {/* Reasoning */} | |
| <div> | |
| <div className="flex items-center gap-2 mb-1"> | |
| <Lightbulb className="h-3.5 w-3.5 text-muted-foreground" /> | |
| <span className="text-xs font-semibold text-muted-foreground uppercase tracking-wide"> | |
| Reasoning | |
| </span> | |
| </div> | |
| <p className="text-sm ml-5 text-muted-foreground">{thought.reasoning}</p> | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| </div> | |
| </div> | |
| ) | |
| } | |
| // Compact version for inline display | |
| export function ThinkingProcessCompact({ | |
| reasoning | |
| }: { | |
| reasoning: ReasoningPath | |
| }) { | |
| const [isExpanded, setIsExpanded] = useState(false) | |
| return ( | |
| <div className="border-l-2 border-primary/20 pl-4 py-2"> | |
| <button | |
| onClick={() => setIsExpanded(!isExpanded)} | |
| className="flex items-center gap-2 text-sm font-medium hover:text-primary transition-colors" | |
| > | |
| <Brain className="h-4 w-4" /> | |
| <span>View thinking process ({reasoning.thoughts.length} steps)</span> | |
| {isExpanded ? ( | |
| <ChevronDown className="h-4 w-4" /> | |
| ) : ( | |
| <ChevronRight className="h-4 w-4" /> | |
| )} | |
| </button> | |
| {isExpanded && ( | |
| <div className="mt-3 space-y-2"> | |
| {reasoning.thoughts.map((thought) => ( | |
| <div key={thought.step} className="text-sm"> | |
| <span className="font-semibold">{thought.step}.</span> {thought.action} | |
| <p className="text-muted-foreground ml-4 text-xs">{thought.observation}</p> | |
| </div> | |
| ))} | |
| </div> | |
| )} | |
| </div> | |
| ) | |
| } | |
| // Loading skeleton | |
| export function ThinkingProcessSkeleton() { | |
| return ( | |
| <Card className="border-2 border-primary/20"> | |
| <CardHeader> | |
| <div className="flex items-center justify-between"> | |
| <div className="flex items-center gap-2"> | |
| <Brain className="h-5 w-5 text-primary animate-pulse" /> | |
| <CardTitle className="text-lg">Thinking...</CardTitle> | |
| </div> | |
| <Badge variant="secondary" className="animate-pulse"> | |
| Processing | |
| </Badge> | |
| </div> | |
| </CardHeader> | |
| <CardContent> | |
| <div className="space-y-3"> | |
| {[1, 2, 3].map((i) => ( | |
| <div key={i} className="flex items-center gap-3"> | |
| <div className="w-8 h-8 rounded-full bg-primary/20 animate-pulse" /> | |
| <div className="flex-1 h-12 bg-accent/50 rounded-lg animate-pulse" /> | |
| </div> | |
| ))} | |
| </div> | |
| </CardContent> | |
| </Card> | |
| ) | |
| } | |
| export default ThinkingProcess | |