import type {
AgentActivity,
AgentErrorEvent,
AgentStepEvent,
HistoricalEvent,
ObservationEvent,
RetryEvent,
} from '@page-agent/core'
import {
CheckCircle,
Eye,
Globe,
Keyboard,
Mouse,
MoveVertical,
RefreshCw,
Sparkles,
XCircle,
Zap,
} from 'lucide-react'
import { Fragment, useState } from 'react'
import { cn } from '@/lib/utils'
// Result card for done action
function ResultCard({
success,
text,
children,
}: {
success: boolean
text: string
children?: React.ReactNode
}) {
return (
{success ? (
) : (
)}
Result: {success ? 'Success' : 'Failed'}
{text}
{children}
)
}
// Single reflection item with truncation
function ReflectionItem({ icon, value }: { icon: string; value: string }) {
const [expanded, setExpanded] = useState(false)
return (
{icon}
setExpanded(!expanded)}
>
{value}
)
}
// Reflection section in step card
function ReflectionSection({
reflection,
}: {
reflection: {
evaluation_previous_goal?: string
memory?: string
next_goal?: string
}
}) {
const items = [
{ icon: '☑️', label: 'eval', value: reflection.evaluation_previous_goal },
{ icon: '🧠', label: 'memory', value: reflection.memory },
{ icon: '🎯', label: 'goal', value: reflection.next_goal },
].filter((item) => item.value)
if (items.length === 0) return null
return (
{/*
Reflection
*/}
{items.map((item) => (
))}
)
}
// Get icon for action type
function ActionIcon({ name, className }: { name: string; className?: string }) {
const icons: Record = {
click_element_by_index: ,
input: ,
scroll: ,
go_to_url: ,
}
return icons[name] ||
}
// Copy button with "Copied!" feedback
function CopyButton({ text, label }: { text: string; label: string }) {
const [copied, setCopied] = useState(false)
return (
)
}
// Extract message content by role from raw request
function extractPrompt(rawRequest: unknown, role: 'system' | 'user'): string | null {
const messages = (rawRequest as { messages?: { role: string; content?: unknown }[] })?.messages
if (!messages) return null
const msg =
role === 'system'
? messages.find((m) => m.role === role)
: messages.findLast((m) => m.role === role)
if (!msg?.content) return null
return typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content, null, 2)
}
// Raw request/response section (collapsible tabs, for debugging)
function RawSection({ rawRequest, rawResponse }: { rawRequest?: unknown; rawResponse?: unknown }) {
const [activeTab, setActiveTab] = useState<'request' | 'response' | null>(null)
if (!rawRequest && !rawResponse) return null
const handleTabClick = (tab: 'request' | 'response') => {
setActiveTab(activeTab === tab ? null : tab)
}
const content =
activeTab === 'request' ? rawRequest : activeTab === 'response' ? rawResponse : null
const systemPrompt = activeTab === 'request' ? extractPrompt(rawRequest, 'system') : null
const userPrompt = activeTab === 'request' ? extractPrompt(rawRequest, 'user') : null
return (
{rawRequest != null && (
)}
{rawResponse != null && (
)}
{content != null && (
{systemPrompt && }
{userPrompt && }
{JSON.stringify(content, null, 4)}
)}
)
}
function StepCard({ event }: { event: AgentStepEvent }) {
return (
Step #{event.stepIndex! + 1}
{/* Reflection */}
{event.reflection &&
}
{/* Action */}
{event.action && (
Actions
{event.action.name}
{event.action.name !== 'done' && (
{JSON.stringify(event.action.input)}
)}
└
{event.action.output}
)}
{/* Raw Response */}
)
}
function ObservationCard({ event }: { event: ObservationEvent }) {
return (
{/*
Observation
*/}
{event.content}
)
}
function RetryCard({ event }: { event: RetryEvent }) {
return (
{event.message} ({event.attempt}/{event.maxAttempts})
)
}
function ErrorCard({ event }: { event: AgentErrorEvent }) {
return (
)
}
// History event card component
export function EventCard({ event }: { event: HistoricalEvent }) {
// Done action - show as result card
if (event.type === 'step' && event.action?.name === 'done') {
const input = event.action.input as { text?: string; success?: boolean }
return (
<>
>
)
}
if (event.type === 'step') {
return
}
if (event.type === 'observation') {
return
}
if (event.type === 'retry') {
return
}
if (event.type === 'error') {
return
}
return null
}
// Activity card with animation
export function ActivityCard({ activity }: { activity: AgentActivity }) {
const getActivityInfo = () => {
switch (activity.type) {
case 'thinking':
return { text: 'Thinking...', color: 'text-blue-500' }
case 'executing':
return { text: `Executing ${activity.tool}...`, color: 'text-amber-500' }
case 'executed':
return { text: `Done: ${activity.tool}`, color: 'text-green-500' }
case 'retrying':
return {
text: `Retrying (${activity.attempt}/${activity.maxAttempts})...`,
color: 'text-amber-500',
}
case 'error':
return { text: activity.message, color: 'text-destructive' }
}
}
const info = getActivityInfo()
return (
)
}