feat(ext): enhance error handling and improve UI structure

This commit is contained in:
Simon
2026-01-21 16:50:55 +08:00
parent 52454305fb
commit 9531fcff5a
5 changed files with 79 additions and 21 deletions

View File

@@ -145,6 +145,10 @@ function registerCommandHandlers(): void {
// Execute task (don't await - runs in background) // Execute task (don't await - runs in background)
agent.execute(task).catch((error) => { agent.execute(task).catch((error) => {
console.error('[PageAgentExt] Task execution error:', error) console.error('[PageAgentExt] Task execution error:', error)
const message = error instanceof Error ? error.message : String(error)
// Broadcast error as a history event so it persists in UI
const errorEvent: HistoricalEvent = { type: 'error', message }
eventBroadcaster.history([errorEvent])
eventBroadcaster.status('error') eventBroadcaster.status('error')
}) })
}) })

View File

@@ -109,7 +109,7 @@ export default function App() {
return ( return (
<div className="flex flex-col h-screen bg-background"> <div className="flex flex-col h-screen bg-background">
{/* Header */} {/* Header */}
<div className="flex items-center justify-between border-b px-3 py-2"> <header className="flex items-center justify-between border-b px-3 py-2">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Logo className="size-5" /> <Logo className="size-5" />
<span className="text-sm font-medium">Page Agent Ext</span> <span className="text-sm font-medium">Page Agent Ext</span>
@@ -120,10 +120,10 @@ export default function App() {
<Settings className="size-3.5" /> <Settings className="size-3.5" />
</Button> </Button>
</div> </div>
</div> </header>
{/* Content */} {/* Content */}
<div className="flex-1 overflow-hidden flex flex-col"> <main className="flex-1 overflow-hidden flex flex-col">
{/* Current task */} {/* Current task */}
{currentTask && ( {currentTask && (
<div className="border-b px-3 py-2 bg-muted/30"> <div className="border-b px-3 py-2 bg-muted/30">
@@ -145,10 +145,10 @@ export default function App() {
{/* Activity indicator at bottom */} {/* Activity indicator at bottom */}
{activity && <ActivityCard activity={activity} />} {activity && <ActivityCard activity={activity} />}
</div> </div>
</div> </main>
{/* Input */} {/* Input */}
<div className="border-t p-3"> <footer className="border-t p-3">
<InputGroup className="relative rounded-lg"> <InputGroup className="relative rounded-lg">
<InputGroupTextarea <InputGroupTextarea
ref={textareaRef} ref={textareaRef}
@@ -183,7 +183,7 @@ export default function App() {
)} )}
</InputGroupAddon> </InputGroupAddon>
</InputGroup> </InputGroup>
</div> </footer>
</div> </div>
) )
} }

View File

@@ -0,0 +1,49 @@
import { AlertTriangle, RotateCcw } from 'lucide-react'
import { Component, type ErrorInfo, type ReactNode } from 'react'
import { Button } from '@/components/ui/button'
interface Props {
children: ReactNode
}
interface State {
hasError: boolean
error: Error | null
}
export class ErrorBoundary extends Component<Props, State> {
state: State = { hasError: false, error: null }
static getDerivedStateFromError(error: Error): State {
return { hasError: true, error }
}
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
console.error('[ErrorBoundary]', error, errorInfo.componentStack)
}
handleReload = () => {
window.location.reload()
}
render() {
if (!this.state.hasError) {
return this.props.children
}
return (
<div className="flex flex-col items-center justify-center h-screen bg-background p-6 text-center">
<AlertTriangle className="size-12 text-destructive mb-4" />
<h2 className="text-lg font-semibold mb-2">Something went wrong</h2>
<p className="text-sm text-muted-foreground mb-4 max-w-xs">
{this.state.error?.message || 'An unexpected error occurred'}
</p>
<Button variant="outline" size="sm" onClick={this.handleReload}>
<RotateCcw className="size-3.5 mr-2" />
Reload Panel
</Button>
</div>
)
}
}

View File

@@ -60,11 +60,11 @@ function ReflectionItem({ icon, value }: { icon: string; value: string }) {
return ( return (
<Fragment> <Fragment>
<span className="text-xs">{icon}</span> <span className="text-xs flex justify-center">{icon}</span>
<span <span
className={cn( className={cn(
'text-[11px] text-muted-foreground cursor-pointer hover:text-muted-foreground/70', 'text-[11px] text-muted-foreground cursor-pointer hover:text-muted-foreground/70',
!expanded && 'line-clamp-2' !expanded && 'line-clamp-1'
)} )}
onClick={() => setExpanded(!expanded)} onClick={() => setExpanded(!expanded)}
> >
@@ -93,11 +93,11 @@ function ReflectionSection({
if (items.length === 0) return null if (items.length === 0) return null
return ( return (
<div className="mb-3"> <div className="mb-2">
<div className="text-[11px] font-semibold text-foreground uppercase tracking-wide mb-2"> {/* <div className="text-[11px] font-semibold text-foreground uppercase tracking-wide mb-2">
Reflection Reflection
</div> </div> */}
<div className="grid grid-cols-[auto_1fr] gap-x-2 gap-y-2"> <div className="grid grid-cols-[14px_1fr] gap-x-2 gap-y-2">
{items.map((item) => ( {items.map((item) => (
<ReflectionItem key={item.label} icon={item.icon} value={item.value!} /> <ReflectionItem key={item.label} icon={item.icon} value={item.value!} />
))} ))}
@@ -162,15 +162,17 @@ export function EventCard({ event }: { event: HistoricalEvent }) {
if (event.type === 'step') { if (event.type === 'step') {
return ( return (
<div className="rounded-lg border bg-card p-2.5"> <div className="rounded-lg border-l-2 border-l-blue-500/50 border bg-muted/40 p-2.5">
<div className="text-[11px] font-semibold text-foreground tracking-wide mb-2">Step</div>
{/* Reflection */} {/* Reflection */}
{event.reflection && <ReflectionSection reflection={event.reflection} />} {event.reflection && <ReflectionSection reflection={event.reflection} />}
{/* Action */} {/* Action */}
{event.action && ( {event.action && (
<div> <div>
<div className="text-[11px] font-semibold text-foreground uppercase tracking-wide mb-2"> <div className="text-[11px] font-semibold text-foreground tracking-wide mb-1">
Action Actions
</div> </div>
<div className="flex items-start gap-2"> <div className="flex items-start gap-2">
<ActionIcon <ActionIcon
@@ -179,7 +181,7 @@ export function EventCard({ event }: { event: HistoricalEvent }) {
/> />
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<p className="text-xs text-foreground/80 mb-0.5"> <p className="text-xs text-foreground/80 mb-0.5">
<span className="font-medium">{event.action.name}</span> <span className="font-medium text-foreground/70">{event.action.name}</span>
<span className="text-muted-foreground/70 ml-1.5"> <span className="text-muted-foreground/70 ml-1.5">
{JSON.stringify(event.action.input)} {JSON.stringify(event.action.input)}
</span> </span>
@@ -198,10 +200,10 @@ export function EventCard({ event }: { event: HistoricalEvent }) {
if (event.type === 'observation') { if (event.type === 'observation') {
return ( return (
<div className="rounded-lg border bg-card p-2.5"> <div className="rounded-lg border-l-2 border-l-green-500/50 border bg-muted/40 p-2.5">
<div className="text-[11px] font-semibold text-foreground uppercase tracking-wide mb-2"> {/* <div className="text-[11px] font-semibold text-foreground uppercase tracking-wide mb-2">
Observation Observation
</div> </div> */}
<div className="flex items-start gap-2"> <div className="flex items-start gap-2">
<Eye className="size-3.5 text-green-500 shrink-0 mt-0.5" /> <Eye className="size-3.5 text-green-500 shrink-0 mt-0.5" />
<span className="text-[11px] text-muted-foreground">{event.content}</span> <span className="text-[11px] text-muted-foreground">{event.content}</span>
@@ -248,7 +250,7 @@ export function ActivityCard({ activity }: { activity: AgentActivity }) {
const info = getActivityInfo() const info = getActivityInfo()
return ( return (
<div className="flex items-center gap-2 rounded-lg border bg-card/50 p-2.5 animate-pulse"> <div className="flex items-center gap-2 rounded-lg border bg-muted/40 p-2.5 animate-pulse">
<div className="relative"> <div className="relative">
<Sparkles className={cn('size-3.5', info.color)} /> <Sparkles className={cn('size-3.5', info.color)} />
<span <span

View File

@@ -2,11 +2,14 @@ import React from 'react'
import ReactDOM from 'react-dom/client' import ReactDOM from 'react-dom/client'
import App from './App' import App from './App'
import { ErrorBoundary } from './components/ErrorBoundary'
import '@/assets/index.css' import '@/assets/index.css'
ReactDOM.createRoot(document.getElementById('root')!).render( ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode> <React.StrictMode>
<App /> <ErrorBoundary>
<App />
</ErrorBoundary>
</React.StrictMode> </React.StrictMode>
) )