Compare commits

..

11 Commits

Author SHA1 Message Date
Simon 6abe75f199 docs(ext): clarify data handling and privacy in PRIVACY.md 2026-02-14 17:03:41 +08:00
Simon e27ebda960 chore(ext): docs and locales 2026-02-14 16:51:37 +08:00
Simon 9b603d90ba docs: update limitations page 2026-02-14 16:23:34 +08:00
Simon b89228693d feat(ext): add lang setting 2026-02-14 16:10:46 +08:00
Simon 66cc9e0a3c style: pretty up; fix extension shadcn 2026-02-14 15:12:45 +08:00
Simon 53144a8021 chore(ext): rm zh doc 2026-02-14 15:12:03 +08:00
Simon 43b7c1b136 style(ext): make over 2026-02-13 19:09:46 +08:00
Simon 3e7851849f chore(version): bump version to 1.3.0 2026-02-13 18:50:32 +08:00
Simon dffcb53db9 feat!: Refine lifecycle hooks; fix abortSignal
- add `stop` method. agent can be reused after stopped
- agent can not be reused after disposed
- extension DO NOT exposes `dispose` anymore. only `stop`.
- update panel for new `stop` method
- fix MultiPageAgent dispose event
- better handling abortSignal
2026-02-13 17:57:12 +08:00
Simon 4dc332a32c docs: update changelog 2026-02-12 17:23:35 +08:00
Simon f19b3cc2cc docs: update extension related docs 2026-02-12 17:19:14 +08:00
45 changed files with 1037 additions and 938 deletions
+3 -2
View File
@@ -18,7 +18,7 @@
"wouter"
],
"files.exclude": {
"packages/*/node_modules": true,
"packages/*/node_modules": true
},
"markdownlint.config": {
// "comment": "Relaxed rules",
@@ -32,6 +32,7 @@
"first-line-h1": false,
"block-spacing": false,
"blanks-around-lists": false,
"ol-prefix": false
"ol-prefix": false,
"no-duplicate-heading": false
}
}
+2 -1
View File
@@ -22,6 +22,7 @@ npm start # Start website dev server
npm run build # Build all packages
npm run build:libs # Build all libraries
npm run lint # ESLint with TypeScript strict rules
npm run zip -w @page-agent/ext # Zip the extension package
```
## Architecture
@@ -36,7 +37,7 @@ packages/
├── page-agent/ # npm: "page-agent" entry class (with UI + controller + demo builds)
├── website/ # @page-agent/website (private)
├── llms/ # @page-agent/llms
├── extension/ # 🚧 WIP: Browser extension (WXT + React)
├── extension/ # Browser extension (WXT + React)
├── page-controller/ # @page-agent/page-controller
└── ui/ # @page-agent/ui
```
+59
View File
@@ -5,6 +5,65 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.3.0] - 2026-02-13
### Breaking Changes
- **Lifecycle: `stop()` vs `dispose()`** - New `stop()` method to cancel the current task while keeping the agent reusable. `dispose()` is now terminal — a disposed agent cannot be reused. This affects both `PageAgentCore` and `PanelAgentAdapter`.
### Features
- **Panel action button** - The panel button now morphs between Stop (■) and Close (X) based on agent status
- **Error history** - Errors and max-step failures are now recorded in `history` as `AgentErrorEvent`, making post-task analysis more complete
### Bug Fixes
- **AbortError handling** - `AbortError` is no longer retried by the LLM client, and shows a clean "Task stopped" message instead of a raw error stack
---
## [1.2.0] - 2026-02-11
### Features
- **Observe Phase** - Agent now observes the page before each action, improving decision accuracy on dynamic pages
- **Better Abort Handling** - Improved `abortSignal` support for cleaner task cancellation
### Improvements
- Pruned system prompts for lower token usage and faster responses
- Improved error handling during agent steps with better error messages
- Zod tree-shaking for smaller bundle size
### Bug Fixes
- Fixed indentation lost in DOM extraction caused by `trimLines`
- Fixed `gpt-5-mini` temperature configuration
---
## [1.1.0] - 2026-02-02
### Features
- **Custom System Prompt** - New `systemPrompt` config option to customize or extend the default system prompt
- **Chrome Extension** - Extension with multi-tab control, main-world API with token auth, and tab lifecycle management
### Improvements
- Renamed `include_attributes` to `includeAttributes` in PageController config (camelCase consistency)
- Lazy-loaded mask module for faster initialization
- Better date formatting and error messages from LLM client
- Added `rawRequest` to step history for easier debugging
### Bug Fixes
- Fixed CSP errors by using local SVGs for cursor mask instead of inline styles
- Fixed `AbortError` being incorrectly retried and shown to users
- Fixed mask not working correctly when starting a new task after stopping a previous one
---
## [1.0.0] - 2026-01-19
### 🎉 First Stable Release
+12 -9
View File
@@ -20,10 +20,11 @@ Thank you for your interest in contributing to Page-Agent! We welcome contributi
### Project Structure
This is a **monorepo** with npm workspaces containing **3 main packages**:
This is a **monorepo** with npm workspaces containing **4 main packages**:
- **Page Agent** (`packages/page-agent/`) - Main entry with built-in UI Panel, published as `page-agent` on npm
- **Core** (`packages/core/`) - Core agent logic without UI (npm: `@page-agent/core`)
- **Extension** (`packages/extension/`) - Chrome extension for multi-page tasks and browser-level automation
- **Website** (`packages/website/`) - React documentation and landing page. Also as demo and test page for the core lib. private package `@page-agent/website`
We use a simplified monorepo solution with `native npm-workspace + ts reference + vite alias`. No fancy tooling. Hoisting is required.
@@ -145,6 +146,16 @@ If your lame AI assistant does not support [AGENTS.md](https://agents.md/). Add
npm start
```
### Extension Development
```bash
npm run dev -w @page-agent/ext
npm run zip -w @page-agent/ext
```
- Load extension in Chrome via `chrome://extensions` -> **Load unpacked**
- Use `packages/extension/docs/extension_api.md` (EN) or `packages/extension/docs/extension_api_zh.md` (ZH) for API integration details
### Testing on Other Websites
- Start and serve a local `iife` script
@@ -193,14 +204,6 @@ By contributing to this project, you agree that your contributions will be licen
> You may need to sign a github CLA before you create a PR.
### Browser-Use Attribution
Parts of this project are derived from the [browser-use](https://github.com/browser-use/browser-use) project (MIT License). When contributing to DOM-related functionality:
- Maintain existing attribution comments
- Follow similar patterns for consistency
- Credit browser-use for derived concepts
## 💬 Questions?
- Open a GitHub issue for technical questions
+23 -28
View File
@@ -1,4 +1,4 @@
# PageAgent 🤖🪄
# Page Agent
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://img.alicdn.com/imgextra/i4/O1CN01qKig1P1FnhpFKNdi6_!!6000000000532-2-tps-1280-256.png">
@@ -19,18 +19,16 @@
## ✨ Features
- **🎯 轻松集成**
- 无需 Python,无需无头浏览器,无需浏览器插件。纯页面内脚本
- **🔐 端侧运行**
- **🧠 HTML 脱水**
- **💬 自然语言接口**
- **🎨 HITL 交互界面**
以及 😉
- **🧪 实验性的 Chrome 扩展,支持跨页面控制** - `packages/extension`
👉 [**🗺️ Roadmap**](https://github.com/alibaba/page-agent/issues/96)
- **🎯 轻松集成**
- 无需 `浏览器插件` / `Python` / `无头浏览器`
- 纯页面内 JavaScript,一切都在你的网页中完成。
- The best tool for your agent to control web pages.
- **📖 基于文本的 DOM 操作**
- 无需截图,无需 OCR 或多模态模型。
- 无需特殊权限。
- **🧠 用你自己的 LLM**
- **🎨 精美 UI,支持人机协同**
- **🐙 可选的 [Chrome 扩展](https://alibaba.github.io/page-agent/#/docs/features/chrome-extension),支持跨页面任务。**
## 🚀 快速开始
@@ -39,19 +37,15 @@
通过我们免费的 Demo LLM 快速体验 PageAgent
```html
<script
src="https://registry.npmmirror.com/page-agent/1.2.0/files/dist/iife/page-agent.demo.js"
crossorigin="true"
></script>
<script src="{URL}" crossorigin="true"></script>
```
> - **⚠️ 仅用于技术评估。** Demo LLM 有速率和使用限制,可能随时变更。
> - **🌷 建议使用自己的 LLM API。**
| Mirrors | URL |
| ------- | ---------------------------------------------------------------------------------- |
| Global | https://cdn.jsdelivr.net/npm/page-agent@1.2.0/dist/iife/page-agent.demo.js |
| China | https://registry.npmmirror.com/page-agent/1.2.0/files/dist/iife/page-agent.demo.js |
| Global | https://cdn.jsdelivr.net/npm/page-agent@1.3.0/dist/iife/page-agent.demo.js |
| China | https://registry.npmmirror.com/page-agent/1.3.0/files/dist/iife/page-agent.demo.js |
> **⚠️ 仅用于技术评估。** Demo LLM 有速率和使用限制,速度较慢,可能随时变更。
### NPM 安装
@@ -72,7 +66,7 @@ const agent = new PageAgent({
await agent.execute('点击登录按钮')
```
适用于无法使用 NPM 的环境,我们也提供了 IIFE 构建的 CDN 方式。[@see CDN Usage](https://alibaba.github.io/page-agent/#/docs/integration/cdn-setup)
更多编程用法,请参阅 [📖 文档](https://alibaba.github.io/page-agent/#/docs/introduction/overview)
## 🏗️ 架构设计
@@ -80,12 +74,13 @@ PageAgent adopts a simplified monorepo structure:
```
packages/
├── core/ # ** Core agent logic without UI(npm: @page-agent/core) **
├── page-agent/ # Exported agent and demo(npm: page-agent)
├── core/ # ** Core agent logic (npm: @page-agent/core) **
├── llms/ # LLM 客户端 (npm: @page-agent/llms)
├── page-controller/ # DOM 操作 & 蒙层 & 模拟鼠标 (npm: @page-agent/page-controller)
├── ui/ # 面板 & i18n (npm: @page-agent/ui)
── website/ # 文档站点
├── page-controller/ # DOM 操作 (npm: @page-agent/page-controller)
├── ui/ # 面板 UI (npm: @page-agent/ui)
── page-agent/ # 入口类 & iife 包 (npm: page-agent)
├── extension/ # Chrome 扩展,支持跨页面任务
└── website/ # 网站 & 文档站点
```
## 🤝 贡献
+24 -27
View File
@@ -1,4 +1,4 @@
# PageAgent 🤖🪄
# Page Agent
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://img.alicdn.com/imgextra/i4/O1CN01qKig1P1FnhpFKNdi6_!!6000000000532-2-tps-1280-256.png">
@@ -19,18 +19,16 @@ The GUI Agent Living in Your Webpage. Control web interfaces with natural langua
## ✨ Features
- **🎯 Easy Integration**
- No python. No headless browser. No browser extension. Just in-page scripts.
- **🔐 Client-Side Processing**
- **🧠 DOM Extraction**
- **💬 Natural Language Interface**
- **🎨 UI with Human in the loop**
And 😉
- **🧪 `cross-page` control with an experimental chrome extension** - `packages/extension`
👉 [**🗺️ Roadmap**](https://github.com/alibaba/page-agent/issues/96)
- **🎯 Easy integration**
- No need for `browser extension` / `python` / `headless browser`.
- Just in-page javascript. Everything happens in your web page.
- The best tool for your agent to control web pages.
- **📖 Text-based DOM manipulation**
- No screenshots. No OCR or multi-modal LLMs needed.
- No special permissions required.
- **🧠 Bring your own LLMs**
- **🎨 Pretty UI with human-in-the-loop**
- **🐙 Optional [chrome extension](https://alibaba.github.io/page-agent/#/docs/features/chrome-extension) for multi-page tasks.**
## 🚀 Quick Start
@@ -39,19 +37,15 @@ And 😉
Fastest way to try PageAgent with our free Demo LLM:
```html
<script
src="https://cdn.jsdelivr.net/npm/page-agent@1.2.0/dist/iife/page-agent.demo.js"
crossorigin="true"
></script>
<script src="{URL}" crossorigin="true"></script>
```
> - **⚠️ For technical evaluation only.** Demo LLM has rate limits and usage restrictions. May change without notice.
> - **🌷 Bring your own LLM API.**
| Mirrors | URL |
| ------- | ---------------------------------------------------------------------------------- |
| Global | https://cdn.jsdelivr.net/npm/page-agent@1.2.0/dist/iife/page-agent.demo.js |
| China | https://registry.npmmirror.com/page-agent/1.2.0/files/dist/iife/page-agent.demo.js |
| Global | https://cdn.jsdelivr.net/npm/page-agent@1.3.0/dist/iife/page-agent.demo.js |
| China | https://registry.npmmirror.com/page-agent/1.3.0/files/dist/iife/page-agent.demo.js |
> **⚠️ For technical evaluation only.** Demo LLM has rate limits and usage restrictions. Slow. May change without notice.
### NPM Installation
@@ -72,18 +66,21 @@ const agent = new PageAgent({
await agent.execute('Click the login button')
```
For more programmatic usage, see [📖 Documentations](https://alibaba.github.io/page-agent/#/docs/introduction/overview).
## 🏗️ Structure
PageAgent adopts a simplified monorepo structure:
```
packages/
├── core/ # ** Core agent logic without UI(npm: @page-agent/core) **
├── page-agent/ # Exported agent and demo(npm: page-agent)
├── core/ # ** Core agent logic (npm: @page-agent/core) **
├── llms/ # LLM client (npm: @page-agent/llms)
├── page-controller/ # DOM operations & Visual Mask (npm: @page-agent/page-controller)
├── ui/ # Panel & i18n (npm: @page-agent/ui)
── website/ # Demo & Documentation site
├── page-controller/ # DOM operations (npm: @page-agent/page-controller)
├── ui/ # Panel UI (npm: @page-agent/ui)
── page-agent/ # Entry class and iife builds(npm: page-agent)
├── extension/ # Chrome extension for multi-page tasks
└── website/ # Website & Documentation site
```
## 🤝 Contributing
+20 -19
View File
@@ -1,12 +1,12 @@
{
"name": "root",
"version": "1.2.0",
"version": "1.3.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "root",
"version": "1.2.0",
"version": "1.3.0",
"license": "MIT",
"workspaces": [
"packages/page-controller",
@@ -11063,24 +11063,25 @@
},
"packages/core": {
"name": "@page-agent/core",
"version": "1.2.0",
"version": "1.3.0",
"license": "MIT",
"dependencies": {
"@page-agent/llms": "1.2.0",
"@page-agent/page-controller": "1.2.0",
"@page-agent/llms": "1.3.0",
"@page-agent/page-controller": "1.3.0",
"chalk": "^5.6.2",
"zod": "^4.3.5"
}
},
"packages/extension": {
"name": "@page-agent/ext",
"version": "0.1.5",
"version": "0.1.7",
"hasInstallScript": true,
"dependencies": {
"@page-agent/core": "1.2.0",
"@page-agent/llms": "1.2.0",
"@page-agent/page-controller": "1.2.0",
"@page-agent/ui": "1.2.0",
"@page-agent/core": "1.3.0",
"@page-agent/llms": "1.3.0",
"@page-agent/page-controller": "1.3.0",
"@page-agent/ui": "1.3.0",
"ai-motion": "^0.4.8",
"chalk": "^5.6.2",
"zod": "^4.3.5"
},
@@ -11115,7 +11116,7 @@
},
"packages/llms": {
"name": "@page-agent/llms",
"version": "1.2.0",
"version": "1.3.0",
"license": "MIT",
"dependencies": {
"chalk": "^5.6.2",
@@ -11123,20 +11124,20 @@
}
},
"packages/page-agent": {
"version": "1.2.0",
"version": "1.3.0",
"license": "MIT",
"dependencies": {
"@page-agent/core": "1.2.0",
"@page-agent/llms": "1.2.0",
"@page-agent/page-controller": "1.2.0",
"@page-agent/ui": "1.2.0",
"@page-agent/core": "1.3.0",
"@page-agent/llms": "1.3.0",
"@page-agent/page-controller": "1.3.0",
"@page-agent/ui": "1.3.0",
"chalk": "^5.6.2",
"zod": "^4.3.5"
}
},
"packages/page-controller": {
"name": "@page-agent/page-controller",
"version": "1.2.0",
"version": "1.3.0",
"license": "MIT",
"dependencies": {
"ai-motion": "^0.4.8"
@@ -11144,12 +11145,12 @@
},
"packages/ui": {
"name": "@page-agent/ui",
"version": "1.2.0",
"version": "1.3.0",
"license": "MIT"
},
"packages/website": {
"name": "@page-agent/website",
"version": "1.2.0",
"version": "1.3.0",
"devDependencies": {
"@radix-ui/react-icons": "^1.3.2",
"@radix-ui/react-separator": "^1.1.8",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "root",
"private": true,
"version": "1.2.0",
"version": "1.3.0",
"type": "module",
"workspaces": [
"packages/page-controller",
+3 -3
View File
@@ -1,7 +1,7 @@
{
"name": "@page-agent/core",
"private": false,
"version": "1.2.0",
"version": "1.3.0",
"type": "module",
"main": "./dist/esm/page-agent-core.js",
"module": "./dist/esm/page-agent-core.js",
@@ -45,7 +45,7 @@
"dependencies": {
"chalk": "^5.6.2",
"zod": "^4.3.5",
"@page-agent/llms": "1.2.0",
"@page-agent/page-controller": "1.2.0"
"@page-agent/llms": "1.3.0",
"@page-agent/page-controller": "1.3.0"
}
}
+22 -3
View File
@@ -7,7 +7,7 @@ import type { BrowserState, PageController } from '@page-agent/page-controller'
import chalk from 'chalk'
import * as zod from 'zod'
import { type PageAgentConfig } from './config'
import { type PageAgentConfig, type SupportedLanguage } from './config'
import { DEFAULT_MAX_STEPS } from './config/constants'
import SYSTEM_PROMPT from './prompts/system_prompt.md?raw'
import { tools } from './tools'
@@ -24,6 +24,7 @@ import type {
import { assert, normalizeResponse, uid, waitFor } from './utils'
export { type PageAgentConfig }
export type { SupportedLanguage }
export { tool, type PageAgentTool } from './tools'
export type * from './types'
@@ -68,6 +69,8 @@ export class PageAgentCore extends EventTarget {
taskId = ''
/** History events */
history: HistoricalEvent[] = []
/** Whether this agent has been disposed */
disposed = false
/**
* Callback for when agent needs user input (ask_user tool)
@@ -183,7 +186,15 @@ export class PageAgentCore extends EventTarget {
this.#observations.push(content)
}
/** Stop the current task. Agent remains reusable. */
stop() {
this.pageController.cleanUpHighlights()
this.pageController.hideMask()
this.#abortController.abort()
}
async execute(task: string): Promise<ExecutionResult> {
if (this.disposed) throw new Error('PageAgent has been disposed. Create a new instance.')
if (!task) throw new Error('Task is required')
this.task = task
this.taskId = uid()
@@ -300,9 +311,13 @@ export class PageAgentCore extends EventTarget {
}
} catch (error: unknown) {
console.groupEnd() // to prevent nested groups
const isAbortError = (error as any)?.rawError?.name === 'AbortError'
console.error('Task failed', error)
const errorMessage = String(error)
const errorMessage = isAbortError ? 'Task stopped' : String(error)
this.#emitActivity({ type: 'error', message: errorMessage })
this.history.push({ type: 'error', message: errorMessage, rawResponse: error })
this.#emitHistoryChange()
this.#onDone(false)
const result: ExecutionResult = {
success: false,
@@ -315,10 +330,13 @@ export class PageAgentCore extends EventTarget {
step++
if (step > this.config.maxSteps) {
const errorMessage = 'Step count exceeded maximum limit'
this.history.push({ type: 'error', message: errorMessage })
this.#emitHistoryChange()
this.#onDone(false)
const result: ExecutionResult = {
success: false,
data: 'Step count exceeded maximum limit',
data: errorMessage,
history: this.history,
}
await onAfterTask?.(this, result)
@@ -601,6 +619,7 @@ export class PageAgentCore extends EventTarget {
dispose() {
console.log('Disposing PageAgent...')
this.disposed = true
this.pageController.dispose()
// this.history = []
this.#abortController.abort()
+7 -4
View File
@@ -1,6 +1,6 @@
# Privacy Policy for Page Agent Extension
**Last updated:** January 2026
**Last updated:** February 2026
## Overview
@@ -10,24 +10,27 @@ Page Agent Extension is a browser automation tool that uses AI to help you inter
### Local Processing
The extension performs DOM analysis and automation actions **locally in your browser**. Your browsing history, passwords, and form data are not accessed or collected by the extension itself.
The extension performs DOM analysis and automation actions **locally in your browser**. Your browsing history, passwords, and form data are not accessed or collected by the extension developer.
### Data Transmission
Data is transmitted to external servers **only when you initiate an automation task**. When this occurs:
- Your task instructions (natural language commands)
- Sanitized page structure (simplified DOM, excluding sensitive form values)
- Simplified page structure (cleaned DOM) of all pages under the extension's control
are sent to the LLM API endpoint configured in **your settings**.
> **Note:** The DOM cleaning process simplifies page structure for AI readability but **does not guarantee removal of sensitive information** (e.g., visible text, form values, or personal data on the page). Please be mindful of the page content when initiating tasks.
**If you configure a third-party LLM provider** (e.g., OpenAI, Anthropic, or others), data is sent directly to that provider. Their privacy policies apply.
**If you use our testing endpoint**, your requests are proxied to [DeepSeek](https://deepseek.com) for AI processing. Regarding this test endpoint:
- This endpoint is provided for evaluation purposes only and is not recommended for production or daily use
- The free model and their service providers may change at any time without prior notice
- We do **not** store your task content, page content, or visited URLs
- Minimal logging (timestamps, request metadata, IP addresses) may occur for abuse prevention and service stability
- Minimal logging (timestamps, request metadata, IP addresses) may be collected for abuse prevention and service stability
- DeepSeek's [Privacy Policy](https://cdn.deepseek.com/policies/en-US/deepseek-privacy-policy.html) applies to their processing of your requests
## Data Storage
+2 -2
View File
@@ -5,7 +5,7 @@
"tsx": true,
"tailwind": {
"config": "",
"css": "src/index.css",
"css": "src/assets/index.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
@@ -16,7 +16,7 @@
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
"hooks": "@/lib/hooks"
},
"registries": {
"@magicui": "https://magicui.design/r/{name}.json"
+30
View File
@@ -0,0 +1,30 @@
AI-powered browser automation. Control web pages with natural language.
Page Agent Ext — AI-Powered Browser Automation
🌟 What is Page Agent Ext?
Page Agent Ext brings AI-powered automation to your browser. Built on the open-source Page Agent framework, it lets you control web pages across multiple tabs using natural language — no scripting required.
🌟 Key Features:
- Natural Language Control — Command your browser in plain language, no code needed
- Cross-Tab Automation — Seamlessly operate across multiple tabs and pages
- Smart HTML Cleaning — Intelligently extracts and simplifies page structure for accurate AI understanding
- Bring Your Own LLM — Use OpenAI, Anthropic, or any compatible API with full data control
- Privacy-First — Zero data collection; all data flows directly to your chosen LLM provider
- Open Source — MIT licensed, built on the Page Agent framework with full transparency
🌟 How It Works & Privacy:
Page Agent Ext performs DOM analysis locally in your browser. When you initiate a task, sanitized page structure is sent to the LLM API you configure. Your data is never collected or stored by us.
- Your API Key — Configure your own LLM API (OpenAI, Anthropic, etc.). Data goes directly to your provider
- Test API — A free test endpoint is available for evaluation; we recommend your own key for regular use
Privacy policy: https://github.com/alibaba/page-agent/blob/main/packages/extension/PRIVACY.md
🌟 Open Source:
This project is MIT licensed. Review the code, verify privacy claims, or extend it for your needs:
https://github.com/alibaba/page-agent
@@ -0,0 +1,30 @@
AI 驱动的浏览器自动化助手,用自然语言控制网页。
Page Agent Ext — AI 驱动的浏览器自动化助手
🌟 什么是 Page Agent Ext
Page Agent Ext 为浏览器带来 AI 自动化能力。基于开源的 Page Agent 框架,你可以用自然语言跨标签页控制网页,无需编写任何脚本。
🌟 核心特性:
- 自然语言控制 — 用日常语言指挥浏览器,无需编程
- 跨标签页操作 — 在多个标签页之间无缝切换和操控
- HTML 智能清洗 — 智能提取和精简页面结构,让 AI 准确理解网页内容
- 使用你自己的模型 — 支持 OpenAI、Anthropic 或任何兼容 API,数据完全自主可控
- 隐私优先 — 零数据收集,所有数据直接发送到你配置的 LLM 服务商
- 开源透明 — MIT 协议,基于 Page Agent 开源框架,代码完全公开
🌟 工作原理与隐私:
Page Agent Ext 在浏览器本地进行 DOM 分析。当你发起任务时,经清洗的页面结构会发送到你配置的 LLM API。我们不会收集或存储你的任何数据。
- 你的 API Key — 配置你自己的 LLM APIOpenAI、Anthropic 等),数据直接发送到你的服务商
- 测试 API — 提供免费测试端点供体验,日常使用建议配置自己的 Key
隐私政策:https://github.com/alibaba/page-agent/blob/main/packages/extension/PRIVACY.md
🌟 开源项目:
MIT 协议开源。查看源码、验证隐私承诺,或按需扩展:
https://github.com/alibaba/page-agent
+43 -108
View File
@@ -1,12 +1,18 @@
# Page Agent Extension API
This document describes how to integrate the Page Agent browser extension into your web application.
Integrate the Page Agent extension into your web app and trigger multi-page browser tasks from page JavaScript.
## Installation
### 1. Install the browser extension
Install the Page Agent extension from the Chrome Web Store.
Primary channel:
- Chrome Web Store: https://chromewebstore.google.com/detail/page-agent-ext/akldabonmimlicnjlflnapfeklbfemhj
Latest updates are often published earlier on:
- GitHub Releases: https://github.com/alibaba/page-agent/releases
### 2. Install type definitions (recommended)
@@ -14,11 +20,19 @@ Install the Page Agent extension from the Chrome Web Store.
npm install @page-agent/core --save-dev
```
### 3. Set up authentication
### 3. Authorization (Token)
The extension only injects APIs when it detects a valid token in `localStorage`.
The token allows your page JS to call the extension API (`window.PAGE_AGENT_EXT`) and execute multi-page tasks.
1. Open the extension's side panel to get your authorization token
Why token-based access is required:
- The extension has broad browser permissions (page access, navigation, multi-tab control).
- If abused, it can harm user privacy and security.
- Users must explicitly provide the token only to applications they trust.
Setup:
1. Open the extension side panel and copy your auth token.
2. Set the token in your page:
```typescript
@@ -60,36 +74,36 @@ if (await waitForExtension()) {
## Global API
The extension injects the following APIs into the `window` object:
After token match, the extension injects APIs into `window`.
### `window.PAGE_AGENT_EXT_VERSION`
Extension version string (e.g., `"1.0.0"`). This is exposed separately to allow version checking before accessing the main API object.
Extension version string (for capability checks before using the main API).
### `window.PAGE_AGENT_EXT`
Main API namespace object containing:
Main namespace object.
#### `PAGE_AGENT_EXT.execute(task, config)`
Execute an agent task.
Execute one agent task.
**Parameters:**
Parameters:
| Name | Type | Required | Description |
|------|------|----------|-------------|
| ---- | ---- | -------- | ----------- |
| `task` | `string` | Yes | Task description |
| `config` | `ExecuteConfig` | Yes | Execution configuration (LLM settings, options, and event callbacks) |
| `config` | `ExecuteConfig` | Yes | LLM settings, options, and callbacks |
**Returns:** `Promise<ExecutionResult>`
Returns: `Promise<ExecutionResult>`
#### `PAGE_AGENT_EXT.dispose()`
#### `PAGE_AGENT_EXT.stop()`
Stop and destroy the current running agent.
Stop the current task.
## Types
Install `@page-agent/core` for full type definitions:
Install `@page-agent/core` for complete types:
```typescript
import type {
@@ -104,35 +118,24 @@ export interface ExecuteConfig {
apiKey: string
model: string
/**
* Whether to include the initial tab (that holds this main world script) in the task.
* @default true
*/
// Include the initial tab where page JS starts. Default: true.
includeInitialTab?: boolean
onStatusChange?: (status: AgentStatus) => void
onActivity?: (activity: AgentActivity) => void
onHistoryUpdate?: (history: HistoricalEvent[]) => void
onDispose?: () => void
}
export type Execute = (task: string, config: ExecuteConfig) => Promise<ExecutionResult>
```
### AgentStatus
`AgentStatus`
```typescript
type AgentStatus = 'idle' | 'running' | 'completed' | 'error'
```
| Status | Description |
|--------|-------------|
| `idle` | Agent is idle, ready to execute |
| `running` | Agent is executing a task |
| `completed` | Task completed successfully |
| `error` | Task failed with an error |
### AgentActivity
`AgentActivity`
```typescript
type AgentActivity =
@@ -143,15 +146,7 @@ type AgentActivity =
| { type: 'error'; message: string }
```
| Type | Description |
|------|-------------|
| `thinking` | Agent is analyzing the page and planning |
| `executing` | Agent is executing a tool action |
| `executed` | Tool execution completed |
| `retrying` | Retrying after a failure |
| `error` | An error occurred |
### HistoricalEvent
`HistoricalEvent`
```typescript
type HistoricalEvent =
@@ -162,7 +157,7 @@ type HistoricalEvent =
| { type: 'error'; message: string; rawResponse?: unknown }
```
### ExecutionResult
`ExecutionResult`
```typescript
interface ExecutionResult {
@@ -183,81 +178,22 @@ const result = await window.PAGE_AGENT_EXT!.execute(
baseURL: 'https://api.openai.com/v1',
apiKey: process.env.OPENAI_API_KEY!,
model: 'gpt-5.2',
}
)
if (result.success) {
console.log('Task completed:', result.data)
} else {
console.error('Task failed')
}
```
### Exclude Initial Tab
By default, the agent includes the initial tab (where the script runs) in the task. Set `includeInitialTab: false` to exclude it:
```typescript
const result = await window.PAGE_AGENT_EXT!.execute(
'Open a new tab and search for page-agent on GitHub',
{
baseURL: 'https://api.openai.com/v1',
apiKey: process.env.OPENAI_API_KEY!,
model: 'gpt-5.2',
includeInitialTab: false, // Agent will open new tabs only
includeInitialTab: false, // Optional: exclude current tab
onStatusChange: (status) => console.log(status),
onActivity: (activity) => console.log(activity),
}
)
```
### With Event Callbacks
### Stop the Current Task
```typescript
await window.PAGE_AGENT_EXT!.execute('Navigate to the settings page', {
baseURL: 'https://api.openai.com/v1',
apiKey: process.env.OPENAI_API_KEY!,
model: 'gpt-5.2',
onStatusChange: (status) => {
updateUI({ agentStatus: status })
},
onActivity: (activity) => {
switch (activity.type) {
case 'thinking':
showSpinner('Agent is thinking...')
break
case 'executing':
showSpinner(`Executing: ${activity.tool}`)
break
case 'executed':
log(`${activity.tool} completed in ${activity.duration}ms`)
break
case 'error':
showError(activity.message)
break
}
},
onHistoryUpdate: (history) => {
renderHistory(history)
},
})
```
### Stop Execution
```typescript
// Start a task
window.PAGE_AGENT_EXT!.execute('Scroll through all pages', {
baseURL: 'https://api.openai.com/v1',
apiKey: process.env.OPENAI_API_KEY!,
model: 'gpt-5.2',
})
// Later, stop it
window.PAGE_AGENT_EXT!.dispose()
window.PAGE_AGENT_EXT!.stop()
```
## Window Type Declaration
If not using `@page-agent/core`, add this to your project:
If you are not importing `@page-agent/core`, add:
```typescript
import type {
@@ -275,7 +211,6 @@ interface ExecuteConfig {
onStatusChange?: (status: AgentStatus) => void
onActivity?: (activity: AgentActivity) => void
onHistoryUpdate?: (history: HistoricalEvent[]) => void
onDispose?: () => void
}
declare global {
@@ -283,8 +218,8 @@ declare global {
PAGE_AGENT_EXT_VERSION?: string
PAGE_AGENT_EXT?: {
version: string
execute: (task: string, config: ExecuteConfig) => Promise<ExecutionResult>
dispose: () => void
execute: Execute
stop: () => void
}
}
}
-291
View File
@@ -1,291 +0,0 @@
# Page Agent 浏览器插件 API
本文档介绍如何在网页应用中接入 Page Agent 浏览器插件。
## 安装
### 1. 安装浏览器插件
从 Chrome 应用商店安装 Page Agent 插件。
### 2. 安装类型定义(推荐)
```bash
npm install @page-agent/core --save-dev
```
### 3. 配置认证
插件在页面加载后检测 `localStorage` 中的 token,匹配时才会注入 API。
1. 打开插件的侧边栏面板,获取授权 token
2. 在页面中设置 token
```typescript
localStorage.setItem('PageAgentExtUserAuthToken', 'your-token')
```
## 快速开始
```typescript
import type {
AgentActivity,
AgentStatus,
ExecutionResult,
HistoricalEvent,
} from '@page-agent/core'
// 等待插件注入(最多 1 秒)
async function waitForExtension(timeout = 1000): Promise<boolean> {
const start = Date.now()
while (Date.now() - start < timeout) {
if (window.PAGE_AGENT_EXT) return true
await new Promise((r) => setTimeout(r, 100))
}
return false
}
// 使用
if (await waitForExtension()) {
const result = await window.PAGE_AGENT_EXT!.execute('点击登录按钮', {
baseURL: 'https://api.openai.com/v1',
apiKey: 'your-api-key',
model: 'gpt-5.2',
onStatusChange: (status) => console.log('状态:', status),
onActivity: (activity) => console.log('活动:', activity),
})
console.log('结果:', result)
}
```
## 全局 API
插件在 `window` 对象上注入以下 API
### `window.PAGE_AGENT_EXT_VERSION`
插件版本号字符串(例如 `"1.0.0"`)。单独暴露版本号,方便在访问主 API 对象前进行版本检查。
### `window.PAGE_AGENT_EXT`
主 API 命名空间对象,包含:
#### `PAGE_AGENT_EXT.execute(task, config)`
执行 Agent 任务。
**参数:**
| 名称 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `task` | `string` | 是 | 任务描述 |
| `config` | `ExecuteConfig` | 是 | 执行配置(LLM 设置、选项和事件回调) |
**返回:** `Promise<ExecutionResult>`
#### `PAGE_AGENT_EXT.dispose()`
停止并销毁当前运行的 Agent。
## 类型定义
安装 `@page-agent/core` 获取完整类型:
```typescript
import type {
AgentActivity,
AgentStatus,
ExecutionResult,
HistoricalEvent,
} from '@page-agent/core'
export interface ExecuteConfig {
baseURL: string
apiKey: string
model: string
/**
* 是否将初始标签页(运行此脚本的页面)包含在任务中。
* @default true
*/
includeInitialTab?: boolean
onStatusChange?: (status: AgentStatus) => void
onActivity?: (activity: AgentActivity) => void
onHistoryUpdate?: (history: HistoricalEvent[]) => void
onDispose?: () => void
}
export type Execute = (task: string, config: ExecuteConfig) => Promise<ExecutionResult>
```
### AgentStatus
```typescript
type AgentStatus = 'idle' | 'running' | 'completed' | 'error'
```
| 状态 | 说明 |
|------|------|
| `idle` | 空闲,准备执行 |
| `running` | 正在执行任务 |
| `completed` | 任务成功完成 |
| `error` | 任务执行失败 |
### AgentActivity
```typescript
type AgentActivity =
| { type: 'thinking' }
| { type: 'executing'; tool: string; input: unknown }
| { type: 'executed'; tool: string; input: unknown; output: string; duration: number }
| { type: 'retrying'; attempt: number; maxAttempts: number }
| { type: 'error'; message: string }
```
| 类型 | 说明 |
|------|------|
| `thinking` | Agent 正在分析页面并规划 |
| `executing` | 正在执行工具操作 |
| `executed` | 工具执行完成 |
| `retrying` | 失败后重试 |
| `error` | 发生错误 |
### HistoricalEvent
```typescript
type HistoricalEvent =
| { type: 'step'; stepIndex: number; reflection: AgentReflection; action: Action }
| { type: 'observation'; content: string }
| { type: 'user_takeover' }
| { type: 'retry'; message: string; attempt: number; maxAttempts: number }
| { type: 'error'; message: string; rawResponse?: unknown }
```
### ExecutionResult
```typescript
interface ExecutionResult {
success: boolean
data: string
history: HistoricalEvent[]
}
```
## 使用示例
### 基础执行
```typescript
const result = await window.PAGE_AGENT_EXT!.execute(
'在邮箱输入框填入 test@example.com 然后点击提交',
{
baseURL: 'https://api.openai.com/v1',
apiKey: process.env.OPENAI_API_KEY!,
model: 'gpt-5.2',
}
)
if (result.success) {
console.log('任务完成:', result.data)
} else {
console.error('任务失败')
}
```
### 排除初始标签页
默认情况下,Agent 会将初始标签页(运行脚本的页面)包含在任务中。设置 `includeInitialTab: false` 可以排除它:
```typescript
const result = await window.PAGE_AGENT_EXT!.execute(
'打开新标签页并在 GitHub 上搜索 page-agent',
{
baseURL: 'https://api.openai.com/v1',
apiKey: process.env.OPENAI_API_KEY!,
model: 'gpt-5.2',
includeInitialTab: false, // Agent 只会打开新标签页
}
)
```
### 使用事件回调
```typescript
await window.PAGE_AGENT_EXT!.execute('导航到设置页面', {
baseURL: 'https://api.openai.com/v1',
apiKey: process.env.OPENAI_API_KEY!,
model: 'gpt-5.2',
onStatusChange: (status) => {
updateUI({ agentStatus: status })
},
onActivity: (activity) => {
switch (activity.type) {
case 'thinking':
showSpinner('Agent 正在思考...')
break
case 'executing':
showSpinner(`正在执行: ${activity.tool}`)
break
case 'executed':
log(`${activity.tool} 完成,耗时 ${activity.duration}ms`)
break
case 'error':
showError(activity.message)
break
}
},
onHistoryUpdate: (history) => {
renderHistory(history)
},
})
```
### 停止执行
```typescript
// 启动任务
window.PAGE_AGENT_EXT!.execute('滚动浏览所有页面', {
baseURL: 'https://api.openai.com/v1',
apiKey: process.env.OPENAI_API_KEY!,
model: 'gpt-5.2',
})
// 稍后停止
window.PAGE_AGENT_EXT!.dispose()
```
## Window 类型声明
如果不使用 `@page-agent/core`,可以添加以下声明:
```typescript
import type {
AgentActivity,
AgentStatus,
ExecutionResult,
HistoricalEvent,
} from '@page-agent/core'
interface ExecuteConfig {
baseURL: string
apiKey: string
model: string
includeInitialTab?: boolean
onStatusChange?: (status: AgentStatus) => void
onActivity?: (activity: AgentActivity) => void
onHistoryUpdate?: (history: HistoricalEvent[]) => void
onDispose?: () => void
}
declare global {
interface Window {
PAGE_AGENT_EXT_VERSION?: string
PAGE_AGENT_EXT?: {
version: string
execute: (task: string, config: ExecuteConfig) => Promise<ExecutionResult>
dispose: () => void
}
}
}
```
+6 -5
View File
@@ -1,7 +1,7 @@
{
"name": "@page-agent/ext",
"private": true,
"version": "0.1.5",
"version": "0.1.7",
"type": "module",
"scripts": {
"dev": "wxt",
@@ -38,10 +38,11 @@
"wxt": "^0.20.14"
},
"dependencies": {
"@page-agent/core": "1.2.0",
"@page-agent/llms": "1.2.0",
"@page-agent/page-controller": "1.2.0",
"@page-agent/ui": "1.2.0",
"@page-agent/core": "1.3.0",
"@page-agent/llms": "1.3.0",
"@page-agent/page-controller": "1.3.0",
"@page-agent/ui": "1.3.0",
"ai-motion": "^0.4.8",
"chalk": "^5.6.2",
"zod": "^4.3.5"
}
@@ -0,0 +1,11 @@
{
"extName": {
"message": "Page Agent Ext"
},
"extDescription": {
"message": "AI-powered browser automation assistant. Control web pages with natural language."
},
"extActionTitle": {
"message": "Open Page Agent"
}
}
@@ -0,0 +1,11 @@
{
"extName": {
"message": "Page Agent Ext"
},
"extDescription": {
"message": "AI 驱动的浏览器自动化助手,用自然语言控制网页。"
},
"extActionTitle": {
"message": "打开 Page Agent"
}
}
@@ -89,8 +89,7 @@ export class MultiPageAgent extends PageAgentCore {
isAgentRunning: false,
})
// no need to dispose tabsController and pageController
// as they do not keep references
tabsController.dispose()
},
})
}
+31 -13
View File
@@ -1,22 +1,34 @@
/**
* React hook for using AgentController
*/
import type { AgentActivity, AgentStatus, HistoricalEvent } from '@page-agent/core'
import type {
AgentActivity,
AgentStatus,
HistoricalEvent,
SupportedLanguage,
} from '@page-agent/core'
import type { LLMConfig } from '@page-agent/llms'
import { useCallback, useEffect, useRef, useState } from 'react'
import { MultiPageAgent } from './MultiPageAgent'
import { DEMO_CONFIG } from './constants'
/** Language preference: undefined means follow system */
export type LanguagePreference = SupportedLanguage | undefined
export interface ExtConfig extends LLMConfig {
language?: LanguagePreference
}
export interface UseAgentResult {
status: AgentStatus
history: HistoricalEvent[]
activity: AgentActivity | null
currentTask: string
config: LLMConfig | null
config: ExtConfig | null
execute: (task: string) => Promise<void>
stop: () => void
configure: (config: LLMConfig) => Promise<void>
configure: (config: ExtConfig) => Promise<void>
}
export function useAgent(): UseAgentResult {
@@ -25,16 +37,17 @@ export function useAgent(): UseAgentResult {
const [history, setHistory] = useState<HistoricalEvent[]>([])
const [activity, setActivity] = useState<AgentActivity | null>(null)
const [currentTask, setCurrentTask] = useState('')
const [config, setConfig] = useState<LLMConfig | null>(null)
const [config, setConfig] = useState<ExtConfig | null>(null)
useEffect(() => {
chrome.storage.local.get('llmConfig').then((result) => {
if (result.llmConfig) {
setConfig(result.llmConfig as LLMConfig)
} else {
chrome.storage.local.get(['llmConfig', 'language']).then((result) => {
const llmConfig = (result.llmConfig as LLMConfig) ?? DEMO_CONFIG
const language = (result.language as SupportedLanguage) || undefined
const full = { ...llmConfig, language }
if (!result.llmConfig) {
chrome.storage.local.set({ llmConfig: DEMO_CONFIG })
setConfig(DEMO_CONFIG)
}
setConfig(full)
})
}, [])
@@ -84,12 +97,17 @@ export function useAgent(): UseAgentResult {
}, [])
const stop = useCallback(() => {
agentRef.current?.dispose()
agentRef.current?.stop()
}, [])
const configure = useCallback(async (newConfig: LLMConfig) => {
await chrome.storage.local.set({ llmConfig: newConfig })
setConfig(newConfig)
const configure = useCallback(async ({ language, ...llmConfig }: ExtConfig) => {
await chrome.storage.local.set({ llmConfig })
if (language) {
await chrome.storage.local.set({ language })
} else {
await chrome.storage.local.remove('language')
}
setConfig({ ...llmConfig, language })
}, [])
return {
+35
View File
@@ -111,6 +111,41 @@
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
--animate-blink-cursor: blink-cursor 1.2s step-end infinite;
@keyframes blink-cursor {
0%,
49% {
opacity: 1;
}
50%,
100% {
opacity: 0;
}
}
}
@keyframes glow-a {
0%,
100% {
opacity: 0.45;
transform: scale(1);
}
50% {
opacity: 0;
transform: scale(1.1);
}
}
@keyframes glow-b {
0%,
100% {
opacity: 0;
transform: scale(1.1);
}
50% {
opacity: 0.45;
transform: scale(1);
}
}
@layer base {
@@ -21,8 +21,9 @@ function InputGroup({ className, ...props }: React.ComponentProps<'div'>) {
'has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-3',
'has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3',
// Focus state.
'has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot=input-group-control]:focus-visible]:ring-[3px]',
// Focus state — soft multi-color glow matching ai-motion palette
'has-[[data-slot=input-group-control]:focus-visible]:border-blue-400/60',
'has-[[data-slot=input-group-control]:focus-visible]:shadow-[0_0_0_1px_rgba(57,182,255,0.2),0_0_8px_rgba(57,182,255,0.15),0_0_16px_rgba(189,69,251,0.1)]',
// Error state.
'has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[[data-slot][aria-invalid=true]]:border-destructive dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40',
@@ -0,0 +1,164 @@
import { MotionProps, motion, useInView } from 'motion/react'
import { useEffect, useMemo, useRef, useState } from 'react'
import { cn } from '@/lib/utils'
interface TypingAnimationProps extends MotionProps {
children?: string
words?: string[]
className?: string
duration?: number
typeSpeed?: number
deleteSpeed?: number
delay?: number
pauseDelay?: number
loop?: boolean
as?: React.ElementType
startOnView?: boolean
showCursor?: boolean
blinkCursor?: boolean
cursorStyle?: 'line' | 'block' | 'underscore'
}
export function TypingAnimation({
children,
words,
className,
duration = 100,
typeSpeed,
deleteSpeed,
delay = 0,
pauseDelay = 1000,
loop = false,
as: Component = 'span',
startOnView = true,
showCursor = true,
blinkCursor = true,
cursorStyle = 'line',
...props
}: TypingAnimationProps) {
const MotionComponent = motion.create(Component, {
forwardMotionProps: true,
})
const [displayedText, setDisplayedText] = useState<string>('')
const [currentWordIndex, setCurrentWordIndex] = useState(0)
const [currentCharIndex, setCurrentCharIndex] = useState(0)
const [phase, setPhase] = useState<'typing' | 'pause' | 'deleting'>('typing')
const elementRef = useRef<HTMLElement | null>(null)
const isInView = useInView(elementRef as React.RefObject<Element>, {
amount: 0.3,
once: true,
})
const wordsToAnimate = useMemo(() => words || (children ? [children] : []), [words, children])
const hasMultipleWords = wordsToAnimate.length > 1
const typingSpeed = typeSpeed || duration
const deletingSpeed = deleteSpeed || typingSpeed / 2
const shouldStart = startOnView ? isInView : true
useEffect(() => {
if (!shouldStart || wordsToAnimate.length === 0) return
const timeoutDelay =
delay > 0 && displayedText === ''
? delay
: phase === 'typing'
? typingSpeed
: phase === 'deleting'
? deletingSpeed
: pauseDelay
const timeout = setTimeout(() => {
const currentWord = wordsToAnimate[currentWordIndex] || ''
const graphemes = Array.from(currentWord)
switch (phase) {
case 'typing':
if (currentCharIndex < graphemes.length) {
setDisplayedText(graphemes.slice(0, currentCharIndex + 1).join(''))
setCurrentCharIndex(currentCharIndex + 1)
} else {
if (hasMultipleWords || loop) {
const isLastWord = currentWordIndex === wordsToAnimate.length - 1
if (!isLastWord || loop) {
setPhase('pause')
}
}
}
break
case 'pause':
setPhase('deleting')
break
case 'deleting':
if (currentCharIndex > 0) {
setDisplayedText(graphemes.slice(0, currentCharIndex - 1).join(''))
setCurrentCharIndex(currentCharIndex - 1)
} else {
const nextIndex = (currentWordIndex + 1) % wordsToAnimate.length
setCurrentWordIndex(nextIndex)
setPhase('typing')
}
break
}
}, timeoutDelay)
return () => clearTimeout(timeout)
}, [
shouldStart,
phase,
currentCharIndex,
currentWordIndex,
displayedText,
wordsToAnimate,
hasMultipleWords,
loop,
typingSpeed,
deletingSpeed,
pauseDelay,
delay,
])
const currentWordGraphemes = Array.from(wordsToAnimate[currentWordIndex] || '')
const isComplete =
!loop &&
currentWordIndex === wordsToAnimate.length - 1 &&
currentCharIndex >= currentWordGraphemes.length &&
phase !== 'deleting'
const shouldShowCursor =
showCursor &&
!isComplete &&
(hasMultipleWords || loop || currentCharIndex < currentWordGraphemes.length)
const getCursorChar = () => {
switch (cursorStyle) {
case 'block':
return '▌'
case 'underscore':
return '_'
case 'line':
default:
return '|'
}
}
return (
<MotionComponent
ref={elementRef}
className={cn('leading-[5rem] tracking-[-0.02em]', className)}
{...props}
>
{displayedText}
{shouldShowCursor && (
<span className={cn('inline-block', blinkCursor && 'animate-blink-cursor')}>
{getCursorChar()}
</span>
)}
</MotionComponent>
)
}
+4 -15
View File
@@ -71,7 +71,8 @@ async function exposeAgentToPage() {
try {
const { task, config } = payload
// create when used
// Dispose old instance before creating new one
multiPageAgent?.dispose()
multiPageAgent = new MultiPageAgent(config)
@@ -116,17 +117,6 @@ async function exposeAgentToPage() {
)
})
multiPageAgent.addEventListener('dispose', () => {
window.postMessage(
{
channel: 'PAGE_AGENT_EXT_RESPONSE',
id,
action: 'dispose_event',
},
'*'
)
})
// result
const result = await multiPageAgent.execute(task)
@@ -155,9 +145,8 @@ async function exposeAgentToPage() {
break
}
case 'dispose': {
// @note stop ongoing processes but can still be re-used later
multiPageAgent?.dispose()
case 'stop': {
multiPageAgent?.stop()
break
}
@@ -16,7 +16,6 @@ export interface ExecuteConfig {
onStatusChange?: (status: AgentStatus) => void
onActivity?: (activity: AgentActivity) => void
onHistoryUpdate?: (history: HistoricalEvent[]) => void
onDispose?: () => void
}
export default defineUnlistedScript(() => {
@@ -60,12 +59,6 @@ export default defineUnlistedScript(() => {
return
}
if (data.action === 'dispose_event' && config.onDispose) {
config.onDispose()
window.removeEventListener('message', handleMessage)
return
}
if (data.action !== 'execute_result') return
// execute_result
@@ -104,14 +97,14 @@ export default defineUnlistedScript(() => {
return promise
}
const dispose = () => {
const stop = () => {
const id = getId()
window.postMessage(
{
channel: 'PAGE_AGENT_EXT_REQUEST',
id,
action: 'dispose',
action: 'stop',
},
'*'
)
@@ -121,6 +114,6 @@ export default defineUnlistedScript(() => {
;(window as any).PAGE_AGENT_EXT = {
version: __EXT_VERSION__,
execute,
dispose,
stop,
}
})
@@ -15,7 +15,7 @@ import { ConfigPanel } from './components/ConfigPanel'
import { HistoryDetail } from './components/HistoryDetail'
import { HistoryList } from './components/HistoryList'
import { ActivityCard, EventCard } from './components/cards'
import { EmptyState, Logo, StatusDot } from './components/misc'
import { EmptyState, Logo, MotionOverlay, StatusDot } from './components/misc'
type View =
| { name: 'chat' }
@@ -117,14 +117,15 @@ export default function App() {
const showEmptyState = !currentTask && history.length === 0 && !isRunning
return (
<div className="flex flex-col h-screen bg-background">
<div className="relative flex flex-col h-screen bg-background">
<MotionOverlay active={isRunning} />
{/* Header */}
<header className="flex items-center justify-between border-b px-3 py-2">
<div className="flex items-center gap-2">
<Logo className="size-5" />
<span className="text-sm font-medium">Page Agent Ext</span>
</div>
<div className="flex items-center gap-3">
<div className="flex items-center gap-1">
<StatusDot status={status} />
<Button
variant="ghost"
@@ -1,15 +1,15 @@
import type { LLMConfig } from '@page-agent/llms'
import { Copy, CornerUpLeft, Eye, EyeOff, HatGlasses, Home, Loader2, Scale } from 'lucide-react'
import { useEffect, useState } from 'react'
import { siGithub } from 'simple-icons'
import { DEMO_API_KEY, DEMO_BASE_URL, DEMO_MODEL } from '@/agent/constants'
import type { ExtConfig, LanguagePreference } from '@/agent/useAgent'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
interface ConfigPanelProps {
config: LLMConfig | null
onSave: (config: LLMConfig) => Promise<void>
config: ExtConfig | null
onSave: (config: ExtConfig) => Promise<void>
onClose: () => void
}
@@ -17,6 +17,7 @@ export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) {
const [apiKey, setApiKey] = useState(config?.apiKey || DEMO_API_KEY)
const [baseURL, setBaseURL] = useState(config?.baseURL || DEMO_BASE_URL)
const [model, setModel] = useState(config?.model || DEMO_MODEL)
const [language, setLanguage] = useState<LanguagePreference>(config?.language)
const [saving, setSaving] = useState(false)
const [userAuthToken, setUserAuthToken] = useState<string>('')
const [copied, setCopied] = useState(false)
@@ -28,6 +29,7 @@ export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) {
setApiKey(config?.apiKey || DEMO_API_KEY)
setBaseURL(config?.baseURL || DEMO_BASE_URL)
setModel(config?.model || DEMO_MODEL)
setLanguage(config?.language)
}, [config])
// Poll for user auth token every second until found
@@ -65,7 +67,7 @@ export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) {
const handleSave = async () => {
setSaving(true)
try {
await onSave({ apiKey, baseURL, model })
await onSave({ apiKey, baseURL, model, language })
} finally {
setSaving(false)
}
@@ -165,6 +167,19 @@ export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) {
</div>
</div>
<div className="flex flex-col gap-1.5">
<label className="text-xs text-muted-foreground">Language</label>
<select
value={language ?? ''}
onChange={(e) => setLanguage((e.target.value || undefined) as LanguagePreference)}
className="h-8 text-xs rounded-md border border-input bg-background px-2 cursor-pointer"
>
<option value="">System</option>
<option value="en-US">English</option>
<option value="zh-CN">中文</option>
</select>
</div>
<div className="flex gap-2 mt-2">
<Button variant="outline" onClick={onClose} className="flex-1 h-8 text-xs cursor-pointer">
Cancel
@@ -1,5 +1,10 @@
import type { AgentStatus } from '@page-agent/core'
import { Motion } from 'ai-motion'
import { BookOpen, Globe } from 'lucide-react'
import { useEffect, useRef } from 'react'
import { siGithub } from 'simple-icons'
import { TypingAnimation } from '@/components/ui/typing-animation'
import { cn } from '@/lib/utils'
// Status dot indicator
@@ -19,7 +24,7 @@ export function StatusDot({ status }: { status: AgentStatus }) {
}[status]
return (
<div className="flex items-center gap-1.5">
<div className="flex items-center gap-1.5 mr-2">
<span
className={cn('size-2 rounded-full', colorClass, status === 'running' && 'animate-pulse')}
/>
@@ -32,14 +37,111 @@ export function Logo({ className }: { className?: string }) {
return <img src="/assets/page-agent-256.webp" alt="Page Agent" className={cn('', className)} />
}
// Empty state with logo
// Full-screen ai-motion glow overlay, shown only while running
export function MotionOverlay({ active }: { active: boolean }) {
const containerRef = useRef<HTMLDivElement>(null)
const motionRef = useRef<Motion | null>(null)
useEffect(() => {
const mode = document.documentElement.classList.contains('dark') ? 'dark' : 'light'
const motion = new Motion({
mode,
borderWidth: 4,
borderRadius: 14,
glowWidth: mode === 'dark' ? 120 : 60,
styles: { position: 'absolute', inset: '0' },
})
motionRef.current = motion
containerRef.current!.appendChild(motion.element)
motion.autoResize(containerRef.current!)
return () => {
motion.dispose()
motionRef.current = null
}
}, [])
useEffect(() => {
const motion = motionRef.current
if (!motion) return
let disposed = false
if (active) {
motion.start()
motion.fadeIn()
} else {
motion.fadeOut().then(() => !disposed && motion.pause())
}
return () => {
disposed = true
}
}, [active])
return (
<div
ref={containerRef}
className="pointer-events-none absolute inset-0 z-10 opacity-60"
style={{ display: active ? undefined : 'none' }}
/>
)
}
// Empty state with logo and breathing glow
export function EmptyState() {
return (
<div className="flex flex-col items-center justify-center h-full gap-3 text-center px-6">
<Logo className="size-20 opacity-80" />
<div className="flex flex-col items-center justify-center h-full gap-4 text-center px-6">
<div className="relative select-none pointer-events-none">
<div className="absolute inset-0 -m-6 rounded-full bg-[conic-gradient(from_180deg,oklch(0.55_0.2_280),oklch(0.5_0.15_230),oklch(0.6_0.18_310),oklch(0.55_0.2_280))] blur-2xl animate-[glow-a_5s_ease-in-out_infinite]" />
<div className="absolute inset-0 -m-6 rounded-full bg-[conic-gradient(from_0deg,oklch(0.55_0.18_160),oklch(0.5_0.2_200),oklch(0.6_0.15_120),oklch(0.55_0.18_160))] blur-2xl animate-[glow-b_5s_ease-in-out_infinite]" />
<Logo className="relative size-20 opacity-80" />
</div>
<div>
<h2 className="text-sm font-medium text-foreground">Page Agent Ext</h2>
<p className="text-xs text-muted-foreground mt-1">Enter a task to automate this page</p>
<h2 className="text-base font-medium text-foreground mb-1">Page Agent Ext</h2>
<TypingAnimation
className="text-sm text-muted-foreground"
words={[
'Enter a task to automate this page',
'Execute multi-page tasks',
'Call this extension from your web page',
'Use this extension in your own agents',
]}
cursorStyle="underscore"
loop
typeSpeed={20}
deleteSpeed={10}
pauseDelay={3000}
/>
</div>
<div className="flex items-center gap-3 mt-1 text-muted-foreground">
<a
href="https://github.com/alibaba/page-agent"
target="_blank"
rel="noopener noreferrer"
className="hover:text-foreground transition-colors"
title="GitHub"
>
<svg role="img" viewBox="0 0 24 24" className="size-4 fill-current">
<path d={siGithub.path} />
</svg>
</a>
<a
href="https://alibaba.github.io/page-agent/#/docs/features/chrome-extension"
target="_blank"
rel="noopener noreferrer"
className="hover:text-foreground transition-colors"
title="Documentation"
>
<BookOpen className="size-4" />
</a>
<a
href="https://alibaba.github.io/page-agent"
target="_blank"
rel="noopener noreferrer"
className="hover:text-foreground transition-colors"
title="Website"
>
<Globe className="size-4" />
</a>
</div>
</div>
)
+4 -4
View File
@@ -38,9 +38,9 @@ export default defineConfig({
},
}),
manifest: {
name: 'Page Agent Ext',
description:
'AI-powered browser automation assistant. Control web pages with natural language.',
default_locale: 'en',
name: '__MSG_extName__',
description: '__MSG_extDescription__',
homepage_url: 'https://alibaba.github.io/page-agent/',
permissions: ['tabs', 'tabGroups', 'sidePanel', 'storage'],
host_permissions: ['<all_urls>'],
@@ -48,7 +48,7 @@ export default defineConfig({
64: 'assets/page-agent-64.png',
},
action: {
default_title: 'Open Page Agent',
default_title: '__MSG_extActionTitle__',
},
web_accessible_resources: [
{
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@page-agent/llms",
"version": "1.2.0",
"version": "1.3.0",
"type": "module",
"main": "./dist/lib/page-agent-llms.js",
"module": "./dist/lib/page-agent-llms.js",
+4 -2
View File
@@ -54,8 +54,10 @@ export class OpenAIClient implements LLMClient {
signal: abortSignal,
})
} catch (error: unknown) {
console.error(error)
throw new InvokeError(InvokeErrorType.NETWORK_ERROR, 'Network request failed', error)
const isAbortError = (error as any)?.name === 'AbortError'
const errorMessage = isAbortError ? 'Network request aborted' : 'Network request failed'
if (!isAbortError) console.error(error)
throw new InvokeError(InvokeErrorType.NETWORK_ERROR, errorMessage, error)
}
// 3. Handle HTTP errors
+5 -2
View File
@@ -34,12 +34,15 @@ export class InvokeError extends Error {
super(message)
this.name = 'InvokeError'
this.type = type
this.retryable = this.isRetryable(type)
this.retryable = this.isRetryable(type, rawError)
this.rawError = rawError
this.rawResponse = rawResponse
}
private isRetryable(type: InvokeErrorType): boolean {
private isRetryable(type: InvokeErrorType, rawError?: unknown): boolean {
const isAbortError = (rawError as any)?.name === 'AbortError'
if (isAbortError) return false
const retryableTypes: InvokeErrorType[] = [
InvokeErrorType.NETWORK_ERROR,
InvokeErrorType.RATE_LIMIT,
+5 -5
View File
@@ -1,7 +1,7 @@
{
"name": "page-agent",
"private": false,
"version": "1.2.0",
"version": "1.3.0",
"type": "module",
"main": "./dist/esm/page-agent.js",
"module": "./dist/esm/page-agent.js",
@@ -44,10 +44,10 @@
"postpublish": "node -e \"['README.md','LICENSE'].forEach(f=>{try{require('fs').unlinkSync(f)}catch{}})\""
},
"dependencies": {
"@page-agent/core": "1.2.0",
"@page-agent/llms": "1.2.0",
"@page-agent/page-controller": "1.2.0",
"@page-agent/ui": "1.2.0",
"@page-agent/core": "1.3.0",
"@page-agent/llms": "1.3.0",
"@page-agent/page-controller": "1.3.0",
"@page-agent/ui": "1.3.0",
"chalk": "^5.6.2",
"zod": "^4.3.5"
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@page-agent/page-controller",
"version": "1.2.0",
"version": "1.3.0",
"type": "module",
"main": "./dist/lib/page-controller.js",
"module": "./dist/lib/page-controller.js",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@page-agent/ui",
"version": "1.2.0",
"version": "1.3.0",
"type": "module",
"main": "./dist/lib/page-agent-ui.js",
"module": "./dist/lib/page-agent-ui.js",
+2
View File
@@ -12,6 +12,7 @@ const enUS = {
question: 'Question: {{question}}',
waitingPlaceholder: 'Waiting for task to start...',
stop: 'Stop',
close: 'Close',
expand: 'Expand history',
collapse: 'Collapse history',
step: 'Step {{number}} · {{time}}{{duration}}',
@@ -59,6 +60,7 @@ const zhCN = {
question: '询问: {{question}}',
waitingPlaceholder: '等待任务开始...',
stop: '终止',
close: '关闭',
expand: '展开历史',
collapse: '收起历史',
step: '步骤 {{number}} · {{time}}{{duration}}',
+22 -9
View File
@@ -33,7 +33,7 @@ export class Panel {
#statusText: HTMLElement
#historySection: HTMLElement
#expandButton: HTMLElement
#stopButton: HTMLElement
#actionButton: HTMLElement
#inputSection: HTMLElement
#taskInput: HTMLInputElement
@@ -76,7 +76,7 @@ export class Panel {
this.#statusText = this.#wrapper.querySelector(`.${styles.statusText}`)!
this.#historySection = this.#wrapper.querySelector(`.${styles.historySection}`)!
this.#expandButton = this.#wrapper.querySelector(`.${styles.expandButton}`)!
this.#stopButton = this.#wrapper.querySelector(`.${styles.stopButton}`)!
this.#actionButton = this.#wrapper.querySelector(`.${styles.stopButton}`)!
this.#inputSection = this.#wrapper.querySelector(`.${styles.inputSectionWrapper}`)!
this.#taskInput = this.#wrapper.querySelector(`.${styles.taskInput}`)!
@@ -105,6 +105,15 @@ export class Panel {
status === 'running' ? 'thinking' : status === 'idle' ? 'thinking' : status
this.#updateStatusIndicator(indicatorType)
// Morph action button: running = stop (■), not running = close (X)
if (status === 'running') {
this.#actionButton.textContent = '■'
this.#actionButton.title = this.#i18n.t('ui.panel.stop')
} else {
this.#actionButton.textContent = 'X'
this.#actionButton.title = this.#i18n.t('ui.panel.close')
}
// Show/hide based on status
if (status === 'running') {
this.show()
@@ -266,10 +275,14 @@ export class Panel {
}
/**
* Stop Agent
* Action button handler: stop when running, close (dispose) when idle
*/
#stopAgent(): void {
this.#agent.dispose()
#handleActionButton(): void {
if (this.#agent.status === 'running') {
this.#agent.stop()
} else {
this.#agent.dispose()
}
}
/**
@@ -383,7 +396,7 @@ export class Panel {
<button class="${styles.controlButton} ${styles.expandButton}" title="${this.#i18n.t('ui.panel.expand')}">
</button>
<button class="${styles.controlButton} ${styles.stopButton}" title="${this.#i18n.t('ui.panel.stop')}">
<button class="${styles.controlButton} ${styles.stopButton}" title="${this.#i18n.t('ui.panel.close')}">
X
</button>
</div>
@@ -420,10 +433,10 @@ export class Panel {
this.#toggle()
})
// Stop button
this.#stopButton.addEventListener('click', (e) => {
// Action button (stop / close)
this.#actionButton.addEventListener('click', (e) => {
e.stopPropagation()
this.#stopAgent()
this.#handleActionButton()
})
// Submit on Enter key in input field
+4 -1
View File
@@ -68,6 +68,9 @@ export interface PanelAgentAdapter extends EventTarget {
/** Execute a task */
execute(task: string): Promise<unknown>
/** Dispose the agent */
/** Stop the current task (agent remains reusable) */
stop(): void
/** Dispose the agent (terminal, cannot be reused) */
dispose(): void
}
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@page-agent/website",
"private": true,
"version": "1.2.0",
"version": "1.3.0",
"type": "module",
"scripts": {
"dev": "vite --host 0.0.0.0",
+2 -2
View File
@@ -1,8 +1,8 @@
// Demo build (auto-init with demo LLM, for quick testing)
export const CDN_DEMO_URL =
'https://cdn.jsdelivr.net/npm/page-agent@1.2.0/dist/iife/page-agent.demo.js'
'https://cdn.jsdelivr.net/npm/page-agent@1.3.0/dist/iife/page-agent.demo.js'
export const CDN_DEMO_CN_URL =
'https://registry.npmmirror.com/page-agent/1.2.0/files/dist/iife/page-agent.demo.js'
'https://registry.npmmirror.com/page-agent/1.3.0/files/dist/iife/page-agent.demo.js'
// Demo LLM for website testing
export const DEMO_MODEL = 'PAGE-AGENT-FREE-TESTING-RANDOM'
@@ -1,11 +1,13 @@
import { siGithub } from 'simple-icons'
import { siChromewebstore, siGithub } from 'simple-icons'
import BetaNotice from '@/components/BetaNotice'
import CodeEditor from '@/components/CodeEditor'
import { useLanguage } from '@/i18n/context'
export default function ChromeExtension() {
const { isZh } = useLanguage()
const chromeWebStoreUrl =
'https://chromewebstore.google.com/detail/page-agent-ext/akldabonmimlicnjlflnapfeklbfemhj'
const githubReleasesUrl = 'https://github.com/alibaba/page-agent/releases'
return (
<div>
@@ -13,70 +15,92 @@ export default function ChromeExtension() {
<p className="text-xl text-gray-600 dark:text-gray-300 mb-8 leading-relaxed">
{isZh
? '可选的 Chrome 扩展,解锁多页任务和第三方 API 集成。'
: 'Optional Chrome extension that unlocks multi-page tasks and third-party API integration.'}
? '可选的 Chrome 扩展。PageAgent.js 继续负责页面内自动化;扩展 API 额外提供多页面任务、浏览器级控制,以及从浏览器外部发起任务的能力。'
: 'An optional Chrome extension. PageAgent.js keeps handling in-page automation, while the extension API adds multi-page tasks, browser-level control, and tasks initiated from outside the browser.'}
</p>
<BetaNotice />
<div className="space-y-8 mt-8">
{/* Hero Section */}
<section className="p-6 bg-linear-to-r from-blue-50 to-purple-50 dark:from-blue-900/20 dark:to-purple-900/20 rounded-xl">
<div className="flex items-start gap-4">
<div>
<p className="text-gray-600 dark:text-gray-300">
{isZh
? '解锁多页任务!借助 Chrome 扩展,Agent 可以跨标签页和页面导航,突破单页限制。'
: 'Unlock multi-page tasks! With the Chrome extension, your agent can navigate across tabs and pages, breaking the single-page limitation.'}
</p>
</div>
</div>
</section>
{/* Features */}
<section>
<h2 className="text-2xl font-bold mb-4">{isZh ? '核心特性' : 'Key Features'}</h2>
<div className="grid md:grid-cols-2 gap-4">
<div className="grid md:grid-cols-3 gap-4">
<div className="p-4 bg-gray-50 dark:bg-gray-800 rounded-lg">
<h3 className="font-semibold mb-2">🔓 {isZh ? '多页任务' : 'Multi-Page Tasks'}</h3>
<p className="text-gray-600 dark:text-gray-300 text-sm">
{isZh
? '跨多个页面和标签页执行任务,不再限于单页操作。'
: 'Execute tasks across multiple pages and tabs. No longer limited to single-page operations.'}
? '跨多个页面和标签页连续执行任务,不再限于单页上下文。'
: 'Run tasks across multiple pages and tabs without being limited to a single page context.'}
</p>
</div>
<div className="p-4 bg-gray-50 dark:bg-gray-800 rounded-lg">
<h3 className="font-semibold mb-2">
🔌 {isZh ? '开放第三方接口' : 'Third-Party API'}
🧭 {isZh ? '浏览器级控制' : 'Browser-Level Control'}
</h3>
<p className="text-gray-600 dark:text-gray-300 text-sm">
{isZh
? '用户授权后,你的网页、本地 Agent 或云端 Agent 都能通过扩展操作用户浏览器!'
: 'After user authorization, your webpage, local agent, or cloud agent can control the browser through the extension.'}
? '支持跨标签导航、页面切换和更完整的浏览器自动化能力。'
: 'Enable richer browser automation, including cross-tab navigation and page switching.'}
</p>
</div>
<div className="p-4 bg-gray-50 dark:bg-gray-800 rounded-lg">
<h3 className="font-semibold mb-2">
🔌 {isZh ? '开放集成接口' : 'Open Integration API'}
</h3>
<p className="text-gray-600 dark:text-gray-300 text-sm">
{isZh
? '用户主动授权后,页面 JS、本地 Agent 或云端 Agent 可通过扩展发起多页面任务。'
: 'With explicit user authorization, page JS, local agents, or cloud agents can trigger multi-page tasks through the extension.'}
</p>
</div>
</div>
</section>
{/* Download */}
{/* Install */}
<section>
<h2 className="text-2xl font-bold mb-4">{isZh ? '下载测试版' : 'Download Beta'}</h2>
<p className="text-gray-600 dark:text-gray-300 mb-4">
{isZh
? '扩展目前处于 Beta 阶段,请从 GitHub Releases 下载最新版本。'
: 'The extension is currently in beta. Download the latest version from GitHub Releases.'}
</p>
<a
href="https://github.com/alibaba/page-agent/releases"
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-2 px-6 py-3 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-lg transition-colors"
>
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
<path d={siGithub.path} />
</svg>
{isZh ? '前往 GitHub Releases 下载' : 'Download from GitHub Releases'}
</a>
<h2 className="text-2xl font-bold mb-4">{isZh ? '获取扩展' : 'Get the Extension'}</h2>
<div className="flex flex-wrap gap-3">
<a
href={chromeWebStoreUrl}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-2 px-6 py-3 bg-blue-600 hover:bg-blue-700 text-white! font-medium rounded-lg transition-colors"
>
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
<path d={siChromewebstore.path} />
</svg>
{isZh ? '从 Chrome 应用商店安装' : 'Install from Chrome Web Store'}
</a>
<a
href={githubReleasesUrl}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-2 px-6 py-3 bg-gray-900 hover:bg-gray-800 dark:bg-gray-700 dark:hover:bg-gray-600 text-white! font-medium rounded-lg transition-colors"
>
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
<path d={siGithub.path} />
</svg>
{isZh ? 'GitHub Releases(更新版本)' : 'GitHub Releases (faster updates)'}
</a>
</div>
</section>
{/* Relationship with PageAgent.js */}
<section>
<h2 className="text-2xl font-bold mb-4">
{isZh ? '与 PageAgent.js 的关系' : 'How It Relates to PageAgent.js'}
</h2>
<div className="p-5 bg-gray-50 dark:bg-gray-800 rounded-lg space-y-3 text-gray-600 dark:text-gray-300">
<p>
{isZh
? 'PageAgent.js 本身即可在页面内完成自动化。Chrome 扩展是可选的能力扩展。'
: 'PageAgent.js already works for in-page automation. The Chrome extension is optional, not a dependency.'}
</p>
<p>
{isZh
? '通过扩展,你可以执行多页面任务、控制浏览器,以及从浏览器外部(本地服务或云端服务)发起任务。'
: 'With the extension, you can perform multi-page tasks, browser-level control, and tasks triggered outside the browser (local or cloud services).'}
</p>
</div>
</section>
{/* Third-party Integration */}
@@ -86,32 +110,33 @@ export default function ChromeExtension() {
</h2>
<p className="text-gray-600 dark:text-gray-300 mb-4">
{isZh
? '用户授权后,外部应用可以调用扩展 API 来控制浏览器。'
: 'After user authorization, external applications can call the extension API to control the browser.'}
? '通过页面 JavaScript 调用 `window.PAGE_AGENT_EXT`,你的应用可以发起跨页面任务并控制浏览器行为。'
: 'By calling `window.PAGE_AGENT_EXT` from page JavaScript, your app can trigger multi-page tasks and control browser behavior.'}
</p>
{/* Auth Flow */}
<h3 className="text-xl font-semibold mb-3">{isZh ? '授权流程' : 'Authorization Flow'}</h3>
<h3 className="text-xl font-semibold mb-3">
{isZh ? '授权与安全' : 'Authorization and Security'}
</h3>
<p className="text-gray-600 dark:text-gray-300 mb-4">
{isZh
? '扩展使用基于 Token 的授权机制,扩展端和页面端必须持有匹配的 Token。'
: 'The extension uses a token-based authorization mechanism. Both extension and page must have matching tokens.'}
? '扩展权限范围较广(例如页面访问、导航、多标签控制)。若被滥用,可能危害用户隐私。为此,调用能力由 Token 保护,用户必须主动将 Token 提供给其信任的应用。'
: 'The extension has broad permissions (such as page access, navigation, and multi-tab control). If abused, it can harm user privacy. That is why access is protected by a token, and users must actively share the token only with applications they trust.'}
</p>
<CodeEditor
code={
isZh
? `// 1. 用户安装扩展并在扩展设置中配置 auth token
// 2. 你的页面读取相同的 token 并存入 localStorage
// 3. Token 匹配后,扩展会暴露 window.PAGE_AGENT_EXT 对象
? `// 1) 用户在扩展侧边栏获取 auth token
// 2) 仅在可信应用中设置该 token
// 3) token 匹配后,扩展会暴露 window.PAGE_AGENT_EXT
// ⚠️ 请在扩展弹窗中查看你的 auth token,然后填入下方
// ⚠️ 不要把 token 提供给不可信页面或脚本
localStorage.setItem('PageAgentExtUserAuthToken', '<从扩展中获取的-token>')`
: `// 1. User installs extension and sets an auth token in extension settings
// 2. Your page reads the same token and stores it in localStorage
// 3. After token match, extension exposes window.PAGE_AGENT_EXT object
: `// 1) Get auth token from the extension side panel
// 2) Set it only in trusted applications
// 3) After token match, extension exposes window.PAGE_AGENT_EXT
// ⚠️ Check your extension popup for the auth token
// ⚠️ Never provide the token to untrusted pages or scripts
localStorage.setItem('PageAgentExtUserAuthToken', '<your-token-from-extension>')`
}
language="javascript"
@@ -139,26 +164,90 @@ localStorage.setItem('PageAgentExtUserAuthToken', '<your-token-from-extension>')
rel="noopener noreferrer"
className="block text-sm text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300 hover:underline"
>
📄 {isZh ? '英文版 API 文档' : 'API Documentation (English)'}
</a>
<a
href="https://github.com/alibaba/page-agent/blob/main/packages/extension/docs/extension_api_zh.md"
target="_blank"
rel="noopener noreferrer"
className="block text-sm text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300 hover:underline"
>
📄 {isZh ? '中文版 API 文档' : 'API Documentation (Chinese)'}
📄 {isZh ? 'API 文档' : 'API Documentation'}
</a>
</div>
</section>
<h3 className="text-xl font-semibold my-3">PAGE_AGENT_EXT.execute(task, config)</h3>
{/* TypeScript Declaration */}
<h2 className="text-2xl font-bold mb-4">
{isZh ? 'TypeScript 类型声明' : 'TypeScript Declaration'}
</h2>
<p className="text-gray-600 dark:text-gray-300 mb-4">
{isZh
? '使用配置执行任务。返回一个 Promise,在任务完成时 resolve。config 参数包含 LLM 设置、选项和事件回调。'
: 'Execute a task with configuration. Returns a Promise that resolves when the task completes. Config includes LLM settings, options, and event callbacks.'}
? '推荐把 `execute` 的类型声明加入你的项目,获得完整类型提示。'
: 'Add this `execute` declaration to your project for full type support.'}
</p>
<CodeEditor
code={
isZh
? `import type {
AgentActivity,
AgentStatus,
ExecutionResult,
HistoricalEvent
} from '@page-agent/core'
interface ExecuteConfig {
baseURL: string // LLM API 端点
apiKey: string // API 密钥
model: string // 模型名称
includeInitialTab?: boolean
onStatusChange?: (status: AgentStatus) => void
onActivity?: (activity: AgentActivity) => void
onHistoryUpdate?: (history: HistoricalEvent[]) => void
}
type Execute = (task: string, config: ExecuteConfig) => Promise<ExecutionResult>
declare global {
interface Window {
PAGE_AGENT_EXT_VERSION?: string
PAGE_AGENT_EXT?: {
version: string
execute: Execute
stop: () => void
}
}
}`
: `import type {
AgentActivity,
AgentStatus,
ExecutionResult,
HistoricalEvent
} from '@page-agent/core'
interface ExecuteConfig {
baseURL: string // LLM API endpoint
apiKey: string // API key
model: string // Model name
includeInitialTab?: boolean
onStatusChange?: (status: AgentStatus) => void
onActivity?: (activity: AgentActivity) => void
onHistoryUpdate?: (history: HistoricalEvent[]) => void
}
type Execute = (task: string, config: ExecuteConfig) => Promise<ExecutionResult>
declare global {
interface Window {
PAGE_AGENT_EXT_VERSION?: string
PAGE_AGENT_EXT?: {
version: string
execute: Execute
stop: () => void
}
}
}`
}
language="typescript"
/>
<h3 className="text-xl font-semibold mt-6 mb-3">PAGE_AGENT_EXT.execute(task, config)</h3>
<CodeEditor
code={
isZh
@@ -168,12 +257,11 @@ const result = await window.PAGE_AGENT_EXT.execute(
{
baseURL: 'https://api.openai.com/v1',
apiKey: 'your-api-key',
model: 'gpt-5-2',
model: 'gpt-5.2',
// includeInitialTab: false, // 设为 false 排除初始标签页
onStatusChange: status => console.log('状态变化:', status),
onActivity: activity => console.log('活动:', activity),
onHistoryUpdate: history => console.log('历史更新:', history),
onDispose: () => console.log('已停止')
onHistoryUpdate: history => console.log('历史更新:', history)
}
)
@@ -184,12 +272,11 @@ const result = await window.PAGE_AGENT_EXT.execute(
{
baseURL: 'https://api.openai.com/v1',
apiKey: 'your-api-key',
model: 'gpt-5-2',
model: 'gpt-5.2',
// includeInitialTab: false, // Set to false to exclude initial tab
onStatusChange: status => console.log('Status change:', status),
onActivity: activity => console.log('Activity:', activity),
onHistoryUpdate: history => console.log('History update:', history),
onDispose: () => console.log('Disposed')
onHistoryUpdate: history => console.log('History update:', history)
}
)
@@ -198,130 +285,47 @@ console.log(result) // Task execution result`
language="javascript"
/>
<h3 className="text-xl font-semibold mt-6 mb-3">PAGE_AGENT_EXT.dispose()</h3>
<h3 className="text-xl font-semibold mt-6 mb-3">PAGE_AGENT_EXT.stop()</h3>
<p className="text-gray-600 dark:text-gray-300 mb-4">
{isZh
? '停止当前正在运行的任务。停止后 Agent 可以重新使用。'
: 'Stop the current running task. The agent can be reused after disposal.'}
{isZh ? '停止当前正在运行的任务。' : 'Stop the current running task.'}
</p>
<CodeEditor
code={
isZh
? `// 停止当前任务
window.PAGE_AGENT_EXT.dispose()`
window.PAGE_AGENT_EXT.stop()`
: `// Stop current task execution
window.PAGE_AGENT_EXT.dispose()`
window.PAGE_AGENT_EXT.stop()`
}
language="javascript"
/>
</section>
{/* ExecuteConfig */}
<section>
<h2 className="text-2xl font-bold mb-4">{isZh ? '执行配置' : 'Execute Configuration'}</h2>
<p className="text-gray-600 dark:text-gray-300 mb-4">
{isZh
? 'config 参数包含 LLM 设置、选项和事件回调,用于控制任务执行行为。'
: 'The config parameter includes LLM settings, options, and event callbacks to control task execution behavior.'}
</p>
<CodeEditor
code={
isZh
? `interface ExecuteConfig {
baseURL: string // LLM API 端点
apiKey: string // API 密钥
model: string // 模型名称
// 是否将初始标签页包含在任务中,默认 true
includeInitialTab?: boolean
// Agent 状态变化时调用(idle, running, error, completed 等)
onStatusChange?: (status: AgentStatus) => void
// Agent 执行活动时调用(如点击、输入、导航等操作)
onActivity?: (activity: AgentActivity) => void
// 历史记录更新时调用(包含完整的事件历史)
onHistoryUpdate?: (history: HistoricalEvent[]) => void
// Agent 被停止时调用
onDispose?: () => void
}`
: `interface ExecuteConfig {
baseURL: string // LLM API endpoint
apiKey: string // API key
model: string // Model name
// Whether to include the initial tab in the task, default true
includeInitialTab?: boolean
// Called when agent status changes (idle, running, error, completed, etc.)
onStatusChange?: (status: AgentStatus) => void
// Called when agent performs an activity (click, input, navigation, etc.)
onActivity?: (activity: AgentActivity) => void
// Called when history is updated (contains full event history)
onHistoryUpdate?: (history: HistoricalEvent[]) => void
// Called when agent is disposed
onDispose?: () => void
}`
}
language="typescript"
/>
</section>
{/* Security Notice */}
<section className="p-4 bg-yellow-50 dark:bg-yellow-900/20 rounded-lg">
<h3 className="text-lg font-semibold text-yellow-900 dark:text-yellow-300 mb-2">
{isZh ? '安全须知' : 'Security Notes'}
</h3>
<ul className="text-gray-600 dark:text-gray-300 space-y-1 text-sm">
<li>
{' '}
{isZh
? '用户必须在扩展设置中显式授权每个域名'
: 'Users must explicitly authorize each domain in extension settings'}
</li>
<li>
{' '}
{isZh
? '生产环境建议使用后端代理 LLM API Key'
: 'Consider using backend proxy for LLM API keys in production'}
</li>
</ul>
</section>
{/* Integration Guide */}
<section>
<h2 className="text-2xl font-bold mb-4">
{isZh
? '将 MultiPageAgent 融入你自己的插件'
? '将 MultiPageAgent 集成你自己的插件'
: 'Integrate MultiPageAgent into Your Extension'}
</h2>
<p>@TODO</p>
<p className="text-gray-600 dark:text-gray-300 mb-4">
{isZh
? '你可以将 MultiPageAgent 集成到自己的浏览器扩展中,实现跨页面的 AI 自动化能力。'
: 'You can integrate MultiPageAgent into your own browser extension for cross-page AI automation capabilities.'}
? '建议先阅读扩展 API 文档,再参考 background entry implementation。'
: 'Start with the extension API docs, then use the background entry implementation as a reference.'}
<a
href="https://github.com/alibaba/page-agent/blob/main/packages/extension/src/entrypoints/background.ts"
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-2 text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
>
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
<path d={siGithub.path} />
</svg>
packages/extension/src/entrypoints/background.ts
</a>
</p>
<p className="text-gray-600 dark:text-gray-300 mb-4">TODO</p>
<p className="text-gray-600 dark:text-gray-300 mb-4">
{isZh ? '参考源码实现:' : 'Reference implementation:'}
</p>
<a
href="https://github.com/alibaba/page-agent/blob/main/packages/extension/src/entrypoints/background.ts"
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-2 text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
>
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
<path d={siGithub.path} />
</svg>
packages/extension/src/entrypoints/background.ts
</a>
</section>
</div>
</div>
@@ -1,3 +1,5 @@
import { Link } from 'wouter'
import { useLanguage } from '@/i18n/context'
export default function LimitationsPage() {
@@ -11,184 +13,144 @@ export default function LimitationsPage() {
</h1>
<p className="text-xl text-gray-600 dark:text-gray-300">
{isZh
? '了解 page-agent 当前的功能边界和技术限制'
: "Understand page-agent's current capabilities and technical constraints"}
? 'Page Agent 基于 DOM 理解网页并执行操作。这决定了它的能力边界。'
: 'Page Agent understands web pages via DOM and performs actions accordingly. This defines its capability boundary.'}
</p>
</div>
<div className="prose prose-lg dark:prose-invert max-w-none">
{/* PageAgent.js vs PageAgentExt */}
<h2 className="text-2xl font-bold mb-3">
{isZh ? '页面支持限制' : 'Page Support Limitations'}
{isZh ? 'PageAgent.js vs PageAgentExt' : 'PageAgent.js vs PageAgentExt'}
</h2>
<div className="bg-blue-50 dark:bg-blue-900/20 border-l-4 border-blue-400 p-4 mb-6">
<h3 className="font-semibold text-blue-800 dark:text-blue-200 mb-2">
{isZh ? '单页应用限制' : 'Single Page Application Limits'}
</h3>
<ul className="text-blue-700 dark:text-blue-300 space-y-2">
<li>
{isZh
? '• 仅支持单页应用(SPA):目前只能在单个页面内进行操作'
: '• SPA only: Currently operates within a single page'}
</li>
<li>
{isZh
? '• 多页接力功能正在设计中:暂时无法跨页面执行连续任务'
: '• Multi-page relay in design: Cannot execute continuous tasks across pages yet'}
</li>
<li>
{isZh
? '• 无法操作未接入该能力的网站:需要目标网站主动集成 page-agent'
: '• Requires integration: Cannot operate on sites without page-agent'}
</li>
</ul>
<p className="text-gray-600 dark:text-gray-300 mb-4">
{isZh
? 'PageAgent.js 是核心库,运行在页面内。PageAgentExt 是可选的浏览器扩展,提供额外的浏览器级控制能力。'
: 'PageAgent.js is the core library running inside a page. PageAgentExt is an optional browser extension that adds browser-level control.'}
</p>
<div className="overflow-x-auto mb-6">
<table className="w-full text-sm border-collapse">
<thead>
<tr className="border-b border-gray-200 dark:border-gray-700">
<th className="text-left py-3 pr-4"></th>
<th className="text-left py-3 px-4 font-semibold">PageAgent.js</th>
<th className="text-left py-3 pl-4 font-semibold">
PageAgentExt{' '}
<Link
href="/docs/features/chrome-extension"
className="text-xs font-normal text-blue-600 dark:text-blue-400 hover:underline"
>
{isZh ? '了解更多' : 'learn more'}
</Link>
</th>
</tr>
</thead>
<tbody className="text-gray-600 dark:text-gray-300">
<tr className="border-b border-gray-100 dark:border-gray-800">
<td className="py-3 pr-4 font-medium text-gray-900 dark:text-white">
{isZh ? '接入方式' : 'Integration'}
</td>
<td className="py-3 px-4">
{isZh ? '网站开发者主动集成' : 'Site developer integrates the library'}
</td>
<td className="py-3 pl-4">
{isZh ? '用户安装浏览器扩展' : 'User installs a browser extension'}
</td>
</tr>
<tr className="border-b border-gray-100 dark:border-gray-800">
<td className="py-3 pr-4 font-medium text-gray-900 dark:text-white">
{isZh ? '可操作范围' : 'Scope'}
</td>
<td className="py-3 px-4">
{isZh ? '当前页面(为 SPA 设计)' : 'Current page (designed for SPAs)'}
</td>
<td className="py-3 pl-4">
{isZh ? '任意网页、多标签页' : 'Any web page, multi-tab'}
</td>
</tr>
<tr>
<td className="py-3 pr-4 font-medium text-gray-900 dark:text-white">
{isZh ? '额外能力' : 'Extra capabilities'}
</td>
<td className="py-3 px-4"></td>
<td className="py-3 pl-4">
{isZh ? '新建/切换/关闭标签页' : 'Open / switch / close tabs'}
</td>
</tr>
</tbody>
</table>
</div>
{/* Interaction Limitations */}
<h2 className="text-2xl font-bold mb-3">
{isZh ? '交互行为限制' : 'Interaction Limitations'}
{isZh ? '交互能力' : 'Interaction Capabilities'}
</h2>
<div className="bg-gray-50 dark:bg-gray-800 rounded-lg p-6 mb-6">
<h3 className="font-semibold mb-4">{isZh ? '支持的操作' : 'Supported Operations'}</h3>
<div className="grid md:grid-cols-2 gap-4 mb-6">
<div className="space-y-2">
<div className="flex items-center text-green-600 dark:text-green-400">
<span className="mr-2"></span>
<span>{isZh ? '点击操作' : 'Click'}</span>
</div>
<div className="flex items-center text-green-600 dark:text-green-400">
<span className="mr-2"></span>
<span>{isZh ? '文本输入' : 'Text input'}</span>
</div>
<div className="flex items-center text-green-600 dark:text-green-400">
<span className="mr-2"></span>
<span>{isZh ? '页面滚动' : 'Scroll'}</span>
</div>
<div className="flex items-center text-green-600 dark:text-green-400">
<span className="mr-2"></span>
<span>{isZh ? '表单提交' : 'Form submit'}</span>
</div>
</div>
<div className="space-y-2">
<div className="flex items-center text-green-600 dark:text-green-400">
<span className="mr-2"></span>
<span>{isZh ? '选择操作' : 'Select'}</span>
</div>
<div className="flex items-center text-green-600 dark:text-green-400">
<span className="mr-2"></span>
<span>{isZh ? '焦点切换' : 'Focus'}</span>
</div>
</div>
</div>
<h3 className="font-semibold mb-4">{isZh ? '不支持的操作' : 'Unsupported Operations'}</h3>
<div className="grid md:grid-cols-2 gap-4">
<div className="space-y-2">
<div className="flex items-center text-red-600 dark:text-red-400">
<span className="mr-2"></span>
<span>{isZh ? '鼠标悬停(hover' : 'Mouse hover'}</span>
</div>
<div className="flex items-center text-red-600 dark:text-red-400">
<span className="mr-2"></span>
<span>{isZh ? '拖拽操作' : 'Drag & drop'}</span>
</div>
<div className="flex items-center text-red-600 dark:text-red-400">
<span className="mr-2"></span>
<span>{isZh ? '右键菜单' : 'Right-click menu'}</span>
</div>
</div>
<div className="space-y-2">
<div className="flex items-center text-red-600 dark:text-red-400">
<span className="mr-2"></span>
<span>{isZh ? '图形绘制' : 'Drawing'}</span>
</div>
<div className="flex items-center text-red-600 dark:text-red-400">
<span className="mr-2"></span>
<span>{isZh ? '键盘快捷键' : 'Keyboard shortcuts'}</span>
</div>
<div className="flex items-center text-red-600 dark:text-red-400">
<span className="mr-2"></span>
<span>{isZh ? '基于点击区域或鼠标位置的控制' : 'Position-based control'}</span>
</div>
</div>
</div>
</div>
<h2 className="text-2xl font-bold mb-3">
{isZh ? '网页理解限制' : 'Understanding Limitations'}
</h2>
<div className="bg-red-50 dark:bg-red-900/20 border-l-4 border-red-400 p-4 mb-6">
<h3 className="font-semibold text-red-800 dark:text-red-200 mb-2">
{isZh ? '无视觉能力' : 'No Visual Recognition'}
</h3>
<p className="text-red-700 dark:text-red-300 mb-3">
{isZh
? 'page-agent 基于 DOM 结构进行理解和操作,没有视觉识别能力,无法理解以下内容:'
: 'page-agent operates based on DOM structure with no visual recognition. Cannot understand:'}
</p>
<ul className="text-red-700 dark:text-red-300 space-y-1">
<li>
{isZh
? '• 图片内容:无法识别图片中的文字、图标或视觉元素'
: '• Image content: Cannot recognize text, icons, or visual elements in images'}
</li>
<li>
{isZh
? '• Canvas 画布:无法理解 Canvas 中绘制的图形和内容'
: '• Canvas: Cannot understand graphics drawn on Canvas'}
</li>
<li>
{isZh
? '• WebGL 3D 内容:无法操作 3D 场景中的元素'
: '• WebGL 3D: Cannot operate elements in 3D scenes'}
</li>
<li>
{isZh
? '• SVG 图形:无法理解 SVG 中的视觉内容和路径'
: '• SVG graphics: Cannot understand visual content and paths in SVG'}
</li>
</ul>
</div>
<h2 className="text-2xl font-bold mb-3">
{isZh ? '被操作网站要求' : 'Website Requirements'}
</h2>
<div className="bg-gray-50 dark:bg-gray-800 rounded-lg p-6 mb-6">
<div className="space-y-4">
<div className="grid md:grid-cols-2 gap-6">
<div>
<h3 className="font-semibold mb-2">
{isZh ? '语义化和易用性' : 'Semantic & Usability'}
<h3 className="font-semibold mb-3 text-green-700 dark:text-green-400">
{isZh ? '支持' : 'Supported'}
</h3>
<p className="text-gray-600 dark:text-gray-300">
{isZh
? '所有操作都基于 DOM 元素的语义化标签和属性。如果页面结构不够语义化,或者没有任何 accessibility 特性,可能影响 AI 的理解准确性。'
: 'All operations rely on semantic tags and attributes. Poor semantic structure or lack of accessibility features may affect AI understanding accuracy.'}
</p>
<ul className="space-y-1.5 text-sm">
{[
isZh ? '点击、文本输入、选择' : 'Click, text input, select',
isZh ? '页面滚动(垂直 / 水平)' : 'Scroll (vertical / horizontal)',
isZh ? '表单提交、焦点切换' : 'Form submit, focus',
isZh ? '执行 JavaScript(可选)' : 'Execute JavaScript (opt-in)',
].map((text) => (
<li key={text} className="flex items-center text-gray-700 dark:text-gray-300">
<span className="mr-2 text-green-600 dark:text-green-400"></span>
{text}
</li>
))}
</ul>
</div>
<div>
<h3 className="font-semibold mb-2">{isZh ? 'UI/UX' : 'UI/UX'}</h3>
<p className="text-gray-600 dark:text-gray-300">
{isZh
? '反常识的交互规则、基于视觉的操作提示、复杂的鼠标交互、快速出现快速消失的元素等,都会影响 AI 的理解和操作。'
: 'Counter-intuitive interaction rules, visual-only operation hints, complex mouse interactions, or rapidly appearing/disappearing elements can affect AI understanding and operation.'}
</p>
</div>
<div>
<h3 className="font-semibold mb-2">{isZh ? '环境要求' : 'Environment'}</h3>
<p className="text-gray-600 dark:text-gray-300">modern browser</p>
<h3 className="font-semibold mb-3 text-red-700 dark:text-red-400">
{isZh ? '不支持' : 'Not supported'}
</h3>
<ul className="space-y-1.5 text-sm">
{[
isZh ? '悬停、拖拽、右键菜单' : 'Hover, drag & drop, right-click',
isZh ? '键盘快捷键' : 'Keyboard shortcuts',
isZh ? '坐标定位操作' : 'Position-based control',
isZh ? '绘图操作' : 'Drawing',
].map((text) => (
<li key={text} className="flex items-center text-gray-700 dark:text-gray-300">
<span className="mr-2 text-red-600 dark:text-red-400"></span>
{text}
</li>
))}
</ul>
</div>
</div>
</div>
<h2>{isZh ? '未来规划' : 'Future Plans'}</h2>
<div className="bg-green-50 dark:bg-green-900/20 border-l-4 border-green-400 p-4">
<h3 className="font-semibold text-green-800 dark:text-green-200 mb-2">
{isZh ? '即将支持' : 'Coming Soon'}
</h3>
<ul className="text-green-700 dark:text-green-300 space-y-1">
<li>{isZh ? '• 多页面接力操作能力' : '• Multi-page relay capabilities'}</li>
<li>{isZh ? '• 更丰富的鼠标交互支持' : '• Richer mouse interaction support'}</li>
<li>{isZh ? '• 基础的视觉理解能力' : '• Basic visual understanding'}</li>
<li>{isZh ? '• 更智能的错误恢复机制' : '• Smarter error recovery'}</li>
</ul>
{/* Understanding Limitations */}
<h2 className="text-2xl font-bold mb-3">{isZh ? '理解能力' : 'Understanding'}</h2>
<div className="bg-amber-50 dark:bg-amber-900/20 border-l-4 border-amber-400 p-4 mb-6">
<p className="text-amber-800 dark:text-amber-200 mb-2 font-medium">
{isZh
? 'Page Agent 不使用多模态模型,不截图,没有视觉能力。仅通过 DOM 结构理解页面。'
: 'Page Agent does not use multimodal models, does not take screenshots, and has no visual capability. It reads pages through DOM structure only.'}
</p>
<p className="text-amber-700 dark:text-amber-300 text-sm">
{isZh
? '图片、Canvas、WebGL、SVG 等视觉内容无法被识别。页面的语义化程度和可访问性直接影响 AI 的理解准确性。'
: 'Images, Canvas, WebGL, SVG and other visual content cannot be recognized. Page semantic quality and accessibility directly affect AI accuracy.'}
</p>
</div>
{/* Website Quality */}
<h2 className="text-2xl font-bold mb-3">
{isZh ? '网页质量影响' : 'Page Quality Matters'}
</h2>
<div className="bg-gray-50 dark:bg-gray-800 rounded-lg p-6">
<p className="text-gray-600 dark:text-gray-300 text-sm">
{isZh
? '反常识的交互逻辑、纯视觉的操作提示、快速出现消失的元素等都会降低自动化成功率。语义化的 HTML 和良好的可访问性会显著提升效果。'
: 'Counter-intuitive interactions, visual-only cues, and rapidly appearing/disappearing elements reduce automation success. Semantic HTML and good accessibility significantly improve results.'}
</p>
</div>
</div>
</div>
-15
View File
@@ -28,20 +28,5 @@
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
// "paths": {
// // Simplified monorepo solution (raw npm workspace with hoisting)
// "@page-agent/page-controller": ["./packages/page-controller/src/PageController.ts"],
// "page-agent": ["./packages/page-agent/src/PageAgent.ts"]
// }
}
// "references": [
// { "path": "./packages/page-controller" },
// { "path": "./packages/page-agent" },
// { "path": "./packages/website" }
// ],
// "include": ["packages/*/src/**/*.ts", "packages/*/src/**/*.tsx"],
// "exclude": ["node_modules", "dist", "packages/*/dist"]
// "files": ["env.d.ts"]
// "files": []
}
+2
View File
@@ -1,3 +1,5 @@
// this is only for IDE ts language server to work.
// do not use this for building or linting.
{
"extends": "./tsconfig.base.json",
"references": [