import { Send, Settings, Square } from 'lucide-react' import { useCallback, useEffect, useRef, useState } from 'react' import { Button } from '@/components/ui/button' import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupTextarea, } from '@/components/ui/input-group' 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' import { EmptyState, Logo, StatusDot } from './components' import { ConfigPanel } from './components/ConfigPanel' import { ActivityCard, EventCard } from './components/cards' 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" > )}
) }