Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| af36f43104 | |||
| b5f260207c | |||
| 9075204c29 | |||
| ce3a80bc53 | |||
| 64c36f67bb | |||
| 78d4919289 | |||
| 1af2607cca | |||
| ab713f3d3a | |||
| 05cc4f36c8 | |||
| 54e9bdad7d | |||
| 0fc88c0080 | |||
| 02f97ef2fa | |||
| 0853ffbbc6 | |||
| 60ac6da09f | |||
| 11a9fb1181 | |||
| 3762d0f1e0 | |||
| ebbe92f7ca | |||
| 9f239b2de3 | |||
| 2410c27b55 | |||
| 6d6c82648c | |||
| f41119c1ad | |||
| 9e6e1b996c | |||
| 1ab23e181f | |||
| a1b485691f | |||
| 694e05b800 | |||
| b761795c1a | |||
| 8cc54983aa | |||
| 59a599285e | |||
| 2f8c2b395c | |||
| 28f836674c | |||
| 6f01632231 | |||
| 41123ac561 | |||
| c8bf0dd20f | |||
| ec2b744bab | |||
| eab9cf64e8 | |||
| 42130d6f1d | |||
| c0ce6e7d14 | |||
| 8f5f98f889 | |||
| ee4719cdcf | |||
| b4c82b7833 | |||
| aefc3cfb89 | |||
| 7f2b8c2766 | |||
| 62e24a3050 | |||
| 4f6249a252 | |||
| 62519416bb | |||
| a1e70e663d | |||
| 338e317be1 | |||
| 9a66d5205f | |||
| 7d45bf6598 |
@@ -58,4 +58,4 @@ body:
|
||||
- label: I have read the Code of Conduct. / 我已阅读行为准则。
|
||||
required: true
|
||||
validations:
|
||||
required: true
|
||||
required: true
|
||||
|
||||
@@ -44,4 +44,4 @@ body:
|
||||
- label: I have read the CODE_OF_CONDUCT.md and CONTRIBUTING.md. / 我已阅读行为准则。
|
||||
required: true
|
||||
validations:
|
||||
required: true
|
||||
required: true
|
||||
|
||||
@@ -6,7 +6,7 @@ on:
|
||||
- 'v*'
|
||||
|
||||
permissions:
|
||||
id-token: write # Required for OIDC
|
||||
id-token: write # Required for OIDC
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
# Ensure npm 11.5.1 or later is installed
|
||||
- name: Update npm
|
||||
run: npm install -g npm@latest
|
||||
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
@@ -32,4 +32,4 @@ jobs:
|
||||
run: npm run build:libs
|
||||
|
||||
- name: Publish all public packages
|
||||
run: npm publish --workspaces --access public
|
||||
run: npm publish --workspaces --access public
|
||||
|
||||
Vendored
+1
-1
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"recommendations": ["dbaeumer.vscode-eslint", "prettier.prettier-vscode"]
|
||||
"recommendations": ["dbaeumer.vscode-eslint", "prettier.prettier-vscode"]
|
||||
}
|
||||
|
||||
Vendored
+27
-26
@@ -1,28 +1,29 @@
|
||||
{
|
||||
"editor.fontLigatures": true,
|
||||
"cSpell.words": [
|
||||
"deepseek",
|
||||
"HITL",
|
||||
"innerhtml",
|
||||
"llms",
|
||||
"opensource",
|
||||
"qwen",
|
||||
"retryable",
|
||||
"shadcn",
|
||||
"wouter"
|
||||
],
|
||||
"markdownlint.config": {
|
||||
// "comment": "Relaxed rules",
|
||||
"default": true,
|
||||
"whitespace": false,
|
||||
"line_length": false,
|
||||
"ul-indent": false,
|
||||
"no-inline-html": false,
|
||||
"no-bare-urls": false,
|
||||
"fenced-code-language": false,
|
||||
"first-line-h1": false,
|
||||
"block-spacing": false,
|
||||
"blanks-around-lists": false,
|
||||
"ol-prefix": false
|
||||
}
|
||||
"editor.fontLigatures": true,
|
||||
"cSpell.words": [
|
||||
"deepseek",
|
||||
"HITL",
|
||||
"innerhtml",
|
||||
"llms",
|
||||
"onwarn",
|
||||
"opensource",
|
||||
"qwen",
|
||||
"retryable",
|
||||
"shadcn",
|
||||
"wouter"
|
||||
],
|
||||
"markdownlint.config": {
|
||||
// "comment": "Relaxed rules",
|
||||
"default": true,
|
||||
"whitespace": false,
|
||||
"line_length": false,
|
||||
"ul-indent": false,
|
||||
"no-inline-html": false,
|
||||
"no-bare-urls": false,
|
||||
"fenced-code-language": false,
|
||||
"first-line-h1": false,
|
||||
"block-spacing": false,
|
||||
"blanks-around-lists": false,
|
||||
"ol-prefix": false
|
||||
}
|
||||
}
|
||||
|
||||
+61
-51
@@ -7,16 +7,16 @@ Thank you for your interest in contributing to Page-Agent! We welcome contributi
|
||||
### Development Setup
|
||||
|
||||
1. **Prerequisites**
|
||||
- `node.js >= 20` with `npm >= 10`
|
||||
- An editor that supports `ts/eslint/prettier`
|
||||
- Make sure `eslint`, `prettier` and `commitlint` work well
|
||||
- `node.js >= 20` with `npm >= 10`
|
||||
- An editor that supports `ts/eslint/prettier`
|
||||
- Make sure `eslint`, `prettier` and `commitlint` work well
|
||||
|
||||
2. **Setup**
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm start # Start demo and documentation site
|
||||
```
|
||||
```bash
|
||||
npm ci
|
||||
npm start # Start demo and documentation site
|
||||
```
|
||||
|
||||
### 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
|
||||
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 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
|
||||
|
||||
@@ -44,40 +44,39 @@ We use a simplified monorepo solution with `native npm-workspace + ts reference
|
||||
|
||||
1. **Fork and Clone**
|
||||
|
||||
```bash
|
||||
git clone https://github.com/your-username/page-agent.git
|
||||
cd page-agent
|
||||
```
|
||||
```bash
|
||||
git clone https://github.com/your-username/page-agent.git
|
||||
cd page-agent
|
||||
```
|
||||
|
||||
2. **Create Feature Branch**
|
||||
|
||||
```bash
|
||||
git checkout -b feat/your-feature-name
|
||||
```
|
||||
```bash
|
||||
git checkout -b feat/your-feature-name
|
||||
```
|
||||
|
||||
3. **Make Changes**
|
||||
- Follow existing code style and patterns
|
||||
- Add tests for new functionality
|
||||
- Update documentation as needed
|
||||
- Follow existing code style and patterns
|
||||
- Add tests for new functionality
|
||||
- Update documentation as needed
|
||||
|
||||
4. **Test Your Changes**
|
||||
|
||||
```bash
|
||||
# TODO
|
||||
```
|
||||
- Test in our demo website
|
||||
- Test it on other websites if applicable
|
||||
- `@TODO: test suite`
|
||||
|
||||
5. **Commit and Push**
|
||||
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "feat: add awesome feature"
|
||||
git push origin feat/your-feature-name
|
||||
```
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "feat: add awesome feature"
|
||||
git push origin feat/your-feature-name
|
||||
```
|
||||
|
||||
6. **Create Pull Request**
|
||||
- Provide clear description of changes
|
||||
- Link related issues
|
||||
- Include screenshots for UI changes
|
||||
- Provide clear description of changes
|
||||
- Link related issues
|
||||
- Include screenshots for UI changes
|
||||
|
||||
## 📝 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**.
|
||||
- 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.
|
||||
- Update the AI instructions when structure changed.
|
||||
- Cursor and Cline: `./.cursor/rules`
|
||||
- Github Copilot: `./.github/copilot-instructions.md`
|
||||
- Claude Code: `./CLAUDE.md`
|
||||
- Update the [AI instructions](AGENTS.md) when structure changed.
|
||||
|
||||
## 🔧 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
|
||||
|
||||
```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
|
||||
- Start and serve a local umd script
|
||||
```bash
|
||||
npm run dev:iife # Serving IIFE with auto rebuild at http://localhost:5174/page-agent.js
|
||||
```
|
||||
|
||||
```bash
|
||||
npm run dev:umd # Serving UMD with auto rebuild at http://localhost:5173/page-agent.umd.cjs
|
||||
```
|
||||
- Add a new bookmark
|
||||
|
||||
- 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);})();
|
||||
```
|
||||
|
||||
```
|
||||
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);})();
|
||||
```
|
||||
- Click the bookmark on any page to load Page-Agent
|
||||
|
||||
> Warning: AK in your local `.env` will be inlined in the iife script.
|
||||
|
||||
### Adding Documentation
|
||||
|
||||
1. Create `website/src/docs/section/page-name/page.tsx`
|
||||
2. Add route to `website/src/router.tsx`
|
||||
3. Add navigation link to `website/src/components/DocsLayout.tsx`
|
||||
Ask an AI to help you add documentation to the `website/` package. Follow the existing style.
|
||||
|
||||
> Our AGENTS.md file and guardrails are designed for this purpose. But please be careful and review anything AI generated.
|
||||
|
||||
## 🎯 Contribution Areas
|
||||
|
||||
@@ -143,7 +153,7 @@ We especially welcome contributions in:
|
||||
|
||||
## 🚫 What We Don't Accept
|
||||
|
||||
- Changes that break existing API compatibility
|
||||
- Changes that break existing API compatibility (Discuss first)
|
||||
- Heavy dependencies to core library
|
||||
- Contributions without proper testing
|
||||
- Code that doesn't follow project conventions
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
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
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
+40
-46
@@ -1,8 +1,11 @@
|
||||
# PageAgent 🤖🪄
|
||||
|
||||

