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]}
+
+
+
+
+ {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 (
+
+ )
+ }
+
+ 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 && (
+
+ {phase}
+
+ )}
+
+ )
+ })}
+
+
+ )
+}
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}
+
+ )
+}