Merge pull request #118 from alibaba/feat/make-panel-optional
feat: make all ui 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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -61,6 +61,27 @@ export default function Configuration() {
|
|||||||
code={`interface AgentConfig {
|
code={`interface AgentConfig {
|
||||||
language?: 'en-US' | 'zh-CN'
|
language?: 'en-US' | 'zh-CN'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 or override built-in tools */
|
/** Custom tools to extend or override built-in tools */
|
||||||
customTools?: Record<string, PageAgentTool | null>
|
customTools?: Record<string, PageAgentTool | null>
|
||||||
|
|
||||||
@@ -140,6 +161,39 @@ interface PageControllerConfig extends DomConfig {
|
|||||||
code={`type PageAgentConfig = LLMConfig & AgentConfig & PageControllerConfig`}
|
code={`type PageAgentConfig = LLMConfig & AgentConfig & PageControllerConfig`}
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
{/* Programmatic Usage Example */}
|
||||||
|
<section className="mb-10">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">
|
||||||
|
{isZh ? '程序化使用配置' : 'Programmatic Usage'}
|
||||||
|
</h2>
|
||||||
|
<p className="text-gray-600 dark:text-gray-400 mb-4">
|
||||||
|
{isZh
|
||||||
|
? '对于程序化集成场景,可以禁用 UI。'
|
||||||
|
: 'For programmatic integration, you can disable UI.'}
|
||||||
|
</p>
|
||||||
|
<CodeEditor
|
||||||
|
language="typescript"
|
||||||
|
code={`const agent = new PageAgent({
|
||||||
|
baseURL: 'https://api.openai.com/v1',
|
||||||
|
apiKey: 'your-api-key',
|
||||||
|
model: 'your-model-name',
|
||||||
|
|
||||||
|
// Disable all UI features for pure programmatic usage
|
||||||
|
enablePanel: false, // Don't create Panel UI
|
||||||
|
enableMask: false, // Don't show visual overlay (mask and pointer)
|
||||||
|
// enableAskUser is automatically disabled when enablePanel is false
|
||||||
|
|
||||||
|
// Or keep Panel but disable post-task prompts
|
||||||
|
// enablePanel: true,
|
||||||
|
// promptForNextTask: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Pure programmatic execution
|
||||||
|
const result = await agent.execute('search for TypeScript documentation')
|
||||||
|
console.log(result.success, result.data, result.history)`}
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ const pageAgentTool = {
|
|||||||
},
|
},
|
||||||
execute: async (params) => {
|
execute: async (params) => {
|
||||||
const result = await pageAgent.execute(params.instruction)
|
const result = await pageAgent.execute(params.instruction)
|
||||||
return { success: result.success, message: result.message }
|
return { success: result.success, message: result.data }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user