feat(ext): enhance error handling and improve UI structure
This commit is contained in:
@@ -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')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
<ErrorBoundary>
|
||||||
<App />
|
<App />
|
||||||
|
</ErrorBoundary>
|
||||||
</React.StrictMode>
|
</React.StrictMode>
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user