Compare commits

..

36 Commits

Author SHA1 Message Date
Simon af36f43104 chore(version): bump version to 0.2.5 2026-01-17 17:47:50 +08:00
Simon b5f260207c chore: wording 2026-01-17 17:47:04 +08:00
Simon 9075204c29 Merge pull request #118 from alibaba/feat/make-panel-optional
feat: make all ui optional
2026-01-17 17:43:40 +08:00
Simon ce3a80bc53 docs: typo 2026-01-17 17:30:16 +08:00
Simon 64c36f67bb docs: update config; instructions for programmatic usage 2026-01-17 17:28:28 +08:00
Simon 78d4919289 feat: make panel optional 2026-01-17 17:24:17 +08:00
Simon 1af2607cca feat: remove ask_user tool from sys_prompt 2026-01-17 17:18:31 +08:00
Simon ab713f3d3a chore(version): bump version to 0.2.4 2026-01-17 16:22:45 +08:00
Simon 05cc4f36c8 chore: add .prettierignore for system_prompt.md 2026-01-17 16:22:28 +08:00
Simon 54e9bdad7d chore: prettier json and markdowns 2026-01-17 16:15:36 +08:00
Simon 0fc88c0080 chore(version): bump version to 0.2.3 2026-01-17 16:03:11 +08:00
Simon 02f97ef2fa chore: banner 2026-01-17 15:57:22 +08:00
Simon 0853ffbbc6 chore: banner 2026-01-17 15:48:30 +08:00
Simon 60ac6da09f chore: new demo video 2026-01-17 15:17:14 +08:00
Simon 11a9fb1181 chore: new demo video 2026-01-17 15:15:48 +08:00
Simon 3762d0f1e0 chore: refine docs 2026-01-17 14:59:09 +08:00
Simon ebbe92f7ca fix(website): update links to use relative paths 2026-01-17 02:13:19 +08:00
Simon 9f239b2de3 chore: comment out unused config 2026-01-17 02:05:30 +08:00
Simon 2410c27b55 chore(website): update config docs 2026-01-17 02:05:09 +08:00
Simon 6d6c82648c chore: cleanup 2026-01-17 01:46:35 +08:00
Simon f41119c1ad fix(website): fix router 2026-01-17 01:36:15 +08:00
Simon 9e6e1b996c chore(website): simplify chunks 2026-01-17 01:27:19 +08:00
Simon 1ab23e181f chore: clean up 2026-01-17 01:23:23 +08:00
Simon a1b485691f chore(version): bump version to 0.2.2 2026-01-17 00:59:55 +08:00
Simon 694e05b800 chore: npm audit fix 2026-01-17 00:54:20 +08:00
Simon b761795c1a feat!: local iife testing
BREAKING CHANGES: remove umd build from npm package and cdn
2026-01-16 19:04:26 +08:00
Simon 8cc54983aa chore(version): bump version to 0.2.1 2026-01-16 16:16:18 +08:00
Simon 59a599285e Merge pull request #115 from alibaba/fix/phantom-panel
fix(panel): hide panel by default
2026-01-16 16:15:43 +08:00
Simon 2f8c2b395c fix(panel): hide panel by default 2026-01-16 16:06:57 +08:00
Simon 28f836674c feat: edge feedback for scroll actions 2026-01-16 15:53:47 +08:00
Simon 6f01632231 fix(website): build error 2026-01-16 15:09:30 +08:00
Simon 41123ac561 chore(website): optimize bundle size 2026-01-16 15:05:09 +08:00
Simon c8bf0dd20f chore: get rid of build warnings 2026-01-16 14:50:56 +08:00
Simon ec2b744bab Merge pull request #114 from alibaba/feat/user-takeover
feat: remove `pause`
2026-01-15 21:51:01 +08:00
Simon eab9cf64e8 feat: includes history events in panel 2026-01-15 21:48:14 +08:00
Simon 42130d6f1d feat: remove pause 2026-01-15 21:37:03 +08:00
58 changed files with 863 additions and 788 deletions
+1 -1
View File
@@ -58,4 +58,4 @@ body:
- label: I have read the Code of Conduct. / 我已阅读行为准则。 - label: I have read the Code of Conduct. / 我已阅读行为准则。
required: true required: true
validations: validations:
required: true required: true
+1 -1
View File
@@ -44,4 +44,4 @@ body:
- label: I have read the CODE_OF_CONDUCT.md and CONTRIBUTING.md. / 我已阅读行为准则。 - label: I have read the CODE_OF_CONDUCT.md and CONTRIBUTING.md. / 我已阅读行为准则。
required: true required: true
validations: validations:
required: true required: true
+3 -3
View File
@@ -6,7 +6,7 @@ on:
- 'v*' - 'v*'
permissions: permissions:
id-token: write # Required for OIDC id-token: write # Required for OIDC
contents: read contents: read
jobs: jobs:
@@ -24,7 +24,7 @@ jobs:
# Ensure npm 11.5.1 or later is installed # Ensure npm 11.5.1 or later is installed
- name: Update npm - name: Update npm
run: npm install -g npm@latest run: npm install -g npm@latest
- name: Install dependencies - name: Install dependencies
run: npm ci run: npm ci
@@ -32,4 +32,4 @@ jobs:
run: npm run build:libs run: npm run build:libs
- name: Publish all public packages - name: Publish all public packages
run: npm publish --workspaces --access public run: npm publish --workspaces --access public
+1 -1
View File
@@ -1,3 +1,3 @@
{ {
"recommendations": ["dbaeumer.vscode-eslint", "prettier.prettier-vscode"] "recommendations": ["dbaeumer.vscode-eslint", "prettier.prettier-vscode"]
} }
+27 -26
View File
@@ -1,28 +1,29 @@
{ {
"editor.fontLigatures": true, "editor.fontLigatures": true,
"cSpell.words": [ "cSpell.words": [
"deepseek", "deepseek",
"HITL", "HITL",
"innerhtml", "innerhtml",
"llms", "llms",
"opensource", "onwarn",
"qwen", "opensource",
"retryable", "qwen",
"shadcn", "retryable",
"wouter" "shadcn",
], "wouter"
"markdownlint.config": { ],
// "comment": "Relaxed rules", "markdownlint.config": {
"default": true, // "comment": "Relaxed rules",
"whitespace": false, "default": true,
"line_length": false, "whitespace": false,
"ul-indent": false, "line_length": false,
"no-inline-html": false, "ul-indent": false,
"no-bare-urls": false, "no-inline-html": false,
"fenced-code-language": false, "no-bare-urls": false,
"first-line-h1": false, "fenced-code-language": false,
"block-spacing": false, "first-line-h1": false,
"blanks-around-lists": false, "block-spacing": false,
"ol-prefix": false "blanks-around-lists": false,
} "ol-prefix": false
}
} }
+61 -51
View File
@@ -7,16 +7,16 @@ Thank you for your interest in contributing to Page-Agent! We welcome contributi
### Development Setup ### Development Setup
1. **Prerequisites** 1. **Prerequisites**
- `node.js >= 20` with `npm >= 10` - `node.js >= 20` with `npm >= 10`
- An editor that supports `ts/eslint/prettier` - An editor that supports `ts/eslint/prettier`
- Make sure `eslint`, `prettier` and `commitlint` work well - Make sure `eslint`, `prettier` and `commitlint` work well
2. **Setup** 2. **Setup**
```bash ```bash
npm install npm ci
npm start # Start demo and documentation site npm start # Start demo and documentation site
``` ```
### Project Structure ### Project Structure
@@ -25,11 +25,11 @@ This is a **monorepo** with npm workspaces containing **two main packages**:
1. **Core Library** (`packages/page-agent/`) - Pure JavaScript/TypeScript AI agent library for browser DOM automation, published as `page-agent` on npm 1. **Core Library** (`packages/page-agent/`) - Pure JavaScript/TypeScript AI agent library for browser DOM automation, published as `page-agent` on npm
2. **Website** (`packages/website/`) - React documentation and landing page. Also as demo and test page for the core lib. private package `@page-agent/website` 2. **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. We use a simplified monorepo solution with `native npm-workspace + ts reference + vite alias`. No fancy tooling. Hoisting is required.
- When developing. Use alias so that we don't have to pre-build. - When developing. Use alias so that we don't have to pre-build.
- When bundling. Use external and disable ts `paths` alias to leave deps out. - When bundling. Use external and disable ts `paths` alias to leave deps out.
- When bundling `UMD` and `Website`. Bundle everything including local packages. - When bundling `IIFE` and `Website`. Bundle everything including local packages.
## 🤝 How to Contribute ## 🤝 How to Contribute
@@ -44,40 +44,39 @@ We use a simplified monorepo solution with `native npm-workspace + ts reference
1. **Fork and Clone** 1. **Fork and Clone**
```bash ```bash
git clone https://github.com/your-username/page-agent.git git clone https://github.com/your-username/page-agent.git
cd page-agent cd page-agent
``` ```
2. **Create Feature Branch** 2. **Create Feature Branch**
```bash ```bash
git checkout -b feat/your-feature-name git checkout -b feat/your-feature-name
``` ```
3. **Make Changes** 3. **Make Changes**
- Follow existing code style and patterns - Follow existing code style and patterns
- Add tests for new functionality - Add tests for new functionality
- Update documentation as needed - Update documentation as needed
4. **Test Your Changes** 4. **Test Your Changes**
- Test in our demo website
```bash - Test it on other websites if applicable
# TODO - `@TODO: test suite`
```
5. **Commit and Push** 5. **Commit and Push**
```bash ```bash
git add . git add .
git commit -m "feat: add awesome feature" git commit -m "feat: add awesome feature"
git push origin feat/your-feature-name git push origin feat/your-feature-name
``` ```
6. **Create Pull Request** 6. **Create Pull Request**
- Provide clear description of changes - Provide clear description of changes
- Link related issues - Link related issues
- Include screenshots for UI changes - Include screenshots for UI changes
## 📝 Code Style ## 📝 Code Style
@@ -94,41 +93,52 @@ We use a simplified monorepo solution with `native npm-workspace + ts reference
- It's **recommended** to heavily rely on AI (aka "vibe coding") when maintaining **demo pages and tests**. - It's **recommended** to heavily rely on AI (aka "vibe coding") when maintaining **demo pages and tests**.
- BUT **NOT the core lib!!!** Be very careful if AI ever touched the core lib!!! - BUT **NOT the core lib!!!** Be very careful if AI ever touched the core lib!!!
- Review anything AI wrote before make a commit. You are the author of anything you commit. NOT AI. - Review anything AI wrote before make a commit. You are the author of anything you commit. NOT AI.
- Update the AI instructions when structure changed. - Update the [AI instructions](AGENTS.md) when structure changed.
- Cursor and Cline: `./.cursor/rules`
- Github Copilot: `./.github/copilot-instructions.md`
- Claude Code: `./CLAUDE.md`
## 🔧 Development Workflows ## 🔧 Development Workflows
### Test With Your Own LLM API
- Create a `.env` file in the repo root with your LLM API config
```env
LLM_MODEL_NAME=gpt-5.2
LLM_API_KEY=your-api-key
LLM_BASE_URL=https://api.your-llm-provider.com/v1
```
- Restart the dev server to load new env vars
- If not provided, the demo will the free testing proxy by default
### Website Development ### Website Development
```bash ```bash
npm start # React development server npm start
``` ```
### Core Lib Development and Testing ### Testing on Other Websites
> @TODO this part is outdated. Update this. - Start and serve a local `iife` script
- Config your LLM API ```bash
- Start and serve a local umd script npm run dev:iife # Serving IIFE with auto rebuild at http://localhost:5174/page-agent.js
```
```bash - Add a new bookmark
npm run dev:umd # Serving UMD with auto rebuild at http://localhost:5173/page-agent.umd.cjs
```
- Add a new bookmark enable it on other website ```javascript
javascript:(function(){var s=document.createElement('script');s.src=`http://localhost:5174/page-agent.js?t=${Math.random()}`;s.onload=()=>console.log(%27PageAgent ready!%27);document.head.appendChild(s);})();
```
``` - Click the bookmark on any page to load Page-Agent
javascript:(function(){var s=document.createElement('script');s.src=`http://localhost:5173/page-agent.umd.cjs?t=${Math.random()}`;s.onload=()=>console.log(%27PageAgent ready!%27);document.head.appendChild(s);})();
``` > Warning: AK in your local `.env` will be inlined in the iife script.
### Adding Documentation ### Adding Documentation
1. Create `website/src/docs/section/page-name/page.tsx` Ask an AI to help you add documentation to the `website/` package. Follow the existing style.
2. Add route to `website/src/router.tsx`
3. Add navigation link to `website/src/components/DocsLayout.tsx` > Our AGENTS.md file and guardrails are designed for this purpose. But please be careful and review anything AI generated.
## 🎯 Contribution Areas ## 🎯 Contribution Areas
@@ -143,7 +153,7 @@ We especially welcome contributions in:
## 🚫 What We Don't Accept ## 🚫 What We Don't Accept
- Changes that break existing API compatibility - Changes that break existing API compatibility (Discuss first)
- Heavy dependencies to core library - Heavy dependencies to core library
- Contributions without proper testing - Contributions without proper testing
- Code that doesn't follow project conventions - Code that doesn't follow project conventions
+2 -1
View File
@@ -1,6 +1,7 @@
MIT License MIT License
Copyright (c) 2025 Alibaba Copyright (c) 2026 Alibaba
Copyright (c) 2026 Simon
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
+32 -50
View File
@@ -1,8 +1,11 @@
# PageAgent 🤖🪄 # PageAgent 🤖🪄
![banner](https://img.alicdn.com/imgextra/i1/O1CN01RY0Wvh26ATVeDIX7v_!!6000000007621-0-tps-1672-512.jpg) <picture>
<source media="(prefers-color-scheme: dark)" srcset="https://img.alicdn.com/imgextra/i4/O1CN01qKig1P1FnhpFKNdi6_!!6000000000532-2-tps-1280-256.png">
<img alt="Page Agent Banner" src="https://img.alicdn.com/imgextra/i1/O1CN01NCMKXj1Gn4tkFTsxf_!!6000000000666-2-tps-1280-256.png">
</picture>
[![npm version](https://badge.fury.io/js/page-agent.svg)](https://badge.fury.io/js/page-agent) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![TypeScript](https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg)](http://www.typescriptlang.org/) [![Downloads](https://img.shields.io/npm/dt/page-agent.svg)](https://www.npmjs.com/package/page-agent) [![Bundle Size](https://img.shields.io/bundlephobia/minzip/page-agent)](https://bundlephobia.com/package/page-agent) [![GitHub stars](https://img.shields.io/github/stars/alibaba/page-agent.svg)](https://github.com/alibaba/page-agent) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![TypeScript](https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg)](http://www.typescriptlang.org/) [![Downloads](https://img.shields.io/npm/dt/page-agent.svg)](https://www.npmjs.com/package/page-agent) [![Bundle Size](https://img.shields.io/bundlephobia/minzip/page-agent)](https://bundlephobia.com/package/page-agent) [![GitHub stars](https://img.shields.io/github/stars/alibaba/page-agent.svg)](https://github.com/alibaba/page-agent)
纯 JS 实现的 GUI agent。使用自然语言操作你的 Web 应用。无须后端、客户端、浏览器插件。 纯 JS 实现的 GUI agent。使用自然语言操作你的 Web 应用。无须后端、客户端、浏览器插件。
@@ -10,7 +13,7 @@
👉 <a href="https://alibaba.github.io/page-agent/" target="_blank"><b>🚀 Demo</b></a> | <a href="https://alibaba.github.io/page-agent/#/docs/introduction/overview" target="_blank"><b>📖 Documentation</b></a> 👉 <a href="https://alibaba.github.io/page-agent/" target="_blank"><b>🚀 Demo</b></a> | <a href="https://alibaba.github.io/page-agent/#/docs/introduction/overview" target="_blank"><b>📖 Documentation</b></a>
<video id="demo-video" src="https://github.com/user-attachments/assets/141bbb01-8022-4d1f-919d-9efc9a1dc1cf" width="640" crossorigin muted autoplay loop></video> <video id="demo-video" src="https://github.com/user-attachments/assets/34d8444d-cbfb-44a3-a24e-fd5c167bb0bf" controls crossorigin muted></video>
--- ---
@@ -28,22 +31,25 @@
## 🚀 快速开始 ## 🚀 快速开始
### 快速体验 (Demo CDN) ### 快速体验 (With Testing LLM)
最快的体验方式: 最快的体验方式:
```html ```html
<script src="https://registry.npmmirror.com/@page-agent/cdn/latest/files/dist/page-agent.demo.js" crossorigin="true"></script> <script
src="https://registry.npmmirror.com/@page-agent/cdn/latest/files/dist/page-agent.demo.js"
crossorigin="true"
></script>
``` ```
> ⚠️ **仅用于技术评估。** Demo 模型有速率限制和使用限制,生产环境请使用 NPM 方式。 > ⚠️ **仅用于技术评估。** Demo 模型有速率限制和使用限制,生产环境请使用 NPM 方式。
| 位置 | URL | | Mirrors | URL |
| ------ | --------------------------------------------------------------------------------------- | | ------- | ----------------------------------------------------------------------------------- |
| 全球 | https://cdn.jsdelivr.net/npm/@page-agent/cdn/dist/page-agent.demo.js | | Global | https://cdn.jsdelivr.net/npm/@page-agent/cdn/dist/page-agent.demo.js |
| 中国 | https://registry.npmmirror.com/@page-agent/cdn/latest/files/dist/page-agent.demo.js | | China | https://registry.npmmirror.com/@page-agent/cdn/latest/files/dist/page-agent.demo.js |
### NPM 安装(推荐) ### NPM 安装
```bash ```bash
npm install page-agent npm install page-agent
@@ -53,35 +59,16 @@ npm install page-agent
import { PageAgent } from 'page-agent' import { PageAgent } from 'page-agent'
const agent = new PageAgent({ const agent = new PageAgent({
model: 'deepseek-chat', model: 'deepseek-chat',
baseURL: 'https://api.deepseek.com', baseURL: 'https://api.deepseek.com',
apiKey: 'YOUR_API_KEY', apiKey: 'YOUR_API_KEY',
language: 'zh-CN', language: 'zh-CN',
}) })
await agent.execute('点击登录按钮') await agent.execute('点击登录按钮')
``` ```
### CDN 引入 适用于无法使用 NPM 的环境,我们也提供了 IIFE 构建的 CDN 方式。[@see CDN Usage](https://alibaba.github.io/page-agent/#/docs/integration/cdn-setup)
适用于无法使用 NPM 的环境,用法与 NPM 一致:
```html
<script src="https://registry.npmmirror.com/@page-agent/cdn/latest/files/dist/page-agent.js" crossorigin="true"></script>
<script>
const agent = new PageAgent({
model: 'deepseek-chat',
baseURL: 'https://api.deepseek.com',
apiKey: 'YOUR_API_KEY',
})
await agent.execute('点击登录按钮')
</script>
```
| 位置 | URL |
| ------ | ----------------------------------------------------------------------------------- |
| 全球 | https://cdn.jsdelivr.net/npm/@page-agent/cdn/dist/page-agent.js |
| 中国 | https://registry.npmmirror.com/@page-agent/cdn/latest/files/dist/page-agent.js |
## 🏗️ 架构设计 ## 🏗️ 架构设计
@@ -101,28 +88,19 @@ packages/
欢迎社区贡献!以下是参与方式: 欢迎社区贡献!以下是参与方式:
### 开发环境 1. Fork 并 clone。`git clone https://github.com/alibaba/page-agent.git && cd page-agent`
2. 安装依赖: `npm install`
3. 启动开发: `npm start`
1. Fork 项目仓库 更多详情请参阅 [CONTRIBUTING.md](CONTRIBUTING.md)。
2. Clone or fork: `git clone https://github.com/alibaba/page-agent.git && cd page-agent`
3. 安装依赖: `npm install`
4. 启动开发: `npm start`
### 贡献指南 请在贡献前阅读我们的[行为准则](CODE_OF_CONDUCT.md)。
请在贡献前阅读我们的[行为准则](CODE_OF_CONDUCT.md)和[贡献指南](CONTRIBUTING.md)。
## 👏 致谢 ## 👏 致谢
本项目基于以下优秀项目构建 本项目基于 **[`browser-use`](https://github.com/browser-use/browser-use)** 的优秀工作构建
- **[browser-use](https://github.com/browser-use/browser-use)** `PageAgent` 专为**客户端网页增强**设计,不是服务端自动化工具。
PageAgent 专为**客户端网页增强**设计,不是服务端自动化工具。
## 📄 许可证
MIT 许可证 - 详见 [LICENSE](LICENSE) 文件。
``` ```
DOM processing components and prompt are derived from browser-use: DOM processing components and prompt are derived from browser-use:
@@ -141,6 +119,10 @@ Third-party dependencies and their licenses can be found in the package.json
file and in the node_modules directory after installation. file and in the node_modules directory after installation.
``` ```
## 📄 许可证
[MIT License](LICENSE)
--- ---
**⭐ 如果觉得 PageAgent 有用或有趣,请给项目点个星!** **⭐ 如果觉得 PageAgent 有用或有趣,请给项目点个星!**
+32 -50
View File
@@ -1,8 +1,11 @@
# PageAgent 🤖🪄 # PageAgent 🤖🪄
![banner](https://img.alicdn.com/imgextra/i3/O1CN01MyVCS21EoKkIHUT1s_!!6000000000398-49-tps-1280-353.webp) <picture>
<source media="(prefers-color-scheme: dark)" srcset="https://img.alicdn.com/imgextra/i4/O1CN01qKig1P1FnhpFKNdi6_!!6000000000532-2-tps-1280-256.png">
<img alt="Page Agent Banner" src="https://img.alicdn.com/imgextra/i1/O1CN01NCMKXj1Gn4tkFTsxf_!!6000000000666-2-tps-1280-256.png">
</picture>
[![npm version](https://badge.fury.io/js/page-agent.svg)](https://badge.fury.io/js/page-agent) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![TypeScript](https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg)](http://www.typescriptlang.org/) [![Downloads](https://img.shields.io/npm/dt/page-agent.svg)](https://www.npmjs.com/package/page-agent) [![Bundle Size](https://img.shields.io/bundlephobia/minzip/page-agent)](https://bundlephobia.com/package/page-agent) [![GitHub stars](https://img.shields.io/github/stars/alibaba/page-agent.svg)](https://github.com/alibaba/page-agent) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![TypeScript](https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg)](http://www.typescriptlang.org/) [![Downloads](https://img.shields.io/npm/dt/page-agent.svg)](https://www.npmjs.com/package/page-agent) [![Bundle Size](https://img.shields.io/bundlephobia/minzip/page-agent)](https://bundlephobia.com/package/page-agent) [![GitHub stars](https://img.shields.io/github/stars/alibaba/page-agent.svg)](https://github.com/alibaba/page-agent)
The GUI Agent Living in Your Webpage. Control web interfaces with natural language. The GUI Agent Living in Your Webpage. Control web interfaces with natural language.
@@ -10,7 +13,7 @@ The GUI Agent Living in Your Webpage. Control web interfaces with natural langua
👉 <a href="https://alibaba.github.io/page-agent/" target="_blank"><b>🚀 Demo</b></a> | <a href="https://alibaba.github.io/page-agent/#/docs/introduction/overview" target="_blank"><b>📖 Documentation</b></a> 👉 <a href="https://alibaba.github.io/page-agent/" target="_blank"><b>🚀 Demo</b></a> | <a href="https://alibaba.github.io/page-agent/#/docs/introduction/overview" target="_blank"><b>📖 Documentation</b></a>
<video id="demo-video" src="https://github.com/user-attachments/assets/de8d1964-8bde-494f-a52f-2975469557a5" width="640" crossorigin muted autoplay loop></video> <video id="demo-video" src="https://github.com/user-attachments/assets/34d8444d-cbfb-44a3-a24e-fd5c167bb0bf" controls crossorigin muted></video>
--- ---
@@ -28,22 +31,25 @@ The GUI Agent Living in Your Webpage. Control web interfaces with natural langua
## 🚀 Quick Start ## 🚀 Quick Start
### Quick Try (Demo CDN) ### Quick Try (With Testing LLM)
Fastest way to try PageAgent: Fastest way to try PageAgent:
```html ```html
<script src="https://cdn.jsdelivr.net/npm/@page-agent/cdn/dist/page-agent.demo.js" crossorigin="true"></script> <script
src="https://cdn.jsdelivr.net/npm/@page-agent/cdn/dist/page-agent.demo.js"
crossorigin="true"
></script>
``` ```
> ⚠️ **For technical evaluation only.** Demo model has rate limits and usage restrictions. Use NPM for production. > ⚠️ **For technical evaluation only.** Demo model has rate limits and usage restrictions. Use NPM for production.
| Location | URL | | Mirrors | URL |
| -------- | --------------------------------------------------------------------------------------- | | ------- | ----------------------------------------------------------------------------------- |
| Global | https://cdn.jsdelivr.net/npm/@page-agent/cdn/dist/page-agent.demo.js | | Global | https://cdn.jsdelivr.net/npm/@page-agent/cdn/dist/page-agent.demo.js |
| China | https://registry.npmmirror.com/@page-agent/cdn/latest/files/dist/page-agent.demo.js | | China | https://registry.npmmirror.com/@page-agent/cdn/latest/files/dist/page-agent.demo.js |
### NPM Installation (Recommended) ### NPM Installation
```bash ```bash
npm install page-agent npm install page-agent
@@ -53,35 +59,16 @@ npm install page-agent
import { PageAgent } from 'page-agent' import { PageAgent } from 'page-agent'
const agent = new PageAgent({ const agent = new PageAgent({
model: 'deepseek-chat', model: 'deepseek-chat',
baseURL: 'https://api.deepseek.com', baseURL: 'https://api.deepseek.com',
apiKey: 'YOUR_API_KEY', apiKey: 'YOUR_API_KEY',
language: 'en-US', language: 'en-US',
}) })
await agent.execute('Click the login button') await agent.execute('Click the login button')
``` ```
### CDN Build For environments where NPM is not available. We do offer a IIFE build via CDN. [@see CDN Usage](https://alibaba.github.io/page-agent/#/docs/integration/cdn-setup)
For environments where NPM is not available. Usage is identical to NPM:
```html
<script src="https://cdn.jsdelivr.net/npm/@page-agent/cdn/dist/page-agent.js" crossorigin="true"></script>
<script>
const agent = new PageAgent({
model: 'deepseek-chat',
baseURL: 'https://api.deepseek.com',
apiKey: 'YOUR_API_KEY',
})
await agent.execute('Click the login button')
</script>
```
| Location | URL |
| -------- | ----------------------------------------------------------------------------------- |
| Global | https://cdn.jsdelivr.net/npm/@page-agent/cdn/dist/page-agent.js |
| China | https://registry.npmmirror.com/@page-agent/cdn/latest/files/dist/page-agent.js |
## 🏗️ Structure ## 🏗️ Structure
@@ -101,28 +88,19 @@ packages/
We welcome contributions from the community! Here's how to get started: We welcome contributions from the community! Here's how to get started:
### Setup 1. Fork and clone. `git clone https://github.com/alibaba/page-agent.git && cd page-agent`
2. Install dependencies: `npm install`
3. Start development: `npm start`
1. Fork the repository More details in [CONTRIBUTING.md](CONTRIBUTING.md).
2. Clone your fork: `git clone https://github.com/alibaba/page-agent.git && cd page-agent`
3. Install dependencies: `npm install`
4. Start development: `npm start`
### Contributing Guidelines Please read our [Code of Conduct](CODE_OF_CONDUCT.md) before contributing.
Please read our [Code of Conduct](CODE_OF_CONDUCT.md) and [Contributing Guide](CONTRIBUTING.md) before contributing.
## 👏 Acknowledgments ## 👏 Acknowledgments
This project builds upon the excellent work of: This project builds upon the excellent work of **[`browser-use`](https://github.com/browser-use/browser-use)**.
- **[browser-use](https://github.com/browser-use/browser-use)** `PageAgent` is designed for **client-side web enhancement**, not server-side automation.
PageAgent is designed for **client-side web enhancement**, not server-side automation.
## 📄 License
MIT License - see the [LICENSE](LICENSE) file for details.
``` ```
DOM processing components and prompt are derived from browser-use: DOM processing components and prompt are derived from browser-use:
@@ -141,6 +119,10 @@ Third-party dependencies and their licenses can be found in the package.json
file and in the node_modules directory after installation. file and in the node_modules directory after installation.
``` ```
## 📄 License
[MIT License](LICENSE)
--- ---
**⭐ Star this repo if you find PageAgent helpful!** **⭐ Star this repo if you find PageAgent helpful!**
+15 -29
View File
@@ -1,12 +1,12 @@
{ {
"name": "root", "name": "root",
"version": "0.2.0", "version": "0.2.5",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "root", "name": "root",
"version": "0.2.0", "version": "0.2.5",
"license": "MIT", "license": "MIT",
"workspaces": [ "workspaces": [
"packages/page-controller", "packages/page-controller",
@@ -1540,20 +1540,6 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/@microsoft/api-extractor/node_modules/typescript": {
"version": "5.8.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz",
"integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/@microsoft/api-extractor/node_modules/yallist": { "node_modules/@microsoft/api-extractor/node_modules/yallist": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
@@ -4627,9 +4613,9 @@
} }
}, },
"node_modules/diff": { "node_modules/diff": {
"version": "8.0.2", "version": "8.0.3",
"resolved": "https://registry.npmjs.org/diff/-/diff-8.0.2.tgz", "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.3.tgz",
"integrity": "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==", "integrity": "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==",
"dev": true, "dev": true,
"license": "BSD-3-Clause", "license": "BSD-3-Clause",
"engines": { "engines": {
@@ -8147,15 +8133,15 @@
}, },
"packages/cdn": { "packages/cdn": {
"name": "@page-agent/cdn", "name": "@page-agent/cdn",
"version": "0.2.0", "version": "0.2.5",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"page-agent": "0.2.0" "page-agent": "0.2.5"
} }
}, },
"packages/llms": { "packages/llms": {
"name": "@page-agent/llms", "name": "@page-agent/llms",
"version": "0.2.0", "version": "0.2.5",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"chalk": "^5.6.2", "chalk": "^5.6.2",
@@ -8163,19 +8149,19 @@
} }
}, },
"packages/page-agent": { "packages/page-agent": {
"version": "0.2.0", "version": "0.2.5",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@page-agent/llms": "0.2.0", "@page-agent/llms": "0.2.5",
"@page-agent/page-controller": "0.2.0", "@page-agent/page-controller": "0.2.5",
"@page-agent/ui": "0.2.0", "@page-agent/ui": "0.2.5",
"chalk": "^5.6.2", "chalk": "^5.6.2",
"zod": "^4.3.5" "zod": "^4.3.5"
} }
}, },
"packages/page-controller": { "packages/page-controller": {
"name": "@page-agent/page-controller", "name": "@page-agent/page-controller",
"version": "0.2.0", "version": "0.2.5",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"ai-motion": "^0.4.8" "ai-motion": "^0.4.8"
@@ -8183,12 +8169,12 @@
}, },
"packages/ui": { "packages/ui": {
"name": "@page-agent/ui", "name": "@page-agent/ui",
"version": "0.2.0", "version": "0.2.5",
"license": "MIT" "license": "MIT"
}, },
"packages/website": { "packages/website": {
"name": "@page-agent/website", "name": "@page-agent/website",
"version": "0.2.0", "version": "0.2.5",
"dependencies": { "dependencies": {
"@radix-ui/react-icons": "^1.3.2", "@radix-ui/react-icons": "^1.3.2",
"@radix-ui/react-separator": "^1.1.8", "@radix-ui/react-separator": "^1.1.8",
+22 -3
View File
@@ -1,7 +1,7 @@
{ {
"name": "root", "name": "root",
"private": true, "private": true,
"version": "0.2.0", "version": "0.2.5",
"type": "module", "type": "module",
"workspaces": [ "workspaces": [
"packages/page-controller", "packages/page-controller",
@@ -29,7 +29,7 @@
"build:website": "npm run build:website --workspace=@page-agent/website", "build:website": "npm run build:website --workspace=@page-agent/website",
"build:libs": "npm run build --workspaces --if-present", "build:libs": "npm run build --workspaces --if-present",
"build": "npm run build:libs && npm run build:website", "build": "npm run build:libs && npm run build:website",
"dev:umd": "npm run dev:umd --workspace=page-agent", "dev:iife": "npm run dev:iife --workspace=page-agent",
"version": "node scripts/sync-version.js", "version": "node scripts/sync-version.js",
"lint": "eslint .", "lint": "eslint .",
"prepare": "husky" "prepare": "husky"
@@ -59,6 +59,9 @@
"vite": "^7.3.1", "vite": "^7.3.1",
"vite-plugin-css-injected-by-js": "^3.5.2" "vite-plugin-css-injected-by-js": "^3.5.2"
}, },
"overrides": {
"typescript": "^5.9.3"
},
"lint-staged": { "lint-staged": {
"*.{js,ts,cjs,cts,mjs,mts}": [ "*.{js,ts,cjs,cts,mjs,mts}": [
"npx prettier --write --ignore-unknown", "npx prettier --write --ignore-unknown",
@@ -99,6 +102,22 @@
".css$" ".css$"
], ],
"importOrderSeparation": true, "importOrderSeparation": true,
"importOrderSortSpecifiers": true "importOrderSortSpecifiers": true,
"overrides": [
{
"files": "*.md",
"options": {
"useTabs": false,
"tabWidth": 4
}
},
{
"files": "*.json",
"options": {
"useTabs": false,
"tabWidth": 4
}
}
]
} }
} }
+2 -2
View File
@@ -1,7 +1,7 @@
{ {
"name": "@page-agent/cdn", "name": "@page-agent/cdn",
"private": false, "private": false,
"version": "0.2.0", "version": "0.2.5",
"type": "module", "type": "module",
"files": [ "files": [
"dist/" "dist/"
@@ -18,6 +18,6 @@
"build": "vite build && vite build --mode demo" "build": "vite build && vite build --mode demo"
}, },
"dependencies": { "dependencies": {
"page-agent": "0.2.0" "page-agent": "0.2.5"
} }
} }
+7
View File
@@ -34,6 +34,13 @@ export default defineConfig(({ mode }) => {
outDir: resolve(__dirname, 'dist'), outDir: resolve(__dirname, 'dist'),
emptyOutDir: !isDemo, // only empty on first build (full) emptyOutDir: !isDemo, // only empty on first build (full)
minify: false, minify: false,
cssCodeSplit: true,
rollupOptions: {
onwarn: function (message, handler) {
if (message.code === 'EVAL') return
handler(message)
},
},
}, },
define: { define: {
'process.env.NODE_ENV': '"production"', 'process.env.NODE_ENV': '"production"',
-41
View File
@@ -1,41 +0,0 @@
# @page-agent/llms
LLM client with a **reflection-before-action** mental model for page-agent.
## Why This Package Exists
The LLM module and the agent logic are inherently coupled. This package exists not to decouple them, but to **define the interface contract** between the LLM and the agent.
The core abstraction is the `MacroToolInput` — a structured output format that **forces the model to reflect before acting**.
## The Reflection-Before-Action Model
Every tool call must first output its reasoning state before the actual action:
```typescript
interface MacroToolInput {
// Reflection (mandatory before any action)
evaluation_previous_goal?: string // How well did the previous action work?
memory?: string // Key information to remember
next_goal?: string // What to accomplish next
// Action (the actual operation)
action: Record<string, any>
}
```
This design ensures that:
1. **The model evaluates its previous action** before deciding the next step
2. **Working memory is explicitly maintained** across conversation turns
3. **Goals are clearly stated**, making the agent's reasoning transparent and debuggable
## Key Components
| Export | Description |
|--------|-------------|
| `LLM` | Main LLM client class with retry logic |
| `MacroToolInput` | The reflection-before-action input schema |
| `AgentBrain` | Agent's thinking state (eval, memory, goal) |
| `LLMConfig` | Configuration for LLM connection |
| `parseLLMConfig` | Parse and apply defaults to config |
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@page-agent/llms", "name": "@page-agent/llms",
"version": "0.2.0", "version": "0.2.5",
"type": "module", "type": "module",
"main": "./dist/lib/page-agent-llms.js", "main": "./dist/lib/page-agent-llms.js",
"module": "./dist/lib/page-agent-llms.js", "module": "./dist/lib/page-agent-llms.js",
+7 -8
View File
@@ -1,10 +1,9 @@
{ {
"extends": "./tsconfig.json", "extends": "./tsconfig.json",
"compilerOptions": { "compilerOptions": {
// @workaround DTS bug // @workaround DTS bug
// dts do not work with monorepo path mapping // dts do not work with monorepo path mapping
// disable path mapping for it // disable path mapping for it
"paths": {} "paths": {}
} }
} }
+10 -11
View File
@@ -1,13 +1,12 @@
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.base.json",
"compilerOptions": { "compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo", "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo",
"noEmit": false, "noEmit": false,
"allowImportingTsExtensions": false, "allowImportingTsExtensions": false,
"baseUrl": ".", "baseUrl": ".",
"outDir": "dist" "outDir": "dist"
}, },
"include": ["**/*.ts"], "include": ["**/*.ts"],
"exclude": ["dist", "node_modules"] "exclude": ["dist", "node_modules"]
} }
-1
View File
@@ -34,4 +34,3 @@ export default defineConfig({
'process.env.NODE_ENV': '"production"', 'process.env.NODE_ENV': '"production"',
}, },
}) })
+6 -7
View File
@@ -1,7 +1,7 @@
{ {
"name": "page-agent", "name": "page-agent",
"private": false, "private": false,
"version": "0.2.0", "version": "0.2.5",
"type": "module", "type": "module",
"main": "./dist/esm/page-agent.js", "main": "./dist/esm/page-agent.js",
"module": "./dist/esm/page-agent.js", "module": "./dist/esm/page-agent.js",
@@ -37,17 +37,16 @@
}, },
"homepage": "https://alibaba.github.io/page-agent/", "homepage": "https://alibaba.github.io/page-agent/",
"scripts": { "scripts": {
"build": "vite build && vite build --config vite.umd.config.js", "build": "vite build",
"serve": "npx serve dist/umd -p 5173", "dev:iife": "concurrently \"vite build --config vite.iife.config.js --watch\" \"npx serve dist/iife -p 5174\"",
"dev:umd": "concurrently \"vite build --config vite.umd.config.js --watch\" \"npm run serve\"",
"prepublishOnly": "node -e \"const fs=require('fs');['README.md','LICENSE'].forEach(f=>fs.copyFileSync('../../'+f,f))\"", "prepublishOnly": "node -e \"const fs=require('fs');['README.md','LICENSE'].forEach(f=>fs.copyFileSync('../../'+f,f))\"",
"postpublish": "node -e \"['README.md','LICENSE'].forEach(f=>{try{require('fs').unlinkSync(f)}catch{}})\"" "postpublish": "node -e \"['README.md','LICENSE'].forEach(f=>{try{require('fs').unlinkSync(f)}catch{}})\""
}, },
"dependencies": { "dependencies": {
"chalk": "^5.6.2", "chalk": "^5.6.2",
"zod": "^4.3.5", "zod": "^4.3.5",
"@page-agent/llms": "0.2.0", "@page-agent/llms": "0.2.5",
"@page-agent/page-controller": "0.2.0", "@page-agent/page-controller": "0.2.5",
"@page-agent/ui": "0.2.0" "@page-agent/ui": "0.2.5"
} }
} }
+33 -34
View File
@@ -12,7 +12,7 @@ import type { PageAgentConfig } from './config'
import { MAX_STEPS } from './config/constants' import { MAX_STEPS } from './config/constants'
import SYSTEM_PROMPT from './prompts/system_prompt.md?raw' import SYSTEM_PROMPT from './prompts/system_prompt.md?raw'
import { tools } from './tools' import { tools } from './tools'
import { normalizeResponse, trimLines, uid, waitUntil } from './utils' import { normalizeResponse, trimLines, uid } from './utils'
import { assert } from './utils/assert' import { assert } from './utils/assert'
/** /**
@@ -90,9 +90,6 @@ export interface UserTakeoverEvent {
*/ */
export type HistoryEvent = AgentStep | ObservationEvent | UserTakeoverEvent export type HistoryEvent = AgentStep | ObservationEvent | UserTakeoverEvent
/** @deprecated Use AgentStep instead */
export type AgentHistory = AgentStep
export interface ExecutionResult { export interface ExecutionResult {
success: boolean success: boolean
data: string data: string
@@ -102,9 +99,8 @@ 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
paused = false
disposed = false disposed = false
task = '' task = ''
taskId = '' taskId = ''
@@ -126,7 +122,7 @@ export class PageAgent extends EventTarget {
lastURL: '', lastURL: '',
} }
/** History event stream */ /** History events */
history: HistoryEvent[] = [] history: HistoryEvent[] = []
constructor(config: PageAgentConfig) { constructor(config: PageAgentConfig) {
@@ -134,16 +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({
onPauseToggle: () => { language: this.config.language,
this.paused = !this.paused onExecuteTask: (task) => this.execute(task),
return this.paused onStop: () => this.dispose(),
}, promptForNextTask: this.config.promptForNextTask,
getPaused: () => this.paused, })
}) }
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)
@@ -155,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)
@@ -178,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')
} }
@@ -190,6 +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 })
} }
async execute(task: string): Promise<ExecutionResult> { async execute(task: string): Promise<ExecutionResult> {
@@ -207,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()
@@ -237,12 +240,10 @@ export class PageAgent extends EventTarget {
// abort // abort
if (this.#abortController.signal.aborted) throw new Error('AbortError') if (this.#abortController.signal.aborted) throw new Error('AbortError')
// pause
await waitUntil(() => !this.paused)
// 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(
[ [
@@ -362,8 +363,6 @@ export class PageAgent extends EventTarget {
execute: async (input: MacroToolInput): Promise<MacroToolResult> => { execute: async (input: MacroToolInput): Promise<MacroToolResult> => {
// abort // abort
if (this.#abortController.signal.aborted) throw new Error('AbortError') if (this.#abortController.signal.aborted) throw new Error('AbortError')
// pause
await waitUntil(() => !this.paused)
console.log(chalk.blue.bold('MacroTool execute'), input) console.log(chalk.blue.bold('MacroTool execute'), input)
const action = input.action const action = input.action
@@ -382,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
@@ -390,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()
@@ -406,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,
@@ -569,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()
@@ -605,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')
+23 -2
View File
@@ -11,6 +11,27 @@ export interface AgentConfig {
// theme?: 'light' | 'dark' // theme?: 'light' | 'dark'
language?: SupportedLanguage language?: SupportedLanguage
/**
* Whether to prompt for next task after task completion
* @default true
*/
promptForNextTask?: boolean
/**
* Enable the UI panel for visual feedback and user interaction
* When disabled, the panel will not be created and all UI operations will be skipped.
* Useful for automated testing or when integrating PageAgent as a library.
* @default true
*/
enablePanel?: boolean
/**
* Enable the ask_user tool for agent to ask questions
* When disabled, the agent cannot ask user questions during execution.
* @default true
*/
enableAskUser?: boolean
/** /**
* Custom tools to extend PageAgent capabilities * Custom tools to extend PageAgent capabilities
* @experimental * @experimental
@@ -108,14 +129,14 @@ export interface AgentConfig {
* @note PageAgent will try to detect new pages and decide if it's caused by an action. But not very reliable. * @note PageAgent will try to detect new pages and decide if it's caused by an action. But not very reliable.
* @todo remove `this` binding, pass agent as explicit parameter instead * @todo remove `this` binding, pass agent as explicit parameter instead
*/ */
onNewPageOpen?: (this: PageAgent, url: string) => Promise<void> | void // onNewPageOpen?: (this: PageAgent, url: string) => Promise<void> | void
/** /**
* TODO: @unimplemented * TODO: @unimplemented
* try to navigate to a new page instead of opening a new tab/window. * try to navigate to a new page instead of opening a new tab/window.
* @note will unload the current page when a action tries to open a new page. so that things keep in the same tab/window. * @note will unload the current page when a action tries to open a new page. so that things keep in the same tab/window.
*/ */
experimentalPreventNewPage?: boolean // experimentalPreventNewPage?: boolean
} }
export type PageAgentConfig = LLMConfig & AgentConfig & PageControllerConfig export type PageAgentConfig = LLMConfig & AgentConfig & PageControllerConfig
@@ -36,9 +36,13 @@ setTimeout(() => {
} else { } else {
console.log('🚀 page-agent.js no current script detected, using default demo config') console.log('🚀 page-agent.js no current script detected, using default demo config')
const config: PageAgentConfig = { const config: PageAgentConfig = {
model: DEMO_MODEL, // model: DEMO_MODEL,
baseURL: DEMO_BASE_URL, // baseURL: DEMO_BASE_URL,
apiKey: DEMO_API_KEY, // apiKey: DEMO_API_KEY,
model: import.meta.env.LLM_MODEL_NAME ? import.meta.env.LLM_MODEL_NAME : DEMO_MODEL,
baseURL: import.meta.env.LLM_BASE_URL ? import.meta.env.LLM_BASE_URL : DEMO_BASE_URL,
apiKey: import.meta.env.LLM_API_KEY ? import.meta.env.LLM_API_KEY : DEMO_API_KEY,
} }
window.pageAgent = new PageAgent(config) window.pageAgent = new PageAgent(config)
} }
@@ -0,0 +1 @@
system_prompt.md
@@ -92,7 +92,6 @@ Strictly follow these rules while using the browser and navigating the web:
- User can be wrong. If the request of user is not achievable, inappropriate or you do not have enough information or tools to achieve it. Tell user to make a better request. - User can be wrong. If the request of user is not achievable, inappropriate or you do not have enough information or tools to achieve it. Tell user to make a better request.
- Webpage can be broken. All webpages or apps have bugs. Some bug will make it hard for your job. It's encouraged to tell user the problem of current page. Your feedbacks (including failing) are valuable for user. - Webpage can be broken. All webpages or apps have bugs. Some bug will make it hard for your job. It's encouraged to tell user the problem of current page. Your feedbacks (including failing) are valuable for user.
- Trying to hard can be harmful. Repeating some action back and forth or pushing for a complex procedure with little knowledge can cause unwanted result and harmful side-effects. User would rather you to complete the task with a fail. - Trying to hard can be harmful. Repeating some action back and forth or pushing for a complex procedure with little knowledge can cause unwanted result and harmful side-effects. User would rather you to complete the task with a fail.
- If you are not clear about the request or steps. `ask_user` to clarify it.
- If you do not have knowledge for the current webpage or task. You must require user to give specific instructions and detailed steps. - If you do not have knowledge for the current webpage or task. You must require user to give specific instructions and detailed steps.
</capability> </capability>
@@ -120,7 +119,7 @@ Exhibit the following reasoning patterns to successfully achieve the <user_reque
- Analyze all relevant items in <agent_history> and <browser_state> to understand your state. - Analyze all relevant items in <agent_history> and <browser_state> to understand your state.
- Explicitly judge success/failure/uncertainty of the last action. Never assume an action succeeded just because it appears to be executed in your last step in <agent_history>. If the expected change is missing, mark the last action as failed (or uncertain) and plan a recovery. - Explicitly judge success/failure/uncertainty of the last action. Never assume an action succeeded just because it appears to be executed in your last step in <agent_history>. If the expected change is missing, mark the last action as failed (or uncertain) and plan a recovery.
- Analyze whether you are stuck, e.g. when you repeat the same actions multiple times without any progress. Then consider alternative approaches e.g. scrolling for more context or ask user for help. - Analyze whether you are stuck, e.g. when you repeat the same actions multiple times without any progress. Then consider alternative approaches e.g. scrolling for more context or ask user for help.
- `ask_user` for help if you have any difficulty. Users want to be kept in the loop. - Ask user for help if you have any difficulty. Keep user in the loop.
- If you see information relevant to <user_request>, plan saving the information to memory. - If you see information relevant to <user_request>, plan saving the information to memory.
- Always reason about the <user_request>. Make sure to carefully analyze the specific steps and information required. E.g. specific filters, specific form fields, specific information to search. Make sure to always compare the current trajectory with the user request and think carefully if thats how the user requested it. - Always reason about the <user_request>. Make sure to carefully analyze the specific steps and information required. E.g. specific filters, specific form fields, specific information to search. Make sure to always compare the current trajectory with the user request and think carefully if thats how the user requested it.
</reasoning_rules> </reasoning_rules>
+3
View File
@@ -80,6 +80,9 @@ tools.set(
question: zod.string(), question: zod.string(),
}), }),
execute: async function (this: PageAgent, input) { execute: async function (this: PageAgent, input) {
if (!this.panel) {
throw new Error('ask_user tool requires panel to be enabled')
}
const answer = await this.panel.askUser(input.question) const answer = await this.panel.askUser(input.question)
return `✅ Received user answer: ${answer}` return `✅ Received user answer: ${answer}`
}, },
-2
View File
@@ -6,8 +6,6 @@ export interface PageAgentEventMap {
// PageAgent status events // PageAgent status events
// 'agent:execute': { params: { task: string } } // 'agent:execute': { params: { task: string } }
// 'agent:done': { params: { text: string; success: boolean } } // 'agent:done': { params: { text: string; success: boolean } }
// 'agent:paused': { params: undefined }
// 'agent:resumed': { params: undefined }
// 'agent:disposed': { params: undefined } // 'agent:disposed': { params: undefined }
// 'agent:error': { params: { error: string | Error } } // 'agent:error': { params: { error: string | Error } }
+7 -7
View File
@@ -1,9 +1,9 @@
{ {
"extends": "./tsconfig.json", "extends": "./tsconfig.json",
"compilerOptions": { "compilerOptions": {
// @workaround DTS bug // @workaround DTS bug
// dts do not work with monorepo path mapping // dts do not work with monorepo path mapping
// disable path mapping for it // disable path mapping for it
"paths": {} "paths": {}
} }
} }
+22 -22
View File
@@ -1,24 +1,24 @@
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.base.json",
"compilerOptions": { "compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo", "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo",
"noEmit": false, "noEmit": false,
"allowImportingTsExtensions": false, "allowImportingTsExtensions": false,
"baseUrl": ".", "baseUrl": ".",
"outDir": "dist", "outDir": "dist",
"paths": { "paths": {
// //
"@page-agent/llms": ["../llms/src/index.ts"], "@page-agent/llms": ["../llms/src/index.ts"],
"@page-agent/page-controller": ["../page-controller/src/PageController.ts"], "@page-agent/page-controller": ["../page-controller/src/PageController.ts"],
"@page-agent/ui": ["../ui/src/index.ts"] "@page-agent/ui": ["../ui/src/index.ts"]
} }
}, },
"include": ["**/*.ts"], "include": ["**/*.ts"],
"exclude": ["dist", "node_modules"], "exclude": ["dist", "node_modules"],
"references": [ "references": [
// //
{ "path": "../llms" }, { "path": "../llms" },
{ "path": "../page-controller" }, { "path": "../page-controller" },
{ "path": "../ui" } { "path": "../ui" }
] ]
} }
@@ -1,4 +1,5 @@
// @ts-check // @ts-check
import { config as dotenvConfig } from 'dotenv'
import { dirname, resolve } from 'path' import { dirname, resolve } from 'path'
import { fileURLToPath } from 'url' import { fileURLToPath } from 'url'
import { defineConfig } from 'vite' import { defineConfig } from 'vite'
@@ -6,11 +7,14 @@ import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js'
const __dirname = dirname(fileURLToPath(import.meta.url)) const __dirname = dirname(fileURLToPath(import.meta.url))
// Load .env from repo root
dotenvConfig({ path: resolve(__dirname, '../../.env') })
// UMD Bundle for CDN // UMD Bundle for CDN
// - alias all local packages so that they can be build in // - alias all local packages so that they can be build in
// - no external // - no external
// - no d.ts. dts does not work with monorepo aliasing // - no d.ts. dts does not work with monorepo aliasing
export default defineConfig({ export default defineConfig(({ mode }) => ({
plugins: [cssInjectedByJsPlugin({ relativeCSSInjection: true })], plugins: [cssInjectedByJsPlugin({ relativeCSSInjection: true })],
publicDir: false, publicDir: false,
esbuild: { esbuild: {
@@ -25,21 +29,28 @@ export default defineConfig({
}, },
build: { build: {
lib: { lib: {
entry: resolve(__dirname, 'src/umd.ts'), entry: resolve(__dirname, 'src/iife.ts'),
name: 'PageAgent', name: 'PageAgent',
fileName: 'page-agent', fileName: 'page-agent',
formats: ['umd'], formats: ['iife'],
}, },
outDir: resolve(__dirname, 'dist', 'umd'), outDir: resolve(__dirname, 'dist', 'iife'),
cssCodeSplit: true,
minify: false,
rollupOptions: { rollupOptions: {
output: { output: {
// force use .js as extension // force use .js as extension
entryFileNames: 'page-agent.js', entryFileNames: 'page-agent.js',
}, },
onwarn: function (message, handler) {
if (message.code === 'EVAL') return
handler(message)
},
}, },
cssCodeSplit: true,
}, },
define: { define: {
'process.env.NODE_ENV': '"production"', 'import.meta.env.LLM_MODEL_NAME': JSON.stringify(process.env.LLM_MODEL_NAME),
'import.meta.env.LLM_API_KEY': JSON.stringify(process.env.LLM_API_KEY),
'import.meta.env.LLM_BASE_URL': JSON.stringify(process.env.LLM_BASE_URL),
}, },
}) }))
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@page-agent/page-controller", "name": "@page-agent/page-controller",
"version": "0.2.0", "version": "0.2.5",
"type": "module", "type": "module",
"main": "./dist/lib/page-controller.js", "main": "./dist/lib/page-controller.js",
"module": "./dist/lib/page-controller.js", "module": "./dist/lib/page-controller.js",
+87 -6
View File
@@ -287,12 +287,52 @@ export async function scrollVertically(
(document.documentElement as HTMLElement) (document.documentElement as HTMLElement)
if (el === document.scrollingElement || el === document.documentElement || el === document.body) { if (el === document.scrollingElement || el === document.documentElement || el === document.body) {
// Page-level scroll
const scrollBefore = window.scrollY
const scrollMax = document.documentElement.scrollHeight - window.innerHeight
window.scrollBy(0, dy) window.scrollBy(0, dy)
return `✅ Scrolled page by ${dy}px.`
const scrollAfter = window.scrollY
const scrolled = scrollAfter - scrollBefore
if (Math.abs(scrolled) < 1) {
return dy > 0
? `⚠️ Already at the bottom of the page, cannot scroll down further.`
: `⚠️ Already at the top of the page, cannot scroll up further.`
}
const reachedBottom = dy > 0 && scrollAfter >= scrollMax - 1
const reachedTop = dy < 0 && scrollAfter <= 1
if (reachedBottom) return `✅ Scrolled page by ${scrolled}px. Reached the bottom of the page.`
if (reachedTop) return `✅ Scrolled page by ${scrolled}px. Reached the top of the page.`
return `✅ Scrolled page by ${scrolled}px.`
} else { } else {
// Container scroll
const scrollBefore = el!.scrollTop
const scrollMax = el!.scrollHeight - el!.clientHeight
el!.scrollBy({ top: dy, behavior: 'smooth' }) el!.scrollBy({ top: dy, behavior: 'smooth' })
await waitFor(0.1) // Animation playback await waitFor(0.1)
return `✅ Scrolled container (${el!.tagName}) by ${dy}px.`
const scrollAfter = el!.scrollTop
const scrolled = scrollAfter - scrollBefore
if (Math.abs(scrolled) < 1) {
return dy > 0
? `⚠️ Already at the bottom of container (${el!.tagName}), cannot scroll down further.`
: `⚠️ Already at the top of container (${el!.tagName}), cannot scroll up further.`
}
const reachedBottom = dy > 0 && scrollAfter >= scrollMax - 1
const reachedTop = dy < 0 && scrollAfter <= 1
if (reachedBottom)
return `✅ Scrolled container (${el!.tagName}) by ${scrolled}px. Reached the bottom.`
if (reachedTop)
return `✅ Scrolled container (${el!.tagName}) by ${scrolled}px. Reached the top.`
return `✅ Scrolled container (${el!.tagName}) by ${scrolled}px.`
} }
} }
@@ -410,11 +450,52 @@ export async function scrollHorizontally(
(document.documentElement as HTMLElement) (document.documentElement as HTMLElement)
if (el === document.scrollingElement || el === document.documentElement || el === document.body) { if (el === document.scrollingElement || el === document.documentElement || el === document.body) {
// Page-level scroll
const scrollBefore = window.scrollX
const scrollMax = document.documentElement.scrollWidth - window.innerWidth
window.scrollBy(dx, 0) window.scrollBy(dx, 0)
return `✅ Scrolled page horizontally by ${dx}px`
const scrollAfter = window.scrollX
const scrolled = scrollAfter - scrollBefore
if (Math.abs(scrolled) < 1) {
return dx > 0
? `⚠️ Already at the right edge of the page, cannot scroll right further.`
: `⚠️ Already at the left edge of the page, cannot scroll left further.`
}
const reachedRight = dx > 0 && scrollAfter >= scrollMax - 1
const reachedLeft = dx < 0 && scrollAfter <= 1
if (reachedRight)
return `✅ Scrolled page by ${scrolled}px. Reached the right edge of the page.`
if (reachedLeft) return `✅ Scrolled page by ${scrolled}px. Reached the left edge of the page.`
return `✅ Scrolled page horizontally by ${scrolled}px.`
} else { } else {
// Container scroll
const scrollBefore = el!.scrollLeft
const scrollMax = el!.scrollWidth - el!.clientWidth
el!.scrollBy({ left: dx, behavior: 'smooth' }) el!.scrollBy({ left: dx, behavior: 'smooth' })
await waitFor(0.1) // Animation playback await waitFor(0.1)
return `✅ Scrolled container (${el!.tagName}) horizontally by ${dx}px`
const scrollAfter = el!.scrollLeft
const scrolled = scrollAfter - scrollBefore
if (Math.abs(scrolled) < 1) {
return dx > 0
? `⚠️ Already at the right edge of container (${el!.tagName}), cannot scroll right further.`
: `⚠️ Already at the left edge of container (${el!.tagName}), cannot scroll left further.`
}
const reachedRight = dx > 0 && scrollAfter >= scrollMax - 1
const reachedLeft = dx < 0 && scrollAfter <= 1
if (reachedRight)
return `✅ Scrolled container (${el!.tagName}) by ${scrolled}px. Reached the right edge.`
if (reachedLeft)
return `✅ Scrolled container (${el!.tagName}) by ${scrolled}px. Reached the left edge.`
return `✅ Scrolled container (${el!.tagName}) horizontally by ${scrolled}px.`
} }
} }
+7 -7
View File
@@ -1,9 +1,9 @@
{ {
"extends": "./tsconfig.json", "extends": "./tsconfig.json",
"compilerOptions": { "compilerOptions": {
// @workaround DTS bug // @workaround DTS bug
// dts do not work with monorepo path mapping // dts do not work with monorepo path mapping
// disable path mapping for it // disable path mapping for it
"paths": {} "paths": {}
} }
} }
+10 -10
View File
@@ -1,12 +1,12 @@
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.base.json",
"compilerOptions": { "compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo", "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo",
"noEmit": false, "noEmit": false,
"allowImportingTsExtensions": false, "allowImportingTsExtensions": false,
"baseUrl": ".", "baseUrl": ".",
"outDir": "dist" "outDir": "dist"
}, },
"include": ["**/*.ts", "**/*.js"], "include": ["**/*.ts", "**/*.js"],
"exclude": ["dist", "node_modules"] "exclude": ["dist", "node_modules"]
} }
+4
View File
@@ -30,6 +30,10 @@ export default defineConfig({
outDir: resolve(__dirname, 'dist', 'lib'), outDir: resolve(__dirname, 'dist', 'lib'),
rollupOptions: { rollupOptions: {
external: ['@page-agent/*', 'ai-motion'], external: ['@page-agent/*', 'ai-motion'],
onwarn: function (message, handler) {
if (message.code === 'EVAL') return
handler(message)
},
}, },
minify: false, minify: false,
sourcemap: true, sourcemap: true,
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@page-agent/ui", "name": "@page-agent/ui",
"version": "0.2.0", "version": "0.2.5",
"type": "module", "type": "module",
"main": "./dist/lib/page-agent-ui.js", "main": "./dist/lib/page-agent-ui.js",
"module": "./dist/lib/page-agent-ui.js", "module": "./dist/lib/page-agent-ui.js",
+5 -12
View File
@@ -209,18 +209,6 @@
} }
} }
.pauseButton {
font-weight: 600;
&.paused {
background: rgba(34, 197, 94, 0.2); /* 绿色背景表示可以继续 */
color: rgb(34, 197, 94);
&:hover {
background: rgba(34, 197, 94, 0.3);
}
}
}
.stopButton { .stopButton {
background: rgba(239, 68, 68, 0.2); background: rgba(239, 68, 68, 0.2);
color: rgb(255, 41, 41); color: rgb(255, 41, 41);
@@ -364,6 +352,11 @@
background: linear-gradient(135deg, rgba(255, 214, 0, 0.1), rgba(255, 214, 0, 0.05)); background: linear-gradient(135deg, rgba(255, 214, 0, 0.1), rgba(255, 214, 0, 0.05));
} }
&.observation {
border-left-color: rgb(147, 51, 234);
background: linear-gradient(135deg, rgba(147, 51, 234, 0.1), rgba(147, 51, 234, 0.05));
}
/* 突出显示 done 成功结果 */ /* 突出显示 done 成功结果 */
&.doneSuccess { &.doneSuccess {
background: linear-gradient( background: linear-gradient(
+23 -56
View File
@@ -11,8 +11,11 @@ export interface PanelConfig {
language?: SupportedLanguage language?: SupportedLanguage
onExecuteTask: (task: string) => void onExecuteTask: (task: string) => void
onStop: () => void onStop: () => void
onPauseToggle: () => boolean // returns new paused state /**
getPaused: () => boolean * Whether to prompt for next task after task completion
* @default true
*/
promptForNextTask?: boolean
} }
/** /**
@@ -29,6 +32,7 @@ export type PanelUpdate =
| { type: 'completed' } | { type: 'completed' }
| { type: 'toolExecuting'; toolName: string; args: any } | { type: 'toolExecuting'; toolName: string; args: any }
| { type: 'toolCompleted'; toolName: string; args: any; result?: string; duration?: number } | { type: 'toolCompleted'; toolName: string; args: any; result?: string; duration?: number }
| { type: 'observation'; content: string }
/** /**
* Agent control panel * Agent control panel
@@ -39,7 +43,6 @@ export class Panel {
#statusText: HTMLElement #statusText: HTMLElement
#historySection: HTMLElement #historySection: HTMLElement
#expandButton: HTMLElement #expandButton: HTMLElement
#pauseButton: HTMLElement
#stopButton: HTMLElement #stopButton: HTMLElement
#inputSection: HTMLElement #inputSection: HTMLElement
#taskInput: HTMLInputElement #taskInput: HTMLInputElement
@@ -66,7 +69,6 @@ export class Panel {
this.#statusText = this.#wrapper.querySelector(`.${styles.statusText}`)! this.#statusText = this.#wrapper.querySelector(`.${styles.statusText}`)!
this.#historySection = this.#wrapper.querySelector(`.${styles.historySection}`)! this.#historySection = this.#wrapper.querySelector(`.${styles.historySection}`)!
this.#expandButton = this.#wrapper.querySelector(`.${styles.expandButton}`)! this.#expandButton = this.#wrapper.querySelector(`.${styles.expandButton}`)!
this.#pauseButton = this.#wrapper.querySelector(`.${styles.pauseButton}`)!
this.#stopButton = this.#wrapper.querySelector(`.${styles.stopButton}`)! this.#stopButton = this.#wrapper.querySelector(`.${styles.stopButton}`)!
this.#inputSection = this.#wrapper.querySelector(`.${styles.inputSectionWrapper}`)! this.#inputSection = this.#wrapper.querySelector(`.${styles.inputSectionWrapper}`)!
this.#taskInput = this.#wrapper.querySelector(`.${styles.taskInput}`)! this.#taskInput = this.#wrapper.querySelector(`.${styles.taskInput}`)!
@@ -75,6 +77,8 @@ export class Panel {
this.#startHeaderUpdateLoop() this.#startHeaderUpdateLoop()
this.#showInputArea() this.#showInputArea()
this.hide() // Start hidden
} }
/** /**
@@ -120,11 +124,6 @@ export class Panel {
this.#updateStatusIndicator('thinking') this.#updateStatusIndicator('thinking')
this.#updateHistory() this.#updateHistory()
this.#collapse() this.#collapse()
// Reset pause state via callback
if (this.#config.getPaused()) {
this.#config.onPauseToggle()
}
this.#updatePauseButton()
// Reset user input state // Reset user input state
this.#isWaitingForUserAnswer = false this.#isWaitingForUserAnswer = false
this.#userAnswerResolver = null this.#userAnswerResolver = null
@@ -205,6 +204,8 @@ export class Panel {
duration: data.duration, duration: data.duration,
} }
} }
case 'observation':
return { type: 'observation', displayText: data.content }
} }
} }
@@ -277,39 +278,6 @@ export class Panel {
} }
} }
/**
* Toggle pause state
*/
#togglePause(): void {
const paused = this.#config.onPauseToggle()
this.#updatePauseButton()
// Update status display
if (paused) {
this.#statusText.textContent = this.#i18n.t('ui.panel.paused')
this.#updateStatusIndicator('thinking')
} else {
this.#statusText.textContent = this.#i18n.t('ui.panel.continueExecution')
this.#updateStatusIndicator('tool_executing')
}
}
/**
* Update pause button state
*/
#updatePauseButton(): void {
const paused = this.#config.getPaused()
if (paused) {
this.#pauseButton.textContent = '▶'
this.#pauseButton.title = this.#i18n.t('ui.panel.continue')
this.#pauseButton.classList.add(styles.paused)
} else {
this.#pauseButton.textContent = '⏸︎'
this.#pauseButton.title = this.#i18n.t('ui.panel.pause')
this.#pauseButton.classList.remove(styles.paused)
}
}
/** /**
* Stop Agent * Stop Agent
*/ */
@@ -395,13 +363,20 @@ 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 {
const wrapper = document.createElement('div') const wrapper = document.createElement('div')
wrapper.id = 'page-agent-runtime_agent-panel' wrapper.id = 'page-agent-runtime_agent-panel'
wrapper.className = `${styles.wrapper} ${styles.collapsed}` wrapper.className = styles.wrapper
wrapper.setAttribute('data-browser-use-ignore', 'true') wrapper.setAttribute('data-browser-use-ignore', 'true')
wrapper.innerHTML = ` wrapper.innerHTML = `
@@ -426,9 +401,6 @@ export class Panel {
<button class="${styles.controlButton} ${styles.expandButton}" title="${this.#i18n.t('ui.panel.expand')}"> <button class="${styles.controlButton} ${styles.expandButton}" title="${this.#i18n.t('ui.panel.expand')}">
</button> </button>
<button class="${styles.controlButton} ${styles.pauseButton}" title="${this.#i18n.t('ui.panel.pause')}">
⏸︎
</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.stop')}">
X X
</button> </button>
@@ -466,12 +438,6 @@ export class Panel {
this.#toggle() this.#toggle()
}) })
// Pause/continue button
this.#pauseButton.addEventListener('click', (e) => {
e.stopPropagation()
this.#togglePause()
})
// Stop button // Stop button
this.#stopButton.addEventListener('click', (e) => { this.#stopButton.addEventListener('click', (e) => {
e.stopPropagation() e.stopPropagation()
@@ -503,7 +469,6 @@ export class Panel {
#expand(): void { #expand(): void {
this.#isExpanded = true this.#isExpanded = true
this.wrapper.classList.remove(styles.collapsed)
this.wrapper.classList.add(styles.expanded) this.wrapper.classList.add(styles.expanded)
this.#expandButton.textContent = '▲' this.#expandButton.textContent = '▲'
} }
@@ -511,7 +476,6 @@ export class Panel {
#collapse(): void { #collapse(): void {
this.#isExpanded = false this.#isExpanded = false
this.wrapper.classList.remove(styles.expanded) this.wrapper.classList.remove(styles.expanded)
this.wrapper.classList.add(styles.collapsed)
this.#expandButton.textContent = '▼' this.#expandButton.textContent = '▼'
} }
@@ -632,7 +596,7 @@ export class Panel {
typeClass = styles.error typeClass = styles.error
statusIcon = '❌' statusIcon = '❌'
} else if (step.type === 'tool_executing') { } else if (step.type === 'tool_executing') {
statusIcon = '⚙️' statusIcon = '🔨'
} else if (step.type === 'output') { } else if (step.type === 'output') {
typeClass = styles.output typeClass = styles.output
statusIcon = '🤖' statusIcon = '🤖'
@@ -642,6 +606,9 @@ export class Panel {
} else if (step.type === 'retry') { } else if (step.type === 'retry') {
typeClass = styles.retry typeClass = styles.retry
statusIcon = '🔄' statusIcon = '🔄'
} else if (step.type === 'observation') {
typeClass = styles.observation
statusIcon = '👁️'
} else { } else {
statusIcon = '🧠' statusIcon = '🧠'
} }
+10 -2
View File
@@ -6,7 +6,15 @@ export interface Step {
id: string id: string
stepNumber: number stepNumber: number
timestamp: Date timestamp: Date
type: 'thinking' | 'tool_executing' | 'completed' | 'error' | 'output' | 'input' | 'retry' type:
| 'thinking'
| 'tool_executing'
| 'completed'
| 'error'
| 'output'
| 'input'
| 'retry'
| 'observation'
// Tool execution related // Tool execution related
toolName?: string toolName?: string
@@ -18,7 +26,7 @@ export interface Step {
duration?: number duration?: number
} }
export type AgentStatus = 'idle' | 'running' | 'paused' | 'completed' | 'error' export type AgentStatus = 'idle' | 'running' | 'completed' | 'error'
export class UIState { export class UIState {
private steps: Step[] = [] private steps: Step[] = []
-8
View File
@@ -4,17 +4,13 @@ const enUS = {
panel: { panel: {
ready: 'Ready', ready: 'Ready',
thinking: 'Thinking...', thinking: 'Thinking...',
paused: 'Paused',
taskInput: 'Enter new task, describe steps in detail, press Enter to submit', taskInput: 'Enter new task, describe steps in detail, press Enter to submit',
userAnswerPrompt: 'Please answer the question above, press Enter to submit', userAnswerPrompt: 'Please answer the question above, press Enter to submit',
taskTerminated: 'Task terminated', taskTerminated: 'Task terminated',
taskCompleted: 'Task completed', taskCompleted: 'Task completed',
continueExecution: 'Continue execution',
userAnswer: 'User answer: {{input}}', userAnswer: 'User answer: {{input}}',
question: 'Question: {{question}}', question: 'Question: {{question}}',
waitingPlaceholder: 'Waiting for task to start...', waitingPlaceholder: 'Waiting for task to start...',
pause: 'Pause',
continue: 'Continue',
stop: 'Stop', stop: 'Stop',
expand: 'Expand history', expand: 'Expand history',
collapse: 'Collapse history', collapse: 'Collapse history',
@@ -54,17 +50,13 @@ const zhCN = {
panel: { panel: {
ready: '准备就绪', ready: '准备就绪',
thinking: '正在思考...', thinking: '正在思考...',
paused: '暂停中,稍后',
taskInput: '输入新任务,详细描述步骤,回车提交', taskInput: '输入新任务,详细描述步骤,回车提交',
userAnswerPrompt: '请回答上面问题,回车提交', userAnswerPrompt: '请回答上面问题,回车提交',
taskTerminated: '任务已终止', taskTerminated: '任务已终止',
taskCompleted: '任务结束', taskCompleted: '任务结束',
continueExecution: '继续执行',
userAnswer: '用户回答: {{input}}', userAnswer: '用户回答: {{input}}',
question: '询问: {{question}}', question: '询问: {{question}}',
waitingPlaceholder: '等待任务开始...', waitingPlaceholder: '等待任务开始...',
pause: '暂停',
continue: '继续',
stop: '终止', stop: '终止',
expand: '展开历史', expand: '展开历史',
collapse: '收起历史', collapse: '收起历史',
+1 -1
View File
@@ -2,4 +2,4 @@ This is the CSS implementation of ai-motion.
Easy to use but Terrible performance. Causing full screen glitching in some browsers. Easy to use but Terrible performance. Causing full screen glitching in some browsers.
Use it only in a small area. Use it only in a small area.
+7 -8
View File
@@ -1,10 +1,9 @@
{ {
"extends": "./tsconfig.json", "extends": "./tsconfig.json",
"compilerOptions": { "compilerOptions": {
// @workaround DTS bug // @workaround DTS bug
// dts do not work with monorepo path mapping // dts do not work with monorepo path mapping
// disable path mapping for it // disable path mapping for it
"paths": {} "paths": {}
} }
} }
+10 -11
View File
@@ -1,13 +1,12 @@
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.base.json",
"compilerOptions": { "compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo", "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo",
"noEmit": false, "noEmit": false,
"allowImportingTsExtensions": false, "allowImportingTsExtensions": false,
"baseUrl": ".", "baseUrl": ".",
"outDir": "dist" "outDir": "dist"
}, },
"include": ["**/*.ts", "**/*.js"], "include": ["**/*.ts", "**/*.js"],
"exclude": ["dist", "node_modules"] "exclude": ["dist", "node_modules"]
} }
-1
View File
@@ -1 +0,0 @@
# Landing Page & Docs
+22 -22
View File
@@ -1,24 +1,24 @@
{ {
"$schema": "https://ui.shadcn.com/schema.json", "$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york", "style": "new-york",
"rsc": false, "rsc": false,
"tsx": true, "tsx": true,
"tailwind": { "tailwind": {
"config": "", "config": "",
"css": "src/index.css", "css": "src/index.css",
"baseColor": "neutral", "baseColor": "neutral",
"cssVariables": true, "cssVariables": true,
"prefix": "" "prefix": ""
}, },
"iconLibrary": "lucide", "iconLibrary": "lucide",
"aliases": { "aliases": {
"components": "@/components", "components": "@/components",
"utils": "@/lib/utils", "utils": "@/lib/utils",
"ui": "@/components/ui", "ui": "@/components/ui",
"lib": "@/lib", "lib": "@/lib",
"hooks": "@/hooks" "hooks": "@/hooks"
}, },
"registries": { "registries": {
"@magicui": "https://magicui.design/r/{name}.json" "@magicui": "https://magicui.design/r/{name}.json"
} }
} }
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "@page-agent/website", "name": "@page-agent/website",
"private": true, "private": true,
"version": "0.2.0", "version": "0.2.5",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite --host 0.0.0.0", "dev": "vite --host 0.0.0.0",
+4 -4
View File
@@ -22,7 +22,7 @@ export default function Header() {
<div className="flex items-center justify-between gap-2"> <div className="flex items-center justify-between gap-2">
{/* Logo */} {/* Logo */}
<Link <Link
href="/" href="~/"
className="flex items-center gap-2 sm:gap-3 group shrink-0" className="flex items-center gap-2 sm:gap-3 group shrink-0"
aria-label={t('header.logo_alt')} aria-label={t('header.logo_alt')}
onClick={() => setMobileMenuOpen(false)} onClick={() => setMobileMenuOpen(false)}
@@ -55,7 +55,7 @@ export default function Header() {
aria-label="Mobile navigation" aria-label="Mobile navigation"
> >
<Link <Link
href="/docs/introduction/overview" href="~/docs/introduction/overview"
className="p-2 rounded-lg text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-blue-600 dark:hover:text-blue-400 transition-colors duration-200 shrink-0" className="p-2 rounded-lg text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-blue-600 dark:hover:text-blue-400 transition-colors duration-200 shrink-0"
aria-label={t('header.nav_docs')} aria-label={t('header.nav_docs')}
> >
@@ -89,7 +89,7 @@ export default function Header() {
{import.meta.env.VERSION} {import.meta.env.VERSION}
</span> </span>
<Link <Link
href="/docs/introduction/overview" href="~/docs/introduction/overview"
className="flex items-center gap-1.5 text-gray-600 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 transition-colors duration-200" className="flex items-center gap-1.5 text-gray-600 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 transition-colors duration-200"
> >
<BookOpen className="w-4 h-4" /> <BookOpen className="w-4 h-4" />
@@ -137,7 +137,7 @@ export default function Header() {
role="navigation" role="navigation"
> >
<Link <Link
href="/docs/introduction/overview" href="~/docs/introduction/overview"
className="flex items-center gap-2 px-3 py-2 rounded-lg text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-blue-600 dark:hover:text-blue-400 transition-colors duration-200" className="flex items-center gap-2 px-3 py-2 rounded-lg text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-blue-600 dark:hover:text-blue-400 transition-colors duration-200"
onClick={() => setMobileMenuOpen(false)} onClick={() => setMobileMenuOpen(false)}
> >
+3 -4
View File
@@ -28,9 +28,9 @@ pages/i18n/
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
function MyComponent() { function MyComponent() {
const { t } = useTranslation('common') // 指定命名空间 const { t } = useTranslation('common') // 指定命名空间
return <h1>{t('header.nav_docs')}</h1> return <h1>{t('header.nav_docs')}</h1>
} }
``` ```
@@ -71,4 +71,3 @@ t('common:header.nav_docs')
- [ ] 文档页翻译(`docs.json` - [ ] 文档页翻译(`docs.json`
- [ ] DocsLayout 导航结构国际化 - [ ] DocsLayout 导航结构国际化
- [ ] 404 页面国际化 - [ ] 404 页面国际化
+10 -10
View File
@@ -1,6 +1,5 @@
/* eslint-disable react-dom/no-dangerously-set-innerhtml */ /* eslint-disable react-dom/no-dangerously-set-innerhtml */
import { Bot, Box, MessageSquare, PlayCircle, Shield, Sparkles, Users, Zap } from 'lucide-react' import { Bot, Box, MessageSquare, PlayCircle, Shield, Sparkles, Users, Zap } from 'lucide-react'
import { PageAgent } from 'page-agent'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { Link, useSearchParams } from 'wouter' import { Link, useSearchParams } from 'wouter'
@@ -59,13 +58,12 @@ export default function HomePage() {
const handleExecute = async () => { const handleExecute = async () => {
if (!task.trim()) return if (!task.trim()) return
let pageAgent: PageAgent
const win = window as any const win = window as any
if (win.pageAgent && !win.pageAgent.disposed) { // Lazy load PageAgent only when user clicks execute
pageAgent = win.pageAgent if (!win.pageAgent || win.pageAgent.disposed) {
} else { const { PageAgent } = await import('page-agent')
pageAgent = new PageAgent({ win.pageAgent = new PageAgent({
// 把 react 根元素排除掉,挂了很多冒泡时间导致假阳 // 把 react 根元素排除掉,挂了很多冒泡时间导致假阳
interactiveBlacklist: [document.getElementById('root')!], interactiveBlacklist: [document.getElementById('root')!],
language: i18n.language as any, language: i18n.language as any,
@@ -91,12 +89,14 @@ 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,
}) })
win.pageAgent = pageAgent
} }
const result = await pageAgent.execute(task) const result = await win.pageAgent.execute(task)
console.log(result) console.log(result)
} }
@@ -107,7 +107,7 @@ export default function HomePage() {
{/* Hero Section */} {/* Hero Section */}
<main id="main-content"> <main id="main-content">
<section <section
className="relative px-6 py-22 lg:py-28 overflow-hidden" className="relative px-6 py-22 pb-18 lg:py-28 overflow-hidden"
aria-labelledby="hero-heading" aria-labelledby="hero-heading"
> >
<div className="max-w-7xl mx-auto text-center"> <div className="max-w-7xl mx-auto text-center">
+13 -13
View File
@@ -24,38 +24,38 @@ export default function DocsLayout({ children }: DocsLayoutProps) {
{ {
title: t('nav.introduction'), title: t('nav.introduction'),
items: [ items: [
{ title: t('nav.overview'), path: '/docs/introduction/overview' }, { title: t('nav.overview'), path: '/introduction/overview' },
{ title: t('nav.quick_start'), path: '/docs/introduction/quick-start' }, { title: t('nav.quick_start'), path: '/introduction/quick-start' },
{ title: t('nav.limitations'), path: '/docs/introduction/limitations' }, { title: t('nav.limitations'), path: '/introduction/limitations' },
], ],
}, },
{ {
title: t('nav.features'), title: t('nav.features'),
items: [ items: [
{ title: t('nav.models'), path: '/docs/features/models' }, { title: t('nav.models'), path: '/features/models' },
{ title: t('nav.custom_tools'), path: '/docs/features/custom-tools' }, { title: t('nav.custom_tools'), path: '/features/custom-tools' },
{ title: t('nav.knowledge_injection'), path: '/docs/features/custom-instructions' }, { title: t('nav.knowledge_injection'), path: '/features/custom-instructions' },
{ title: t('nav.data_masking'), path: '/docs/features/data-masking' }, { title: t('nav.data_masking'), path: '/features/data-masking' },
], ],
}, },
{ {
title: t('nav.integration'), title: t('nav.integration'),
items: [ items: [
{ title: t('nav.configuration'), path: '/docs/integration/configuration' }, { title: t('nav.configuration'), path: '/integration/configuration' },
{ title: t('nav.third_party_agent'), path: '/docs/integration/third-party-agent' }, { title: t('nav.third_party_agent'), path: '/integration/third-party-agent' },
{ title: t('nav.cdn_setup'), path: '/docs/integration/cdn-setup' }, { title: t('nav.cdn_setup'), path: '/integration/cdn-setup' },
{ {
title: '🚧 ' + t('nav.security_permissions'), title: '🚧 ' + t('nav.security_permissions'),
path: '/docs/integration/security-permissions', path: '/integration/security-permissions',
}, },
{ title: '🚧 ' + t('nav.best_practices'), path: '/docs/integration/best-practices' }, { title: '🚧 ' + t('nav.best_practices'), path: '/integration/best-practices' },
], ],
}, },
] ]
return ( return (
<div className="max-w-7xl mx-auto px-6 py-8 overflow-x-auto"> <div className="max-w-7xl mx-auto px-6 py-8 overflow-x-auto">
<div className="flex gap-8 min-w-[900px]"> <div className="flex gap-8 min-w-225">
{/* Sidebar */} {/* Sidebar */}
<aside className="w-64 shrink-0" role="complementary" aria-label="文档导航"> <aside className="w-64 shrink-0" role="complementary" aria-label="文档导航">
<div className="sticky"> <div className="sticky">
+110
View File
@@ -0,0 +1,110 @@
import { Suspense } from 'react'
import { Route, Switch } from 'wouter'
import Header from '../../components/Header'
import DocsLayout from './Layout'
import Instructions from './features/custom-instructions/page'
// Features
import CustomTools from './features/custom-tools/page'
import DataMasking from './features/data-masking/page'
import Models from './features/models/page'
import BestPractices from './integration/best-practices/page'
// Integration
import CdnSetup from './integration/cdn-setup/page'
import Configuration from './integration/configuration/page'
import SecurityPermissions from './integration/security-permissions/page'
import ThirdPartyAgent from './integration/third-party-agent/page'
import Limitations from './introduction/limitations/page'
// Introduction
import Overview from './introduction/overview/page'
import QuickStart from './introduction/quick-start/page'
function DocsPage({ children }: { children: React.ReactNode }) {
return (
<div className="min-h-screen bg-white dark:bg-gray-900">
<Header />
<DocsLayout>
<Suspense>{children}</Suspense>
</DocsLayout>
</div>
)
}
export default function DocsRouter() {
return (
<Switch>
{/* Introduction */}
<Route path="/introduction/overview">
<DocsPage>
<Overview />
</DocsPage>
</Route>
<Route path="/introduction/quick-start">
<DocsPage>
<QuickStart />
</DocsPage>
</Route>
<Route path="/introduction/limitations">
<DocsPage>
<Limitations />
</DocsPage>
</Route>
{/* Features */}
<Route path="/features/custom-tools">
<DocsPage>
<CustomTools />
</DocsPage>
</Route>
<Route path="/features/data-masking">
<DocsPage>
<DataMasking />
</DocsPage>
</Route>
<Route path="/features/custom-instructions">
<DocsPage>
<Instructions />
</DocsPage>
</Route>
<Route path="/features/models">
<DocsPage>
<Models />
</DocsPage>
</Route>
{/* Integration */}
<Route path="/integration/cdn-setup">
<DocsPage>
<CdnSetup />
</DocsPage>
</Route>
<Route path="/integration/security-permissions">
<DocsPage>
<SecurityPermissions />
</DocsPage>
</Route>
<Route path="/integration/configuration">
<DocsPage>
<Configuration />
</DocsPage>
</Route>
<Route path="/integration/best-practices">
<DocsPage>
<BestPractices />
</DocsPage>
</Route>
<Route path="/integration/third-party-agent">
<DocsPage>
<ThirdPartyAgent />
</DocsPage>
</Route>
{/* Default redirect or 404 */}
<Route path="/docs">
<DocsPage>
<Overview />
</DocsPage>
</Route>
</Switch>
)
}
@@ -28,15 +28,17 @@ export default function Configuration() {
className="mb-4" className="mb-4"
language="typescript" language="typescript"
code={`interface LLMConfig { code={`interface LLMConfig {
baseURL?: string baseURL: string
apiKey?: string apiKey: string
model?: string model: string
temperature?: number temperature?: number
maxRetries?: number maxRetries?: number
/** /**
* Custom fetch function for LLM API requests. * Custom fetch function for LLM API requests.
* Use this to customize headers, credentials, proxy, etc. * Use this to customize headers, credentials, proxy, etc.
* The response should follow OpenAI API format.
*/ */
customFetch?: typeof globalThis.fetch customFetch?: typeof globalThis.fetch
}`} }`}
@@ -59,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>
@@ -71,12 +94,12 @@ export default function Configuration() {
getPageInstructions?: (url: string) => string | undefined | null getPageInstructions?: (url: string) => string | undefined | null
} }
// Lifecycle hooks // Lifecycle hooks (with \`this\` bound to PageAgent instance)
onBeforeStep?: (stepCnt: number) => Promise<void> | void onBeforeStep?: (this: PageAgent, stepCnt: number) => Promise<void> | void
onAfterStep?: (stepCnt: number, history: AgentHistory[]) => Promise<void> | void onAfterStep?: (this: PageAgent, stepCnt: number, history: HistoryEvent[]) => Promise<void> | void
onBeforeTask?: () => Promise<void> | void onBeforeTask?: (this: PageAgent) => Promise<void> | void
onAfterTask?: (result: ExecutionResult) => Promise<void> | void onAfterTask?: (this: PageAgent, result: ExecutionResult) => Promise<void> | void
onDispose?: (reason?: string) => void onDispose?: (this: PageAgent, reason?: string) => void
/** /**
* Transform page content before sending to LLM. * Transform page content before sending to LLM.
@@ -103,7 +126,7 @@ export default function Configuration() {
<CodeEditor <CodeEditor
className="mb-4" className="mb-4"
language="typescript" language="typescript"
code={`interface PageControllerConfig { code={`interface DomConfig {
/** Elements to exclude from interaction */ /** Elements to exclude from interaction */
interactiveBlacklist?: (Element | (() => Element))[] interactiveBlacklist?: (Element | (() => Element))[]
@@ -118,15 +141,13 @@ export default function Configuration() {
/** Highlight label opacity (0-1) */ /** Highlight label opacity (0-1) */
highlightLabelOpacity?: number highlightLabelOpacity?: number
}
/** Viewport expansion in pixels (-1 for full page) */ interface PageControllerConfig extends DomConfig {
/** Viewport expansion in pixels */
viewportExpansion?: number viewportExpansion?: number
/** /** Enable visual mask overlay during operations (default: false) */
* Enable visual mask overlay during automation.
* Blocks user interaction while agent is running.
* Default: false for PageController, true for PageAgent.
*/
enableMask?: boolean enableMask?: boolean
}`} }`}
/> />
@@ -140,6 +161,39 @@ export default function Configuration() {
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 }
} }
} }
@@ -22,13 +22,6 @@ export default function Overview() {
{/* Status Badges */} {/* Status Badges */}
<div className="flex flex-wrap gap-2 items-center"> <div className="flex flex-wrap gap-2 items-center">
<a
href="https://www.npmjs.com/package/page-agent"
target="_blank"
rel="noopener noreferrer"
>
<img src="https://badge.fury.io/js/page-agent.svg" alt="npm version" />
</a>
<a href="https://opensource.org/licenses/MIT" target="_blank" rel="noopener noreferrer"> <a href="https://opensource.org/licenses/MIT" target="_blank" rel="noopener noreferrer">
<img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="MIT License" /> <img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="MIT License" />
</a> </a>
+24 -138
View File
@@ -1,148 +1,34 @@
import { Suspense, lazy } from 'react'
import { Route, Switch } from 'wouter' import { Route, Switch } from 'wouter'
import Header from './components/Header' // Lazy load pages
import HomePage from './pages/Home' const HomePage = lazy(() => import('./pages/Home'))
import DocsLayout from './pages/docs/Layout' const DocsPages = lazy(() => import('./pages/docs/index'))
// Features pages
import Instructions from './pages/docs/features/custom-instructions/page'
import CustomTools from './pages/docs/features/custom-tools/page'
import DataMasking from './pages/docs/features/data-masking/page'
import Models from './pages/docs/features/models/page'
// Integration pages
import BestPractices from './pages/docs/integration/best-practices/page'
import CdnSetup from './pages/docs/integration/cdn-setup/page'
import Configuration from './pages/docs/integration/configuration/page'
import SecurityPermissions from './pages/docs/integration/security-permissions/page'
import ThirdPartyAgent from './pages/docs/integration/third-party-agent/page'
// Introduction pages
import Limitations from './pages/docs/introduction/limitations/page'
import Overview from './pages/docs/introduction/overview/page'
import QuickStart from './pages/docs/introduction/quick-start/page'
export default function Router() { export default function Router() {
return ( return (
<Switch> <Suspense>
{/* Home page */} <Switch>
<Route path="/" component={HomePage} /> {/* Home page */}
<Route path="/">
<HomePage />
</Route>
{/* Documentation pages with layout */} {/* All docs pages */}
<Route path="/docs/introduction/overview"> <Route path="/docs" nest>
<div className="min-h-screen bg-white dark:bg-gray-900"> <DocsPages />
<Header /> </Route>
<DocsLayout>
<Overview />
</DocsLayout>
</div>
</Route>
<Route path="/docs/introduction/quick-start"> {/* 404 */}
<div className="min-h-screen bg-white dark:bg-gray-900"> <Route>
<Header /> <div className="min-h-screen bg-white dark:bg-gray-900 flex items-center justify-center">
<DocsLayout> <div className="text-center">
<QuickStart /> <h1 className="text-4xl font-bold mb-4 text-gray-900 dark:text-white">404</h1>
</DocsLayout> <p className="text-xl text-gray-600 dark:text-gray-300"></p>
</div> </div>
</Route>
<Route path="/docs/introduction/limitations">
<div className="min-h-screen bg-white dark:bg-gray-900">
<Header />
<DocsLayout>
<Limitations />
</DocsLayout>
</div>
</Route>
<Route path="/docs/features/custom-tools">
<div className="min-h-screen bg-white dark:bg-gray-900">
<Header />
<DocsLayout>
<CustomTools />
</DocsLayout>
</div>
</Route>
<Route path="/docs/features/data-masking">
<div className="min-h-screen bg-white dark:bg-gray-900">
<Header />
<DocsLayout>
<DataMasking />
</DocsLayout>
</div>
</Route>
<Route path="/docs/features/custom-instructions">
<div className="min-h-screen bg-white dark:bg-gray-900">
<Header />
<DocsLayout>
<Instructions />
</DocsLayout>
</div>
</Route>
<Route path="/docs/features/models">
<div className="min-h-screen bg-white dark:bg-gray-900">
<Header />
<DocsLayout>
<Models />
</DocsLayout>
</div>
</Route>
<Route path="/docs/integration/cdn-setup">
<div className="min-h-screen bg-white dark:bg-gray-900">
<Header />
<DocsLayout>
<CdnSetup />
</DocsLayout>
</div>
</Route>
<Route path="/docs/integration/security-permissions">
<div className="min-h-screen bg-white dark:bg-gray-900">
<Header />
<DocsLayout>
<SecurityPermissions />
</DocsLayout>
</div>
</Route>
<Route path="/docs/integration/configuration">
<div className="min-h-screen bg-white dark:bg-gray-900">
<Header />
<DocsLayout>
<Configuration />
</DocsLayout>
</div>
</Route>
<Route path="/docs/integration/best-practices">
<div className="min-h-screen bg-white dark:bg-gray-900">
<Header />
<DocsLayout>
<BestPractices />
</DocsLayout>
</div>
</Route>
<Route path="/docs/integration/third-party-agent">
<div className="min-h-screen bg-white dark:bg-gray-900">
<Header />
<DocsLayout>
<ThirdPartyAgent />
</DocsLayout>
</div>
</Route>
{/* 404 page */}
<Route>
<div className="min-h-screen bg-white dark:bg-gray-900 flex items-center justify-center">
<div className="text-center">
<h1 className="text-4xl font-bold mb-4 text-gray-900 dark:text-white">404</h1>
<p className="text-xl text-gray-600 dark:text-gray-300"></p>
</div> </div>
</div> </Route>
</Route> </Switch>
</Switch> </Suspense>
) )
} }
+26 -26
View File
@@ -1,29 +1,29 @@
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.base.json",
"compilerOptions": { "compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo", "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo",
"noEmit": false, "noEmit": false,
"allowImportingTsExtensions": false, "allowImportingTsExtensions": false,
"baseUrl": ".", "baseUrl": ".",
"outDir": "dist", "outDir": "dist",
"paths": { "paths": {
// Self root // Self root
"@/*": ["src/*"], "@/*": ["src/*"],
// Simplified monorepo solution (raw npm workspace with hoisting) // Simplified monorepo solution (raw npm workspace with hoisting)
"page-agent": ["../page-agent/src/PageAgent.ts"], "page-agent": ["../page-agent/src/PageAgent.ts"],
"@page-agent/llms": ["../llms/src/index.ts"], "@page-agent/llms": ["../llms/src/index.ts"],
"@page-agent/page-controller": ["../page-controller/src/PageController.ts"], "@page-agent/page-controller": ["../page-controller/src/PageController.ts"],
"@page-agent/ui": ["../ui/src/index.ts"] "@page-agent/ui": ["../ui/src/index.ts"]
} }
}, },
"include": ["**/*.ts", "**/*.tsx"], "include": ["**/*.ts", "**/*.tsx"],
"exclude": ["dist", "node_modules"], "exclude": ["dist", "node_modules"],
"references": [ "references": [
// //
{ "path": "../llms" }, { "path": "../llms" },
{ "path": "../page-agent" }, { "path": "../page-agent" },
{ "path": "../page-controller" }, { "path": "../page-controller" },
{ "path": "../ui" } { "path": "../ui" }
] ]
} }
+17 -5
View File
@@ -16,10 +16,20 @@ const pageAgentPkg = JSON.parse(
dotenvConfig({ path: resolve(__dirname, '../../.env') }) dotenvConfig({ path: resolve(__dirname, '../../.env') })
// Website Config (React Documentation Site) // Website Config (React Documentation Site)
export default defineConfig({ export default defineConfig(({ mode }) => ({
base: './', base: './',
clearScreen: false, clearScreen: false,
plugins: [react(), tailwindcss()], plugins: [react(), tailwindcss()],
build: {
chunkSizeWarningLimit: 2000,
cssCodeSplit: true,
rollupOptions: {
onwarn: function (message, handler) {
if (message.code === 'EVAL') return
handler(message)
},
},
},
resolve: { resolve: {
alias: { alias: {
// Self root // Self root
@@ -33,9 +43,11 @@ export default defineConfig({
}, },
}, },
define: { define: {
'import.meta.env.LLM_MODEL_NAME': JSON.stringify(process.env.LLM_MODEL_NAME), ...(mode === 'development' && {
'import.meta.env.LLM_API_KEY': JSON.stringify(process.env.LLM_API_KEY), 'import.meta.env.LLM_MODEL_NAME': JSON.stringify(process.env.LLM_MODEL_NAME),
'import.meta.env.LLM_BASE_URL': JSON.stringify(process.env.LLM_BASE_URL), 'import.meta.env.LLM_API_KEY': JSON.stringify(process.env.LLM_API_KEY),
'import.meta.env.LLM_BASE_URL': JSON.stringify(process.env.LLM_BASE_URL),
}),
'import.meta.env.VERSION': JSON.stringify(pageAgentPkg.version), 'import.meta.env.VERSION': JSON.stringify(pageAgentPkg.version),
}, },
}) }))
+41 -41
View File
@@ -1,47 +1,47 @@
{ {
"compilerOptions": { "compilerOptions": {
"composite": true, "composite": true,
"target": "ES2024", "target": "ES2024",
"useDefineForClassFields": true, "useDefineForClassFields": true,
"lib": ["ES2024", "DOM", "DOM.Iterable"], "lib": ["ES2024", "DOM", "DOM.Iterable"],
"module": "ESNext", "module": "ESNext",
"skipLibCheck": true, "skipLibCheck": true,
"allowJs": true, "allowJs": true,
// "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo", // "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo",
// "baseUrl": "src", // "baseUrl": "src",
"baseUrl": ".", "baseUrl": ".",
"outDir": "dist", "outDir": "dist",
// "incremental": true, // "incremental": true,
/* Bundler mode */ /* Bundler mode */
"moduleResolution": "bundler", "moduleResolution": "bundler",
"verbatimModuleSyntax": false, "verbatimModuleSyntax": false,
"noEmit": true, "noEmit": true,
"jsx": "react-jsx", "jsx": "react-jsx",
"allowImportingTsExtensions": true, "allowImportingTsExtensions": true,
/* Linting */ /* Linting */
"strict": true, "strict": true,
"noUnusedLocals": false, "noUnusedLocals": false,
"noUnusedParameters": false, "noUnusedParameters": false,
"erasableSyntaxOnly": true, "erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true "noUncheckedSideEffectImports": true
// "paths": { // "paths": {
// // Simplified monorepo solution (raw npm workspace with hoisting) // // Simplified monorepo solution (raw npm workspace with hoisting)
// "@page-agent/page-controller": ["./packages/page-controller/src/PageController.ts"], // "@page-agent/page-controller": ["./packages/page-controller/src/PageController.ts"],
// "page-agent": ["./packages/page-agent/src/PageAgent.ts"] // "page-agent": ["./packages/page-agent/src/PageAgent.ts"]
// } // }
} }
// "references": [ // "references": [
// { "path": "./packages/page-controller" }, // { "path": "./packages/page-controller" },
// { "path": "./packages/page-agent" }, // { "path": "./packages/page-agent" },
// { "path": "./packages/website" } // { "path": "./packages/website" }
// ], // ],
// "include": ["packages/*/src/**/*.ts", "packages/*/src/**/*.tsx"], // "include": ["packages/*/src/**/*.ts", "packages/*/src/**/*.tsx"],
// "exclude": ["node_modules", "dist", "packages/*/dist"] // "exclude": ["node_modules", "dist", "packages/*/dist"]
// "files": ["env.d.ts"] // "files": ["env.d.ts"]
// "files": [] // "files": []
} }
+10 -10
View File
@@ -1,12 +1,12 @@
{ {
"extends": "./tsconfig.base.json", "extends": "./tsconfig.base.json",
"references": [ "references": [
{ "path": "./packages/page-controller" }, { "path": "./packages/page-controller" },
{ "path": "./packages/ui" }, { "path": "./packages/ui" },
{ "path": "./packages/llms" }, { "path": "./packages/llms" },
{ "path": "./packages/page-agent" }, { "path": "./packages/page-agent" },
{ "path": "./packages/website" } { "path": "./packages/website" }
], ],
"include": ["packages/*/src/**/*.ts", "packages/*/src/**/*.tsx"], "include": ["packages/*/src/**/*.ts", "packages/*/src/**/*.tsx"],
"exclude": ["node_modules", "dist", "packages/*/dist"] "exclude": ["node_modules", "dist", "packages/*/dist"]
} }