feat: make panel optional
This commit is contained in:
@@ -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')
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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}`
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user