diff --git a/src/app/(dashboard)/projects/[id]/page.tsx b/src/app/(dashboard)/projects/[id]/page.tsx index 8094a4f..f434831 100644 --- a/src/app/(dashboard)/projects/[id]/page.tsx +++ b/src/app/(dashboard)/projects/[id]/page.tsx @@ -1,11 +1,11 @@ import { createClient } from '@/lib/supabase/server' import { notFound } from 'next/navigation' -import { ProjectChat } from '@/components/chat/project-chat' import { Badge } from '@/components/ui/badge' import Link from 'next/link' import { ArrowLeft, Settings } from 'lucide-react' import { Button } from '@/components/ui/button' import type { Database } from '@/types/database' +import { ProjectTabs } from './project-tabs' type Project = Database['public']['Tables']['projects']['Row'] type Message = Database['public']['Tables']['messages']['Row'] @@ -24,7 +24,6 @@ export default async function ProjectPage({ params }: { params: Promise<{ id: st notFound() } - // Get messages for this project const { data: messages } = await supabase .from('messages') .select('*') @@ -33,7 +32,6 @@ export default async function ProjectPage({ params }: { params: Promise<{ id: st return (
- {/* Project Header */}
@@ -58,10 +56,11 @@ export default async function ProjectPage({ params }: { params: Promise<{ id: st
- {/* Chat Interface */} -
- -
+
) } diff --git a/src/app/(dashboard)/projects/[id]/project-tabs.tsx b/src/app/(dashboard)/projects/[id]/project-tabs.tsx new file mode 100644 index 0000000..aec6682 --- /dev/null +++ b/src/app/(dashboard)/projects/[id]/project-tabs.tsx @@ -0,0 +1,55 @@ +'use client' + +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' +import { ProjectChat } from '@/components/chat/project-chat' +import { AgenticDashboard } from '@/components/dashboard/agentic-dashboard' +import { MessageSquare, Workflow } from 'lucide-react' +import type { Database } from '@/types/database' + +type Message = Database['public']['Tables']['messages']['Row'] + +interface ProjectTabsProps { + projectId: string + projectName: string + initialMessages: Message[] +} + +export function ProjectTabs({ projectId, projectName, initialMessages }: ProjectTabsProps) { + return ( + +
+
+ + + + Chat + + + + Workflow + + +
+
+ + + + + + +
+ +
+
+
+ ) +} diff --git a/src/components/dashboard/agentic-dashboard.tsx b/src/components/dashboard/agentic-dashboard.tsx new file mode 100644 index 0000000..3adc989 --- /dev/null +++ b/src/components/dashboard/agentic-dashboard.tsx @@ -0,0 +1,298 @@ +'use client' + +import { useState } from 'react' +import { motion, AnimatePresence } from 'motion/react' +import { cn } from '@/lib/utils' +import { ProgressTimeline, PHASES, type Phase } from '@/components/ui/progress-timeline' +import { PhaseIndicator } from '@/components/ui/phase-indicator' +import { StatusBadge } from '@/components/ui/status-badge' +import { ActivityFeed, type Activity } from '@/components/ui/activity-feed' +import { MetricCard } from '@/components/ui/metric-card' +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' +import { Button } from '@/components/ui/button' +import { + Brain, + Search, + Lightbulb, + Map, + Hammer, + Rocket, + BarChart3, + Sparkles, + ChevronRight, + Play, + Pause, + RotateCcw, + Zap, + Clock, + Target, + TrendingUp +} from 'lucide-react' + +const phaseIcons: Record = { + think: , + evaluate: , + ideate: , + plan: , + create: , + deploy: , + analyze: , + enhance: +} + +const phaseDescriptions: Record = { + think: 'Define the problem space and user needs', + evaluate: 'Assess technical feasibility and risks', + ideate: 'Generate solution options through divergent thinking', + plan: 'Select best approach and break into tasks', + create: 'Build the MVP with iterative development', + deploy: 'Ship to production with monitoring', + analyze: 'Collect metrics and user feedback', + enhance: 'Iterate based on data insights' +} + +interface AgenticDashboardProps { + projectId: string + projectName: string + initialPhase?: Phase + initialCompleted?: Phase[] + className?: string +} + +export function AgenticDashboard({ + projectName, + initialPhase = 'think', + initialCompleted = [], + className +}: AgenticDashboardProps) { + const [currentPhase, setCurrentPhase] = useState(initialPhase) + const [completedPhases, setCompletedPhases] = useState(initialCompleted) + const [isRunning, setIsRunning] = useState(false) + const [selectedPhase, setSelectedPhase] = useState(null) + + const [activities] = useState([ + { id: '1', type: 'success', message: 'Project initialized successfully', timestamp: new Date(Date.now() - 3600000) }, + { id: '2', type: 'phase_change', message: 'Started Think phase', timestamp: new Date(Date.now() - 1800000) }, + { id: '3', type: 'action', message: 'Analyzing user requirements...', timestamp: new Date(Date.now() - 900000) }, + ]) + + const progress = (completedPhases.length / PHASES.length) * 100 + const currentIndex = PHASES.indexOf(currentPhase) + + const handleAdvancePhase = () => { + if (currentIndex < PHASES.length - 1) { + setCompletedPhases([...completedPhases, currentPhase]) + setCurrentPhase(PHASES[currentIndex + 1]) + } else { + setCompletedPhases([...completedPhases, currentPhase]) + setCurrentPhase('think') + } + } + + const handleReset = () => { + setCurrentPhase('think') + setCompletedPhases([]) + setIsRunning(false) + } + + return ( +
+ {/* Header */} +
+
+

{projectName}

+
+ + {isRunning ? 'Running' : 'Paused'} + + + Iteration #{Math.floor(completedPhases.length / PHASES.length) + 1} + +
+
+
+ + + +
+
+ + {/* Progress Timeline */} + + + +
+ {Math.round(progress)}% + complete +
+
+
+ + {/* Phase Grid + Details */} +
+ {/* Phase Cards */} +
+ {PHASES.map((phase) => { + const isCompleted = completedPhases.includes(phase) + const isCurrent = phase === currentPhase + const status = isCompleted ? 'completed' : isCurrent ? 'in_progress' : 'not_started' + + return ( + setSelectedPhase(phase)} + > + + +
+
+ {phaseIcons[phase]} +
+
+
+ {phase} + +
+

+ {phaseDescriptions[phase]} +

+
+
+
+
+
+ ) + })} +
+ + {/* Activity Feed */} +
+ + + + + Activity + + + + + + +
+
+ + {/* Metrics */} +
+ } + /> + } + /> + } + /> + } + /> +
+ + {/* Phase Detail Panel */} + + {selectedPhase && ( + + + + +
+ {phaseIcons[selectedPhase]} +
+
+ {selectedPhase} +

+ {phaseDescriptions[selectedPhase]} +

+
+
+
+ +
+
+

Entry Criteria

+
    +
  • • Previous phase completed
  • +
  • • Required inputs available
  • +
  • • Team capacity confirmed
  • +
+
+
+

Key Activities

+
    +
  • • Research & discovery
  • +
  • • Documentation
  • +
  • • Stakeholder alignment
  • +
+
+
+

Exit Criteria

+
    +
  • • Deliverables complete
  • +
  • • Quality checks passed
  • +
  • • Ready for next phase
  • +
+
+
+
+ +
+
+
+
+ )} +
+
+ ) +} diff --git a/src/components/ui/activity-feed.tsx b/src/components/ui/activity-feed.tsx new file mode 100644 index 0000000..898cdf1 --- /dev/null +++ b/src/components/ui/activity-feed.tsx @@ -0,0 +1,80 @@ +'use client' + +import { motion, AnimatePresence } from 'motion/react' +import { formatDistanceToNow } from 'date-fns' +import { cn } from '@/lib/utils' +import { + CheckCircle2, + AlertCircle, + Info, + Zap, + ArrowRight, + Clock +} from 'lucide-react' + +export interface Activity { + id: string + type: 'phase_change' | 'action' | 'notification' | 'error' | 'success' + message: string + timestamp: Date + metadata?: Record +} + +interface ActivityFeedProps { + activities: Activity[] + maxItems?: number + className?: string +} + +const typeConfig = { + phase_change: { icon: ArrowRight, color: 'text-blue-500', bg: 'bg-blue-500/10' }, + action: { icon: Zap, color: 'text-violet-500', bg: 'bg-violet-500/10' }, + notification: { icon: Info, color: 'text-muted-foreground', bg: 'bg-muted' }, + error: { icon: AlertCircle, color: 'text-red-500', bg: 'bg-red-500/10' }, + success: { icon: CheckCircle2, color: 'text-emerald-500', bg: 'bg-emerald-500/10' }, +} + +export function ActivityFeed({ activities, maxItems = 10, className }: ActivityFeedProps) { + const displayActivities = activities.slice(0, maxItems) + + if (displayActivities.length === 0) { + return ( +
+ +

No activity yet

+
+ ) + } + + return ( +
+ + {displayActivities.map((activity, index) => { + const config = typeConfig[activity.type] + const Icon = config.icon + + return ( + +
+ +
+
+

{activity.message}

+

+ {formatDistanceToNow(activity.timestamp, { addSuffix: true })} +

+
+
+ ) + })} +
+
+ ) +} diff --git a/src/components/ui/metric-card.tsx b/src/components/ui/metric-card.tsx new file mode 100644 index 0000000..7e74ea2 --- /dev/null +++ b/src/components/ui/metric-card.tsx @@ -0,0 +1,76 @@ +'use client' + +import { motion, useSpring, useTransform } from 'motion/react' +import { useEffect } from 'react' +import { cn } from '@/lib/utils' +import { TrendingUp, TrendingDown, Minus } from 'lucide-react' + +interface MetricCardProps { + label: string + value: number + format?: 'number' | 'percent' | 'duration' + trend?: 'up' | 'down' | 'neutral' + trendValue?: string + icon?: React.ReactNode + className?: string +} + +function AnimatedNumber({ value, format = 'number' }: { value: number; format?: string }) { + const spring = useSpring(0, { stiffness: 100, damping: 30 }) + const display = useTransform(spring, (current) => { + if (format === 'percent') return `${Math.round(current)}%` + if (format === 'duration') return `${current.toFixed(1)}s` + return Math.round(current).toLocaleString() + }) + + useEffect(() => { + spring.set(value) + }, [spring, value]) + + return {display} +} + +export function MetricCard({ + label, + value, + format = 'number', + trend, + trendValue, + icon, + className +}: MetricCardProps) { + const TrendIcon = trend === 'up' ? TrendingUp : trend === 'down' ? TrendingDown : Minus + + return ( + +
+ {label} + {icon && {icon}} +
+
+ + + + {trend && trendValue && ( + + + {trendValue} + + )} +
+
+ ) +} diff --git a/src/components/ui/phase-indicator.tsx b/src/components/ui/phase-indicator.tsx new file mode 100644 index 0000000..e92f232 --- /dev/null +++ b/src/components/ui/phase-indicator.tsx @@ -0,0 +1,58 @@ +'use client' + +import { motion } from 'motion/react' +import { cn } from '@/lib/utils' +import { Check, Circle, Loader2, AlertTriangle, X, SkipForward } from 'lucide-react' + +export type PhaseStatus = 'not_started' | 'in_progress' | 'completed' | 'blocked' | 'failed' | 'skipped' + +interface PhaseIndicatorProps { + phase: string + status: PhaseStatus + label?: string + size?: 'sm' | 'md' | 'lg' + showLabel?: boolean +} + +const statusConfig: Record = { + not_started: { icon: Circle, color: 'text-muted-foreground', bg: 'bg-muted', ring: '' }, + in_progress: { icon: Loader2, color: 'text-blue-500', bg: 'bg-blue-500/10', ring: 'ring-2 ring-blue-500/30', animate: true }, + completed: { icon: Check, color: 'text-emerald-500', bg: 'bg-emerald-500/10', ring: '' }, + blocked: { icon: AlertTriangle, color: 'text-amber-500', bg: 'bg-amber-500/10', ring: 'ring-2 ring-amber-500/30' }, + failed: { icon: X, color: 'text-red-500', bg: 'bg-red-500/10', ring: '' }, + skipped: { icon: SkipForward, color: 'text-muted-foreground/50', bg: 'bg-muted/50', ring: '' }, +} + +const sizeConfig = { + sm: { wrapper: 'p-1.5', icon: 'w-3 h-3', text: 'text-xs' }, + md: { wrapper: 'p-2', icon: 'w-4 h-4', text: 'text-sm' }, + lg: { wrapper: 'p-3', icon: 'w-5 h-5', text: 'text-base' }, +} + +export function PhaseIndicator({ phase, status, label, size = 'md', showLabel = true }: PhaseIndicatorProps) { + const config = statusConfig[status] + const sizes = sizeConfig[size] + const Icon = config.icon + + return ( + + + + + {showLabel && ( + + {label || phase} + + )} + + ) +} diff --git a/src/components/ui/progress-timeline.tsx b/src/components/ui/progress-timeline.tsx new file mode 100644 index 0000000..d75ec21 --- /dev/null +++ b/src/components/ui/progress-timeline.tsx @@ -0,0 +1,141 @@ +'use client' + +import { motion } from 'motion/react' +import { cn } from '@/lib/utils' +import { Check, Circle, Loader2 } from 'lucide-react' + +export const PHASES = ['think', 'evaluate', 'ideate', 'plan', 'create', 'deploy', 'analyze', 'enhance'] as const +export type Phase = typeof PHASES[number] + +interface ProgressTimelineProps { + currentPhase: Phase + completedPhases: Phase[] + className?: string + variant?: 'horizontal' | 'vertical' + showLabels?: boolean +} + +export function ProgressTimeline({ + currentPhase, + completedPhases, + className, + variant = 'horizontal', + showLabels = true +}: ProgressTimelineProps) { + const progress = (completedPhases.length / PHASES.length) * 100 + + if (variant === 'vertical') { + return ( +
+ {PHASES.map((phase, index) => { + const isCompleted = completedPhases.includes(phase) + const isCurrent = phase === currentPhase + const isPast = PHASES.indexOf(phase) < PHASES.indexOf(currentPhase) + + return ( + +
+ + {isCompleted ? ( + + ) : isCurrent ? ( + + ) : ( + + )} + + {index < PHASES.length - 1 && ( +
+ )} +
+ {showLabels && ( + + {phase} + + )} + + ) + })} +
+ ) + } + + return ( +
+
+ + +
+ +
+ {PHASES.map((phase, index) => { + const isCompleted = completedPhases.includes(phase) + const isCurrent = phase === currentPhase + + return ( + + + {showLabels && ( + + )} + + ) + })} +
+
+ ) +} diff --git a/src/components/ui/status-badge.tsx b/src/components/ui/status-badge.tsx new file mode 100644 index 0000000..cc560a4 --- /dev/null +++ b/src/components/ui/status-badge.tsx @@ -0,0 +1,50 @@ +import { cn } from '@/lib/utils' + +const variants = { + default: 'bg-muted text-muted-foreground border-muted', + success: 'bg-emerald-500/10 text-emerald-600 border-emerald-500/20', + warning: 'bg-amber-500/10 text-amber-600 border-amber-500/20', + error: 'bg-red-500/10 text-red-600 border-red-500/20', + info: 'bg-blue-500/10 text-blue-600 border-blue-500/20', + purple: 'bg-violet-500/10 text-violet-600 border-violet-500/20', +} + +interface StatusBadgeProps { + variant?: keyof typeof variants + children: React.ReactNode + pulse?: boolean + className?: string +} + +export function StatusBadge({ variant = 'default', children, pulse, className }: StatusBadgeProps) { + const pulseColor = { + default: 'bg-muted-foreground', + success: 'bg-emerald-500', + warning: 'bg-amber-500', + error: 'bg-red-500', + info: 'bg-blue-500', + purple: 'bg-violet-500', + } + + return ( + + {pulse && ( + + + + + )} + {children} + + ) +}