feat: make panel optional

This commit is contained in:
Simon
2026-01-17 17:24:17 +08:00
parent 1af2607cca
commit 78d4919289
5 changed files with 72 additions and 21 deletions

View File

@@ -99,7 +99,7 @@ export interface ExecutionResult {
export class PageAgent extends EventTarget { export class PageAgent extends EventTarget {
config: PageAgentConfig config: PageAgentConfig
id = uid() id = uid()
panel: Panel panel: Panel | null = null
tools: typeof tools tools: typeof tools
disposed = false disposed = false
task = '' task = ''
@@ -130,11 +130,17 @@ export class PageAgent extends EventTarget {
this.config = config this.config = config
this.#llm = new LLM(this.config) this.#llm = new LLM(this.config)
this.panel = new Panel({
language: this.config.language, // Conditionally initialize Panel
onExecuteTask: (task) => this.execute(task), if (this.config.enablePanel !== false) {
onStop: () => this.dispose(), this.panel = new Panel({
}) language: this.config.language,
onExecuteTask: (task) => this.execute(task),
onStop: () => this.dispose(),
promptForNextTask: this.config.promptForNextTask,
})
}
this.tools = new Map(tools) this.tools = new Map(tools)
// Initialize PageController with config (mask enabled by default) // Initialize PageController with config (mask enabled by default)
@@ -146,11 +152,11 @@ export class PageAgent extends EventTarget {
// Listen to LLM events // Listen to LLM events
this.#llmRetryListener = (e) => { this.#llmRetryListener = (e) => {
const { current, max } = (e as CustomEvent).detail const { current, max } = (e as CustomEvent).detail
this.panel.update({ type: 'retry', current, max }) this.panel?.update({ type: 'retry', current, max })
} }
this.#llmErrorListener = (e) => { this.#llmErrorListener = (e) => {
const { error } = (e as CustomEvent).detail const { error } = (e as CustomEvent).detail
this.panel.update({ type: 'error', message: `step failed: ${error.message}` }) this.panel?.update({ type: 'error', message: `step failed: ${error.message}` })
} }
this.#llm.addEventListener('retry', this.#llmRetryListener) this.#llm.addEventListener('retry', this.#llmRetryListener)
this.#llm.addEventListener('error', this.#llmErrorListener) this.#llm.addEventListener('error', this.#llmErrorListener)
@@ -169,6 +175,11 @@ export class PageAgent extends EventTarget {
this.tools.delete('execute_javascript') this.tools.delete('execute_javascript')
} }
// Disable ask_user tool if enableAskUser is false or if panel is disabled
if (this.config.enableAskUser === false || this.config.enablePanel === false) {
this.tools.delete('ask_user')
}
this.#beforeUnloadListener = (e) => { this.#beforeUnloadListener = (e) => {
if (!this.disposed) this.dispose('PAGE_UNLOADING') if (!this.disposed) this.dispose('PAGE_UNLOADING')
} }
@@ -181,7 +192,7 @@ export class PageAgent extends EventTarget {
*/ */
pushObservation(content: string): void { pushObservation(content: string): void {
this.history.push({ type: 'observation', content }) this.history.push({ type: 'observation', content })
this.panel.update({ type: 'observation', content }) this.panel?.update({ type: 'observation', content })
} }
async execute(task: string): Promise<ExecutionResult> { async execute(task: string): Promise<ExecutionResult> {
@@ -199,10 +210,10 @@ export class PageAgent extends EventTarget {
// Show mask and panel // Show mask and panel
this.pageController.showMask() this.pageController.showMask()
this.panel.show() this.panel?.show()
this.panel.reset() this.panel?.reset()
this.panel.update({ type: 'input', task: this.task }) this.panel?.update({ type: 'input', task: this.task })
if (this.#abortController) { if (this.#abortController) {
this.#abortController.abort() this.#abortController.abort()
@@ -232,7 +243,7 @@ export class PageAgent extends EventTarget {
// Update status to thinking // Update status to thinking
console.log(chalk.blue('Thinking...')) console.log(chalk.blue('Thinking...'))
this.panel.update({ type: 'thinking' }) this.panel?.update({ type: 'thinking' })
const result = await this.#llm.invoke( const result = await this.#llm.invoke(
[ [
@@ -370,7 +381,7 @@ export class PageAgent extends EventTarget {
if (reflectionText) { if (reflectionText) {
console.log(reflectionText) console.log(reflectionText)
this.panel.update({ type: 'thinking', text: reflectionText }) this.panel?.update({ type: 'thinking', text: reflectionText })
} }
// Find the corresponding tool // Find the corresponding tool
@@ -378,7 +389,7 @@ export class PageAgent extends EventTarget {
assert(tool, `Tool ${toolName} not found. (@note should have been caught before this!!!)`) assert(tool, `Tool ${toolName} not found. (@note should have been caught before this!!!)`)
console.log(chalk.blue.bold(`Executing tool: ${toolName}`), toolInput) console.log(chalk.blue.bold(`Executing tool: ${toolName}`), toolInput)
this.panel.update({ type: 'toolExecuting', toolName, args: toolInput }) this.panel?.update({ type: 'toolExecuting', toolName, args: toolInput })
const startTime = Date.now() const startTime = Date.now()
@@ -394,7 +405,7 @@ export class PageAgent extends EventTarget {
} }
// Briefly display execution result // Briefly display execution result
this.panel.update({ this.panel?.update({
type: 'toolCompleted', type: 'toolCompleted',
toolName, toolName,
args: toolInput, args: toolInput,
@@ -557,13 +568,13 @@ export class PageAgent extends EventTarget {
// Update panel status // Update panel status
if (success) { if (success) {
this.panel.update({ type: 'output', text }) this.panel?.update({ type: 'output', text })
} else { } else {
this.panel.update({ type: 'error', message: text }) this.panel?.update({ type: 'error', message: text })
} }
// Task completed // Task completed
this.panel.update({ type: 'completed' }) this.panel?.update({ type: 'completed' })
this.pageController.hideMask() this.pageController.hideMask()
@@ -593,7 +604,7 @@ export class PageAgent extends EventTarget {
console.log('Disposing PageAgent...') console.log('Disposing PageAgent...')
this.disposed = true this.disposed = true
this.pageController.dispose() this.pageController.dispose()
this.panel.dispose() this.panel?.dispose()
this.history = [] this.history = []
this.#abortController.abort(reason ?? 'PageAgent disposed') this.#abortController.abort(reason ?? 'PageAgent disposed')

View File

@@ -11,6 +11,27 @@ export interface AgentConfig {
// theme?: 'light' | 'dark' // theme?: 'light' | 'dark'
language?: SupportedLanguage language?: SupportedLanguage
/**
* Whether to prompt for next task after task completion
* @default true
*/
promptForNextTask?: boolean
/**
* Enable the UI panel for visual feedback and user interaction
* When disabled, the panel will not be created and all UI operations will be skipped.
* Useful for automated testing or when integrating PageAgent as a library.
* @default true
*/
enablePanel?: boolean
/**
* Enable the ask_user tool for agent to ask questions
* When disabled, the agent cannot ask user questions during execution.
* @default true
*/
enableAskUser?: boolean
/** /**
* Custom tools to extend PageAgent capabilities * Custom tools to extend PageAgent capabilities
* @experimental * @experimental

View File

@@ -80,6 +80,9 @@ tools.set(
question: zod.string(), question: zod.string(),
}), }),
execute: async function (this: PageAgent, input) { execute: async function (this: PageAgent, input) {
if (!this.panel) {
throw new Error('ask_user tool requires panel to be enabled')
}
const answer = await this.panel.askUser(input.question) const answer = await this.panel.askUser(input.question)
return `✅ Received user answer: ${answer}` return `✅ Received user answer: ${answer}`
}, },

View File

@@ -11,6 +11,11 @@ export interface PanelConfig {
language?: SupportedLanguage language?: SupportedLanguage
onExecuteTask: (task: string) => void onExecuteTask: (task: string) => void
onStop: () => void onStop: () => void
/**
* Whether to prompt for next task after task completion
* @default true
*/
promptForNextTask?: boolean
} }
/** /**
@@ -358,7 +363,14 @@ export class Panel {
} }
const lastStep = steps[steps.length - 1] const lastStep = steps[steps.length - 1]
return lastStep.type === 'completed' || lastStep.type === 'error' const isTaskEnded = lastStep.type === 'completed' || lastStep.type === 'error'
// Only show input area after task completion if configured to do so
if (isTaskEnded) {
return this.#config.promptForNextTask ?? true
}
return false
} }
#createWrapper(): HTMLElement { #createWrapper(): HTMLElement {

View File

@@ -89,6 +89,10 @@ export default function HomePage() {
import.meta.env.DEV && import.meta.env.LLM_API_KEY import.meta.env.DEV && import.meta.env.LLM_API_KEY
? import.meta.env.LLM_API_KEY ? import.meta.env.LLM_API_KEY
: DEMO_API_KEY, : DEMO_API_KEY,
// enableAskUser: false,
// promptForNextTask: false,
// enablePanel: false,
}) })
} }