Compare commits

...

37 Commits

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