From 4200c7832700f6f07ea334722c4ae02e2d6b6ba4 Mon Sep 17 00:00:00 2001
From: Simon <10131203+gaomeng1900@users.noreply.github.com>
Date: Tue, 20 Jan 2026 21:13:44 +0800
Subject: [PATCH] feat(ext): style; code split
---
.../src/entrypoints/sidepanel/App.tsx | 356 +-----------------
.../sidepanel/components/ConfigPanel.tsx | 82 ++++
.../sidepanel/components/cards/index.tsx | 255 +++++++++++++
.../sidepanel/components/index.tsx | 45 +++
4 files changed, 387 insertions(+), 351 deletions(-)
create mode 100644 packages/extension/src/entrypoints/sidepanel/components/ConfigPanel.tsx
create mode 100644 packages/extension/src/entrypoints/sidepanel/components/cards/index.tsx
create mode 100644 packages/extension/src/entrypoints/sidepanel/components/index.tsx
diff --git a/packages/extension/src/entrypoints/sidepanel/App.tsx b/packages/extension/src/entrypoints/sidepanel/App.tsx
index ed50053..7e459a8 100644
--- a/packages/extension/src/entrypoints/sidepanel/App.tsx
+++ b/packages/extension/src/entrypoints/sidepanel/App.tsx
@@ -1,367 +1,21 @@
-import {
- ArrowRight,
- CheckCircle,
- ChevronDown,
- ChevronRight,
- Loader2,
- MessageSquare,
- Send,
- Settings,
- Sparkles,
- Square,
- XCircle,
-} from 'lucide-react'
-import { Fragment, useCallback, useEffect, useRef, useState } from 'react'
+import { Send, Settings, Square } from 'lucide-react'
+import { 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,
- children,
-}: {
- success: boolean
- text: string
- children?: React.ReactNode
-}) {
- return (
-
-
- {success ? (
-
- ) : (
-
- )}
-
- Result: {success ? 'Success' : 'Failed'}
-
-
-
{text}
- {children}
-
- )
-}
-
-// 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}
-
- ))}
-
-
- )
-}
-
-// Raw response section (collapsible, for debugging)
-function RawResponseSection({ rawResponse }: { rawResponse: unknown }) {
- const [expanded, setExpanded] = useState(false)
-
- return (
-
-
- {expanded && (
-
- {JSON.stringify(rawResponse, null, 4)}
-
- )}
-
- )
-}
-
-// 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 (
-
-
- {!event.rawResponse || }
-
-
- )
- }
-
- if (event.type === 'step') {
- return (
-
- {/* Reflection */}
- {event.reflection &&
}
-
- {/* Action */}
- {event.action && (
-
-
- {event.action.name}
-
-
-
-
-
- {JSON.stringify(event.action.input)}
-
-
→ {event.action.output}
-
-
-
- )}
-
- {/* Raw Response */}
- {!event.rawResponse ||
}
-
- )
- }
-
- if (event.type === 'observation') {
- return (
-
-
- {event.content}
-
- )
- }
-
- if (event.type === 'error') {
- return (
-
-
-
- {event.message}
-
- {!event.rawResponse ||
}
-
- )
- }
-
- 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 (
-
- )
-}
-
-// 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}
-
- )
-}
-
-function Logo({ className }: { className?: string }) {
- return
-}
-
-// Empty state with logo
-function EmptyState() {
- return (
-
-
-
-
Page Agent Ext
-
Enter a task to automate this page
-
-
- )
-}
+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)
diff --git a/packages/extension/src/entrypoints/sidepanel/components/ConfigPanel.tsx b/packages/extension/src/entrypoints/sidepanel/components/ConfigPanel.tsx
new file mode 100644
index 0000000..451dfee
--- /dev/null
+++ b/packages/extension/src/entrypoints/sidepanel/components/ConfigPanel.tsx
@@ -0,0 +1,82 @@
+import { Loader2 } from 'lucide-react'
+
+import { Button } from '@/components/ui/button'
+import { Input } from '@/components/ui/input'
+import { agentCommands } from '@/messaging'
+
+// Configuration panel component
+export 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"
+ />
+
+
+
+
+
+
+
+ )
+}
diff --git a/packages/extension/src/entrypoints/sidepanel/components/cards/index.tsx b/packages/extension/src/entrypoints/sidepanel/components/cards/index.tsx
new file mode 100644
index 0000000..569120c
--- /dev/null
+++ b/packages/extension/src/entrypoints/sidepanel/components/cards/index.tsx
@@ -0,0 +1,255 @@
+import {
+ CheckCircle,
+ ChevronDown,
+ ChevronRight,
+ Eye,
+ Globe,
+ Keyboard,
+ Mouse,
+ MoveVertical,
+ Sparkles,
+ XCircle,
+ Zap,
+} from 'lucide-react'
+import { Fragment } from 'react/jsx-runtime'
+
+import { cn } from '@/lib/utils'
+import { AgentActivity, HistoricalEvent } from '@/messaging'
+
+// 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}
+
+ )
+}
+
+// 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}
+
+ ))}
+
+
+ )
+}
+
+// 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] ||
+}
+
+// Raw response section (collapsible, for debugging)
+function RawResponseSection({ rawResponse }: { rawResponse: unknown }) {
+ const [expanded, setExpanded] = useState(false)
+
+ return (
+
+
+ {expanded && (
+
+ {JSON.stringify(rawResponse, null, 4)}
+
+ )}
+
+ )
+}
+
+// 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 (
+
+
+ {!event.rawResponse || }
+
+
+ )
+ }
+
+ if (event.type === 'step') {
+ return (
+
+ {/* Reflection */}
+ {event.reflection &&
}
+
+ {/* Action */}
+ {event.action && (
+
+
+ Action
+
+
+
+
+
+ {event.action.name}
+
+ {JSON.stringify(event.action.input)}
+
+
+
→ {event.action.output}
+
+
+
+ )}
+
+ {/* Raw Response */}
+ {!event.rawResponse ||
}
+
+ )
+ }
+
+ if (event.type === 'observation') {
+ return (
+
+
+ Observation
+
+
+
+ {event.content}
+
+
+ )
+ }
+
+ if (event.type === 'error') {
+ return (
+
+
+
+ {event.message}
+
+ {!event.rawResponse ||
}
+
+ )
+ }
+
+ 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 (
+
+ )
+}
diff --git a/packages/extension/src/entrypoints/sidepanel/components/index.tsx b/packages/extension/src/entrypoints/sidepanel/components/index.tsx
new file mode 100644
index 0000000..a50e5c4
--- /dev/null
+++ b/packages/extension/src/entrypoints/sidepanel/components/index.tsx
@@ -0,0 +1,45 @@
+import { cn } from '@/lib/utils'
+import { AgentStatus } from '@/messaging'
+
+// Status dot indicator
+export 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}
+
+ )
+}
+
+export function Logo({ className }: { className?: string }) {
+ return
+}
+
+// Empty state with logo
+export function EmptyState() {
+ return (
+
+
+
+
Page Agent Ext
+
Enter a task to automate this page
+
+
+ )
+}