import { ArrowRight, Bot, CheckCircle, Loader2, MessageSquare, Send, Settings, Sparkles, Square, XCircle, } from 'lucide-react' import { Fragment, useCallback, useEffect, useRef, useState } from 'react' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupTextarea, } from '@/components/ui/input-group' import { cn } from '@/lib/utils' import { subscribeToEvents } from '@/messaging/events' import { agentCommands } from '@/messaging/protocol' import type { AgentActivity, AgentState, AgentStatus, HistoricalEvent } from '@/messaging/protocol' import { DEMO_API_KEY, DEMO_BASE_URL, DEMO_MODEL } from '@/utils/constants' // Configuration panel component function ConfigPanel({ onClose }: { onClose: () => void }) { const [apiKey, setApiKey] = useState(DEMO_API_KEY) const [baseURL, setBaseURL] = useState(DEMO_BASE_URL) const [model, setModel] = useState(DEMO_MODEL) const [saving, setSaving] = useState(false) useEffect(() => { chrome.storage.local.get('llmConfig').then((result) => { const config = result.llmConfig as | { apiKey?: string; baseURL?: string; model?: string } | undefined if (config) { setApiKey(config.apiKey || DEMO_API_KEY) setBaseURL(config.baseURL || DEMO_BASE_URL) setModel(config.model || DEMO_MODEL) } }) }, []) const handleSave = async () => { setSaving(true) try { await agentCommands.sendMessage('agent:configure', { apiKey, baseURL, model }) onClose() } finally { setSaving(false) } } return (

Settings

setApiKey(e.target.value)} className="text-xs h-8" />
setBaseURL(e.target.value)} className="text-xs h-8" />
setModel(e.target.value)} className="text-xs h-8" />
) } // Result card for done action function ResultCard({ success, text }: { success: boolean; text: string }) { return (
{success ? ( ) : ( )} Result: {success ? 'Success' : 'Failed'}

{text}

) } // 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) => ( {item.icon} {item.value} ))}
) } // History event card component 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 (
{/* Reflection */} {event.reflection && } {/* Action */} {event.action && (
{event.action.name}

{JSON.stringify(event.action.input)}

→ {event.action.output}

)}
) } if (event.type === 'observation') { return (
{event.content}
) } if (event.type === 'error') { return (
{event.message}
) } return null } // Activity card with animation 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 (
{info.text}
) } // Status dot indicator function StatusDot({ status }: { status: AgentStatus }) { const colorClass = { idle: 'bg-muted-foreground', running: 'bg-blue-500', completed: 'bg-green-500', error: 'bg-destructive', }[status] const label = { idle: 'Ready', running: 'Running', completed: 'Done', error: 'Error', }[status] return (
{label}
) } // Logo component (Bot icon as placeholder until real logo is added) function Logo({ className }: { className?: string }) { return } // Empty state with logo function EmptyState() { return (

Page Agent Ext

Enter a task to automate this page

) } export default function App() { const [showConfig, setShowConfig] = useState(false) const [task, setTask] = useState('') const [status, setStatus] = useState('idle') const [history, setHistory] = useState([]) const [activity, setActivity] = useState(null) const [currentTask, setCurrentTask] = useState('') const historyRef = useRef(null) const textareaRef = useRef(null) // Subscribe to agent events useEffect(() => { // Initialize with demo config if not set chrome.storage.local.get('llmConfig').then((result) => { if (!result.llmConfig) { chrome.storage.local.set({ llmConfig: { apiKey: DEMO_API_KEY, baseURL: DEMO_BASE_URL, model: DEMO_MODEL }, }) } }) const unsubscribe = subscribeToEvents({ onStatus: (newStatus) => { setStatus(newStatus) if (newStatus === 'idle' || newStatus === 'completed' || newStatus === 'error') { setActivity(null) } }, onHistory: (newHistory) => { setHistory(newHistory) }, onActivity: (newActivity) => { setActivity(newActivity) }, onStateSnapshot: (state) => { setStatus(state.status) setHistory(state.history) setCurrentTask(state.task) }, }) // Get initial state agentCommands.sendMessage('agent:getState', undefined).then((state: AgentState) => { setStatus(state.status) setHistory(state.history) setCurrentTask(state.task) }) return unsubscribe }, []) // Auto-scroll to bottom on new events useEffect(() => { if (historyRef.current) { historyRef.current.scrollTop = historyRef.current.scrollHeight } }, [history, activity]) const handleSubmit = useCallback( async (e?: React.FormEvent) => { e?.preventDefault() if (!task.trim() || status === 'running') return setCurrentTask(task) setHistory([]) await agentCommands.sendMessage('agent:execute', task) setTask('') }, [task, status] ) const handleStop = useCallback(async () => { await agentCommands.sendMessage('agent:stop', undefined) }, []) const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault() handleSubmit() } } if (showConfig) { return setShowConfig(false)} /> } const isRunning = status === 'running' const showEmptyState = !currentTask && history.length === 0 && !isRunning return (
{/* Header */}
Page Agent Ext
{/* Content */}
{/* Current task */} {currentTask && (
Task
{currentTask}
)} {/* History */}
{showEmptyState && } {history.map((event, index) => ( ))} {/* Activity indicator at bottom */} {activity && }
{/* Input */}
setTask(e.target.value)} onKeyDown={handleKeyDown} disabled={isRunning} rows={2} className="text-xs pr-12 min-h-[60px]" /> {isRunning ? ( ) : ( handleSubmit()} disabled={!task.trim()} className="size-7" > )}
) }