|
||||
<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>
|
||||
|
||||
[](https://badge.fury.io/js/page-agent) [](https://opensource.org/licenses/MIT) [](http://www.typescriptlang.org/) [](https://www.npmjs.com/package/page-agent) [](https://bundlephobia.com/package/page-agent) [](https://github.com/alibaba/page-agent)
|
||||
[](https://opensource.org/licenses/MIT) [](http://www.typescriptlang.org/) [](https://www.npmjs.com/package/page-agent) [](https://bundlephobia.com/package/page-agent) [](https://github.com/alibaba/page-agent)
|
||||
|
||||
纯 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>
|
||||
|
||||
<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,6 +31,24 @@
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
### 快速体验 (With Testing LLM)
|
||||
|
||||
最快的体验方式:
|
||||
|
||||
```html
|
||||
<script
|
||||
src="https://registry.npmmirror.com/@page-agent/cdn/latest/files/dist/page-agent.demo.js"
|
||||
crossorigin="true"
|
||||
></script>
|
||||
```
|
||||
|
||||
> ⚠️ **仅用于技术评估。** Demo 模型有速率限制和使用限制,生产环境请使用 NPM 方式。
|
||||
|
||||
| Mirrors | URL |
|
||||
| ------- | ----------------------------------------------------------------------------------- |
|
||||
| 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 |
|
||||
|
||||
### NPM 安装
|
||||
|
||||
```bash
|
||||
@@ -37,39 +58,17 @@ npm install page-agent
|
||||
```javascript
|
||||
import { PageAgent } from 'page-agent'
|
||||
|
||||
// 测试接口
|
||||
// @note: 限流,限制 prompt 内容,限制来源,随时变更,请替换成你自己的
|
||||
// @note: 使用 DeepSeek-chat(3.2) 官方版本,使用协议和隐私策略见 DeepSeek 网站
|
||||
const DEMO_MODEL = 'PAGE-AGENT-FREE-TESTING-RANDOM'
|
||||
const DEMO_BASE_URL = 'https://hwcxiuzfylggtcktqgij.supabase.co/functions/v1/llm-testing-proxy'
|
||||
const DEMO_API_KEY = 'PAGE-AGENT-FREE-TESTING-RANDOM'
|
||||
|
||||
const agent = new PageAgent({
|
||||
model: DEMO_MODEL,
|
||||
baseURL: DEMO_BASE_URL,
|
||||
apiKey: DEMO_API_KEY,
|
||||
language: 'zh-CN',
|
||||
model: 'deepseek-chat',
|
||||
baseURL: 'https://api.deepseek.com',
|
||||
apiKey: 'YOUR_API_KEY',
|
||||
language: 'zh-CN',
|
||||
})
|
||||
|
||||
await agent.execute('点击登录按钮')
|
||||
```
|
||||
|
||||
### CDN 集成
|
||||
|
||||
Fastest way to try PageAgent is to include it via CDN. Demo model will be used by default.
|
||||
|
||||
| Location | URL |
|
||||
| -------- | ----------------------------------------------------------------------------- |
|
||||
| Global | https://cdn.jsdelivr.net/npm/page-agent@latest/dist/umd/page-agent.js |
|
||||
| China | https://registry.npmmirror.com/page-agent/latest/files/dist/umd/page-agent.js |
|
||||
|
||||
```html
|
||||
<script
|
||||
src="https://registry.npmmirror.com/page-agent/latest/files/dist/umd/page-agent.js"
|
||||
crossorigin="true"
|
||||
type="text/javascript"
|
||||
></script>
|
||||
```
|
||||
适用于无法使用 NPM 的环境,我们也提供了 IIFE 构建的 CDN 方式。[@see CDN Usage](https://alibaba.github.io/page-agent/#/docs/integration/cdn-setup)
|
||||
|
||||
## 🏗️ 架构设计
|
||||
|
||||
@@ -89,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 项目仓库
|
||||
2. Clone or fork: `git clone https://github.com/alibaba/page-agent.git && cd page-agent`
|
||||
3. 安装依赖: `npm install`
|
||||
4. 启动开发: `npm start`
|
||||
更多详情请参阅 [CONTRIBUTING.md](CONTRIBUTING.md)。
|
||||
|
||||
### 贡献指南
|
||||
|
||||
请在贡献前阅读我们的[行为准则](CODE_OF_CONDUCT.md)和[贡献指南](CONTRIBUTING.md)。
|
||||
请在贡献前阅读我们的[行为准则](CODE_OF_CONDUCT.md)。
|
||||
|
||||
## 👏 致谢
|
||||
|
||||
本项目基于以下优秀项目构建:
|
||||
本项目基于 **[`browser-use`](https://github.com/browser-use/browser-use)** 的优秀工作构建。
|
||||
|
||||
- **[browser-use](https://github.com/browser-use/browser-use)**
|
||||
|
||||
PageAgent 专为**客户端网页增强**设计,不是服务端自动化工具。
|
||||
|
||||
## 📄 许可证
|
||||
|
||||
MIT 许可证 - 详见 [LICENSE](LICENSE) 文件。
|
||||
`PageAgent` 专为**客户端网页增强**设计,不是服务端自动化工具。
|
||||
|
||||
```
|
||||
DOM processing components and prompt are derived from browser-use:
|
||||
@@ -129,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.
|
||||
```
|
||||
|
||||
## 📄 许可证
|
||||
|
||||
[MIT License](LICENSE)
|
||||
|
||||
---
|
||||
|
||||
**⭐ 如果觉得 PageAgent 有用或有趣,请给项目点个星!**
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
# PageAgent 🤖🪄
|
||||
|
||||

|
||||
<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>
|
||||
|
||||
[](https://badge.fury.io/js/page-agent) [](https://opensource.org/licenses/MIT) [](http://www.typescriptlang.org/) [](https://www.npmjs.com/package/page-agent) [](https://bundlephobia.com/package/page-agent) [](https://github.com/alibaba/page-agent)
|
||||
[](https://opensource.org/licenses/MIT) [](http://www.typescriptlang.org/) [](https://www.npmjs.com/package/page-agent) [](https://bundlephobia.com/package/page-agent) [](https://github.com/alibaba/page-agent)
|
||||
|
||||
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>
|
||||
|
||||
<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,6 +31,24 @@ The GUI Agent Living in Your Webpage. Control web interfaces with natural langua
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### Quick Try (With Testing LLM)
|
||||
|
||||
Fastest way to try PageAgent:
|
||||
|
||||
```html
|
||||
<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.
|
||||
|
||||
| Mirrors | URL |
|
||||
| ------- | ----------------------------------------------------------------------------------- |
|
||||
| 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 |
|
||||
|
||||
### NPM Installation
|
||||
|
||||
```bash
|
||||
@@ -37,39 +58,17 @@ npm install page-agent
|
||||
```javascript
|
||||
import { PageAgent } from 'page-agent'
|
||||
|
||||
// test server
|
||||
// @note: rate limit. prompt limit. Origin limit. May change anytime. Use your own llm!
|
||||
// @note Using official DeepSeek-chat(3.2). Go to DeepSeek website for privacy policy.
|
||||
const DEMO_MODEL = 'PAGE-AGENT-FREE-TESTING-RANDOM'
|
||||
const DEMO_BASE_URL = 'https://hwcxiuzfylggtcktqgij.supabase.co/functions/v1/llm-testing-proxy'
|
||||
const DEMO_API_KEY = 'PAGE-AGENT-FREE-TESTING-RANDOM'
|
||||
|
||||
const agent = new PageAgent({
|
||||
model: DEMO_MODEL,
|
||||
baseURL: DEMO_BASE_URL,
|
||||
apiKey: DEMO_API_KEY,
|
||||
language: 'en-US',
|
||||
model: 'deepseek-chat',
|
||||
baseURL: 'https://api.deepseek.com',
|
||||
apiKey: 'YOUR_API_KEY',
|
||||
language: 'en-US',
|
||||
})
|
||||
|
||||
await agent.execute('Click the login button')
|
||||
```
|
||||
|
||||
### CDN Integration
|
||||
|
||||
Fastest way to try PageAgent is to include it via CDN. Demo model will be used by default.
|
||||
|
||||
| Location | URL |
|
||||
| -------- | ----------------------------------------------------------------------------- |
|
||||
| Global | https://cdn.jsdelivr.net/npm/page-agent@latest/dist/umd/page-agent.js |
|
||||
| China | https://registry.npmmirror.com/page-agent/latest/files/dist/umd/page-agent.js |
|
||||
|
||||
```html
|
||||
<script
|
||||
src="https://cdn.jsdelivr.net/npm/page-agent@latest/dist/umd/page-agent.js"
|
||||
crossorigin="true"
|
||||
type="text/javascript"
|
||||
></script>
|
||||
```
|
||||
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)
|
||||
|
||||
## 🏗️ Structure
|
||||
|
||||
@@ -89,28 +88,19 @@ packages/
|
||||
|
||||
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
|
||||
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`
|
||||
More details in [CONTRIBUTING.md](CONTRIBUTING.md).
|
||||
|
||||
### Contributing Guidelines
|
||||
|
||||
Please read our [Code of Conduct](CODE_OF_CONDUCT.md) and [Contributing Guide](CONTRIBUTING.md) before contributing.
|
||||
Please read our [Code of Conduct](CODE_OF_CONDUCT.md) before contributing.
|
||||
|
||||
## 👏 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.
|
||||
|
||||
## 📄 License
|
||||
|
||||
MIT License - see the [LICENSE](LICENSE) file for details.
|
||||
`PageAgent` is designed for **client-side web enhancement**, not server-side automation.
|
||||
|
||||
```
|
||||
DOM processing components and prompt are derived from browser-use:
|
||||
@@ -129,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.
|
||||
```
|
||||
|
||||
## 📄 License
|
||||
|
||||
[MIT License](LICENSE)
|
||||
|
||||
---
|
||||
|
||||
**⭐ Star this repo if you find PageAgent helpful!**
|
||||
|
||||
Generated
+15
-29
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "root",
|
||||
"version": "0.0.23",
|
||||
"version": "0.2.5",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "root",
|
||||
"version": "0.0.23",
|
||||
"version": "0.2.5",
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"packages/page-controller",
|
||||
@@ -1540,20 +1540,6 @@
|
||||
"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": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
@@ -4627,9 +4613,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/diff": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-8.0.2.tgz",
|
||||
"integrity": "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==",
|
||||
"version": "8.0.3",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-8.0.3.tgz",
|
||||
"integrity": "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
@@ -8147,15 +8133,15 @@
|
||||
},
|
||||
"packages/cdn": {
|
||||
"name": "@page-agent/cdn",
|
||||
"version": "0.0.23",
|
||||
"version": "0.2.5",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"page-agent": "0.0.23"
|
||||
"page-agent": "0.2.5"
|
||||
}
|
||||
},
|
||||
"packages/llms": {
|
||||
"name": "@page-agent/llms",
|
||||
"version": "0.0.23",
|
||||
"version": "0.2.5",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chalk": "^5.6.2",
|
||||
@@ -8163,19 +8149,19 @@
|
||||
}
|
||||
},
|
||||
"packages/page-agent": {
|
||||
"version": "0.0.23",
|
||||
"version": "0.2.5",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@page-agent/llms": "0.0.23",
|
||||
"@page-agent/page-controller": "0.0.23",
|
||||
"@page-agent/ui": "0.0.23",
|
||||
"@page-agent/llms": "0.2.5",
|
||||
"@page-agent/page-controller": "0.2.5",
|
||||
"@page-agent/ui": "0.2.5",
|
||||
"chalk": "^5.6.2",
|
||||
"zod": "^4.3.5"
|
||||
}
|
||||
},
|
||||
"packages/page-controller": {
|
||||
"name": "@page-agent/page-controller",
|
||||
"version": "0.0.23",
|
||||
"version": "0.2.5",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ai-motion": "^0.4.8"
|
||||
@@ -8183,12 +8169,12 @@
|
||||
},
|
||||
"packages/ui": {
|
||||
"name": "@page-agent/ui",
|
||||
"version": "0.0.23",
|
||||
"version": "0.2.5",
|
||||
"license": "MIT"
|
||||
},
|
||||
"packages/website": {
|
||||
"name": "@page-agent/website",
|
||||
"version": "0.0.23",
|
||||
"version": "0.2.5",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-icons": "^1.3.2",
|
||||
"@radix-ui/react-separator": "^1.1.8",
|
||||
|
||||
+22
-3
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "root",
|
||||
"private": true,
|
||||
"version": "0.0.23",
|
||||
"version": "0.2.5",
|
||||
"type": "module",
|
||||
"workspaces": [
|
||||
"packages/page-controller",
|
||||
@@ -29,7 +29,7 @@
|
||||
"build:website": "npm run build:website --workspace=@page-agent/website",
|
||||
"build:libs": "npm run build --workspaces --if-present",
|
||||
"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",
|
||||
"lint": "eslint .",
|
||||
"prepare": "husky"
|
||||
@@ -59,6 +59,9 @@
|
||||
"vite": "^7.3.1",
|
||||
"vite-plugin-css-injected-by-js": "^3.5.2"
|
||||
},
|
||||
"overrides": {
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,ts,cjs,cts,mjs,mts}": [
|
||||
"npx prettier --write --ignore-unknown",
|
||||
@@ -99,6 +102,22 @@
|
||||
".css$"
|
||||
],
|
||||
"importOrderSeparation": true,
|
||||
"importOrderSortSpecifiers": true
|
||||
"importOrderSortSpecifiers": true,
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.md",
|
||||
"options": {
|
||||
"useTabs": false,
|
||||
"tabWidth": 4
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": "*.json",
|
||||
"options": {
|
||||
"useTabs": false,
|
||||
"tabWidth": 4
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@page-agent/cdn",
|
||||
"private": false,
|
||||
"version": "0.0.23",
|
||||
"version": "0.2.5",
|
||||
"type": "module",
|
||||
"files": [
|
||||
"dist/"
|
||||
@@ -18,6 +18,6 @@
|
||||
"build": "vite build && vite build --mode demo"
|
||||
},
|
||||
"dependencies": {
|
||||
"page-agent": "0.0.23"
|
||||
"page-agent": "0.2.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,12 @@ setTimeout(() => {
|
||||
window.pageAgent = new PageAgent(config)
|
||||
} else {
|
||||
console.log('🚀 page-agent.js no current script detected, using default demo config')
|
||||
window.pageAgent = new PageAgent()
|
||||
const config = {
|
||||
model: DEMO_MODEL,
|
||||
baseURL: DEMO_BASE_URL,
|
||||
apiKey: DEMO_API_KEY,
|
||||
}
|
||||
window.pageAgent = new PageAgent(config)
|
||||
}
|
||||
|
||||
console.log('🚀 page-agent.js initialized with config:', window.pageAgent.config)
|
||||
|
||||
@@ -34,6 +34,13 @@ export default defineConfig(({ mode }) => {
|
||||
outDir: resolve(__dirname, 'dist'),
|
||||
emptyOutDir: !isDemo, // only empty on first build (full)
|
||||
minify: false,
|
||||
cssCodeSplit: true,
|
||||
rollupOptions: {
|
||||
onwarn: function (message, handler) {
|
||||
if (message.code === 'EVAL') return
|
||||
handler(message)
|
||||
},
|
||||
},
|
||||
},
|
||||
define: {
|
||||
'process.env.NODE_ENV': '"production"',
|
||||
|
||||
@@ -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,6 +1,6 @@
|
||||
{
|
||||
"name": "@page-agent/llms",
|
||||
"version": "0.0.23",
|
||||
"version": "0.2.5",
|
||||
"type": "module",
|
||||
"main": "./dist/lib/page-agent-llms.js",
|
||||
"module": "./dist/lib/page-agent-llms.js",
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
// Dev environment: use .env config if available, otherwise fallback to testing api
|
||||
export const DEFAULT_MODEL_NAME: string =
|
||||
import.meta.env.DEV && import.meta.env.LLM_MODEL_NAME
|
||||
? import.meta.env.LLM_MODEL_NAME
|
||||
: 'PAGE-AGENT-FREE-TESTING-RANDOM'
|
||||
|
||||
export const DEFAULT_API_KEY: string =
|
||||
import.meta.env.DEV && import.meta.env.LLM_API_KEY
|
||||
? import.meta.env.LLM_API_KEY
|
||||
: 'PAGE-AGENT-FREE-TESTING-RANDOM'
|
||||
|
||||
export const DEFAULT_BASE_URL: string =
|
||||
import.meta.env.DEV && import.meta.env.LLM_BASE_URL
|
||||
? import.meta.env.LLM_BASE_URL
|
||||
: 'https://hwcxiuzfylggtcktqgij.supabase.co/functions/v1/llm-testing-proxy'
|
||||
|
||||
// internal
|
||||
|
||||
// Internal constants
|
||||
export const LLM_MAX_RETRIES = 2
|
||||
export const DEFAULT_TEMPERATURE = 0.7 // higher randomness helps auto-recovery
|
||||
|
||||
+12
-10
@@ -1,21 +1,23 @@
|
||||
import { OpenAIClient } from './OpenAIClient'
|
||||
import {
|
||||
DEFAULT_API_KEY,
|
||||
DEFAULT_BASE_URL,
|
||||
DEFAULT_MODEL_NAME,
|
||||
DEFAULT_TEMPERATURE,
|
||||
LLM_MAX_RETRIES,
|
||||
} from './constants'
|
||||
import { DEFAULT_TEMPERATURE, LLM_MAX_RETRIES } from './constants'
|
||||
import { InvokeError } from './errors'
|
||||
import type { InvokeOptions, InvokeResult, LLMClient, LLMConfig, Message, Tool } from './types'
|
||||
|
||||
export type { InvokeOptions, InvokeResult, LLMClient, LLMConfig, Message, Tool }
|
||||
|
||||
export function parseLLMConfig(config: LLMConfig): Required<LLMConfig> {
|
||||
// Runtime validation as defensive programming (types already guarantee these)
|
||||
if (!config.baseURL || !config.apiKey || !config.model) {
|
||||
throw new Error(
|
||||
'[PageAgent] LLM configuration required. Please provide: baseURL, apiKey, model. ' +
|
||||
'See: https://alibaba.github.io/page-agent/#/docs/features/models'
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
baseURL: config.baseURL ?? DEFAULT_BASE_URL,
|
||||
apiKey: config.apiKey ?? DEFAULT_API_KEY,
|
||||
model: config.model ?? DEFAULT_MODEL_NAME,
|
||||
baseURL: config.baseURL,
|
||||
apiKey: config.apiKey,
|
||||
model: config.model,
|
||||
temperature: config.temperature ?? DEFAULT_TEMPERATURE,
|
||||
maxRetries: config.maxRetries ?? LLM_MAX_RETRIES,
|
||||
customFetch: (config.customFetch ?? fetch).bind(globalThis), // fetch will be illegal unless bound
|
||||
|
||||
@@ -87,9 +87,9 @@ export interface InvokeResult<TResult = unknown> {
|
||||
* LLM configuration
|
||||
*/
|
||||
export interface LLMConfig {
|
||||
baseURL?: string
|
||||
apiKey?: string
|
||||
model?: string
|
||||
baseURL: string
|
||||
apiKey: string
|
||||
model: string
|
||||
|
||||
temperature?: number
|
||||
maxRetries?: number
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
// @workaround DTS bug
|
||||
// dts do not work with monorepo path mapping
|
||||
// disable path mapping for it
|
||||
"paths": {}
|
||||
}
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
// @workaround DTS bug
|
||||
// dts do not work with monorepo path mapping
|
||||
// disable path mapping for it
|
||||
"paths": {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+10
-11
@@ -1,13 +1,12 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo",
|
||||
"noEmit": false,
|
||||
"allowImportingTsExtensions": false,
|
||||
"baseUrl": ".",
|
||||
"outDir": "dist"
|
||||
},
|
||||
"include": ["**/*.ts"],
|
||||
"exclude": ["dist", "node_modules"]
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo",
|
||||
"noEmit": false,
|
||||
"allowImportingTsExtensions": false,
|
||||
"baseUrl": ".",
|
||||
"outDir": "dist"
|
||||
},
|
||||
"include": ["**/*.ts"],
|
||||
"exclude": ["dist", "node_modules"]
|
||||
}
|
||||
|
||||
|
||||
@@ -34,4 +34,3 @@ export default defineConfig({
|
||||
'process.env.NODE_ENV': '"production"',
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "page-agent",
|
||||
"private": false,
|
||||
"version": "0.0.23",
|
||||
"version": "0.2.5",
|
||||
"type": "module",
|
||||
"main": "./dist/esm/page-agent.js",
|
||||
"module": "./dist/esm/page-agent.js",
|
||||
@@ -37,17 +37,16 @@
|
||||
},
|
||||
"homepage": "https://alibaba.github.io/page-agent/",
|
||||
"scripts": {
|
||||
"build": "vite build && vite build --config vite.umd.config.js",
|
||||
"serve": "npx serve dist/umd -p 5173",
|
||||
"dev:umd": "concurrently \"vite build --config vite.umd.config.js --watch\" \"npm run serve\"",
|
||||
"build": "vite build",
|
||||
"dev:iife": "concurrently \"vite build --config vite.iife.config.js --watch\" \"npx serve dist/iife -p 5174\"",
|
||||
"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{}})\""
|
||||
},
|
||||
"dependencies": {
|
||||
"chalk": "^5.6.2",
|
||||
"zod": "^4.3.5",
|
||||
"@page-agent/llms": "0.0.23",
|
||||
"@page-agent/page-controller": "0.0.23",
|
||||
"@page-agent/ui": "0.0.23"
|
||||
"@page-agent/llms": "0.2.5",
|
||||
"@page-agent/page-controller": "0.2.5",
|
||||
"@page-agent/ui": "0.2.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,11 +12,11 @@ import type { PageAgentConfig } from './config'
|
||||
import { MAX_STEPS } from './config/constants'
|
||||
import SYSTEM_PROMPT from './prompts/system_prompt.md?raw'
|
||||
import { tools } from './tools'
|
||||
import { normalizeResponse, trimLines, uid, waitUntil } from './utils'
|
||||
import { normalizeResponse, trimLines, uid } from './utils'
|
||||
import { assert } from './utils/assert'
|
||||
|
||||
/**
|
||||
* Agent brain state - the reflection-before-action model
|
||||
* Agent reflection state - the reflection-before-action model
|
||||
*
|
||||
* Every tool call must first reflect on:
|
||||
* - evaluation_previous_goal: How well did the previous action achieve its goal?
|
||||
@@ -50,8 +50,12 @@ export interface MacroToolResult {
|
||||
export type { PageAgentConfig }
|
||||
export { tool, type PageAgentTool } from './tools'
|
||||
|
||||
export interface AgentHistory {
|
||||
brain: Partial<AgentReflection>
|
||||
/**
|
||||
* A single agent step with reflection and action
|
||||
*/
|
||||
export interface AgentStep {
|
||||
type: 'step'
|
||||
reflection: Partial<AgentReflection>
|
||||
action: {
|
||||
name: string
|
||||
input: any
|
||||
@@ -66,24 +70,42 @@ export interface AgentHistory {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Persistent observation event (stays in memory)
|
||||
*/
|
||||
export interface ObservationEvent {
|
||||
type: 'observation'
|
||||
content: string
|
||||
}
|
||||
|
||||
/**
|
||||
* User takeover event
|
||||
*/
|
||||
export interface UserTakeoverEvent {
|
||||
type: 'user_takeover'
|
||||
}
|
||||
|
||||
/**
|
||||
* Union type for all history events
|
||||
*/
|
||||
export type HistoryEvent = AgentStep | ObservationEvent | UserTakeoverEvent
|
||||
|
||||
export interface ExecutionResult {
|
||||
success: boolean
|
||||
data: string
|
||||
history: AgentHistory[]
|
||||
history: HistoryEvent[]
|
||||
}
|
||||
|
||||
export class PageAgent extends EventTarget {
|
||||
config: PageAgentConfig
|
||||
id = uid()
|
||||
panel: Panel
|
||||
panel: Panel | null = null
|
||||
tools: typeof tools
|
||||
paused = false
|
||||
disposed = false
|
||||
task = ''
|
||||
taskId = ''
|
||||
|
||||
#llm: LLM
|
||||
#totalWaitTime = 0
|
||||
#abortController = new AbortController()
|
||||
#llmRetryListener: ((e: Event) => void) | null = null
|
||||
#llmErrorListener: ((e: Event) => void) | null = null
|
||||
@@ -92,24 +114,33 @@ export class PageAgent extends EventTarget {
|
||||
/** PageController for DOM operations */
|
||||
pageController: PageController
|
||||
|
||||
/** History records */
|
||||
history: AgentHistory[] = []
|
||||
/** Runtime states for tracking across steps */
|
||||
states = {
|
||||
/** Accumulated wait time in seconds, used by wait tool */
|
||||
totalWaitTime: 0,
|
||||
/** Last known URL for detecting navigation */
|
||||
lastURL: '',
|
||||
}
|
||||
|
||||
constructor(config: PageAgentConfig = {}) {
|
||||
/** History events */
|
||||
history: HistoryEvent[] = []
|
||||
|
||||
constructor(config: PageAgentConfig) {
|
||||
super()
|
||||
|
||||
this.config = config
|
||||
this.#llm = new LLM(this.config)
|
||||
this.panel = new Panel({
|
||||
language: this.config.language,
|
||||
onExecuteTask: (task) => this.execute(task),
|
||||
onStop: () => this.dispose(),
|
||||
onPauseToggle: () => {
|
||||
this.paused = !this.paused
|
||||
return this.paused
|
||||
},
|
||||
getPaused: () => this.paused,
|
||||
})
|
||||
|
||||
// Conditionally initialize Panel
|
||||
if (this.config.enablePanel !== false) {
|
||||
this.panel = new Panel({
|
||||
language: this.config.language,
|
||||
onExecuteTask: (task) => this.execute(task),
|
||||
onStop: () => this.dispose(),
|
||||
promptForNextTask: this.config.promptForNextTask,
|
||||
})
|
||||
}
|
||||
|
||||
this.tools = new Map(tools)
|
||||
|
||||
// Initialize PageController with config (mask enabled by default)
|
||||
@@ -121,11 +152,11 @@ export class PageAgent extends EventTarget {
|
||||
// Listen to LLM events
|
||||
this.#llmRetryListener = (e) => {
|
||||
const { current, max } = (e as CustomEvent).detail
|
||||
this.panel.update({ type: 'retry', current, max })
|
||||
this.panel?.update({ type: 'retry', current, max })
|
||||
}
|
||||
this.#llmErrorListener = (e) => {
|
||||
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('error', this.#llmErrorListener)
|
||||
@@ -144,12 +175,26 @@ export class PageAgent extends EventTarget {
|
||||
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) => {
|
||||
if (!this.disposed) this.dispose('PAGE_UNLOADING')
|
||||
}
|
||||
window.addEventListener('beforeunload', this.#beforeUnloadListener)
|
||||
}
|
||||
|
||||
/**
|
||||
* Push a persistent observation to the history event stream.
|
||||
* This will be visible in <agent_history> and remain in memory across steps.
|
||||
*/
|
||||
pushObservation(content: string): void {
|
||||
this.history.push({ type: 'observation', content })
|
||||
this.panel?.update({ type: 'observation', content })
|
||||
}
|
||||
|
||||
async execute(task: string): Promise<ExecutionResult> {
|
||||
if (!task) throw new Error('Task is required')
|
||||
this.task = task
|
||||
@@ -165,10 +210,10 @@ export class PageAgent extends EventTarget {
|
||||
// Show mask and panel
|
||||
this.pageController.showMask()
|
||||
|
||||
this.panel.show()
|
||||
this.panel.reset()
|
||||
this.panel?.show()
|
||||
this.panel?.reset()
|
||||
|
||||
this.panel.update({ type: 'input', task: this.task })
|
||||
this.panel?.update({ type: 'input', task: this.task })
|
||||
|
||||
if (this.#abortController) {
|
||||
this.#abortController.abort()
|
||||
@@ -177,22 +222,28 @@ export class PageAgent extends EventTarget {
|
||||
|
||||
this.history = []
|
||||
|
||||
// Reset states
|
||||
this.states = {
|
||||
totalWaitTime: 0,
|
||||
lastURL: '',
|
||||
}
|
||||
|
||||
try {
|
||||
let step = 0
|
||||
|
||||
while (true) {
|
||||
await this.#generateObservations(step)
|
||||
|
||||
await onBeforeStep.call(this, step)
|
||||
|
||||
console.group(`step: ${step}`)
|
||||
|
||||
// abort
|
||||
if (this.#abortController.signal.aborted) throw new Error('AbortError')
|
||||
// pause
|
||||
await waitUntil(() => !this.paused)
|
||||
|
||||
// Update status to thinking
|
||||
console.log(chalk.blue('Thinking...'))
|
||||
this.panel.update({ type: 'thinking' })
|
||||
this.panel?.update({ type: 'thinking' })
|
||||
|
||||
const result = await this.#llm.invoke(
|
||||
[
|
||||
@@ -216,23 +267,24 @@ export class PageAgent extends EventTarget {
|
||||
const macroResult = result.toolResult as MacroToolResult
|
||||
const input = macroResult.input
|
||||
const output = macroResult.output
|
||||
const brain = {
|
||||
evaluation_previous_goal: input.evaluation_previous_goal || '',
|
||||
memory: input.memory || '',
|
||||
next_goal: input.next_goal || '',
|
||||
const reflection: Partial<AgentReflection> = {
|
||||
evaluation_previous_goal: input.evaluation_previous_goal,
|
||||
memory: input.memory,
|
||||
next_goal: input.next_goal,
|
||||
}
|
||||
const actionName = Object.keys(input.action)[0]
|
||||
const action = {
|
||||
const action: AgentStep['action'] = {
|
||||
name: actionName,
|
||||
input: input.action[actionName],
|
||||
output: output,
|
||||
}
|
||||
|
||||
this.history.push({
|
||||
brain,
|
||||
type: 'step',
|
||||
reflection,
|
||||
action,
|
||||
usage: result.usage,
|
||||
})
|
||||
} as AgentStep)
|
||||
|
||||
console.log(chalk.green('Step finished:'), actionName)
|
||||
console.groupEnd()
|
||||
@@ -311,50 +363,49 @@ export class PageAgent extends EventTarget {
|
||||
execute: async (input: MacroToolInput): Promise<MacroToolResult> => {
|
||||
// abort
|
||||
if (this.#abortController.signal.aborted) throw new Error('AbortError')
|
||||
// pause
|
||||
await waitUntil(() => !this.paused)
|
||||
|
||||
console.log(chalk.blue.bold('MacroTool execute'), input)
|
||||
const action = input.action
|
||||
|
||||
const toolName = Object.keys(action)[0]
|
||||
const toolInput = action[toolName]
|
||||
const brain = trimLines(`✅: ${input.evaluation_previous_goal}
|
||||
💾: ${input.memory}
|
||||
🎯: ${input.next_goal}
|
||||
`)
|
||||
|
||||
console.log(brain)
|
||||
this.panel.update({ type: 'thinking', text: brain })
|
||||
// Build reflection text, only include non-empty fields
|
||||
const reflectionLines: string[] = []
|
||||
if (input.evaluation_previous_goal)
|
||||
reflectionLines.push(`✅: ${input.evaluation_previous_goal}`)
|
||||
if (input.memory) reflectionLines.push(`💾: ${input.memory}`)
|
||||
if (input.next_goal) reflectionLines.push(`🎯: ${input.next_goal}`)
|
||||
|
||||
const reflectionText = reflectionLines.length > 0 ? reflectionLines.join('\n') : ''
|
||||
|
||||
if (reflectionText) {
|
||||
console.log(reflectionText)
|
||||
this.panel?.update({ type: 'thinking', text: reflectionText })
|
||||
}
|
||||
|
||||
// Find the corresponding tool
|
||||
const tool = tools.get(toolName)
|
||||
assert(tool, `Tool ${toolName} not found. (@note should have been caught before this!!!)`)
|
||||
|
||||
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()
|
||||
|
||||
// Execute tool, bind `this` to PageAgent
|
||||
let result = await tool.execute.bind(this)(toolInput)
|
||||
const result = await tool.execute.bind(this)(toolInput)
|
||||
|
||||
const duration = Date.now() - startTime
|
||||
console.log(chalk.green.bold(`Tool (${toolName}) executed for ${duration}ms`), result)
|
||||
|
||||
if (toolName === 'wait') {
|
||||
this.#totalWaitTime += Math.round(toolInput.seconds + duration / 1000)
|
||||
result += `\n<sys> You have waited ${this.#totalWaitTime} seconds accumulatively.`
|
||||
if (this.#totalWaitTime >= 3)
|
||||
result += '\nDo NOT wait any longer unless you have a good reason.\n'
|
||||
result += '</sys>'
|
||||
} else {
|
||||
// For other tools, reset wait time
|
||||
this.#totalWaitTime = 0
|
||||
// Reset wait time for non-wait tools
|
||||
if (toolName !== 'wait') {
|
||||
this.states.totalWaitTime = 0
|
||||
}
|
||||
|
||||
// Briefly display execution result
|
||||
this.panel.update({
|
||||
this.panel?.update({
|
||||
type: 'toolCompleted',
|
||||
toolName,
|
||||
args: toolInput,
|
||||
@@ -427,6 +478,34 @@ export class PageAgent extends EventTarget {
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate observations before each step
|
||||
* - URL change detection
|
||||
* - Too many steps warning
|
||||
* @todo loop detection
|
||||
* @todo console error
|
||||
*/
|
||||
async #generateObservations(stepCount: number): Promise<void> {
|
||||
// Detect URL change
|
||||
const currentURL = await this.pageController.getCurrentUrl()
|
||||
if (currentURL !== this.states.lastURL) {
|
||||
this.pushObservation(`Page navigated to → ${currentURL}`)
|
||||
this.states.lastURL = currentURL
|
||||
}
|
||||
|
||||
// Warn about remaining steps
|
||||
const remaining = MAX_STEPS - stepCount
|
||||
if (remaining === 5) {
|
||||
this.pushObservation(
|
||||
`⚠️ Only ${remaining} steps remaining. Consider wrapping up or calling done with partial results.`
|
||||
)
|
||||
} else if (remaining === 2) {
|
||||
this.pushObservation(
|
||||
`⚠️ Critical: Only ${remaining} steps left! You must finish the task or call done immediately.`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
async #assembleUserPrompt(): Promise<string> {
|
||||
let prompt = ''
|
||||
|
||||
@@ -438,31 +517,42 @@ export class PageAgent extends EventTarget {
|
||||
// - <step_info>
|
||||
// <agent_state>
|
||||
|
||||
const stepCount = this.history.filter((e) => e.type === 'step').length
|
||||
|
||||
prompt += `<agent_state>
|
||||
<user_request>
|
||||
${this.task}
|
||||
</user_request>
|
||||
<step_info>
|
||||
Step ${this.history.length + 1} of ${MAX_STEPS} max possible steps
|
||||
Step ${stepCount + 1} of ${MAX_STEPS} max possible steps
|
||||
Current date and time: ${new Date().toISOString()}
|
||||
</step_info>
|
||||
</agent_state>
|
||||
`
|
||||
|
||||
// <agent_history>
|
||||
// - <step_>
|
||||
// - <step_N> for steps
|
||||
// - <sys> for observations and system messages
|
||||
|
||||
prompt += '\n<agent_history>\n'
|
||||
|
||||
this.history.forEach((history, index) => {
|
||||
prompt += `<step_${index + 1}>
|
||||
Evaluation of Previous Step: ${history.brain.evaluation_previous_goal}
|
||||
Memory: ${history.brain.memory}
|
||||
Next Goal: ${history.brain.next_goal}
|
||||
Action Results: ${history.action.output}
|
||||
</step_${index + 1}>
|
||||
let stepIndex = 0
|
||||
for (const event of this.history) {
|
||||
if (event.type === 'step') {
|
||||
stepIndex++
|
||||
prompt += `<step_${stepIndex}>
|
||||
Evaluation of Previous Step: ${event.reflection.evaluation_previous_goal}
|
||||
Memory: ${event.reflection.memory}
|
||||
Next Goal: ${event.reflection.next_goal}
|
||||
Action Results: ${event.action.output}
|
||||
</step_${stepIndex}>
|
||||
`
|
||||
})
|
||||
} else if (event.type === 'observation') {
|
||||
prompt += `<sys>${event.content}</sys>\n`
|
||||
} else if (event.type === 'user_takeover') {
|
||||
prompt += `<sys>User took over control and made changes to the page.</sys>\n`
|
||||
}
|
||||
}
|
||||
|
||||
prompt += '</agent_history>\n\n'
|
||||
|
||||
@@ -478,13 +568,13 @@ export class PageAgent extends EventTarget {
|
||||
|
||||
// Update panel status
|
||||
if (success) {
|
||||
this.panel.update({ type: 'output', text })
|
||||
this.panel?.update({ type: 'output', text })
|
||||
} else {
|
||||
this.panel.update({ type: 'error', message: text })
|
||||
this.panel?.update({ type: 'error', message: text })
|
||||
}
|
||||
|
||||
// Task completed
|
||||
this.panel.update({ type: 'completed' })
|
||||
this.panel?.update({ type: 'completed' })
|
||||
|
||||
this.pageController.hideMask()
|
||||
|
||||
@@ -492,58 +582,29 @@ export class PageAgent extends EventTarget {
|
||||
}
|
||||
|
||||
async #getBrowserState(): Promise<string> {
|
||||
const pageUrl = await this.pageController.getCurrentUrl()
|
||||
const pageTitle = await this.pageController.getPageTitle()
|
||||
const pi = await this.pageController.getPageInfo()
|
||||
const viewportExpansion = await this.pageController.getViewportExpansion()
|
||||
|
||||
await this.pageController.updateTree()
|
||||
|
||||
let simplifiedHTML = await this.pageController.getSimplifiedHTML()
|
||||
const state = await this.pageController.getBrowserState()
|
||||
|
||||
let content = state.content
|
||||
if (this.config.transformPageContent) {
|
||||
simplifiedHTML = await this.config.transformPageContent(simplifiedHTML)
|
||||
content = await this.config.transformPageContent(content)
|
||||
}
|
||||
|
||||
let prompt = trimLines(`<browser_state>
|
||||
Current Page: [${pageTitle}](${pageUrl})
|
||||
return trimLines(`<browser_state>
|
||||
Current Page: [${state.title}](${state.url})
|
||||
|
||||
Page info: ${pi.viewport_width}x${pi.viewport_height}px viewport, ${pi.page_width}x${pi.page_height}px total page size, ${pi.pages_above.toFixed(1)} pages above, ${pi.pages_below.toFixed(1)} pages below, ${pi.total_pages.toFixed(1)} total pages, at ${(pi.current_page_position * 100).toFixed(0)}% of page
|
||||
|
||||
${viewportExpansion === -1 ? 'Interactive elements from top layer of the current page (full page):' : 'Interactive elements from top layer of the current page inside the viewport:'}
|
||||
${state.header}
|
||||
${content}
|
||||
${state.footer}
|
||||
|
||||
</browser_state>
|
||||
`)
|
||||
|
||||
// Page header info
|
||||
const has_content_above = pi.pixels_above > 4
|
||||
if (has_content_above && viewportExpansion !== -1) {
|
||||
prompt += `... ${pi.pixels_above} pixels above (${pi.pages_above.toFixed(1)} pages) - scroll to see more ...\n`
|
||||
} else {
|
||||
prompt += `[Start of page]\n`
|
||||
}
|
||||
|
||||
// Current viewport info
|
||||
prompt += simplifiedHTML
|
||||
prompt += `\n`
|
||||
|
||||
// Page footer info
|
||||
const has_content_below = pi.pixels_below > 4
|
||||
if (has_content_below && viewportExpansion !== -1) {
|
||||
prompt += `... ${pi.pixels_below} pixels below (${pi.pages_below.toFixed(1)} pages) - scroll to see more ...\n`
|
||||
} else {
|
||||
prompt += `[End of page]\n`
|
||||
}
|
||||
|
||||
prompt += `</browser_state>\n`
|
||||
|
||||
return prompt
|
||||
}
|
||||
|
||||
dispose(reason?: string) {
|
||||
console.log('Disposing PageAgent...')
|
||||
this.disposed = true
|
||||
this.pageController.dispose()
|
||||
this.panel.dispose()
|
||||
this.panel?.dispose()
|
||||
this.history = []
|
||||
this.#abortController.abort(reason ?? 'PageAgent disposed')
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { LLMConfig } from '@page-agent/llms'
|
||||
import type { PageControllerConfig } from '@page-agent/page-controller'
|
||||
import type { SupportedLanguage } from '@page-agent/ui'
|
||||
|
||||
import type { AgentHistory, ExecutionResult, PageAgent } from '../PageAgent'
|
||||
import type { ExecutionResult, HistoryEvent, PageAgent } from '../PageAgent'
|
||||
import type { PageAgentTool } from '../tools'
|
||||
|
||||
export type { LLMConfig }
|
||||
@@ -11,6 +11,27 @@ export interface AgentConfig {
|
||||
// theme?: 'light' | 'dark'
|
||||
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
|
||||
* @experimental
|
||||
@@ -65,7 +86,7 @@ export interface AgentConfig {
|
||||
// @todo: remove `this` binding, pass agent as explicit parameter instead
|
||||
|
||||
onBeforeStep?: (this: PageAgent, stepCnt: number) => Promise<void> | void
|
||||
onAfterStep?: (this: PageAgent, stepCnt: number, history: AgentHistory[]) => Promise<void> | void
|
||||
onAfterStep?: (this: PageAgent, stepCnt: number, history: HistoryEvent[]) => Promise<void> | void
|
||||
onBeforeTask?: (this: PageAgent) => Promise<void> | void
|
||||
onAfterTask?: (this: PageAgent, result: ExecutionResult) => Promise<void> | void
|
||||
|
||||
@@ -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.
|
||||
* @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
|
||||
* 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.
|
||||
*/
|
||||
experimentalPreventNewPage?: boolean
|
||||
// experimentalPreventNewPage?: boolean
|
||||
}
|
||||
|
||||
export type PageAgentConfig = LLMConfig & AgentConfig & PageControllerConfig
|
||||
|
||||
@@ -35,7 +35,16 @@ setTimeout(() => {
|
||||
window.pageAgent = new PageAgent(config)
|
||||
} else {
|
||||
console.log('🚀 page-agent.js no current script detected, using default demo config')
|
||||
window.pageAgent = new PageAgent()
|
||||
const config: PageAgentConfig = {
|
||||
// model: DEMO_MODEL,
|
||||
// baseURL: DEMO_BASE_URL,
|
||||
// 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)
|
||||
}
|
||||
|
||||
console.log('🚀 page-agent.js initialized with config:', window.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.
|
||||
- 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.
|
||||
- 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.
|
||||
</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.
|
||||
- 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.
|
||||
- `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.
|
||||
- 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>
|
||||
|
||||
@@ -57,6 +57,15 @@ tools.set(
|
||||
const actualWaitTime = Math.max(0, input.seconds - (Date.now() - lastTimeUpdate) / 1000)
|
||||
console.log(`actualWaitTime: ${actualWaitTime} seconds`)
|
||||
await waitFor(actualWaitTime)
|
||||
|
||||
this.states.totalWaitTime += input.seconds
|
||||
|
||||
if (this.states.totalWaitTime >= 3) {
|
||||
this.pushObservation(
|
||||
`You have waited ${this.states.totalWaitTime} seconds accumulatively. Do NOT wait any longer unless you have a good reason.`
|
||||
)
|
||||
}
|
||||
|
||||
return `✅ Waited for ${input.seconds} seconds.`
|
||||
},
|
||||
})
|
||||
@@ -71,6 +80,9 @@ tools.set(
|
||||
question: zod.string(),
|
||||
}),
|
||||
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)
|
||||
return `✅ Received user answer: ${answer}`
|
||||
},
|
||||
|
||||
@@ -6,8 +6,6 @@ export interface PageAgentEventMap {
|
||||
// PageAgent status events
|
||||
// 'agent:execute': { params: { task: string } }
|
||||
// 'agent:done': { params: { text: string; success: boolean } }
|
||||
// 'agent:paused': { params: undefined }
|
||||
// 'agent:resumed': { params: undefined }
|
||||
// 'agent:disposed': { params: undefined }
|
||||
// 'agent:error': { params: { error: string | Error } }
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
// @workaround DTS bug
|
||||
// dts do not work with monorepo path mapping
|
||||
// disable path mapping for it
|
||||
"paths": {}
|
||||
}
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
// @workaround DTS bug
|
||||
// dts do not work with monorepo path mapping
|
||||
// disable path mapping for it
|
||||
"paths": {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo",
|
||||
"noEmit": false,
|
||||
"allowImportingTsExtensions": false,
|
||||
"baseUrl": ".",
|
||||
"outDir": "dist",
|
||||
"paths": {
|
||||
//
|
||||
"@page-agent/llms": ["../llms/src/index.ts"],
|
||||
"@page-agent/page-controller": ["../page-controller/src/PageController.ts"],
|
||||
"@page-agent/ui": ["../ui/src/index.ts"]
|
||||
}
|
||||
},
|
||||
"include": ["**/*.ts"],
|
||||
"exclude": ["dist", "node_modules"],
|
||||
"references": [
|
||||
//
|
||||
{ "path": "../llms" },
|
||||
{ "path": "../page-controller" },
|
||||
{ "path": "../ui" }
|
||||
]
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo",
|
||||
"noEmit": false,
|
||||
"allowImportingTsExtensions": false,
|
||||
"baseUrl": ".",
|
||||
"outDir": "dist",
|
||||
"paths": {
|
||||
//
|
||||
"@page-agent/llms": ["../llms/src/index.ts"],
|
||||
"@page-agent/page-controller": ["../page-controller/src/PageController.ts"],
|
||||
"@page-agent/ui": ["../ui/src/index.ts"]
|
||||
}
|
||||
},
|
||||
"include": ["**/*.ts"],
|
||||
"exclude": ["dist", "node_modules"],
|
||||
"references": [
|
||||
//
|
||||
{ "path": "../llms" },
|
||||
{ "path": "../page-controller" },
|
||||
{ "path": "../ui" }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// @ts-check
|
||||
import { config as dotenvConfig } from 'dotenv'
|
||||
import { dirname, resolve } from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
import { defineConfig } from 'vite'
|
||||
@@ -6,11 +7,14 @@ import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js'
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||
|
||||
// Load .env from repo root
|
||||
dotenvConfig({ path: resolve(__dirname, '../../.env') })
|
||||
|
||||
// UMD Bundle for CDN
|
||||
// - alias all local packages so that they can be build in
|
||||
// - no external
|
||||
// - no d.ts. dts does not work with monorepo aliasing
|
||||
export default defineConfig({
|
||||
export default defineConfig(({ mode }) => ({
|
||||
plugins: [cssInjectedByJsPlugin({ relativeCSSInjection: true })],
|
||||
publicDir: false,
|
||||
esbuild: {
|
||||
@@ -25,21 +29,28 @@ export default defineConfig({
|
||||
},
|
||||
build: {
|
||||
lib: {
|
||||
entry: resolve(__dirname, 'src/umd.ts'),
|
||||
entry: resolve(__dirname, 'src/iife.ts'),
|
||||
name: 'PageAgent',
|
||||
fileName: 'page-agent',
|
||||
formats: ['umd'],
|
||||
formats: ['iife'],
|
||||
},
|
||||
outDir: resolve(__dirname, 'dist', 'umd'),
|
||||
outDir: resolve(__dirname, 'dist', 'iife'),
|
||||
cssCodeSplit: true,
|
||||
minify: false,
|
||||
rollupOptions: {
|
||||
output: {
|
||||
// force use .js as extension
|
||||
entryFileNames: 'page-agent.js',
|
||||
},
|
||||
onwarn: function (message, handler) {
|
||||
if (message.code === 'EVAL') return
|
||||
handler(message)
|
||||
},
|
||||
},
|
||||
cssCodeSplit: true,
|
||||
},
|
||||
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,6 +1,6 @@
|
||||
{
|
||||
"name": "@page-agent/page-controller",
|
||||
"version": "0.0.23",
|
||||
"version": "0.2.5",
|
||||
"type": "module",
|
||||
"main": "./dist/lib/page-controller.js",
|
||||
"module": "./dist/lib/page-controller.js",
|
||||
|
||||
@@ -32,6 +32,20 @@ export interface PageControllerConfig extends dom.DomConfig {
|
||||
enableMask?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Structured browser state for LLM consumption
|
||||
*/
|
||||
export interface BrowserState {
|
||||
url: string
|
||||
title: string
|
||||
/** Page info + scroll position hint (e.g. "Page info: 1920x1080px...\n[Start of page]") */
|
||||
header: string
|
||||
/** Simplified HTML of interactive elements */
|
||||
content: string
|
||||
/** Page footer hint (e.g. "... 300 pixels below ..." or "[End of page]") */
|
||||
footer: string
|
||||
}
|
||||
|
||||
interface ActionResult {
|
||||
success: boolean
|
||||
message: string
|
||||
@@ -93,42 +107,6 @@ export class PageController extends EventTarget {
|
||||
return window.location.href
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current page title
|
||||
*/
|
||||
async getPageTitle(): Promise<string> {
|
||||
return document.title
|
||||
}
|
||||
|
||||
/**
|
||||
* Get page scroll and size info
|
||||
*/
|
||||
async getPageInfo() {
|
||||
return getPageInfo()
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the simplified HTML representation of the page.
|
||||
* This is used by LLM to understand the page structure.
|
||||
*/
|
||||
async getSimplifiedHTML(): Promise<string> {
|
||||
return this.simplifiedHTML
|
||||
}
|
||||
|
||||
/**
|
||||
* Get text description for an element by index
|
||||
*/
|
||||
async getElementText(index: number): Promise<string | undefined> {
|
||||
return this.elementTextMap.get(index)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get total number of indexed interactive elements
|
||||
*/
|
||||
async getElementCount(): Promise<number> {
|
||||
return this.selectorMap.size
|
||||
}
|
||||
|
||||
/**
|
||||
* Get last tree update timestamp
|
||||
*/
|
||||
@@ -137,10 +115,43 @@ export class PageController extends EventTarget {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the viewport expansion setting
|
||||
* Get structured browser state for LLM consumption.
|
||||
* Automatically calls updateTree() to refresh the DOM state.
|
||||
*/
|
||||
async getViewportExpansion(): Promise<number> {
|
||||
return this.config.viewportExpansion ?? VIEWPORT_EXPANSION
|
||||
async getBrowserState(): Promise<BrowserState> {
|
||||
const url = window.location.href
|
||||
const title = document.title
|
||||
const pi = getPageInfo()
|
||||
const viewportExpansion = this.config.viewportExpansion ?? VIEWPORT_EXPANSION
|
||||
|
||||
await this.updateTree()
|
||||
|
||||
const content = this.simplifiedHTML
|
||||
|
||||
// Build header: page info + scroll position hint
|
||||
const pageInfoLine = `Page info: ${pi.viewport_width}x${pi.viewport_height}px viewport, ${pi.page_width}x${pi.page_height}px total page size, ${pi.pages_above.toFixed(1)} pages above, ${pi.pages_below.toFixed(1)} pages below, ${pi.total_pages.toFixed(1)} total pages, at ${(pi.current_page_position * 100).toFixed(0)}% of page`
|
||||
|
||||
const elementsLabel =
|
||||
viewportExpansion === -1
|
||||
? 'Interactive elements from top layer of the current page (full page):'
|
||||
: 'Interactive elements from top layer of the current page inside the viewport:'
|
||||
|
||||
const hasContentAbove = pi.pixels_above > 4
|
||||
const scrollHintAbove =
|
||||
hasContentAbove && viewportExpansion !== -1
|
||||
? `... ${pi.pixels_above} pixels above (${pi.pages_above.toFixed(1)} pages) - scroll to see more ...`
|
||||
: '[Start of page]'
|
||||
|
||||
const header = `${pageInfoLine}\n\n${elementsLabel}\n\n${scrollHintAbove}`
|
||||
|
||||
// Build footer: scroll position hint
|
||||
const hasContentBelow = pi.pixels_below > 4
|
||||
const footer =
|
||||
hasContentBelow && viewportExpansion !== -1
|
||||
? `... ${pi.pixels_below} pixels below (${pi.pages_below.toFixed(1)} pages) - scroll to see more ...`
|
||||
: '[End of page]'
|
||||
|
||||
return { url, title, header, content, footer }
|
||||
}
|
||||
|
||||
// ======= DOM Tree Operations =======
|
||||
|
||||
@@ -287,12 +287,52 @@ export async function scrollVertically(
|
||||
(document.documentElement as HTMLElement)
|
||||
|
||||
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)
|
||||
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 {
|
||||
// Container scroll
|
||||
const scrollBefore = el!.scrollTop
|
||||
const scrollMax = el!.scrollHeight - el!.clientHeight
|
||||
|
||||
el!.scrollBy({ top: dy, behavior: 'smooth' })
|
||||
await waitFor(0.1) // Animation playback
|
||||
return `✅ Scrolled container (${el!.tagName}) by ${dy}px.`
|
||||
await waitFor(0.1)
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
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 {
|
||||
// Container scroll
|
||||
const scrollBefore = el!.scrollLeft
|
||||
const scrollMax = el!.scrollWidth - el!.clientWidth
|
||||
|
||||
el!.scrollBy({ left: dx, behavior: 'smooth' })
|
||||
await waitFor(0.1) // Animation playback
|
||||
return `✅ Scrolled container (${el!.tagName}) horizontally by ${dx}px`
|
||||
await waitFor(0.1)
|
||||
|
||||
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.`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
* @edit scrollable element detection
|
||||
* @edit add `data-browser-use-ignore` attribute
|
||||
* @edit improve `sampleRect`, filter out rects with 0 area
|
||||
* @edit exclude aria-hidden elements
|
||||
*/
|
||||
|
||||
export default (
|
||||
@@ -1446,7 +1447,14 @@ export default (
|
||||
* @edit add `data-browser-use-ignore` attribute
|
||||
*/
|
||||
if (node.dataset?.browserUseIgnore === 'true') {
|
||||
return true // Skip this node and its children
|
||||
return null // Skip this node and its children
|
||||
}
|
||||
|
||||
/**
|
||||
* @edit exclude aria-hidden elements
|
||||
*/
|
||||
if (node.getAttribute && node.getAttribute('aria-hidden') === 'true') {
|
||||
return null // Skip this node and its children
|
||||
}
|
||||
|
||||
// Special handling for root node (body)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
// @workaround DTS bug
|
||||
// dts do not work with monorepo path mapping
|
||||
// disable path mapping for it
|
||||
"paths": {}
|
||||
}
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
// @workaround DTS bug
|
||||
// dts do not work with monorepo path mapping
|
||||
// disable path mapping for it
|
||||
"paths": {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo",
|
||||
"noEmit": false,
|
||||
"allowImportingTsExtensions": false,
|
||||
"baseUrl": ".",
|
||||
"outDir": "dist"
|
||||
},
|
||||
"include": ["**/*.ts", "**/*.js"],
|
||||
"exclude": ["dist", "node_modules"]
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo",
|
||||
"noEmit": false,
|
||||
"allowImportingTsExtensions": false,
|
||||
"baseUrl": ".",
|
||||
"outDir": "dist"
|
||||
},
|
||||
"include": ["**/*.ts", "**/*.js"],
|
||||
"exclude": ["dist", "node_modules"]
|
||||
}
|
||||
|
||||
@@ -30,6 +30,10 @@ export default defineConfig({
|
||||
outDir: resolve(__dirname, 'dist', 'lib'),
|
||||
rollupOptions: {
|
||||
external: ['@page-agent/*', 'ai-motion'],
|
||||
onwarn: function (message, handler) {
|
||||
if (message.code === 'EVAL') return
|
||||
handler(message)
|
||||
},
|
||||
},
|
||||
minify: false,
|
||||
sourcemap: true,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@page-agent/ui",
|
||||
"version": "0.0.23",
|
||||
"version": "0.2.5",
|
||||
"type": "module",
|
||||
"main": "./dist/lib/page-agent-ui.js",
|
||||
"module": "./dist/lib/page-agent-ui.js",
|
||||
|
||||
@@ -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 {
|
||||
background: rgba(239, 68, 68, 0.2);
|
||||
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));
|
||||
}
|
||||
|
||||
&.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 成功结果 */
|
||||
&.doneSuccess {
|
||||
background: linear-gradient(
|
||||
|
||||
+23
-56
@@ -11,8 +11,11 @@ export interface PanelConfig {
|
||||
language?: SupportedLanguage
|
||||
onExecuteTask: (task: string) => 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: 'toolExecuting'; toolName: string; args: any }
|
||||
| { type: 'toolCompleted'; toolName: string; args: any; result?: string; duration?: number }
|
||||
| { type: 'observation'; content: string }
|
||||
|
||||
/**
|
||||
* Agent control panel
|
||||
@@ -39,7 +43,6 @@ export class Panel {
|
||||
#statusText: HTMLElement
|
||||
#historySection: HTMLElement
|
||||
#expandButton: HTMLElement
|
||||
#pauseButton: HTMLElement
|
||||
#stopButton: HTMLElement
|
||||
#inputSection: HTMLElement
|
||||
#taskInput: HTMLInputElement
|
||||
@@ -66,7 +69,6 @@ export class Panel {
|
||||
this.#statusText = this.#wrapper.querySelector(`.${styles.statusText}`)!
|
||||
this.#historySection = this.#wrapper.querySelector(`.${styles.historySection}`)!
|
||||
this.#expandButton = this.#wrapper.querySelector(`.${styles.expandButton}`)!
|
||||
this.#pauseButton = this.#wrapper.querySelector(`.${styles.pauseButton}`)!
|
||||
this.#stopButton = this.#wrapper.querySelector(`.${styles.stopButton}`)!
|
||||
this.#inputSection = this.#wrapper.querySelector(`.${styles.inputSectionWrapper}`)!
|
||||
this.#taskInput = this.#wrapper.querySelector(`.${styles.taskInput}`)!
|
||||
@@ -75,6 +77,8 @@ export class Panel {
|
||||
this.#startHeaderUpdateLoop()
|
||||
|
||||
this.#showInputArea()
|
||||
|
||||
this.hide() // Start hidden
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -120,11 +124,6 @@ export class Panel {
|
||||
this.#updateStatusIndicator('thinking')
|
||||
this.#updateHistory()
|
||||
this.#collapse()
|
||||
// Reset pause state via callback
|
||||
if (this.#config.getPaused()) {
|
||||
this.#config.onPauseToggle()
|
||||
}
|
||||
this.#updatePauseButton()
|
||||
// Reset user input state
|
||||
this.#isWaitingForUserAnswer = false
|
||||
this.#userAnswerResolver = null
|
||||
@@ -205,6 +204,8 @@ export class Panel {
|
||||
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
|
||||
*/
|
||||
@@ -395,13 +363,20 @@ export class Panel {
|
||||
}
|
||||
|
||||
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 {
|
||||
const wrapper = document.createElement('div')
|
||||
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.innerHTML = `
|
||||
@@ -426,9 +401,6 @@ export class Panel {
|
||||
<button class="${styles.controlButton} ${styles.expandButton}" title="${this.#i18n.t('ui.panel.expand')}">
|
||||
▼
|
||||
</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')}">
|
||||
X
|
||||
</button>
|
||||
@@ -466,12 +438,6 @@ export class Panel {
|
||||
this.#toggle()
|
||||
})
|
||||
|
||||
// Pause/continue button
|
||||
this.#pauseButton.addEventListener('click', (e) => {
|
||||
e.stopPropagation()
|
||||
this.#togglePause()
|
||||
})
|
||||
|
||||
// Stop button
|
||||
this.#stopButton.addEventListener('click', (e) => {
|
||||
e.stopPropagation()
|
||||
@@ -503,7 +469,6 @@ export class Panel {
|
||||
|
||||
#expand(): void {
|
||||
this.#isExpanded = true
|
||||
this.wrapper.classList.remove(styles.collapsed)
|
||||
this.wrapper.classList.add(styles.expanded)
|
||||
this.#expandButton.textContent = '▲'
|
||||
}
|
||||
@@ -511,7 +476,6 @@ export class Panel {
|
||||
#collapse(): void {
|
||||
this.#isExpanded = false
|
||||
this.wrapper.classList.remove(styles.expanded)
|
||||
this.wrapper.classList.add(styles.collapsed)
|
||||
this.#expandButton.textContent = '▼'
|
||||
}
|
||||
|
||||
@@ -632,7 +596,7 @@ export class Panel {
|
||||
typeClass = styles.error
|
||||
statusIcon = '❌'
|
||||
} else if (step.type === 'tool_executing') {
|
||||
statusIcon = '⚙️'
|
||||
statusIcon = '🔨'
|
||||
} else if (step.type === 'output') {
|
||||
typeClass = styles.output
|
||||
statusIcon = '🤖'
|
||||
@@ -642,6 +606,9 @@ export class Panel {
|
||||
} else if (step.type === 'retry') {
|
||||
typeClass = styles.retry
|
||||
statusIcon = '🔄'
|
||||
} else if (step.type === 'observation') {
|
||||
typeClass = styles.observation
|
||||
statusIcon = '👁️'
|
||||
} else {
|
||||
statusIcon = '🧠'
|
||||
}
|
||||
|
||||
@@ -6,7 +6,15 @@ export interface Step {
|
||||
id: string
|
||||
stepNumber: number
|
||||
timestamp: Date
|
||||
type: 'thinking' | 'tool_executing' | 'completed' | 'error' | 'output' | 'input' | 'retry'
|
||||
type:
|
||||
| 'thinking'
|
||||
| 'tool_executing'
|
||||
| 'completed'
|
||||
| 'error'
|
||||
| 'output'
|
||||
| 'input'
|
||||
| 'retry'
|
||||
| 'observation'
|
||||
|
||||
// Tool execution related
|
||||
toolName?: string
|
||||
@@ -18,7 +26,7 @@ export interface Step {
|
||||
duration?: number
|
||||
}
|
||||
|
||||
export type AgentStatus = 'idle' | 'running' | 'paused' | 'completed' | 'error'
|
||||
export type AgentStatus = 'idle' | 'running' | 'completed' | 'error'
|
||||
|
||||
export class UIState {
|
||||
private steps: Step[] = []
|
||||
|
||||
@@ -4,17 +4,13 @@ const enUS = {
|
||||
panel: {
|
||||
ready: 'Ready',
|
||||
thinking: 'Thinking...',
|
||||
paused: 'Paused',
|
||||
taskInput: 'Enter new task, describe steps in detail, press Enter to submit',
|
||||
userAnswerPrompt: 'Please answer the question above, press Enter to submit',
|
||||
taskTerminated: 'Task terminated',
|
||||
taskCompleted: 'Task completed',
|
||||
continueExecution: 'Continue execution',
|
||||
userAnswer: 'User answer: {{input}}',
|
||||
question: 'Question: {{question}}',
|
||||
waitingPlaceholder: 'Waiting for task to start...',
|
||||
pause: 'Pause',
|
||||
continue: 'Continue',
|
||||
stop: 'Stop',
|
||||
expand: 'Expand history',
|
||||
collapse: 'Collapse history',
|
||||
@@ -54,17 +50,13 @@ const zhCN = {
|
||||
panel: {
|
||||
ready: '准备就绪',
|
||||
thinking: '正在思考...',
|
||||
paused: '暂停中,稍后',
|
||||
taskInput: '输入新任务,详细描述步骤,回车提交',
|
||||
userAnswerPrompt: '请回答上面问题,回车提交',
|
||||
taskTerminated: '任务已终止',
|
||||
taskCompleted: '任务结束',
|
||||
continueExecution: '继续执行',
|
||||
userAnswer: '用户回答: {{input}}',
|
||||
question: '询问: {{question}}',
|
||||
waitingPlaceholder: '等待任务开始...',
|
||||
pause: '暂停',
|
||||
continue: '继续',
|
||||
stop: '终止',
|
||||
expand: '展开历史',
|
||||
collapse: '收起历史',
|
||||
|
||||
@@ -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.
|
||||
|
||||
Use it only in a small area.
|
||||
Use it only in a small area.
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
// @workaround DTS bug
|
||||
// dts do not work with monorepo path mapping
|
||||
// disable path mapping for it
|
||||
"paths": {}
|
||||
}
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
// @workaround DTS bug
|
||||
// dts do not work with monorepo path mapping
|
||||
// disable path mapping for it
|
||||
"paths": {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+10
-11
@@ -1,13 +1,12 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo",
|
||||
"noEmit": false,
|
||||
"allowImportingTsExtensions": false,
|
||||
"baseUrl": ".",
|
||||
"outDir": "dist"
|
||||
},
|
||||
"include": ["**/*.ts", "**/*.js"],
|
||||
"exclude": ["dist", "node_modules"]
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo",
|
||||
"noEmit": false,
|
||||
"allowImportingTsExtensions": false,
|
||||
"baseUrl": ".",
|
||||
"outDir": "dist"
|
||||
},
|
||||
"include": ["**/*.ts", "**/*.js"],
|
||||
"exclude": ["dist", "node_modules"]
|
||||
}
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
# Landing Page & Docs
|
||||
@@ -1,24 +1,24 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "new-york",
|
||||
"rsc": false,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "",
|
||||
"css": "src/index.css",
|
||||
"baseColor": "neutral",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"iconLibrary": "lucide",
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils",
|
||||
"ui": "@/components/ui",
|
||||
"lib": "@/lib",
|
||||
"hooks": "@/hooks"
|
||||
},
|
||||
"registries": {
|
||||
"@magicui": "https://magicui.design/r/{name}.json"
|
||||
}
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "new-york",
|
||||
"rsc": false,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "",
|
||||
"css": "src/index.css",
|
||||
"baseColor": "neutral",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"iconLibrary": "lucide",
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils",
|
||||
"ui": "@/components/ui",
|
||||
"lib": "@/lib",
|
||||
"hooks": "@/hooks"
|
||||
},
|
||||
"registries": {
|
||||
"@magicui": "https://magicui.design/r/{name}.json"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"name": "@page-agent/website",
|
||||
"private": true,
|
||||
"version": "0.0.23",
|
||||
"version": "0.2.5",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"dev": "vite --host 0.0.0.0",
|
||||
"build:website": "vite build",
|
||||
"preview": "vite preview",
|
||||
"typecheck": "tsc --noEmit"
|
||||
|
||||
@@ -22,7 +22,7 @@ export default function Header() {
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
{/* Logo */}
|
||||
<Link
|
||||
href="/"
|
||||
href="~/"
|
||||
className="flex items-center gap-2 sm:gap-3 group shrink-0"
|
||||
aria-label={t('header.logo_alt')}
|
||||
onClick={() => setMobileMenuOpen(false)}
|
||||
@@ -41,6 +41,7 @@ export default function Header() {
|
||||
className="hidden sm:block text-xs text-gray-600 dark:text-gray-300 py-0 font-normal overflow-visible"
|
||||
duration={600}
|
||||
animateOnHover={true}
|
||||
aria-hidden="true"
|
||||
>
|
||||
{t('header.slogan')}
|
||||
</HyperText>
|
||||
@@ -54,7 +55,7 @@ export default function Header() {
|
||||
aria-label="Mobile navigation"
|
||||
>
|
||||
<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"
|
||||
aria-label={t('header.nav_docs')}
|
||||
>
|
||||
@@ -84,8 +85,11 @@ export default function Header() {
|
||||
role="navigation"
|
||||
aria-label={t('header.nav_docs')}
|
||||
>
|
||||
<span className="text-xs font-mono text-gray-400 dark:text-gray-500 tabular-nums before:content-['v']">
|
||||
{import.meta.env.VERSION}
|
||||
</span>
|
||||
<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"
|
||||
>
|
||||
<BookOpen className="w-4 h-4" />
|
||||
@@ -133,7 +137,7 @@ export default function Header() {
|
||||
role="navigation"
|
||||
>
|
||||
<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"
|
||||
onClick={() => setMobileMenuOpen(false)}
|
||||
>
|
||||
|
||||
@@ -1,3 +1,15 @@
|
||||
export const CDN_URL = 'https://cdn.jsdelivr.net/npm/page-agent@latest/dist/umd/page-agent.js'
|
||||
export const CDN_CN_URL =
|
||||
'https://registry.npmmirror.com/page-agent/latest/files/dist/umd/page-agent.js'
|
||||
// Demo build (auto-init with demo LLM, for quick testing)
|
||||
export const CDN_DEMO_URL = 'https://cdn.jsdelivr.net/npm/@page-agent/cdn/dist/page-agent.demo.js'
|
||||
export const CDN_DEMO_CN_URL =
|
||||
'https://registry.npmmirror.com/@page-agent/cdn/latest/files/dist/page-agent.demo.js'
|
||||
|
||||
// Full build (exports PageAgent class, usage identical to npm)
|
||||
export const CDN_FULL_URL = 'https://cdn.jsdelivr.net/npm/@page-agent/cdn/dist/page-agent.js'
|
||||
export const CDN_FULL_CN_URL =
|
||||
'https://registry.npmmirror.com/@page-agent/cdn/latest/files/dist/page-agent.js'
|
||||
|
||||
// Demo LLM for website testing
|
||||
export const DEMO_MODEL = 'PAGE-AGENT-FREE-TESTING-RANDOM'
|
||||
export const DEMO_BASE_URL =
|
||||
'https://hwcxiuzfylggtcktqgij.supabase.co/functions/v1/llm-testing-proxy'
|
||||
export const DEMO_API_KEY = 'PAGE-AGENT-FREE-TESTING-RANDOM'
|
||||
|
||||
Vendored
+4
@@ -1,5 +1,9 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
interface ImportMetaEnv {
|
||||
readonly VERSION: string
|
||||
}
|
||||
|
||||
declare module '*.module.css' {
|
||||
const classes: Record<string, string>
|
||||
export default classes
|
||||
|
||||
@@ -28,9 +28,9 @@ pages/i18n/
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
function MyComponent() {
|
||||
const { t } = useTranslation('common') // 指定命名空间
|
||||
|
||||
return <h1>{t('header.nav_docs')}</h1>
|
||||
const { t } = useTranslation('common') // 指定命名空间
|
||||
|
||||
return <h1>{t('header.nav_docs')}</h1>
|
||||
}
|
||||
```
|
||||
|
||||
@@ -71,4 +71,3 @@ t('common:header.nav_docs')
|
||||
- [ ] 文档页翻译(`docs.json`)
|
||||
- [ ] DocsLayout 导航结构国际化
|
||||
- [ ] 404 页面国际化
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/* eslint-disable react-dom/no-dangerously-set-innerhtml */
|
||||
import { Bot, Box, MessageSquare, PlayCircle, Shield, Sparkles, Users, Zap } from 'lucide-react'
|
||||
import { PageAgent } from 'page-agent'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Link, useSearchParams } from 'wouter'
|
||||
@@ -12,10 +11,16 @@ import { Highlighter } from '../components/ui/highlighter'
|
||||
import { NeonGradientCard } from '../components/ui/neon-gradient-card'
|
||||
import { Particles } from '../components/ui/particles'
|
||||
import { SparklesText } from '../components/ui/sparkles-text'
|
||||
import { CDN_CN_URL, CDN_URL } from '../constants'
|
||||
import {
|
||||
CDN_DEMO_CN_URL,
|
||||
CDN_DEMO_URL,
|
||||
DEMO_API_KEY,
|
||||
DEMO_BASE_URL,
|
||||
DEMO_MODEL,
|
||||
} from '../constants'
|
||||
|
||||
function getInjection(useCN?: boolean) {
|
||||
const cdn = useCN ? CDN_CN_URL : CDN_URL
|
||||
const cdn = useCN ? CDN_DEMO_CN_URL : CDN_DEMO_URL
|
||||
|
||||
const injection = encodeURI(
|
||||
`javascript:(function(){var s=document.createElement('script');s.src=\`${cdn}?t=\${Math.random()}\`;s.setAttribute('crossorigin', true);s.type="text/javascript";s.onload=()=>console.log('PageAgent script loaded!');document.body.appendChild(s);})();`
|
||||
@@ -53,13 +58,12 @@ export default function HomePage() {
|
||||
const handleExecute = async () => {
|
||||
if (!task.trim()) return
|
||||
|
||||
let pageAgent: PageAgent
|
||||
const win = window as any
|
||||
|
||||
if (win.pageAgent && !win.pageAgent.disposed) {
|
||||
pageAgent = win.pageAgent
|
||||
} else {
|
||||
pageAgent = new PageAgent({
|
||||
// Lazy load PageAgent only when user clicks execute
|
||||
if (!win.pageAgent || win.pageAgent.disposed) {
|
||||
const { PageAgent } = await import('page-agent')
|
||||
win.pageAgent = new PageAgent({
|
||||
// 把 react 根元素排除掉,挂了很多冒泡时间导致假阳
|
||||
interactiveBlacklist: [document.getElementById('root')!],
|
||||
language: i18n.language as any,
|
||||
@@ -73,19 +77,26 @@ export default function HomePage() {
|
||||
},
|
||||
},
|
||||
|
||||
// experimentalScriptExecutionTool: true,
|
||||
model:
|
||||
import.meta.env.DEV && import.meta.env.LLM_MODEL_NAME
|
||||
? import.meta.env.LLM_MODEL_NAME
|
||||
: DEMO_MODEL,
|
||||
baseURL:
|
||||
import.meta.env.DEV && import.meta.env.LLM_BASE_URL
|
||||
? import.meta.env.LLM_BASE_URL
|
||||
: DEMO_BASE_URL,
|
||||
apiKey:
|
||||
import.meta.env.DEV && import.meta.env.LLM_API_KEY
|
||||
? import.meta.env.LLM_API_KEY
|
||||
: DEMO_API_KEY,
|
||||
|
||||
// testing server
|
||||
// @note: rate limit. prompt limit.
|
||||
// model: DEMO_MODEL,
|
||||
// baseURL: DEMO_BASE_URL,
|
||||
// apiKey: 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)
|
||||
}
|
||||
|
||||
@@ -95,7 +106,10 @@ export default function HomePage() {
|
||||
|
||||
{/* Hero Section */}
|
||||
<main id="main-content">
|
||||
<section className="relative px-6 py-22 lg:py-28" aria-labelledby="hero-heading">
|
||||
<section
|
||||
className="relative px-6 py-22 pb-18 lg:py-28 overflow-hidden"
|
||||
aria-labelledby="hero-heading"
|
||||
>
|
||||
<div className="max-w-7xl mx-auto text-center">
|
||||
{/* Background Pattern + Particles */}
|
||||
<div className="absolute inset-0 opacity-30" aria-hidden="true">
|
||||
|
||||
@@ -24,38 +24,38 @@ export default function DocsLayout({ children }: DocsLayoutProps) {
|
||||
{
|
||||
title: t('nav.introduction'),
|
||||
items: [
|
||||
{ title: t('nav.overview'), path: '/docs/introduction/overview' },
|
||||
{ title: t('nav.quick_start'), path: '/docs/introduction/quick-start' },
|
||||
{ title: t('nav.limitations'), path: '/docs/introduction/limitations' },
|
||||
{ title: t('nav.overview'), path: '/introduction/overview' },
|
||||
{ title: t('nav.quick_start'), path: '/introduction/quick-start' },
|
||||
{ title: t('nav.limitations'), path: '/introduction/limitations' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: t('nav.features'),
|
||||
items: [
|
||||
{ title: t('nav.models'), path: '/docs/features/models' },
|
||||
{ title: t('nav.custom_tools'), path: '/docs/features/custom-tools' },
|
||||
{ title: t('nav.knowledge_injection'), path: '/docs/features/custom-instructions' },
|
||||
{ title: t('nav.data_masking'), path: '/docs/features/data-masking' },
|
||||
{ title: t('nav.models'), path: '/features/models' },
|
||||
{ title: t('nav.custom_tools'), path: '/features/custom-tools' },
|
||||
{ title: t('nav.knowledge_injection'), path: '/features/custom-instructions' },
|
||||
{ title: t('nav.data_masking'), path: '/features/data-masking' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: t('nav.integration'),
|
||||
items: [
|
||||
{ title: t('nav.configuration'), path: '/docs/integration/configuration' },
|
||||
{ title: t('nav.third_party_agent'), path: '/docs/integration/third-party-agent' },
|
||||
{ title: '🚧 ' + t('nav.cdn_setup'), path: '/docs/integration/cdn-setup' },
|
||||
{ title: t('nav.configuration'), path: '/integration/configuration' },
|
||||
{ title: t('nav.third_party_agent'), path: '/integration/third-party-agent' },
|
||||
{ title: t('nav.cdn_setup'), path: '/integration/cdn-setup' },
|
||||
{
|
||||
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 (
|
||||
<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 */}
|
||||
<aside className="w-64 shrink-0" role="complementary" aria-label="文档导航">
|
||||
<div className="sticky">
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -1,81 +1,124 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import BetaNotice from '@/components/BetaNotice'
|
||||
import CodeEditor from '@/components/CodeEditor'
|
||||
import { CDN_CN_URL, CDN_URL } from '@/constants'
|
||||
import { CDN_DEMO_CN_URL, CDN_DEMO_URL, CDN_FULL_CN_URL, CDN_FULL_URL } from '@/constants'
|
||||
|
||||
export default function CdnSetup() {
|
||||
const { i18n } = useTranslation()
|
||||
const isZh = i18n.language === 'zh-CN'
|
||||
|
||||
return (
|
||||
<div>
|
||||
<BetaNotice />
|
||||
<div className="space-y-10">
|
||||
<header>
|
||||
<h1 className="text-4xl font-bold mb-4">{isZh ? 'CDN 引入' : 'CDN Setup'}</h1>
|
||||
<p className="text-lg text-gray-600 dark:text-gray-300 leading-relaxed">
|
||||
{isZh
|
||||
? 'CDN 提供两种构建版本:Demo 版用于快速体验,标准版用法与 NPM 一致。'
|
||||
: 'CDN provides two builds: Demo for quick testing, standard build with usage identical to NPM.'}
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<h1 className="text-4xl font-bold mb-6">{isZh ? 'CDN 引入' : 'CDN Setup'}</h1>
|
||||
{/* Demo Build Section */}
|
||||
<section>
|
||||
<h2 className="text-2xl font-bold mb-3">{isZh ? '🚀 快速体验' : '🚀 Quick Try'}</h2>
|
||||
|
||||
<p className="text-xl text-gray-600 dark:text-gray-300 mb-8 leading-relaxed">
|
||||
{isZh
|
||||
? '通过 CDN 快速集成 page-agent,无需复杂的构建配置。默认使用演示模型。'
|
||||
: 'Fastest way to integrate page-agent via CDN. No complex build setup needed. Demo model will be used by default.'}
|
||||
</p>
|
||||
|
||||
<section className="mb-8">
|
||||
<h2 className="text-2xl font-bold mb-4">{isZh ? 'CDN 地址' : 'CDN URLs'}</h2>
|
||||
|
||||
<div className="overflow-x-auto mb-6">
|
||||
<table className="w-full border-collapse text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-gray-200 dark:border-gray-700">
|
||||
<th className="text-left py-3 px-4 font-semibold">{isZh ? '位置' : 'Location'}</th>
|
||||
<th className="text-left py-3 px-4 font-semibold">URL</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr className="border-b border-gray-100 dark:border-gray-800">
|
||||
<td className="py-3 px-4">{isZh ? '全球' : 'Global'}</td>
|
||||
<td className="py-3 px-4 font-mono text-xs break-all">{CDN_URL}</td>
|
||||
</tr>
|
||||
<tr className="border-b border-gray-100 dark:border-gray-800">
|
||||
<td className="py-3 px-4">{isZh ? '中国镜像' : 'China Mirror'}</td>
|
||||
<td className="py-3 px-4 font-mono text-xs break-all">{CDN_CN_URL}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="mb-8">
|
||||
<h2 className="text-2xl font-bold mb-4">{isZh ? '快速开始' : 'Quick Start'}</h2>
|
||||
<p className="text-gray-600 dark:text-gray-300 mb-3">
|
||||
{isZh
|
||||
? '自动初始化并使用内置 Demo LLM,无需任何配置:'
|
||||
: 'Auto-initializes with built-in demo LLM, no configuration needed:'}
|
||||
</p>
|
||||
|
||||
<CodeEditor
|
||||
className="mb-6"
|
||||
code={`<script
|
||||
src="${CDN_URL}"
|
||||
crossorigin="true"
|
||||
type="text/javascript"
|
||||
></script>`}
|
||||
className="mb-3"
|
||||
code={`<script src="${CDN_DEMO_URL}" crossorigin="true"></script>`}
|
||||
/>
|
||||
|
||||
<div className="bg-yellow-50 dark:bg-yellow-900/20 px-4 py-3 rounded-lg mb-4 text-sm">
|
||||
<span className="text-yellow-800 dark:text-yellow-200">
|
||||
⚠️{' '}
|
||||
{isZh
|
||||
? '仅用于技术评估,Demo 模型有速率限制。'
|
||||
: 'For evaluation only. Demo model has rate limits.'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<table className="w-full border-collapse text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-gray-200 dark:border-gray-700">
|
||||
<th className="text-left py-2 px-3 font-semibold w-28">
|
||||
{isZh ? '位置' : 'Location'}
|
||||
</th>
|
||||
<th className="text-left py-2 px-3 font-semibold">URL</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr className="border-b border-gray-100 dark:border-gray-800">
|
||||
<td className="py-2 px-3">{isZh ? '全球' : 'Global'}</td>
|
||||
<td className="py-2 px-3 font-mono text-xs break-all">{CDN_DEMO_URL}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="py-2 px-3">{isZh ? '中国' : 'China'}</td>
|
||||
<td className="py-2 px-3 font-mono text-xs break-all">{CDN_DEMO_CN_URL}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<section className="mb-8">
|
||||
<div className="bg-yellow-50 dark:bg-yellow-900/20 p-4 rounded-lg">
|
||||
<h3 className="text-lg font-semibold mb-2 text-yellow-900 dark:text-yellow-300">
|
||||
⚠️ {isZh ? '注意事项' : 'Notes'}
|
||||
</h3>
|
||||
<ul className="text-gray-600 dark:text-gray-300 space-y-1">
|
||||
<li>
|
||||
• {isZh ? '生产环境建议使用固定版本号' : 'Use fixed version number in production'}
|
||||
</li>
|
||||
<li>• {isZh ? '确保 HTTPS 环境下使用' : 'Ensure HTTPS environment'}</li>
|
||||
<li>
|
||||
•{' '}
|
||||
{isZh
|
||||
? '配置 CSP 策略允许脚本执行'
|
||||
: 'Configure CSP policy to allow script execution'}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{/* CDN Build Section */}
|
||||
<section>
|
||||
<h2 className="text-2xl font-bold mb-3">{isZh ? '📦 标准版' : '📦 Standard Build'}</h2>
|
||||
|
||||
<p className="text-gray-600 dark:text-gray-300 mb-3">
|
||||
{isZh
|
||||
? '将 PageAgent 类暴露到 globalThis,用法与 NPM 一致:'
|
||||
: 'Exposes PageAgent class to globalThis, usage identical to NPM:'}
|
||||
</p>
|
||||
|
||||
<CodeEditor
|
||||
className="mb-4"
|
||||
code={`<script src="${CDN_FULL_URL}" 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 submit button')
|
||||
</script>`}
|
||||
/>
|
||||
|
||||
<table className="w-full border-collapse text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-gray-200 dark:border-gray-700">
|
||||
<th className="text-left py-2 px-3 font-semibold w-28">
|
||||
{isZh ? '位置' : 'Location'}
|
||||
</th>
|
||||
<th className="text-left py-2 px-3 font-semibold">URL</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr className="border-b border-gray-100 dark:border-gray-800">
|
||||
<td className="py-2 px-3">{isZh ? '全球' : 'Global'}</td>
|
||||
<td className="py-2 px-3 font-mono text-xs break-all">{CDN_FULL_URL}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="py-2 px-3">{isZh ? '中国' : 'China'}</td>
|
||||
<td className="py-2 px-3 font-mono text-xs break-all">{CDN_FULL_CN_URL}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
{/* Tips */}
|
||||
<section className="bg-blue-50 dark:bg-blue-900/20 p-4 rounded-lg">
|
||||
<h3 className="text-base font-semibold mb-2 text-blue-900 dark:text-blue-300">
|
||||
💡 {isZh ? '提示' : 'Tips'}
|
||||
</h3>
|
||||
<ul className="text-gray-600 dark:text-gray-300 text-sm space-y-1">
|
||||
<li>• {isZh ? '生产环境推荐使用 NPM' : 'NPM is recommended for production'}</li>
|
||||
<li>• {isZh ? '生产环境建议锁定版本号' : 'Lock version number in production'}</li>
|
||||
<li>• {isZh ? '确保 HTTPS 环境' : 'Ensure HTTPS environment'}</li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -28,15 +28,17 @@ export default function Configuration() {
|
||||
className="mb-4"
|
||||
language="typescript"
|
||||
code={`interface LLMConfig {
|
||||
baseURL?: string
|
||||
apiKey?: string
|
||||
model?: string
|
||||
baseURL: string
|
||||
apiKey: string
|
||||
model: string
|
||||
|
||||
temperature?: number
|
||||
maxRetries?: number
|
||||
|
||||
/**
|
||||
* Custom fetch function for LLM API requests.
|
||||
* Use this to customize headers, credentials, proxy, etc.
|
||||
* The response should follow OpenAI API format.
|
||||
*/
|
||||
customFetch?: typeof globalThis.fetch
|
||||
}`}
|
||||
@@ -59,6 +61,27 @@ export default function Configuration() {
|
||||
code={`interface AgentConfig {
|
||||
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 */
|
||||
customTools?: Record<string, PageAgentTool | null>
|
||||
|
||||
@@ -71,12 +94,12 @@ export default function Configuration() {
|
||||
getPageInstructions?: (url: string) => string | undefined | null
|
||||
}
|
||||
|
||||
// Lifecycle hooks
|
||||
onBeforeStep?: (stepCnt: number) => Promise<void> | void
|
||||
onAfterStep?: (stepCnt: number, history: AgentHistory[]) => Promise<void> | void
|
||||
onBeforeTask?: () => Promise<void> | void
|
||||
onAfterTask?: (result: ExecutionResult) => Promise<void> | void
|
||||
onDispose?: (reason?: string) => void
|
||||
// Lifecycle hooks (with \`this\` bound to PageAgent instance)
|
||||
onBeforeStep?: (this: PageAgent, stepCnt: number) => Promise<void> | void
|
||||
onAfterStep?: (this: PageAgent, stepCnt: number, history: HistoryEvent[]) => Promise<void> | void
|
||||
onBeforeTask?: (this: PageAgent) => Promise<void> | void
|
||||
onAfterTask?: (this: PageAgent, result: ExecutionResult) => Promise<void> | void
|
||||
onDispose?: (this: PageAgent, reason?: string) => void
|
||||
|
||||
/**
|
||||
* Transform page content before sending to LLM.
|
||||
@@ -103,7 +126,7 @@ export default function Configuration() {
|
||||
<CodeEditor
|
||||
className="mb-4"
|
||||
language="typescript"
|
||||
code={`interface PageControllerConfig {
|
||||
code={`interface DomConfig {
|
||||
/** Elements to exclude from interaction */
|
||||
interactiveBlacklist?: (Element | (() => Element))[]
|
||||
|
||||
@@ -118,15 +141,13 @@ export default function Configuration() {
|
||||
|
||||
/** Highlight label opacity (0-1) */
|
||||
highlightLabelOpacity?: number
|
||||
}
|
||||
|
||||
/** Viewport expansion in pixels (-1 for full page) */
|
||||
interface PageControllerConfig extends DomConfig {
|
||||
/** Viewport expansion in pixels */
|
||||
viewportExpansion?: number
|
||||
|
||||
/**
|
||||
* Enable visual mask overlay during automation.
|
||||
* Blocks user interaction while agent is running.
|
||||
* Default: false for PageController, true for PageAgent.
|
||||
*/
|
||||
/** Enable visual mask overlay during operations (default: false) */
|
||||
enableMask?: boolean
|
||||
}`}
|
||||
/>
|
||||
@@ -140,6 +161,39 @@ export default function Configuration() {
|
||||
code={`type PageAgentConfig = LLMConfig & AgentConfig & PageControllerConfig`}
|
||||
/>
|
||||
</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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ const pageAgentTool = {
|
||||
},
|
||||
execute: async (params) => {
|
||||
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 */}
|
||||
<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">
|
||||
<img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="MIT License" />
|
||||
</a>
|
||||
|
||||
@@ -2,10 +2,11 @@ import { useTranslation } from 'react-i18next'
|
||||
|
||||
import BetaNotice from '@/components/BetaNotice'
|
||||
import CodeEditor from '@/components/CodeEditor'
|
||||
import { CDN_CN_URL, CDN_URL } from '@/constants'
|
||||
import { CDN_DEMO_CN_URL, CDN_DEMO_URL } from '@/constants'
|
||||
|
||||
export default function QuickStart() {
|
||||
const { t } = useTranslation('docs')
|
||||
const { t, i18n } = useTranslation('docs')
|
||||
const isZh = i18n.language === 'zh-CN'
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -16,64 +17,62 @@ export default function QuickStart() {
|
||||
<h2 className="text-2xl font-bold mb-3">{t('quick_start.installation')}</h2>
|
||||
|
||||
<div className="space-y-4 mb-6">
|
||||
{/* Demo CDN - One Line */}
|
||||
<div className="p-4 bg-blue-50 dark:bg-blue-900/20 rounded-lg">
|
||||
<h3 className="text-lg font-semibold mb-2 text-blue-900 dark:text-blue-300">
|
||||
{t('quick_start.step1_title')}
|
||||
{isZh ? '🚀 快速体验(Demo CDN)' : '🚀 Quick Try (Demo CDN)'}
|
||||
</h3>
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<p className="text-sm font-medium mb-2">{t('quick_start.step1_cdn')}</p>
|
||||
<CodeEditor
|
||||
code={`// CDN: \t${CDN_URL}
|
||||
// Mirror: \t${CDN_CN_URL}
|
||||
<script src="${CDN_URL}" crossorigin="true" type="text/javascript"></script>`}
|
||||
language="html"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-medium mb-2">{t('quick_start.step1_npm')}</p>
|
||||
<CodeEditor
|
||||
code={`// npm install page-agent
|
||||
import PageAgent from 'page-agent'`}
|
||||
language="bash"
|
||||
/>
|
||||
</div>
|
||||
<div className="bg-yellow-50 dark:bg-yellow-900/20 p-2 rounded mb-3 text-sm">
|
||||
<span className="text-yellow-800 dark:text-yellow-200">
|
||||
⚠️ {isZh ? '仅用于技术评估' : 'For evaluation only'}
|
||||
</span>
|
||||
</div>
|
||||
<CodeEditor
|
||||
code={`// Global: ${CDN_DEMO_URL}
|
||||
// China: ${CDN_DEMO_CN_URL}
|
||||
<script src="${isZh ? CDN_DEMO_CN_URL : CDN_DEMO_URL}" crossorigin="true"></script>`}
|
||||
language="html"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* NPM - Recommended */}
|
||||
<div className="p-4 bg-green-50 dark:bg-green-900/20 rounded-lg">
|
||||
<h3 className="text-lg font-semibold mb-2 text-green-900 dark:text-green-300">
|
||||
{t('quick_start.step2_title')}
|
||||
{isZh ? '📦 NPM 安装(推荐)' : '📦 NPM Install (Recommended)'}
|
||||
</h3>
|
||||
<CodeEditor
|
||||
code={`// 仅供测试使用,生产环境需要配置 LLM 接入点,本工具不提供 LLM 服务
|
||||
// 测试接口
|
||||
// @note: 限流,限制 prompt 内容,限制来源,随时变更,请替换成你自己的
|
||||
// @note: 使用 DeepSeek-chat(3.2) 官方版本,使用协议和隐私策略见 DeepSeek 网站
|
||||
const DEMO_MODEL = 'PAGE-AGENT-FREE-TESTING-RANDOM'
|
||||
const DEMO_BASE_URL = 'https://hwcxiuzfylggtcktqgij.supabase.co/functions/v1/llm-testing-proxy'
|
||||
const DEMO_API_KEY = 'PAGE-AGENT-FREE-TESTING-RANDOM'
|
||||
code={`npm install page-agent
|
||||
|
||||
const agent = new PageAgent({
|
||||
model: DEMO_MODEL,
|
||||
baseURL: DEMO_BASE_URL,
|
||||
apiKey: DEMO_API_KEY,
|
||||
language: 'zh-CN'
|
||||
})`}
|
||||
language="javascript"
|
||||
import { PageAgent } from 'page-agent'`}
|
||||
language="bash"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="p-4 bg-purple-50 dark:bg-purple-900/20 rounded-lg">
|
||||
<h3 className="text-lg font-semibold mb-2 text-purple-900 dark:text-purple-300">
|
||||
{t('quick_start.step2_title')}
|
||||
</h3>
|
||||
<CodeEditor
|
||||
code={`const agent = new PageAgent({
|
||||
model: 'deepseek-chat',
|
||||
baseURL: 'https://api.deepseek.com',
|
||||
apiKey: 'YOUR_API_KEY',
|
||||
language: '${isZh ? 'zh-CN' : 'en-US'}'
|
||||
})`}
|
||||
language="javascript"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="p-4 bg-orange-50 dark:bg-orange-900/20 rounded-lg">
|
||||
<h3 className="text-lg font-semibold mb-2 text-orange-900 dark:text-orange-300">
|
||||
{t('quick_start.step3_title')}
|
||||
</h3>
|
||||
<CodeEditor
|
||||
code={`// 程序化执行自然语言指令
|
||||
await pageAgent.execute('点击提交按钮,然后填写用户名为张三');
|
||||
code={`// ${isZh ? '程序化执行自然语言指令' : 'Execute natural language instructions programmatically'}
|
||||
await agent.execute('${isZh ? '点击提交按钮,然后填写用户名为张三' : 'Click submit button, then fill username as John'}');
|
||||
|
||||
// 或者显示对话框让用户输入指令
|
||||
pageAgent.panel.show()
|
||||
// ${isZh ? '或者显示对话框让用户输入指令' : 'Or show panel for user to input instructions'}
|
||||
agent.panel.show()
|
||||
`}
|
||||
language="javascript"
|
||||
/>
|
||||
|
||||
+24
-138
@@ -1,148 +1,34 @@
|
||||
import { Suspense, lazy } from 'react'
|
||||
import { Route, Switch } from 'wouter'
|
||||
|
||||
import Header from './components/Header'
|
||||
import HomePage from './pages/Home'
|
||||
import DocsLayout from './pages/docs/Layout'
|
||||
// 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'
|
||||
// Lazy load pages
|
||||
const HomePage = lazy(() => import('./pages/Home'))
|
||||
const DocsPages = lazy(() => import('./pages/docs/index'))
|
||||
|
||||
export default function Router() {
|
||||
return (
|
||||
<Switch>
|
||||
{/* Home page */}
|
||||
<Route path="/" component={HomePage} />
|
||||
<Suspense>
|
||||
<Switch>
|
||||
{/* Home page */}
|
||||
<Route path="/">
|
||||
<HomePage />
|
||||
</Route>
|
||||
|
||||
{/* Documentation pages with layout */}
|
||||
<Route path="/docs/introduction/overview">
|
||||
<div className="min-h-screen bg-white dark:bg-gray-900">
|
||||
<Header />
|
||||
<DocsLayout>
|
||||
<Overview />
|
||||
</DocsLayout>
|
||||
</div>
|
||||
</Route>
|
||||
{/* All docs pages */}
|
||||
<Route path="/docs" nest>
|
||||
<DocsPages />
|
||||
</Route>
|
||||
|
||||
<Route path="/docs/introduction/quick-start">
|
||||
<div className="min-h-screen bg-white dark:bg-gray-900">
|
||||
<Header />
|
||||
<DocsLayout>
|
||||
<QuickStart />
|
||||
</DocsLayout>
|
||||
</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>
|
||||
{/* 404 */}
|
||||
<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>
|
||||
</Switch>
|
||||
</Route>
|
||||
</Switch>
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo",
|
||||
"noEmit": false,
|
||||
"allowImportingTsExtensions": false,
|
||||
"baseUrl": ".",
|
||||
"outDir": "dist",
|
||||
"paths": {
|
||||
// Self root
|
||||
"@/*": ["src/*"],
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo",
|
||||
"noEmit": false,
|
||||
"allowImportingTsExtensions": false,
|
||||
"baseUrl": ".",
|
||||
"outDir": "dist",
|
||||
"paths": {
|
||||
// Self root
|
||||
"@/*": ["src/*"],
|
||||
|
||||
// Simplified monorepo solution (raw npm workspace with hoisting)
|
||||
"page-agent": ["../page-agent/src/PageAgent.ts"],
|
||||
"@page-agent/llms": ["../llms/src/index.ts"],
|
||||
"@page-agent/page-controller": ["../page-controller/src/PageController.ts"],
|
||||
"@page-agent/ui": ["../ui/src/index.ts"]
|
||||
}
|
||||
},
|
||||
"include": ["**/*.ts", "**/*.tsx"],
|
||||
"exclude": ["dist", "node_modules"],
|
||||
"references": [
|
||||
//
|
||||
{ "path": "../llms" },
|
||||
{ "path": "../page-agent" },
|
||||
{ "path": "../page-controller" },
|
||||
{ "path": "../ui" }
|
||||
]
|
||||
// Simplified monorepo solution (raw npm workspace with hoisting)
|
||||
"page-agent": ["../page-agent/src/PageAgent.ts"],
|
||||
"@page-agent/llms": ["../llms/src/index.ts"],
|
||||
"@page-agent/page-controller": ["../page-controller/src/PageController.ts"],
|
||||
"@page-agent/ui": ["../ui/src/index.ts"]
|
||||
}
|
||||
},
|
||||
"include": ["**/*.ts", "**/*.tsx"],
|
||||
"exclude": ["dist", "node_modules"],
|
||||
"references": [
|
||||
//
|
||||
{ "path": "../llms" },
|
||||
{ "path": "../page-agent" },
|
||||
{ "path": "../page-controller" },
|
||||
{ "path": "../ui" }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,21 +1,35 @@
|
||||
import tailwindcss from '@tailwindcss/vite'
|
||||
import react from '@vitejs/plugin-react-swc'
|
||||
import { config as dotenvConfig } from 'dotenv'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import process from 'node:process'
|
||||
import { dirname, resolve } from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
import { defineConfig } from 'vite'
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||
const pageAgentPkg = JSON.parse(
|
||||
readFileSync(resolve(__dirname, '../page-agent/package.json'), 'utf-8')
|
||||
)
|
||||
|
||||
// Load .env from repo root
|
||||
dotenvConfig({ path: resolve(__dirname, '../../.env') })
|
||||
|
||||
// Website Config (React Documentation Site)
|
||||
export default defineConfig({
|
||||
export default defineConfig(({ mode }) => ({
|
||||
base: './',
|
||||
clearScreen: false,
|
||||
plugins: [react(), tailwindcss()],
|
||||
build: {
|
||||
chunkSizeWarningLimit: 2000,
|
||||
cssCodeSplit: true,
|
||||
rollupOptions: {
|
||||
onwarn: function (message, handler) {
|
||||
if (message.code === 'EVAL') return
|
||||
handler(message)
|
||||
},
|
||||
},
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
// Self root
|
||||
@@ -29,8 +43,11 @@ export default defineConfig({
|
||||
},
|
||||
},
|
||||
define: {
|
||||
'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),
|
||||
...(mode === 'development' && {
|
||||
'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),
|
||||
}),
|
||||
'import.meta.env.VERSION': JSON.stringify(pageAgentPkg.version),
|
||||
},
|
||||
})
|
||||
}))
|
||||
|
||||
+41
-41
@@ -1,47 +1,47 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"target": "ES2024",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2024", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
"allowJs": true,
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"target": "ES2024",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2024", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
"allowJs": true,
|
||||
|
||||
// "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo",
|
||||
// "baseUrl": "src",
|
||||
"baseUrl": ".",
|
||||
"outDir": "dist",
|
||||
// "incremental": true,
|
||||
// "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo",
|
||||
// "baseUrl": "src",
|
||||
"baseUrl": ".",
|
||||
"outDir": "dist",
|
||||
// "incremental": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"verbatimModuleSyntax": false,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"allowImportingTsExtensions": true,
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"verbatimModuleSyntax": false,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"allowImportingTsExtensions": true,
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
|
||||
// "paths": {
|
||||
// // Simplified monorepo solution (raw npm workspace with hoisting)
|
||||
// "@page-agent/page-controller": ["./packages/page-controller/src/PageController.ts"],
|
||||
// "page-agent": ["./packages/page-agent/src/PageAgent.ts"]
|
||||
// }
|
||||
}
|
||||
// "references": [
|
||||
// { "path": "./packages/page-controller" },
|
||||
// { "path": "./packages/page-agent" },
|
||||
// { "path": "./packages/website" }
|
||||
// ],
|
||||
// "include": ["packages/*/src/**/*.ts", "packages/*/src/**/*.tsx"],
|
||||
// "exclude": ["node_modules", "dist", "packages/*/dist"]
|
||||
// "files": ["env.d.ts"]
|
||||
// "files": []
|
||||
// "paths": {
|
||||
// // Simplified monorepo solution (raw npm workspace with hoisting)
|
||||
// "@page-agent/page-controller": ["./packages/page-controller/src/PageController.ts"],
|
||||
// "page-agent": ["./packages/page-agent/src/PageAgent.ts"]
|
||||
// }
|
||||
}
|
||||
// "references": [
|
||||
// { "path": "./packages/page-controller" },
|
||||
// { "path": "./packages/page-agent" },
|
||||
// { "path": "./packages/website" }
|
||||
// ],
|
||||
// "include": ["packages/*/src/**/*.ts", "packages/*/src/**/*.tsx"],
|
||||
// "exclude": ["node_modules", "dist", "packages/*/dist"]
|
||||
// "files": ["env.d.ts"]
|
||||
// "files": []
|
||||
}
|
||||
|
||||
+10
-10
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"extends": "./tsconfig.base.json",
|
||||
"references": [
|
||||
{ "path": "./packages/page-controller" },
|
||||
{ "path": "./packages/ui" },
|
||||
{ "path": "./packages/llms" },
|
||||
{ "path": "./packages/page-agent" },
|
||||
{ "path": "./packages/website" }
|
||||
],
|
||||
"include": ["packages/*/src/**/*.ts", "packages/*/src/**/*.tsx"],
|
||||
"exclude": ["node_modules", "dist", "packages/*/dist"]
|
||||
"extends": "./tsconfig.base.json",
|
||||
"references": [
|
||||
{ "path": "./packages/page-controller" },
|
||||
{ "path": "./packages/ui" },
|
||||
{ "path": "./packages/llms" },
|
||||
{ "path": "./packages/page-agent" },
|
||||
{ "path": "./packages/website" }
|
||||
],
|
||||
"include": ["packages/*/src/**/*.ts", "packages/*/src/**/*.tsx"],
|
||||
"exclude": ["node_modules", "dist", "packages/*/dist"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user