Compare commits
108 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0fa7dc0531 | |||
| 618ae8bf14 | |||
| 5ed5d574fd | |||
| bde9d9023a | |||
| c8a8858d24 | |||
| 6491118cde | |||
| b4acd02007 | |||
| 5695b3d6aa | |||
| 0a160697d1 | |||
| 11d3ca3d6e | |||
| ee88b23891 | |||
| ca68be7a56 | |||
| 4ad2abe997 | |||
| 8f08322721 | |||
| fb043e5a81 | |||
| 1628d48c97 | |||
| 025cb3391e | |||
| 4aab7f991f | |||
| 67846db28f | |||
| cac55fa2bf | |||
| a74b7542ba | |||
| a9f462ed37 | |||
| c33b3d8278 | |||
| de3a6e4660 | |||
| e2c00b1bfc | |||
| 692ac4acca | |||
| c4bdd8fc4b | |||
| aa88221845 | |||
| b425e48afe | |||
| ff54b363aa | |||
| 4d931b4430 | |||
| 6054ca1217 | |||
| 7f9f0d1589 | |||
| ddcfa5b499 | |||
| 441b41c713 | |||
| ed71dd08b9 | |||
| 1c354ab5d3 | |||
| 151760713a | |||
| 4450d6302b | |||
| 2d055d3909 | |||
| 16da7d936d | |||
| efe08f445f | |||
| 4e7f755ae9 | |||
| 28bb2204e7 | |||
| 0c3b4592b2 | |||
| 222bbef670 | |||
| fa8ab451eb | |||
| b4ebb2b98f | |||
| c757270101 | |||
| 5873e68d63 | |||
| 0c124c3b44 | |||
| d46a57f8ef | |||
| 153fa23a71 | |||
| 5852054e3a | |||
| 5988d4ba24 | |||
| 0b6a698f6b | |||
| 01db520881 | |||
| 30b9956c4f | |||
| 4b37a3e538 | |||
| 928d8d2fb3 | |||
| 8ece20f54b | |||
| 26b4afca6a | |||
| 5f43b76b57 | |||
| 15d609aebc | |||
| d66bf8f3cf | |||
| d3b42036af | |||
| e448a43769 | |||
| 349de7bf48 | |||
| eec601e6b2 | |||
| 5b280a4a67 | |||
| 46deaf373f | |||
| e098d5701f | |||
| 21d4be0020 | |||
| 3f6a1856ac | |||
| 816e24a102 | |||
| 80e2a93a8c | |||
| 2f8096883f | |||
| 088f002583 | |||
| 27e9e78636 | |||
| a0c979602e | |||
| 3dc9edb8b5 | |||
| 32b68ab7c8 | |||
| 75a61c05c1 | |||
| bdfa98358b | |||
| dc8b38ccb9 | |||
| e1c288aaeb | |||
| 7a97de2a37 | |||
| 19b03b83ea | |||
| 598b144d06 | |||
| 1cd8a8de60 | |||
| 9beffca985 | |||
| 53db2069ce | |||
| 121104e13d | |||
| 0dacbda9da | |||
| 8bc27414a4 | |||
| 76509d93f0 | |||
| ff31b0c03e | |||
| 6b2ab73d65 | |||
| 2754023a80 | |||
| fa5c65db53 | |||
| 53c2c19d69 | |||
| e5437b445a | |||
| 09bdf9ddaf | |||
| b58d2a09ef | |||
| ce442742ba | |||
| 632f22962e | |||
| 72c3b12fb4 | |||
| 5fec6846b0 |
@@ -23,3 +23,4 @@ Closes #(issue)
|
|||||||
## Requirements / 要求
|
## Requirements / 要求
|
||||||
|
|
||||||
- [ ] I have read and follow the [Code of Conduct](../docs/CODE_OF_CONDUCT.md) and [Contributing Guide](../CONTRIBUTING.md) . / 我已阅读并遵守行为准则。
|
- [ ] I have read and follow the [Code of Conduct](../docs/CODE_OF_CONDUCT.md) and [Contributing Guide](../CONTRIBUTING.md) . / 我已阅读并遵守行为准则。
|
||||||
|
- [ ] This PR is NOT generated by a bot or AI agent acting autonomously. I have authored or meaningfully reviewed every change. / 此 PR 不是由 bot 或 AI 自主生成的,我已亲自编写或充分审查了每一处变更。
|
||||||
|
|||||||
Vendored
+1
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"cSpell.words": [
|
"cSpell.words": [
|
||||||
|
"contenteditable",
|
||||||
"deepseek",
|
"deepseek",
|
||||||
"historychange",
|
"historychange",
|
||||||
"HITL",
|
"HITL",
|
||||||
|
|||||||
+33
-41
@@ -1,21 +1,23 @@
|
|||||||
# Contributing to Page-Agent
|
# Contributing to PageAgent
|
||||||
|
|
||||||
Thank you for your interest in contributing to Page-Agent! We welcome contributions from everyone.
|
♥️ We welcome contributions from everyone.
|
||||||
|
|
||||||
## 🚀 Quick Start
|
## 🚀 Quick Start
|
||||||
|
|
||||||
### Development Setup
|
### Development Setup
|
||||||
|
|
||||||
1. **Prerequisites**
|
1. **Prerequisites**
|
||||||
|
- `macOS` / `Linux` / `WSL`
|
||||||
- `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. Un-linted code won't pass the CI.
|
||||||
|
|
||||||
2. **Setup**
|
2. **Setup**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm ci
|
npm i
|
||||||
npm start # Start demo and documentation site
|
npm start # Start demo and documentation site
|
||||||
|
npm run build # Build libs and website
|
||||||
```
|
```
|
||||||
|
|
||||||
### Project Structure
|
### Project Structure
|
||||||
@@ -27,11 +29,11 @@ This is a **monorepo** with npm workspaces containing **4 main packages**:
|
|||||||
- **Extension** (`packages/extension/`) - Chrome extension for multi-page tasks and browser-level automation
|
- **Extension** (`packages/extension/`) - Chrome extension for multi-page tasks and browser-level automation
|
||||||
- **Website** (`packages/website/`) - React documentation and landing page. Also as demo and test page for the core lib. private package `@page-agent/website`
|
- **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.
|
||||||
- When bundling `IIFE` and `Website`. Bundle everything including local packages.
|
> - When bundling `IIFE` and `Website`. Bundle everything together.
|
||||||
|
|
||||||
## 🤝 How to Contribute
|
## 🤝 How to Contribute
|
||||||
|
|
||||||
@@ -63,6 +65,7 @@ We use a simplified monorepo solution with `native npm-workspace + ts reference
|
|||||||
- Update documentation as needed
|
- Update documentation as needed
|
||||||
|
|
||||||
4. **Test Your Changes**
|
4. **Test Your Changes**
|
||||||
|
- Build and lint everything.
|
||||||
- Test in our demo website
|
- Test in our demo website
|
||||||
- Test it on other websites if applicable
|
- Test it on other websites if applicable
|
||||||
- `@TODO: test suite`
|
- `@TODO: test suite`
|
||||||
@@ -90,14 +93,17 @@ We use a simplified monorepo solution with `native npm-workspace + ts reference
|
|||||||
- Keep functions small and focused
|
- Keep functions small and focused
|
||||||
- Add JSDoc for public APIs
|
- Add JSDoc for public APIs
|
||||||
|
|
||||||
### Vibe coding with AI
|
### Vibe Coding with AI
|
||||||
|
|
||||||
- It's **recommended** to heavily rely on AI (aka "vibe coding") when maintaining **demo pages and tests**.
|
> [Vibe coding](https://en.wikipedia.org/wiki/Vibe_coding)
|
||||||
- Be very careful if AI ever touched the core lib!!!
|
|
||||||
|
- Vibe coding is **RECOMMENDED** when maintaining **the demo, the website, the UI and tests**.
|
||||||
|
- We have a [website/AGENTS.md](packages/website/AGENTS.md) for that.
|
||||||
|
- Vibe coding is **NOT** allowed for the core lib!!!
|
||||||
|
- NEVER try to vibe coding the MV3 extension!!! It is HELL.
|
||||||
- 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](AGENTS.md) when structure changed.
|
|
||||||
|
|
||||||
If your lame AI assistant does not support [AGENTS.md](https://agents.md/). Add an alias for it:
|
If your AI assistant does not support [AGENTS.md](https://agents.md/). Add an alias for it:
|
||||||
|
|
||||||
- claude-code (`CLAUDE.md`)
|
- claude-code (`CLAUDE.md`)
|
||||||
|
|
||||||
@@ -135,26 +141,21 @@ If your lame AI assistant does not support [AGENTS.md](https://agents.md/). Add
|
|||||||
LLM_MODEL_NAME="qwen3:14b"
|
LLM_MODEL_NAME="qwen3:14b"
|
||||||
```
|
```
|
||||||
|
|
||||||
> ⚠️ Add `*` to `OLLAMA_ORIGINS` (403). Models < 10B unlikely strong enough. Requires tool_call support. Set context length > 15k (default 4k will NOT work): `$env:OLLAMA_CONTEXT_LENGTH=64000; ollama serve`
|
> @see https://alibaba.github.io/page-agent/docs/features/models#ollama for configuration
|
||||||
|
|
||||||
- Restart the dev server to load new env vars
|
- **Restart the dev server** to load new env vars
|
||||||
- If not provided, the demo will the free testing proxy by default
|
- If not provided, the demo will use the free testing proxy by default. By using it, you agree to its [terms](https://github.com/alibaba/page-agent/blob/main/docs/terms-and-privacy.md).
|
||||||
|
|
||||||
### Website Development
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm start
|
|
||||||
```
|
|
||||||
|
|
||||||
### Extension Development
|
### Extension Development
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# make sure you ran `npm run build:libs` first
|
||||||
|
# and every time you changed the core libs
|
||||||
npm run dev -w @page-agent/ext
|
npm run dev -w @page-agent/ext
|
||||||
npm run zip -w @page-agent/ext
|
npm run zip -w @page-agent/ext
|
||||||
```
|
```
|
||||||
|
|
||||||
- Load extension in Chrome via `chrome://extensions` -> **Load unpacked**
|
- Update `packages/extension/docs/extension_api.md` for API integration details
|
||||||
- Use `packages/extension/docs/extension_api.md` (EN) or `packages/extension/docs/extension_api_zh.md` (ZH) for API integration details
|
|
||||||
|
|
||||||
### Testing on Other Websites
|
### Testing on Other Websites
|
||||||
|
|
||||||
@@ -172,7 +173,7 @@ npm run zip -w @page-agent/ext
|
|||||||
|
|
||||||
- Click the bookmark on any page to load Page-Agent
|
- Click the bookmark on any page to load Page-Agent
|
||||||
|
|
||||||
> Warning: AK in your local `.env` will be inlined in the iife script.
|
> Warning: AK in your local `.env` will be inlined in the iife script. Be very careful when you distribute the script.
|
||||||
|
|
||||||
### Adding Documentation
|
### Adding Documentation
|
||||||
|
|
||||||
@@ -180,29 +181,20 @@ Ask an AI to help you add documentation to the `website/` package. Follow the ex
|
|||||||
|
|
||||||
> Our AGENTS.md file and guardrails are designed for this purpose. But please be careful and review anything AI generated.
|
> Our AGENTS.md file and guardrails are designed for this purpose. But please be careful and review anything AI generated.
|
||||||
|
|
||||||
## 🎯 Contribution Areas
|
|
||||||
|
|
||||||
We especially welcome contributions in:
|
|
||||||
|
|
||||||
- **Browser compatibility** improvements
|
|
||||||
- **Performance optimizations** for DOM processing
|
|
||||||
- **Documentation** and examples
|
|
||||||
- **Testing** and quality assurance
|
|
||||||
- **Accessibility** features
|
|
||||||
- **Internationalization** support
|
|
||||||
|
|
||||||
## 🚫 What We Don't Accept
|
## 🚫 What We Don't Accept
|
||||||
|
|
||||||
- Changes that break existing API compatibility (Discuss first)
|
- Breaking changes and large PRs without prior discussion
|
||||||
- Heavy dependencies to core library
|
- Heavy dependencies to core libs
|
||||||
- Contributions without proper testing
|
- Contributions without proper testing
|
||||||
- Code that doesn't follow project conventions
|
- Code that doesn't follow project conventions
|
||||||
|
- Dependencies or code with licenses incompatible with MIT
|
||||||
|
- Bot or AI-generated pull requests without meaningful human involvement
|
||||||
|
|
||||||
## 📄 Legal
|
## 📄 Legal
|
||||||
|
|
||||||
By contributing to this project, you agree that your contributions will be licensed under the MIT License.
|
By contributing to this project, you agree that your contributions will be licensed under the MIT License.
|
||||||
|
|
||||||
> You may need to sign a github CLA before you create a PR.
|
> CLA is optional.
|
||||||
|
|
||||||
## 💬 Questions?
|
## 💬 Questions?
|
||||||
|
|
||||||
@@ -210,4 +202,4 @@ By contributing to this project, you agree that your contributions will be licen
|
|||||||
- Check existing documentation and issues first
|
- Check existing documentation and issues first
|
||||||
- Be respectful and constructive in discussions
|
- Be respectful and constructive in discussions
|
||||||
|
|
||||||
Thank you for helping make Page-Agent better! 🎉
|
Thank you for helping make PageAgent better! 🎉
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ The GUI Agent Living in Your Webpage. Control web interfaces with natural langua
|
|||||||
|
|
||||||
🌐 **English** | [中文](./docs/README-zh.md)
|
🌐 **English** | [中文](./docs/README-zh.md)
|
||||||
|
|
||||||
👉 <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> | <a href="https://news.ycombinator.com/item?id=47264138" target="_blank"><b>📢 Join HN Discussion</b></a> | <a href="https://x.com/simonluvramen" target="_blank"><b>𝕏 Follow on X</b></a>
|
||||||
|
|
||||||
<video id="demo-video" src="https://github.com/user-attachments/assets/11aed429-b69c-49d5-8982-fa99b4a0e9a8" controls crossorigin muted></video>
|
<video id="demo-video" src="https://github.com/user-attachments/assets/a1f2eae2-13fb-4aae-98cf-a3fc1620a6c2" controls crossorigin muted></video>
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -22,20 +22,18 @@ The GUI Agent Living in Your Webpage. Control web interfaces with natural langua
|
|||||||
- **🎯 Easy integration**
|
- **🎯 Easy integration**
|
||||||
- No need for `browser extension` / `python` / `headless browser`.
|
- No need for `browser extension` / `python` / `headless browser`.
|
||||||
- Just in-page javascript. Everything happens in your web page.
|
- Just in-page javascript. Everything happens in your web page.
|
||||||
- The best tool for your agent to control web pages.
|
|
||||||
- **📖 Text-based DOM manipulation**
|
- **📖 Text-based DOM manipulation**
|
||||||
- No screenshots. No OCR or multi-modal LLMs needed.
|
- No screenshots. No multi-modal LLMs or special permissions needed.
|
||||||
- No special permissions required.
|
|
||||||
- **🧠 Bring your own LLMs**
|
- **🧠 Bring your own LLMs**
|
||||||
- **🎨 Pretty UI with human-in-the-loop**
|
- **🎨 Pretty UI with human-in-the-loop**
|
||||||
- **🐙 Optional [chrome extension](https://alibaba.github.io/page-agent/docs/features/chrome-extension) for multi-page tasks.**
|
- **🐙 Optional [chrome extension](https://alibaba.github.io/page-agent/docs/features/chrome-extension) for multi-page tasks.**
|
||||||
|
|
||||||
## 💡 Use Cases
|
## 💡 Use Cases
|
||||||
|
|
||||||
- **SaaS AI Copilot** — Ship an AI copilot in your product in lines of code. No backend rewrite needed.
|
- **SaaS AI Copilot** — Ship an AI copilot in your product in lines of code. No backend rewrite.
|
||||||
- **Smart Form Filling** — Turn 20-click workflows into one sentence. Perfect for ERP, CRM, and admin systems.
|
- **Smart Form Filling** — Turn 20-click workflows into one sentence. Perfect for ERP, CRM, and admin systems.
|
||||||
- **Accessibility** — Make any web app accessible through natural language. Voice commands, screen readers, zero barrier.
|
- **Accessibility** — Make any web app accessible through natural language. Voice commands, screen readers, zero barrier.
|
||||||
- **Multi-page Agent** — Extend your agent's reach across browser tabs with the optional [chrome extension](https://alibaba.github.io/page-agent/docs/features/chrome-extension).
|
- **Multi-page Agent** — Extend your own agent's reach across browser tabs with the optional [chrome extension](https://alibaba.github.io/page-agent/docs/features/chrome-extension).
|
||||||
|
|
||||||
## 🚀 Quick Start
|
## 🚀 Quick Start
|
||||||
|
|
||||||
@@ -47,12 +45,12 @@ Fastest way to try PageAgent with our free Demo LLM:
|
|||||||
<script src="{URL}" crossorigin="true"></script>
|
<script src="{URL}" crossorigin="true"></script>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> **⚠️ For technical evaluation only.** This demo CDN uses our free [testing LLM API](https://alibaba.github.io/page-agent/docs/features/models#free-testing-api). By using it, you agree to its [terms](https://github.com/alibaba/page-agent/blob/main/docs/terms-and-privacy.md).
|
||||||
|
|
||||||
| Mirrors | URL |
|
| Mirrors | URL |
|
||||||
| ------- | ---------------------------------------------------------------------------------- |
|
| ------- | ---------------------------------------------------------------------------------- |
|
||||||
| Global | https://cdn.jsdelivr.net/npm/page-agent@1.4.2/dist/iife/page-agent.demo.js |
|
| Global | https://cdn.jsdelivr.net/npm/page-agent@1.5.7/dist/iife/page-agent.demo.js |
|
||||||
| China | https://registry.npmmirror.com/page-agent/1.4.2/files/dist/iife/page-agent.demo.js |
|
| China | https://registry.npmmirror.com/page-agent/1.5.7/files/dist/iife/page-agent.demo.js |
|
||||||
|
|
||||||
> **⚠️ For technical evaluation only.** This demo CDN uses our free [testing LLM API](https://alibaba.github.io/page-agent/docs/features/models#free-testing-api). By using it, you agree to its [terms](https://github.com/alibaba/page-agent/blob/main/docs/terms-and-privacy.md).
|
|
||||||
|
|
||||||
### NPM Installation
|
### NPM Installation
|
||||||
|
|
||||||
@@ -77,10 +75,12 @@ For more programmatic usage, see [📖 Documentations](https://alibaba.github.io
|
|||||||
|
|
||||||
## 🤝 Contributing
|
## 🤝 Contributing
|
||||||
|
|
||||||
We welcome contributions from the community! Follow our instructions in [CONTRIBUTING.md](CONTRIBUTING.md) for environment setup and local development.
|
We welcome contributions from the community! Follow our instructions in [CONTRIBUTING.md](CONTRIBUTING.md) for setup and guidelines.
|
||||||
|
|
||||||
Please read [Code of Conduct](docs/CODE_OF_CONDUCT.md) before contributing.
|
Please read [Code of Conduct](docs/CODE_OF_CONDUCT.md) before contributing.
|
||||||
|
|
||||||
|
Contributions generated entirely by bots or agents without substantial human involvement will not be accepted, and bot accounts may be blocked.
|
||||||
|
|
||||||
## 👏 Acknowledgments
|
## 👏 Acknowledgments
|
||||||
|
|
||||||
This project builds upon the excellent work of **[`browser-use`](https://github.com/browser-use/browser-use)**.
|
This project builds upon the excellent work of **[`browser-use`](https://github.com/browser-use/browser-use)**.
|
||||||
@@ -90,12 +90,10 @@ This project builds upon the excellent work of **[`browser-use`](https://github.
|
|||||||
```
|
```
|
||||||
DOM processing components and prompt are derived from browser-use:
|
DOM processing components and prompt are derived from browser-use:
|
||||||
|
|
||||||
Browser Use
|
Browser Use <https://github.com/browser-use/browser-use>
|
||||||
Copyright (c) 2024 Gregor Zunic
|
Copyright (c) 2024 Gregor Zunic
|
||||||
Licensed under the MIT License
|
Licensed under the MIT License
|
||||||
|
|
||||||
Original browser-use project: <https://github.com/browser-use/browser-use>
|
|
||||||
|
|
||||||
We gratefully acknowledge the browser-use project and its contributors for their
|
We gratefully acknowledge the browser-use project and its contributors for their
|
||||||
excellent work on web automation and DOM interaction patterns that helped make
|
excellent work on web automation and DOM interaction patterns that helped make
|
||||||
this project possible.
|
this project possible.
|
||||||
@@ -111,3 +109,11 @@ file and in the node_modules directory after installation.
|
|||||||
---
|
---
|
||||||
|
|
||||||
**⭐ Star this repo if you find PageAgent helpful!**
|
**⭐ Star this repo if you find PageAgent helpful!**
|
||||||
|
|
||||||
|
<a href="https://www.star-history.com/?repos=alibaba%2Fpage-agent&type=date">
|
||||||
|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/image?repos=alibaba/page-agent&type=date&theme=dark&legend=top-left&v=6" />
|
||||||
|
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/image?repos=alibaba/page-agent&type=date&legend=top-left&v=6" />
|
||||||
|
<img alt="Star History Chart" src="https://api.star-history.com/image?repos=alibaba/page-agent&type=date&legend=top-left&v=6" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
|||||||
+50
@@ -0,0 +1,50 @@
|
|||||||
|
# Security Policy
|
||||||
|
|
||||||
|
## Supported Versions
|
||||||
|
|
||||||
|
We provide security fixes on a best-effort basis for:
|
||||||
|
|
||||||
|
| Version | Supported |
|
||||||
|
| --------------------------------------------------------- | --------- |
|
||||||
|
| `main` | Yes |
|
||||||
|
| Latest npm release of `page-agent` and workspace packages | Yes |
|
||||||
|
| Older releases | No |
|
||||||
|
|
||||||
|
Please upgrade to the latest release before reporting an issue against an older build.
|
||||||
|
|
||||||
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
|
Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.
|
||||||
|
|
||||||
|
Use GitHub's private vulnerability reporting flow:
|
||||||
|
|
||||||
|
- Open https://github.com/alibaba/page-agent/security/policy
|
||||||
|
- Click `Report a vulnerability`
|
||||||
|
|
||||||
|
If private reporting is unavailable, open a minimal public issue only to request a private contact channel. Do not include exploit details.
|
||||||
|
|
||||||
|
## What to Include
|
||||||
|
|
||||||
|
- Affected package or feature
|
||||||
|
- Exact version, commit, or build
|
||||||
|
- Browser, OS, and runtime environment
|
||||||
|
- Reproduction steps or a proof of concept
|
||||||
|
- Expected impact
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
We prioritize reports that show a real security boundary failure, such as:
|
||||||
|
|
||||||
|
- Unauthorized access to data, tokens, or extension capabilities
|
||||||
|
- Bypassing explicit safety constraints
|
||||||
|
- Sensitive data exposure caused by default behavior
|
||||||
|
|
||||||
|
The following usually do not qualify by themselves:
|
||||||
|
|
||||||
|
- Unsafe custom integrations that ignore documented safeguards
|
||||||
|
- Intentionally embedding secrets into client-side builds
|
||||||
|
- Reports against unsupported older versions
|
||||||
|
|
||||||
|
## Disclosure
|
||||||
|
|
||||||
|
Please avoid public disclosure until maintainers have had a reasonable chance to investigate and ship a fix.
|
||||||
+33
-1
@@ -5,6 +5,37 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [1.5.1] - 2026-03-05
|
||||||
|
|
||||||
|
### Breaking Changes
|
||||||
|
|
||||||
|
- **`data-browser-use-ignore` → `data-page-agent-ignore`** - DOM ignore attribute renamed to match the project identity
|
||||||
|
- **Config types restructured** - `PageAgentConfig` split into `AgentConfig` + `PageAgentCoreConfig`; config definitions moved from `config/index.ts` to `types.ts`
|
||||||
|
- **Zod v3/v4 dual support** - Libraries now accept both `zod@^3.25` and `zod@^4.0` as peer dependencies
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- **Experimental `llms.txt` support** - Agent can fetch and include a site's `llms.txt` in context. Enable via `experimentalLlmsTxt: true`
|
||||||
|
|
||||||
|
### Improvements
|
||||||
|
|
||||||
|
- Default `maxSteps` changed from 20 to 40 for better for complex tasks out of the box
|
||||||
|
- Added 400ms wait between agent steps for page reactions
|
||||||
|
- Increased click wait time (100ms → 200ms) for more reliable interactions
|
||||||
|
- Removed debug `console.log` statements from scroll actions
|
||||||
|
- Reset observations on new task start
|
||||||
|
- Improved logging across packages
|
||||||
|
|
||||||
|
### Extension v0.1.9
|
||||||
|
|
||||||
|
> PageAgent 1.5.1
|
||||||
|
|
||||||
|
- **Advanced config panel** - New collapsible section exposing Max Steps, System Instruction, and experimental `llms.txt` toggle
|
||||||
|
- Streamlined User Auth Token description
|
||||||
|
- Moved testing API notice below auth token section
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## [1.4.0] - 2026-02-27
|
## [1.4.0] - 2026-02-27
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
@@ -122,9 +153,10 @@ PageAgent is now ready for production use. The API is stable and breaking change
|
|||||||
- **Ask User Tool** - Agent can ask users for clarification
|
- **Ask User Tool** - Agent can ask users for clarification
|
||||||
- **i18n Support** - English and Chinese localization
|
- **i18n Support** - English and Chinese localization
|
||||||
|
|
||||||
### Configuration
|
### Configuration
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
|
// Version 1.0.0
|
||||||
interface PageAgentConfig {
|
interface PageAgentConfig {
|
||||||
// LLM Configuration (required)
|
// LLM Configuration (required)
|
||||||
baseURL: string
|
baseURL: string
|
||||||
|
|||||||
+20
-16
@@ -11,9 +11,9 @@
|
|||||||
|
|
||||||
🌐 [English](../README.md) | **中文**
|
🌐 [English](../README.md) | **中文**
|
||||||
|
|
||||||
👉 <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> | <a href="https://news.ycombinator.com/item?id=47264138" target="_blank"><b>📢 Join HN Discussion</b></a> | <a href="https://x.com/simonluvramen" target="_blank"><b>𝕏 Follow on X</b></a>
|
||||||
|
|
||||||
<video id="demo-video" src="https://github.com/user-attachments/assets/11aed429-b69c-49d5-8982-fa99b4a0e9a8" controls crossorigin muted></video>
|
<video id="demo-video" src="https://github.com/user-attachments/assets/a1f2eae2-13fb-4aae-98cf-a3fc1620a6c2" controls crossorigin muted></video>
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -22,20 +22,18 @@
|
|||||||
- **🎯 轻松集成**
|
- **🎯 轻松集成**
|
||||||
- 无需 `浏览器插件` / `Python` / `无头浏览器`。
|
- 无需 `浏览器插件` / `Python` / `无头浏览器`。
|
||||||
- 纯页面内 JavaScript,一切都在你的网页中完成。
|
- 纯页面内 JavaScript,一切都在你的网页中完成。
|
||||||
- The best tool for your agent to control web pages.
|
|
||||||
- **📖 基于文本的 DOM 操作**
|
- **📖 基于文本的 DOM 操作**
|
||||||
- 无需截图,无需 OCR 或多模态模型。
|
- 无需截图,无需多模态模型或特殊权限。
|
||||||
- 无需特殊权限。
|
|
||||||
- **🧠 用你自己的 LLM**
|
- **🧠 用你自己的 LLM**
|
||||||
- **🎨 精美 UI,支持人机协同**
|
- **🎨 精美 UI,支持人机协同**
|
||||||
- **🐙 可选的 [Chrome 扩展](https://alibaba.github.io/page-agent/docs/features/chrome-extension),支持跨页面任务。**
|
- **🐙 可选的 [Chrome 扩展](https://alibaba.github.io/page-agent/docs/features/chrome-extension),支持跨页面任务。**
|
||||||
|
|
||||||
## 💡 应用场景
|
## 💡 应用场景
|
||||||
|
|
||||||
- **SaaS AI 副驾驶** — 几行代码为你的产品加上 AI 副驾驶,不需要重写后端。
|
- **SaaS AI 副驾驶** — 几行代码为你的产品加上 AI 副驾驶,无需重写后端。
|
||||||
- **智能表单填写** — 把 20 次点击变成一句话。ERP、CRM、管理后台的最佳拍档。
|
- **智能表单填写** — 把 20 次点击变成一句话。ERP、CRM、管理后台的最佳拍档。
|
||||||
- **无障碍增强** — 用自然语言让任何网页无障碍。语音指令、屏幕阅读器,零门槛。
|
- **无障碍增强** — 用自然语言让任何网页无障碍。语音指令、屏幕阅读器,零门槛。
|
||||||
- **跨页面 Agent** — 通过可选的 [Chrome 扩展](https://alibaba.github.io/page-agent/docs/features/chrome-extension),让你的 Agent 跨标签页工作。
|
- **跨页面 Agent** — 通过可选的 [Chrome 扩展](https://alibaba.github.io/page-agent/docs/features/chrome-extension),让你自己的 Agent 跨标签页工作。
|
||||||
|
|
||||||
## 🚀 快速开始
|
## 🚀 快速开始
|
||||||
|
|
||||||
@@ -47,12 +45,12 @@
|
|||||||
<script src="{URL}" crossorigin="true"></script>
|
<script src="{URL}" crossorigin="true"></script>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> **⚠️ 仅用于技术评估。** 该 Demo CDN 使用了免费的[测试 LLM API](https://alibaba.github.io/page-agent/docs/features/models#free-testing-api),使用即表示您同意其[条款](https://github.com/alibaba/page-agent/blob/main/docs/terms-and-privacy.md)。
|
||||||
|
|
||||||
| Mirrors | URL |
|
| Mirrors | URL |
|
||||||
| ------- | ---------------------------------------------------------------------------------- |
|
| ------- | ---------------------------------------------------------------------------------- |
|
||||||
| Global | https://cdn.jsdelivr.net/npm/page-agent@1.4.2/dist/iife/page-agent.demo.js |
|
| Global | https://cdn.jsdelivr.net/npm/page-agent@1.5.7/dist/iife/page-agent.demo.js |
|
||||||
| China | https://registry.npmmirror.com/page-agent/1.4.2/files/dist/iife/page-agent.demo.js |
|
| China | https://registry.npmmirror.com/page-agent/1.5.7/files/dist/iife/page-agent.demo.js |
|
||||||
|
|
||||||
> **⚠️ 仅用于技术评估。** 该 Demo CDN 使用了免费的[测试 LLM API](https://alibaba.github.io/page-agent/docs/features/models#free-testing-api),使用即表示您同意其[条款](https://github.com/alibaba/page-agent/blob/main/docs/terms-and-privacy.md)。
|
|
||||||
|
|
||||||
### NPM 安装
|
### NPM 安装
|
||||||
|
|
||||||
@@ -77,9 +75,9 @@ await agent.execute('点击登录按钮')
|
|||||||
|
|
||||||
## 🤝 贡献
|
## 🤝 贡献
|
||||||
|
|
||||||
欢迎社区贡献!请参阅 [CONTRIBUTING.md](../CONTRIBUTING.md) 了解环境配置和本地开发说明。
|
欢迎社区贡献!请参阅 [CONTRIBUTING.md](../CONTRIBUTING.md) 了解安装与贡献指南。请在贡献前阅读[行为准则](CODE_OF_CONDUCT.md)。
|
||||||
|
|
||||||
请在贡献前阅读[行为准则](CODE_OF_CONDUCT.md)。
|
我们不接受未经实质性人类参与、完全由 Bot 或 Agent 自动生成的代码,机器人账号可能被禁止参与互动。
|
||||||
|
|
||||||
## 👏 致谢
|
## 👏 致谢
|
||||||
|
|
||||||
@@ -90,12 +88,10 @@ await agent.execute('点击登录按钮')
|
|||||||
```
|
```
|
||||||
DOM processing components and prompt are derived from browser-use:
|
DOM processing components and prompt are derived from browser-use:
|
||||||
|
|
||||||
Browser Use
|
Browser Use <https://github.com/browser-use/browser-use>
|
||||||
Copyright (c) 2024 Gregor Zunic
|
Copyright (c) 2024 Gregor Zunic
|
||||||
Licensed under the MIT License
|
Licensed under the MIT License
|
||||||
|
|
||||||
Original browser-use project: <https://github.com/browser-use/browser-use>
|
|
||||||
|
|
||||||
We gratefully acknowledge the browser-use project and its contributors for their
|
We gratefully acknowledge the browser-use project and its contributors for their
|
||||||
excellent work on web automation and DOM interaction patterns that helped make
|
excellent work on web automation and DOM interaction patterns that helped make
|
||||||
this project possible.
|
this project possible.
|
||||||
@@ -111,3 +107,11 @@ file and in the node_modules directory after installation.
|
|||||||
---
|
---
|
||||||
|
|
||||||
**⭐ 如果觉得 PageAgent 有用或有趣,请给项目点个星!**
|
**⭐ 如果觉得 PageAgent 有用或有趣,请给项目点个星!**
|
||||||
|
|
||||||
|
<a href="https://www.star-history.com/?repos=alibaba%2Fpage-agent&type=date">
|
||||||
|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/image?repos=alibaba/page-agent&type=date&theme=dark&legend=top-left&vv=6" />
|
||||||
|
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/image?repos=alibaba/page-agent&type=date&legend=top-left&vv=6" />
|
||||||
|
<img alt="Star History Chart" src="https://api.star-history.com/image?repos=alibaba/page-agent&type=date&legend=top-left&vv=6" />
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
|||||||
+22
-20
@@ -1,42 +1,44 @@
|
|||||||
# Terms of Use & Privacy
|
# Terms of Use & Privacy
|
||||||
|
|
||||||
**Last updated:** February 2026
|
**Last updated:** March 2026
|
||||||
|
|
||||||
"We" in this document refers to the maintainers of the open-source Page Agent project (https://github.com/alibaba/page-agent). This document covers the Page Agent software itself and the testing services we provide — **not** any third-party product or service built with it.
|
"We" in this document refers to the maintainers of the open-source Page Agent project (https://github.com/alibaba/page-agent). "The software" refers to Page Agent (the JavaScript library) and Page Agent Ext (the browser extension). This document covers the software itself and the testing API we provide — **not** any third-party product or service built with it.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. Open Source Software Privacy
|
## 1. Open Source Software Privacy
|
||||||
|
|
||||||
Page Agent (PageAgent.js and Page Agent Extension) is a **client-side only** tool with a "Bring Your Own Key" (BYOK) architecture. The software itself does **not** include any backend service. The software does **not** collect or transmit any user data on its own, and the maintainers do **not** have access to your browsing activity, page content, or task instructions through the software.
|
The software is a **client-side only** tool with a "Bring Your Own Key" (BYOK) architecture. The software itself does **not** include any backend service. The software does **not** collect or transmit any user data on its own, and we do **not** have access to your browsing activity, page content, or task instructions through the software.
|
||||||
|
|
||||||
All data transmission occurs **only** between your browser and the LLM provider you configure. You are in full control of which provider receives your data.
|
All data transmission occurs **only** between your browser and the LLM provider you configure. You are in full control of which provider receives your data.
|
||||||
|
|
||||||
- You choose which LLM provider to use
|
The project is open source under the [MIT License](https://github.com/alibaba/page-agent/blob/main/LICENSE) and can be audited at: https://github.com/alibaba/page-agent
|
||||||
- You may configure your own API endpoint at any time
|
|
||||||
- The project is open source and can be audited: https://github.com/alibaba/page-agent
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. Testing API and Demo Disclaimer & Terms of Use
|
## 2. Testing API and Demo Disclaimer & Terms of Use
|
||||||
|
|
||||||
To facilitate easy testing and technical evaluation, we provide a free testing LLM API. This API is used in the project homepage's live demo, the pre-built demo CDN bundles, and the browser extension's default configuration. Users may also use it independently for their own technical evaluation of page-agent.
|
To facilitate easy testing and technical evaluation, we provide a free testing LLM API. This API is used in the project homepage's live demo, the pre-built demo CDN bundles, and the browser extension's default configuration. Users may also use it independently for their own technical evaluation of the software.
|
||||||
|
|
||||||
This free testing API is provided **strictly for technical evaluation and R&D purposes only**. It must not be used in any production environment. By using this service, you agree to the following terms:
|
This free testing API is provided **strictly for technical evaluation and R&D purposes only**. It must not be used in any production environment. By using this API, you agree to the following terms:
|
||||||
|
|
||||||
**No Sensitive Data**: You are strictly prohibited from inputting any Personal Identifiable Information (PII), confidential business data, financial/medical records, or using this agent on web pages containing such sensitive information.
|
- **Permitted Use Only**: This API must be used solely for technical evaluation of the software. Any other use — including integration into other products or services, unlawful activities, violation of the underlying LLM provider's usage policies, or automated scraping at scale — is strictly prohibited.
|
||||||
|
|
||||||
**Zero Data Retention**: We do not store, log, or use your prompts or webpage data (DOM) for model training. All data is processed in-transit and immediately discarded. However, we do temporarily process necessary, identity-agnostic network data (such as IP addresses) solely for rate-limiting and anti-abuse purposes.
|
- **No Sensitive Data**: You are strictly prohibited from inputting any Personal Identifiable Information (PII), confidential business data, financial/medical records, or using this agent on web pages containing such sensitive information.
|
||||||
|
|
||||||
**Independent Infrastructure**: The `page-agent` open-source software is completely frontend-based with a "Bring Your Own Key" (BYOK) architecture and no built-in backend. To facilitate easy testing, the maintainers have independently purchased public cloud services from Alibaba Cloud China ([aliyun.com](https://www.aliyun.com) FC and BaiLian Qwen models). This project is not affiliated with, nor endorsed by, Alibaba Cloud.
|
- **Data Processing**: We do not store or log your prompts, webpage data (HTML), or any submitted content, nor do we use such data for model training. All data is processed in-transit and immediately discarded. We perform in-memory request validation to prevent abuse of the testing API, and temporarily process IP addresses for rate-limiting purposes. No data from these processes is retained. Data is processed through Alibaba Cloud infrastructure, which is subject to its own privacy policy.
|
||||||
|
|
||||||
**No Guaranteed Availability**: This testing API may be rate-limited, degraded, or discontinued at any time without prior notice.
|
- **Independent Infrastructure**: The software is completely frontend-based with a "Bring Your Own Key" (BYOK) architecture and **no built-in backend**. To facilitate easy testing, the maintainers have purchased public cloud services from Alibaba Cloud China ([aliyun.com](https://www.aliyun.com) Function Compute and BaiLian Qwen models). This project is not a product of, nor endorsed by, Alibaba Cloud.
|
||||||
|
|
||||||
**"AS IS" & Limitation of Liability**: This service is provided strictly on an "AS IS" and "AS AVAILABLE" basis, without any warranties. The maintainers bear no liability for any data loss, service interruption, or legal consequences arising from your use of this demo.
|
- **No Guaranteed Availability**: This testing API may be rate-limited, degraded, or discontinued at any time without prior notice.
|
||||||
|
|
||||||
**Recommendation for Real Usage**: For secure and continuous usage, we strongly advise using the default BYOK mode with your own legally compliant commercial LLM API keys, or connecting to local, offline models (e.g., Ollama).
|
- **"AS IS" & Limitation of Liability**: This service is provided strictly on an "AS IS" and "AS AVAILABLE" basis, without any warranties. The maintainers bear no liability for any data loss, service interruption, or legal consequences arising from your use of this service.
|
||||||
|
|
||||||
**Note**: This service processes data via servers located in Mainland China. If you are located in a region with strict data localization laws (such as the EU/EEA), please do not use this demo.
|
- **Recommendation for Real Usage**: For secure and continuous usage, we strongly advise using the BYOK mode with your own legally compliant commercial LLM API keys, or connecting to local, offline models (e.g., Ollama).
|
||||||
|
|
||||||
|
**Note**: This free testing LLM API processes data via servers located in Mainland China. If you are located in a region with strict data localization laws (such as the EU/EEA), please do not use this API.
|
||||||
|
|
||||||
|
**Age Requirement**: The software and testing API are not intended for use by individuals under the age of 13 (or the minimum age of digital consent in your jurisdiction).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -49,19 +51,19 @@ The extension performs DOM analysis and automation actions **locally in your bro
|
|||||||
Data is transmitted to external servers **only when you initiate an automation task**. When this occurs:
|
Data is transmitted to external servers **only when you initiate an automation task**. When this occurs:
|
||||||
|
|
||||||
- Your task instructions (natural language commands)
|
- Your task instructions (natural language commands)
|
||||||
- Simplified page structure (cleaned DOM) of all pages under the extension's control
|
- Simplified page structure (cleaned HTML) of all pages under the extension's control
|
||||||
|
|
||||||
are sent to the LLM API endpoint configured in **your settings**.
|
are sent to the LLM API endpoint configured in **your settings**.
|
||||||
|
|
||||||
> **Note:** The DOM cleaning process simplifies page structure for AI readability but **does not guarantee removal of sensitive information** (e.g., visible text, form values, or personal data on the page). Please be mindful of the page content when initiating tasks.
|
> **Note:** The HTML cleaning process simplifies page structure for AI readability but **does not guarantee removal of sensitive information** (e.g., visible text, form values, or personal data on the page). Please be mindful of the page content when initiating tasks.
|
||||||
|
|
||||||
**If you configure a third-party LLM provider** (e.g., OpenAI, Anthropic, or others), data is sent directly to that provider. Their privacy policies apply.
|
**If you configure a third-party LLM provider** (e.g., OpenAI, Anthropic, or others), data is sent directly to that provider. Their privacy policies apply.
|
||||||
|
|
||||||
**If you use the built-in testing API**, the terms in [Section 2](#2-testing-api-and-demo-disclaimer--terms-of-use) apply. By using the extension with the default testing API, you agree to those terms.
|
**If you use the testing API**, the terms in [Section 2](#2-testing-api-and-demo-disclaimer--terms-of-use) apply. By using the extension with the default testing API, you agree to those terms.
|
||||||
|
|
||||||
### Data Storage
|
### Data Storage
|
||||||
|
|
||||||
- **Local storage only**: Your configuration (API endpoint, API key, model selection) is stored in your browser via `chrome.storage.local`
|
- **Local storage only**: Your configuration (API endpoint, API key, model selection) is stored in your browser via `chrome.storage.local` (or equivalent browser storage APIs)
|
||||||
- **No cloud sync**: Configuration is not synced to any external server
|
- **No cloud sync**: Configuration is not synced to any external server
|
||||||
- **No analytics**: The extension does not include any analytics or tracking code
|
- **No analytics**: The extension does not include any analytics or tracking code
|
||||||
|
|
||||||
@@ -76,7 +78,7 @@ are sent to the LLM API endpoint configured in **your settings**.
|
|||||||
|
|
||||||
## Changes
|
## Changes
|
||||||
|
|
||||||
We may update these terms as the project evolves.
|
We may update these terms at our discretion.
|
||||||
|
|
||||||
## Contact
|
## Contact
|
||||||
|
|
||||||
|
|||||||
Generated
+296
-350
File diff suppressed because it is too large
Load Diff
+15
-13
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "root",
|
"name": "root",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.4.2",
|
"version": "1.5.7",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/page-controller",
|
"packages/page-controller",
|
||||||
@@ -24,24 +24,26 @@
|
|||||||
"node": "^20.19.0 || ^22.13.0 || >=24"
|
"node": "^20.19.0 || ^22.13.0 || >=24"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"cleanup": "rm -rf packages/*/dist",
|
|
||||||
"start": "npm run dev --workspace=@page-agent/website",
|
"start": "npm run dev --workspace=@page-agent/website",
|
||||||
"build:website": "npm run build:website --workspace=@page-agent/website",
|
"dev:ext": "npm run dev -w @page-agent/ext",
|
||||||
"build:libs": "npm run build --workspaces --if-present",
|
|
||||||
"build": "npm run build:libs && npm run build:website",
|
|
||||||
"dev:demo": "npm run dev:demo --workspace=page-agent",
|
"dev:demo": "npm run dev:demo --workspace=page-agent",
|
||||||
|
"build": "npm run build:libs && npm run build:website",
|
||||||
|
"build:libs": "npm run build --workspaces --if-present",
|
||||||
|
"build:website": "npm run build:website --workspace=@page-agent/website",
|
||||||
|
"build:ext": "npm run build:libs && npm run zip -w @page-agent/ext",
|
||||||
"version": "node scripts/sync-version.js",
|
"version": "node scripts/sync-version.js",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
|
"cleanup": "rm -rf packages/*/dist",
|
||||||
"prepare": "husky"
|
"prepare": "husky"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commitlint/cli": "^20.4.2",
|
"@commitlint/cli": "^20.4.3",
|
||||||
"@commitlint/config-conventional": "^20.4.2",
|
"@commitlint/config-conventional": "^20.4.3",
|
||||||
"@eslint/js": "^9.39.2",
|
"@eslint/js": "^9.39.2",
|
||||||
"@microsoft/api-extractor": "^7.57.3",
|
"@microsoft/api-extractor": "^7.57.7",
|
||||||
"@tailwindcss/vite": "^4.2.1",
|
"@tailwindcss/vite": "^4.2.1",
|
||||||
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
|
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
|
||||||
"@types/node": "^25.3.0",
|
"@types/node": "^25.4.0",
|
||||||
"@vitejs/plugin-react-swc": "^4.1.0",
|
"@vitejs/plugin-react-swc": "^4.1.0",
|
||||||
"chalk": "^5.6.2",
|
"chalk": "^5.6.2",
|
||||||
"concurrently": "^9.2.1",
|
"concurrently": "^9.2.1",
|
||||||
@@ -52,15 +54,15 @@
|
|||||||
"eslint-plugin-react-hooks": "^7.0.1",
|
"eslint-plugin-react-hooks": "^7.0.1",
|
||||||
"eslint-plugin-react-refresh": "^0.5.2",
|
"eslint-plugin-react-refresh": "^0.5.2",
|
||||||
"eslint-plugin-react-x": "^2.13.0",
|
"eslint-plugin-react-x": "^2.13.0",
|
||||||
"globals": "^17.0.0",
|
"globals": "^17.4.0",
|
||||||
"husky": "^9.1.7",
|
"husky": "^9.1.7",
|
||||||
"lint-staged": "^16.2.4",
|
"lint-staged": "^16.3.2",
|
||||||
"prettier": "^3.8.0",
|
"prettier": "^3.8.0",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
"typescript-eslint": "^8.56.1",
|
"typescript-eslint": "^8.57.0",
|
||||||
"unplugin-dts": "^1.0.0-beta.6",
|
"unplugin-dts": "^1.0.0-beta.6",
|
||||||
"vite": "^7.3.1",
|
"vite": "^7.3.1",
|
||||||
"vite-plugin-css-injected-by-js": "^3.5.2",
|
"vite-plugin-css-injected-by-js": "^4.0.1",
|
||||||
"vite-bundle-analyzer": "^1.3.6"
|
"vite-bundle-analyzer": "^1.3.6"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@page-agent/core",
|
"name": "@page-agent/core",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.4.2",
|
"version": "1.5.7",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/esm/page-agent-core.js",
|
"main": "./dist/esm/page-agent-core.js",
|
||||||
"module": "./dist/esm/page-agent-core.js",
|
"module": "./dist/esm/page-agent-core.js",
|
||||||
@@ -44,8 +44,13 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chalk": "^5.6.2",
|
"chalk": "^5.6.2",
|
||||||
"zod": "^4.3.5",
|
"@page-agent/llms": "1.5.7",
|
||||||
"@page-agent/llms": "1.4.2",
|
"@page-agent/page-controller": "1.5.7"
|
||||||
"@page-agent/page-controller": "1.4.2"
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"zod": "^3.25.0 || ^4.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"zod": "^4.3.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,14 +5,13 @@
|
|||||||
import { InvokeError, LLM, type Tool } from '@page-agent/llms'
|
import { InvokeError, LLM, type Tool } from '@page-agent/llms'
|
||||||
import type { BrowserState, PageController } from '@page-agent/page-controller'
|
import type { BrowserState, PageController } from '@page-agent/page-controller'
|
||||||
import chalk from 'chalk'
|
import chalk from 'chalk'
|
||||||
import * as z from 'zod'
|
import * as z from 'zod/v4'
|
||||||
|
|
||||||
import { type PageAgentConfig, type SupportedLanguage } from './config'
|
|
||||||
import { DEFAULT_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 type {
|
import type {
|
||||||
AgentActivity,
|
AgentActivity,
|
||||||
|
AgentConfig,
|
||||||
AgentReflection,
|
AgentReflection,
|
||||||
AgentStatus,
|
AgentStatus,
|
||||||
AgentStepEvent,
|
AgentStepEvent,
|
||||||
@@ -21,13 +20,13 @@ import type {
|
|||||||
MacroToolInput,
|
MacroToolInput,
|
||||||
MacroToolResult,
|
MacroToolResult,
|
||||||
} from './types'
|
} from './types'
|
||||||
import { assert, normalizeResponse, uid, waitFor } from './utils'
|
import { assert, fetchLlmsTxt, normalizeResponse, uid, waitFor } from './utils'
|
||||||
|
|
||||||
export { type PageAgentConfig }
|
|
||||||
export type { SupportedLanguage }
|
|
||||||
export { tool, type PageAgentTool } from './tools'
|
export { tool, type PageAgentTool } from './tools'
|
||||||
export type * from './types'
|
export type * from './types'
|
||||||
|
|
||||||
|
export type PageAgentCoreConfig = AgentConfig & { pageController: PageController }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AI agent for browser automation.
|
* AI agent for browser automation.
|
||||||
*
|
*
|
||||||
@@ -60,7 +59,7 @@ export type * from './types'
|
|||||||
*/
|
*/
|
||||||
export class PageAgentCore extends EventTarget {
|
export class PageAgentCore extends EventTarget {
|
||||||
readonly id = uid()
|
readonly id = uid()
|
||||||
readonly config: PageAgentConfig & { maxSteps: number }
|
readonly config: PageAgentCoreConfig & { maxSteps: number }
|
||||||
readonly tools: typeof tools
|
readonly tools: typeof tools
|
||||||
/** PageController for DOM operations */
|
/** PageController for DOM operations */
|
||||||
readonly pageController: PageController
|
readonly pageController: PageController
|
||||||
@@ -94,10 +93,10 @@ export class PageAgentCore extends EventTarget {
|
|||||||
browserState: null as BrowserState | null,
|
browserState: null as BrowserState | null,
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(config: PageAgentConfig & { pageController: PageController }) {
|
constructor(config: PageAgentCoreConfig) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
this.config = { ...config, maxSteps: config.maxSteps || DEFAULT_MAX_STEPS }
|
this.config = { ...config, maxSteps: config.maxSteps ?? 40 }
|
||||||
|
|
||||||
this.#llm = new LLM(this.config)
|
this.#llm = new LLM(this.config)
|
||||||
this.tools = new Map(tools)
|
this.tools = new Map(tools)
|
||||||
@@ -177,7 +176,7 @@ export class PageAgentCore extends EventTarget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Push a observation message to the history event stream.
|
* Push an observation message to the history event stream.
|
||||||
* This will be visible in <agent_history> and remain persistent in memory across steps.
|
* This will be visible in <agent_history> and remain persistent in memory across steps.
|
||||||
* @experimental @internal
|
* @experimental @internal
|
||||||
* @note history change will be emitted before next step starts
|
* @note history change will be emitted before next step starts
|
||||||
@@ -222,6 +221,7 @@ export class PageAgentCore extends EventTarget {
|
|||||||
this.history = []
|
this.history = []
|
||||||
this.#setStatus('running')
|
this.#setStatus('running')
|
||||||
this.#emitHistoryChange()
|
this.#emitHistoryChange()
|
||||||
|
this.#observations = []
|
||||||
|
|
||||||
// Reset internal states
|
// Reset internal states
|
||||||
this.#states = { totalWaitTime: 0, lastURL: '', browserState: null }
|
this.#states = { totalWaitTime: 0, lastURL: '', browserState: null }
|
||||||
@@ -462,14 +462,13 @@ export class PageAgentCore extends EventTarget {
|
|||||||
* Get instructions from config
|
* Get instructions from config
|
||||||
*/
|
*/
|
||||||
async #getInstructions(): Promise<string> {
|
async #getInstructions(): Promise<string> {
|
||||||
const { instructions } = this.config
|
const { instructions, experimentalLlmsTxt } = this.config
|
||||||
if (!instructions) return ''
|
|
||||||
|
|
||||||
const systemInstructions = instructions.system?.trim()
|
const systemInstructions = instructions?.system?.trim()
|
||||||
let pageInstructions: string | undefined
|
let pageInstructions: string | undefined
|
||||||
|
|
||||||
const url = this.#states.browserState?.url || ''
|
const url = this.#states.browserState?.url || ''
|
||||||
if (instructions.getPageInstructions && url) {
|
if (instructions?.getPageInstructions && url) {
|
||||||
try {
|
try {
|
||||||
pageInstructions = instructions.getPageInstructions(url)?.trim()
|
pageInstructions = instructions.getPageInstructions(url)?.trim()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -479,7 +478,10 @@ export class PageAgentCore extends EventTarget {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!systemInstructions && !pageInstructions) return ''
|
|
||||||
|
const llmsTxt = experimentalLlmsTxt && url ? await fetchLlmsTxt(url) : undefined
|
||||||
|
|
||||||
|
if (!systemInstructions && !pageInstructions && !llmsTxt) return ''
|
||||||
|
|
||||||
let result = '<instructions>\n'
|
let result = '<instructions>\n'
|
||||||
|
|
||||||
@@ -491,6 +493,10 @@ export class PageAgentCore extends EventTarget {
|
|||||||
result += `<page_instructions>\n${pageInstructions}\n</page_instructions>\n`
|
result += `<page_instructions>\n${pageInstructions}\n</page_instructions>\n`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (llmsTxt) {
|
||||||
|
result += `<llms_txt>\n${llmsTxt}\n</llms_txt>\n`
|
||||||
|
}
|
||||||
|
|
||||||
result += '</instructions>\n\n'
|
result += '</instructions>\n\n'
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
export const DEFAULT_MAX_STEPS = 20
|
|
||||||
@@ -1,152 +0,0 @@
|
|||||||
import type { LLMConfig } from '@page-agent/llms'
|
|
||||||
import type { PageControllerConfig } from '@page-agent/page-controller'
|
|
||||||
|
|
||||||
import type { PageAgentCore } from '../PageAgentCore'
|
|
||||||
import type { PageAgentTool } from '../tools'
|
|
||||||
import type { ExecutionResult, HistoricalEvent } from '../types'
|
|
||||||
|
|
||||||
export type { LLMConfig }
|
|
||||||
|
|
||||||
/** Supported UI languages */
|
|
||||||
export type SupportedLanguage = 'en-US' | 'zh-CN'
|
|
||||||
|
|
||||||
export interface AgentConfig {
|
|
||||||
// theme?: 'light' | 'dark'
|
|
||||||
language?: SupportedLanguage
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maximum number of steps the agent can take per task.
|
|
||||||
* @default 20
|
|
||||||
*/
|
|
||||||
maxSteps?: number
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Custom tools to extend PageAgent capabilities
|
|
||||||
* @experimental
|
|
||||||
* @note You can also override or remove internal tools by using the same name.
|
|
||||||
* @see PageAgentTool
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* // override internal tool
|
|
||||||
* import { tool } from 'page-agent'
|
|
||||||
* const customTools = {
|
|
||||||
* ask_user: tool({
|
|
||||||
* description:
|
|
||||||
* 'Ask the user or parent model a question and wait for their answer. Use this if you need more information or clarification.',
|
|
||||||
* inputSchema: zod.object({
|
|
||||||
* question: zod.string(),
|
|
||||||
* }),
|
|
||||||
* execute: async function (this: PageAgent, input) {
|
|
||||||
* const answer = await do_some_thing(input.question)
|
|
||||||
* return "✅ Received user answer: " + answer
|
|
||||||
* },
|
|
||||||
* })
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* // remove internal tool
|
|
||||||
* const customTools = {
|
|
||||||
* ask_user: null // never ask user questions
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
customTools?: Record<string, PageAgentTool | null>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instructions to guide the agent's behavior
|
|
||||||
*/
|
|
||||||
instructions?: {
|
|
||||||
/**
|
|
||||||
* Global system-level instructions, applied to all tasks
|
|
||||||
*/
|
|
||||||
system?: string
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dynamic page-level instructions callback
|
|
||||||
* Called before each step to get instructions for the current page
|
|
||||||
* @param url - Current page URL (window.location.href)
|
|
||||||
* @returns Instructions string, or undefined/null to skip
|
|
||||||
*/
|
|
||||||
getPageInstructions?: (url: string) => string | undefined | null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lifecycle hooks for task execution.
|
|
||||||
* @experimental API may change in future versions.
|
|
||||||
*
|
|
||||||
* All hooks receive the agent instance as first parameter.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called before each step execution.
|
|
||||||
* @experimental
|
|
||||||
* @param agent - The PageAgentCore instance
|
|
||||||
* @param stepCount - Current step number (0-indexed)
|
|
||||||
*/
|
|
||||||
onBeforeStep?: (agent: PageAgentCore, stepCount: number) => Promise<void> | void
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called after each step execution.
|
|
||||||
* @experimental
|
|
||||||
* @param agent - The PageAgentCore instance
|
|
||||||
* @param history - Current history of events
|
|
||||||
*/
|
|
||||||
onAfterStep?: (agent: PageAgentCore, history: HistoricalEvent[]) => Promise<void> | void
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called before task execution starts.
|
|
||||||
* @experimental
|
|
||||||
* @param agent - The PageAgentCore instance
|
|
||||||
*/
|
|
||||||
onBeforeTask?: (agent: PageAgentCore) => Promise<void> | void
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called after task execution completes (success or failure).
|
|
||||||
* @experimental
|
|
||||||
* @param agent - The PageAgentCore instance
|
|
||||||
* @param result - The execution result
|
|
||||||
*/
|
|
||||||
onAfterTask?: (agent: PageAgentCore, result: ExecutionResult) => Promise<void> | void
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the agent is disposed.
|
|
||||||
* @experimental
|
|
||||||
* @note This hook can block the disposal process if it's async.
|
|
||||||
* @param agent - The PageAgentCore instance
|
|
||||||
* @param reason - Optional reason for disposal
|
|
||||||
*/
|
|
||||||
onDispose?: (agent: PageAgentCore, reason?: string) => void
|
|
||||||
|
|
||||||
// page behavior hooks
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @experimental
|
|
||||||
* Enable the experimental script execution tool that allows executing generated JavaScript code on the page.
|
|
||||||
* @note Can cause unpredictable side effects.
|
|
||||||
* @note May bypass some safe guards and data-masking mechanisms.
|
|
||||||
*/
|
|
||||||
experimentalScriptExecutionTool?: boolean
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transform page content before sending to LLM.
|
|
||||||
* Called after DOM extraction and simplification, before LLM invocation.
|
|
||||||
* Use cases: inspect extraction results, modify page info, mask sensitive data.
|
|
||||||
*
|
|
||||||
* @param content - Simplified page content that will be sent to LLM
|
|
||||||
* @returns Transformed content
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* // Mask phone numbers
|
|
||||||
* transformPageContent: async (content) => {
|
|
||||||
* return content.replace(/1[3-9]\d{9}/g, '***********')
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
transformPageContent?: (content: string) => Promise<string> | string
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Completely override the default system prompt.
|
|
||||||
* @experimental Use with caution - incorrect prompts may break agent behavior.
|
|
||||||
*/
|
|
||||||
customSystemPrompt?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type PageAgentConfig = LLMConfig & AgentConfig & PageControllerConfig
|
|
||||||
@@ -69,7 +69,7 @@ Strictly follow these rules while using the browser and navigating the web:
|
|||||||
- By default, only elements in the visible viewport are listed. Use scrolling actions if you suspect relevant content is offscreen which you need to interact with. Scroll ONLY if there are more pixels below or above the page.
|
- By default, only elements in the visible viewport are listed. Use scrolling actions if you suspect relevant content is offscreen which you need to interact with. Scroll ONLY if there are more pixels below or above the page.
|
||||||
- You can scroll by a specific number of pages using the num_pages parameter (e.g., 0.5 for half page, 2.0 for two pages).
|
- You can scroll by a specific number of pages using the num_pages parameter (e.g., 0.5 for half page, 2.0 for two pages).
|
||||||
- All the elements that are scrollable are marked with `data-scrollable` attribute. Including the scrollable distance in every directions. You can scroll *the element* in case some area are overflowed.
|
- All the elements that are scrollable are marked with `data-scrollable` attribute. Including the scrollable distance in every directions. You can scroll *the element* in case some area are overflowed.
|
||||||
- If a captcha appears, tell user you can not solve captcha. finished the task and ask user to solve it.
|
- If a captcha appears, tell user you can not solve captcha. Finish the task and ask user to solve it.
|
||||||
- If expected elements are missing, try scrolling, or navigating back.
|
- If expected elements are missing, try scrolling, or navigating back.
|
||||||
- If the page is not fully loaded, use the `wait` action.
|
- If the page is not fully loaded, use the `wait` action.
|
||||||
- Do not repeat one action for more than 3 times unless some conditions changed.
|
- Do not repeat one action for more than 3 times unless some conditions changed.
|
||||||
@@ -87,11 +87,11 @@ Strictly follow these rules while using the browser and navigating the web:
|
|||||||
|
|
||||||
<capability>
|
<capability>
|
||||||
- You can only handle single page app. Do not jump out of current page.
|
- You can only handle single page app. Do not jump out of current page.
|
||||||
- Do not click on link if it will open in a new page (etc. <a target="_blank">)
|
- Do not click on link if it will open in a new page (e.g., <a target="_blank">)
|
||||||
- It is ok to fail the task.
|
- It is ok to fail the task.
|
||||||
- User can be wrong. If the request of user is not achievable, inappropriate or you do not have enough information or tools to achieve it. Tell user to make a better request.
|
- User can be wrong. If the request of user is not achievable, inappropriate or you do not have enough information or tools to achieve it. Tell user to make a better request.
|
||||||
- Webpage can be broken. All webpages or apps have bugs. Some bug will make it hard for your job. It's encouraged to tell user the problem of current page. Your feedbacks (including failing) are valuable for user.
|
- Webpage can be broken. All webpages or apps have bugs. Some bug will make it hard for your job. It's encouraged to tell user the problem of current page. Your feedbacks (including failing) are valuable for user.
|
||||||
- Trying to hard can be harmful. Repeating some action back and forth or pushing for a complex procedure with little knowledge can cause unwanted result and harmful side-effects. User would rather you to complete the task with a fail.
|
- Trying too hard can be harmful. Repeating some action back and forth or pushing for a complex procedure with little knowledge can cause unwanted results and harmful side-effects. User would rather you complete the task with a fail.
|
||||||
- If you do not have knowledge for the current webpage or task. You must require user to give specific instructions and detailed steps.
|
- If you do not have knowledge for the current webpage or task. You must require user to give specific instructions and detailed steps.
|
||||||
</capability>
|
</capability>
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* Internal tools for PageAgent.
|
* Internal tools for PageAgent.
|
||||||
* @note Adapted from browser-use
|
* @note Adapted from browser-use
|
||||||
*/
|
*/
|
||||||
import * as z from 'zod'
|
import * as z from 'zod/v4'
|
||||||
|
|
||||||
import type { PageAgentCore } from '../PageAgentCore'
|
import type { PageAgentCore } from '../PageAgentCore'
|
||||||
import { waitFor } from '../utils'
|
import { waitFor } from '../utils'
|
||||||
|
|||||||
@@ -1,3 +1,159 @@
|
|||||||
|
import type { LLMConfig } from '@page-agent/llms'
|
||||||
|
|
||||||
|
// @note circular dependency but okay
|
||||||
|
import type { PageAgentCore } from './PageAgentCore'
|
||||||
|
import type { PageAgentTool } from './tools'
|
||||||
|
|
||||||
|
/** Supported UI languages */
|
||||||
|
export type SupportedLanguage = 'en-US' | 'zh-CN'
|
||||||
|
|
||||||
|
export interface AgentConfig extends LLMConfig {
|
||||||
|
language?: SupportedLanguage
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum number of steps the agent can take per task.
|
||||||
|
* @default 40
|
||||||
|
*/
|
||||||
|
maxSteps?: number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom tools to extend PageAgent capabilities
|
||||||
|
* @experimental
|
||||||
|
* @note You can also override or remove internal tools by using the same name.
|
||||||
|
* @see PageAgentTool
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // override internal tool
|
||||||
|
* import { z } from 'zod/v4'
|
||||||
|
* import { tool } from 'page-agent'
|
||||||
|
* const customTools = {
|
||||||
|
* ask_user: tool({
|
||||||
|
* description:
|
||||||
|
* 'Ask the user or parent model a question and wait for their answer. Use this if you need more information or clarification.',
|
||||||
|
* inputSchema: z.object({
|
||||||
|
* question: z.string(),
|
||||||
|
* }),
|
||||||
|
* execute: async function (this: PageAgent, input) {
|
||||||
|
* const answer = await do_some_thing(input.question)
|
||||||
|
* return "✅ Received user answer: " + answer
|
||||||
|
* },
|
||||||
|
* })
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // remove internal tool
|
||||||
|
* const customTools = {
|
||||||
|
* ask_user: null // never ask user questions
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
customTools?: Record<string, PageAgentTool | null>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instructions to guide the agent's behavior
|
||||||
|
*/
|
||||||
|
instructions?: {
|
||||||
|
/**
|
||||||
|
* Global system-level instructions, applied to all tasks
|
||||||
|
*/
|
||||||
|
system?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dynamic page-level instructions callback
|
||||||
|
* Called before each step to get instructions for the current page
|
||||||
|
* @param url - Current page URL (window.location.href)
|
||||||
|
* @returns Instructions string, or undefined/null to skip
|
||||||
|
*/
|
||||||
|
getPageInstructions?: (url: string) => string | undefined | null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lifecycle hooks for task execution.
|
||||||
|
* @experimental API may change in future versions.
|
||||||
|
*
|
||||||
|
* All hooks receive the agent instance as first parameter.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called before each step execution.
|
||||||
|
* @experimental
|
||||||
|
* @param agent - The PageAgentCore instance
|
||||||
|
* @param stepCount - Current step number (0-indexed)
|
||||||
|
*/
|
||||||
|
onBeforeStep?: (agent: PageAgentCore, stepCount: number) => Promise<void> | void
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called after each step execution.
|
||||||
|
* @experimental
|
||||||
|
* @param agent - The PageAgentCore instance
|
||||||
|
* @param history - Current history of events
|
||||||
|
*/
|
||||||
|
onAfterStep?: (agent: PageAgentCore, history: HistoricalEvent[]) => Promise<void> | void
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called before task execution starts.
|
||||||
|
* @experimental
|
||||||
|
* @param agent - The PageAgentCore instance
|
||||||
|
*/
|
||||||
|
onBeforeTask?: (agent: PageAgentCore) => Promise<void> | void
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called after task execution completes (success or failure).
|
||||||
|
* @experimental
|
||||||
|
* @param agent - The PageAgentCore instance
|
||||||
|
* @param result - The execution result
|
||||||
|
*/
|
||||||
|
onAfterTask?: (agent: PageAgentCore, result: ExecutionResult) => Promise<void> | void
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the agent is disposed.
|
||||||
|
* @experimental
|
||||||
|
* @note This hook can block the disposal process if it's async.
|
||||||
|
* @param agent - The PageAgentCore instance
|
||||||
|
* @param reason - Optional reason for disposal
|
||||||
|
*/
|
||||||
|
onDispose?: (agent: PageAgentCore, reason?: string) => void
|
||||||
|
|
||||||
|
// page behavior hooks
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @experimental
|
||||||
|
* Enable the experimental script execution tool that allows executing generated JavaScript code on the page.
|
||||||
|
* @note Can cause unpredictable side effects.
|
||||||
|
* @note May bypass some safe guards and data-masking mechanisms.
|
||||||
|
*/
|
||||||
|
experimentalScriptExecutionTool?: boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @experimental
|
||||||
|
* Fetch /llms.txt from current site origin and include as context.
|
||||||
|
* Only fetched once per origin per task.
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
experimentalLlmsTxt?: boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform page content before sending to LLM.
|
||||||
|
* Called after DOM extraction and simplification, before LLM invocation.
|
||||||
|
* Use cases: inspect extraction results, modify page info, mask sensitive data.
|
||||||
|
*
|
||||||
|
* @param content - Simplified page content that will be sent to LLM
|
||||||
|
* @returns Transformed content
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Mask phone numbers
|
||||||
|
* transformPageContent: async (content) => {
|
||||||
|
* return content.replace(/1[3-9]\d{9}/g, '***********')
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
transformPageContent?: (content: string) => Promise<string> | string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Completely override the default system prompt.
|
||||||
|
* @experimental Use with caution - incorrect prompts may break agent behavior.
|
||||||
|
*/
|
||||||
|
customSystemPrompt?: string
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Agent reflection state - the reflection-before-action model
|
* Agent reflection state - the reflection-before-action model
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import { InvokeError, InvokeErrorType } from '@page-agent/llms'
|
import { InvokeError, InvokeErrorType } from '@page-agent/llms'
|
||||||
import chalk from 'chalk'
|
import chalk from 'chalk'
|
||||||
import * as z from 'zod'
|
import * as z from 'zod/v4'
|
||||||
|
|
||||||
import type { PageAgentTool } from '../tools'
|
import type { PageAgentTool } from '../tools'
|
||||||
|
|
||||||
|
const log = console.log.bind(console, chalk.yellow('[autoFixer]'))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Normalize LLM response and fix common format issues.
|
* Normalize LLM response and fix common format issues.
|
||||||
*
|
*
|
||||||
@@ -34,7 +36,7 @@ export function normalizeResponse(response: any, tools?: Map<string, PageAgentTo
|
|||||||
|
|
||||||
// case: sometimes the model only returns the action level
|
// case: sometimes the model only returns the action level
|
||||||
if (toolCall.function.name && toolCall.function.name !== 'AgentOutput') {
|
if (toolCall.function.name && toolCall.function.name !== 'AgentOutput') {
|
||||||
console.log(chalk.yellow(`[normalizeResponse] #1: fixing tool_call`))
|
log(`#1: fixing tool_call`)
|
||||||
resolvedArguments = { action: safeJsonParse(resolvedArguments) }
|
resolvedArguments = { action: safeJsonParse(resolvedArguments) }
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -47,13 +49,13 @@ export function normalizeResponse(response: any, tools?: Map<string, PageAgentTo
|
|||||||
|
|
||||||
// case: sometimes the content json includes upper level wrapper
|
// case: sometimes the content json includes upper level wrapper
|
||||||
if (resolvedArguments?.name === 'AgentOutput') {
|
if (resolvedArguments?.name === 'AgentOutput') {
|
||||||
console.log(chalk.yellow(`[normalizeResponse] #2: fixing tool_call`))
|
log(`#2: fixing tool_call`)
|
||||||
resolvedArguments = safeJsonParse(resolvedArguments.arguments)
|
resolvedArguments = safeJsonParse(resolvedArguments.arguments)
|
||||||
}
|
}
|
||||||
|
|
||||||
// case: sometimes even 2-levels of wrapping
|
// case: sometimes even 2-levels of wrapping
|
||||||
if (resolvedArguments?.type === 'function') {
|
if (resolvedArguments?.type === 'function') {
|
||||||
console.log(chalk.yellow(`[normalizeResponse] #3: fixing tool_call`))
|
log(`#3: fixing tool_call`)
|
||||||
resolvedArguments = safeJsonParse(resolvedArguments.function.arguments)
|
resolvedArguments = safeJsonParse(resolvedArguments.function.arguments)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,7 +68,7 @@ export function normalizeResponse(response: any, tools?: Map<string, PageAgentTo
|
|||||||
!resolvedArguments?.next_goal &&
|
!resolvedArguments?.next_goal &&
|
||||||
!resolvedArguments?.thinking
|
!resolvedArguments?.thinking
|
||||||
) {
|
) {
|
||||||
console.log(chalk.yellow(`[normalizeResponse] #4: fixing tool_call`))
|
log(`#4: fixing tool_call`)
|
||||||
resolvedArguments = { action: safeJsonParse(resolvedArguments) }
|
resolvedArguments = { action: safeJsonParse(resolvedArguments) }
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -90,7 +92,7 @@ export function normalizeResponse(response: any, tools?: Map<string, PageAgentTo
|
|||||||
|
|
||||||
// fix incomplete formats
|
// fix incomplete formats
|
||||||
if (!resolvedArguments.action) {
|
if (!resolvedArguments.action) {
|
||||||
console.log(chalk.yellow(`[normalizeResponse] #5: fixing tool_call`))
|
log(`#5: fixing tool_call`)
|
||||||
resolvedArguments.action = { name: 'wait', input: { seconds: 1 } }
|
resolvedArguments.action = { name: 'wait', input: { seconds: 1 } }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,9 +151,7 @@ function validateAction(action: any, tools: Map<string, PageAgentTool>): any {
|
|||||||
(k) => !(schema.shape as Record<string, z.ZodType>)[k].safeParse(undefined).success
|
(k) => !(schema.shape as Record<string, z.ZodType>)[k].safeParse(undefined).success
|
||||||
)
|
)
|
||||||
if (requiredKey) {
|
if (requiredKey) {
|
||||||
console.log(
|
log(`coercing primitive action input for "${toolName}"`)
|
||||||
chalk.yellow(`[normalizeResponse] coercing primitive action input for "${toolName}"`)
|
|
||||||
)
|
|
||||||
value = { [requiredKey]: value }
|
value = { [requiredKey]: value }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import chalk from 'chalk'
|
import chalk from 'chalk'
|
||||||
|
|
||||||
export { normalizeResponse } from './autoFixer'
|
export * from './autoFixer'
|
||||||
|
|
||||||
export async function waitFor(seconds: number): Promise<void> {
|
export async function waitFor(seconds: number): Promise<void> {
|
||||||
await new Promise((resolve) => setTimeout(resolve, seconds * 1000))
|
await new Promise((resolve) => setTimeout(resolve, seconds * 1000))
|
||||||
@@ -31,7 +31,7 @@ export function randomID(existingIDs?: string[]): string {
|
|||||||
id = Math.random().toString(36).substring(2, 11)
|
id = Math.random().toString(36).substring(2, 11)
|
||||||
tryCount++
|
tryCount++
|
||||||
if (tryCount > MAX_TRY) {
|
if (tryCount > MAX_TRY) {
|
||||||
throw new Error('randomID: too many try')
|
throw new Error('randomID: too many tries')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,6 +57,43 @@ export function uid() {
|
|||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const llmsTxtCache = new Map<string, string | null>()
|
||||||
|
|
||||||
|
/** Fetch /llms.txt for a URL's origin. Cached per origin, `null` = tried and not found. */
|
||||||
|
export async function fetchLlmsTxt(url: string): Promise<string | null> {
|
||||||
|
let origin: string
|
||||||
|
try {
|
||||||
|
origin = new URL(url).origin
|
||||||
|
} catch {
|
||||||
|
return null // Invalid URL
|
||||||
|
}
|
||||||
|
// about:blank, data:, file:
|
||||||
|
if (origin === 'null') return null
|
||||||
|
|
||||||
|
if (llmsTxtCache.has(origin)) return llmsTxtCache.get(origin)!
|
||||||
|
|
||||||
|
const endpoint = `${origin}/llms.txt`
|
||||||
|
let result: string | null = null
|
||||||
|
try {
|
||||||
|
console.log(chalk.gray(`[llms.txt] Fetching ${endpoint}`))
|
||||||
|
const res = await fetch(endpoint, { signal: AbortSignal.timeout(3000) })
|
||||||
|
if (res.ok) {
|
||||||
|
result = await res.text()
|
||||||
|
console.log(chalk.green(`[llms.txt] Found (${result.length} chars)`))
|
||||||
|
if (result.length > 1000) {
|
||||||
|
console.log(chalk.yellow(`[llms.txt] Truncating to 1000 chars`))
|
||||||
|
result = truncate(result, 1000)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.debug(chalk.gray(`[llms.txt] ${res.status} for ${endpoint}`))
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.debug(chalk.gray(`[llms.txt] not found for ${endpoint}`), e)
|
||||||
|
}
|
||||||
|
llmsTxtCache.set(origin, result)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simple assertion function that throws an error if the condition is falsy
|
* Simple assertion function that throws an error if the condition is falsy
|
||||||
* @param condition - The condition to assert
|
* @param condition - The condition to assert
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export default defineConfig({
|
|||||||
external: [
|
external: [
|
||||||
'chalk',
|
'chalk',
|
||||||
'zod',
|
'zod',
|
||||||
|
'zod/v4',
|
||||||
// all the internal packages
|
// all the internal packages
|
||||||
/^@page-agent\//,
|
/^@page-agent\//,
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@page-agent/ext",
|
"name": "@page-agent/ext",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.1.8",
|
"version": "1.5.7",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "wxt",
|
"dev": "wxt",
|
||||||
@@ -20,17 +20,17 @@
|
|||||||
"@types/chrome": "^0.1.37",
|
"@types/chrome": "^0.1.37",
|
||||||
"@types/react": "^19.2.14",
|
"@types/react": "^19.2.14",
|
||||||
"@types/react-dom": "^19.2.1",
|
"@types/react-dom": "^19.2.1",
|
||||||
"@wxt-dev/module-react": "^1.1.5",
|
"@wxt-dev/module-react": "^1.2.1",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"idb": "^8.0.3",
|
"idb": "^8.0.3",
|
||||||
"lucide-react": "^0.575.0",
|
"lucide-react": "^0.577.0",
|
||||||
"motion": "^12.34.3",
|
"motion": "^12.35.2",
|
||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"react": "^19.2.4",
|
"react": "^19.2.4",
|
||||||
"react-dom": "^19.2.4",
|
"react-dom": "^19.2.4",
|
||||||
"rough-notation": "^0.5.1",
|
"rough-notation": "^0.5.1",
|
||||||
"simple-icons": "^16.9.0",
|
"simple-icons": "^16.11.0",
|
||||||
"sonner": "^2.0.7",
|
"sonner": "^2.0.7",
|
||||||
"tailwind-merge": "^3.5.0",
|
"tailwind-merge": "^3.5.0",
|
||||||
"tailwindcss": "^4.1.14",
|
"tailwindcss": "^4.1.14",
|
||||||
@@ -38,12 +38,14 @@
|
|||||||
"wxt": "^0.20.18"
|
"wxt": "^0.20.18"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@page-agent/core": "1.4.2",
|
"@page-agent/core": "1.5.7",
|
||||||
"@page-agent/llms": "1.4.2",
|
"@page-agent/llms": "1.5.7",
|
||||||
"@page-agent/page-controller": "1.4.2",
|
"@page-agent/page-controller": "1.5.7",
|
||||||
"@page-agent/ui": "1.4.2",
|
"@page-agent/ui": "1.5.7",
|
||||||
"ai-motion": "^0.4.8",
|
"ai-motion": "^0.4.8",
|
||||||
"chalk": "^5.6.2",
|
"chalk": "^5.6.2"
|
||||||
"zod": "^4.3.5"
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"zod": "^3.25.0 || ^4.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { type PageAgentConfig, PageAgentCore } from '@page-agent/core'
|
import { type AgentConfig, PageAgentCore } from '@page-agent/core'
|
||||||
|
|
||||||
import { RemotePageController } from './RemotePageController'
|
import { RemotePageController } from './RemotePageController'
|
||||||
import { TabsController } from './TabsController'
|
import { TabsController } from './TabsController'
|
||||||
@@ -17,7 +17,7 @@ function detectLanguage(): 'en-US' | 'zh-CN' {
|
|||||||
* - can be used from a side panel or a content script
|
* - can be used from a side panel or a content script
|
||||||
*/
|
*/
|
||||||
export class MultiPageAgent extends PageAgentCore {
|
export class MultiPageAgent extends PageAgentCore {
|
||||||
constructor(config: Omit<PageAgentConfig, 'pageController'> & { includeInitialTab?: boolean }) {
|
constructor(config: AgentConfig & { includeInitialTab?: boolean }) {
|
||||||
// multi page controller
|
// multi page controller
|
||||||
const tabsController = new TabsController()
|
const tabsController = new TabsController()
|
||||||
const pageController = new RemotePageController(tabsController)
|
const pageController = new RemotePageController(tabsController)
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ Strictly follow these rules while using the browser and navigating the web:
|
|||||||
- By default, only elements in the visible viewport are listed. Use scrolling actions if you suspect relevant content is offscreen which you need to interact with. Scroll ONLY if there are more pixels below or above the page.
|
- By default, only elements in the visible viewport are listed. Use scrolling actions if you suspect relevant content is offscreen which you need to interact with. Scroll ONLY if there are more pixels below or above the page.
|
||||||
- You can scroll by a specific number of pages using the num_pages parameter (e.g., 0.5 for half page, 2.0 for two pages).
|
- You can scroll by a specific number of pages using the num_pages parameter (e.g., 0.5 for half page, 2.0 for two pages).
|
||||||
- All the elements that are scrollable are marked with `data-scrollable` attribute. Including the scrollable distance in every directions. You can scroll *the element* in case some area are overflowed.
|
- All the elements that are scrollable are marked with `data-scrollable` attribute. Including the scrollable distance in every directions. You can scroll *the element* in case some area are overflowed.
|
||||||
- If a captcha appears, tell user you can not solve captcha. finished the task and ask user to solve it.
|
- If a captcha appears, tell user you can not solve captcha. Finish the task and ask user to solve it.
|
||||||
- If expected elements are missing, try scrolling, or navigating back.
|
- If expected elements are missing, try scrolling, or navigating back.
|
||||||
- If the page is not fully loaded, use the `wait` action.
|
- If the page is not fully loaded, use the `wait` action.
|
||||||
- Do not repeat one action for more than 3 times unless some conditions changed.
|
- Do not repeat one action for more than 3 times unless some conditions changed.
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
* - switch_to_tab: Switch to an existing tab
|
* - switch_to_tab: Switch to an existing tab
|
||||||
* - close_tab: Close a tab (optionally switch to another)
|
* - close_tab: Close a tab (optionally switch to another)
|
||||||
*/
|
*/
|
||||||
import * as z from 'zod'
|
import * as z from 'zod/v4'
|
||||||
|
|
||||||
import type { TabsController } from './TabsController'
|
import type { TabsController } from './TabsController'
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,13 @@ import { DEMO_CONFIG, migrateLegacyEndpoint } from './constants'
|
|||||||
/** Language preference: undefined means follow system */
|
/** Language preference: undefined means follow system */
|
||||||
export type LanguagePreference = SupportedLanguage | undefined
|
export type LanguagePreference = SupportedLanguage | undefined
|
||||||
|
|
||||||
export interface ExtConfig extends LLMConfig {
|
export interface AdvancedConfig {
|
||||||
|
maxSteps?: number
|
||||||
|
systemInstruction?: string
|
||||||
|
experimentalLlmsTxt?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExtConfig extends LLMConfig, AdvancedConfig {
|
||||||
language?: LanguagePreference
|
language?: LanguagePreference
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,9 +46,10 @@ export function useAgent(): UseAgentResult {
|
|||||||
const [config, setConfig] = useState<ExtConfig | null>(null)
|
const [config, setConfig] = useState<ExtConfig | null>(null)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
chrome.storage.local.get(['llmConfig', 'language']).then((result) => {
|
chrome.storage.local.get(['llmConfig', 'language', 'advancedConfig']).then((result) => {
|
||||||
let llmConfig = (result.llmConfig as LLMConfig) ?? DEMO_CONFIG
|
let llmConfig = (result.llmConfig as LLMConfig) ?? DEMO_CONFIG
|
||||||
const language = (result.language as SupportedLanguage) || undefined
|
const language = (result.language as SupportedLanguage) || undefined
|
||||||
|
const advancedConfig = (result.advancedConfig as AdvancedConfig) ?? {}
|
||||||
|
|
||||||
// Auto-migrate legacy testing endpoints
|
// Auto-migrate legacy testing endpoints
|
||||||
const migrated = migrateLegacyEndpoint(llmConfig)
|
const migrated = migrateLegacyEndpoint(llmConfig)
|
||||||
@@ -53,14 +60,18 @@ export function useAgent(): UseAgentResult {
|
|||||||
chrome.storage.local.set({ llmConfig: DEMO_CONFIG })
|
chrome.storage.local.set({ llmConfig: DEMO_CONFIG })
|
||||||
}
|
}
|
||||||
|
|
||||||
setConfig({ ...llmConfig, language })
|
setConfig({ ...llmConfig, ...advancedConfig, language })
|
||||||
})
|
})
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!config) return
|
if (!config) return
|
||||||
|
|
||||||
const agent = new MultiPageAgent(config)
|
const { systemInstruction, ...agentConfig } = config
|
||||||
|
const agent = new MultiPageAgent({
|
||||||
|
...agentConfig,
|
||||||
|
instructions: systemInstruction ? { system: systemInstruction } : undefined,
|
||||||
|
})
|
||||||
agentRef.current = agent
|
agentRef.current = agent
|
||||||
|
|
||||||
const handleStatusChange = (e: Event) => {
|
const handleStatusChange = (e: Event) => {
|
||||||
@@ -106,15 +117,26 @@ export function useAgent(): UseAgentResult {
|
|||||||
agentRef.current?.stop()
|
agentRef.current?.stop()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const configure = useCallback(async ({ language, ...llmConfig }: ExtConfig) => {
|
const configure = useCallback(
|
||||||
await chrome.storage.local.set({ llmConfig })
|
async ({
|
||||||
if (language) {
|
language,
|
||||||
await chrome.storage.local.set({ language })
|
maxSteps,
|
||||||
} else {
|
systemInstruction,
|
||||||
await chrome.storage.local.remove('language')
|
experimentalLlmsTxt,
|
||||||
}
|
...llmConfig
|
||||||
setConfig({ ...llmConfig, language })
|
}: ExtConfig) => {
|
||||||
}, [])
|
await chrome.storage.local.set({ llmConfig })
|
||||||
|
if (language) {
|
||||||
|
await chrome.storage.local.set({ language })
|
||||||
|
} else {
|
||||||
|
await chrome.storage.local.remove('language')
|
||||||
|
}
|
||||||
|
const advancedConfig: AdvancedConfig = { maxSteps, systemInstruction, experimentalLlmsTxt }
|
||||||
|
await chrome.storage.local.set({ advancedConfig })
|
||||||
|
setConfig({ ...llmConfig, ...advancedConfig, language })
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status,
|
status,
|
||||||
|
|||||||
@@ -110,9 +110,9 @@ export default defineUnlistedScript(() => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
;(window as any).PAGE_AGENT_EXT_VERSION = __EXT_VERSION__
|
;(window as any).PAGE_AGENT_EXT_VERSION = __VERSION__
|
||||||
;(window as any).PAGE_AGENT_EXT = {
|
;(window as any).PAGE_AGENT_EXT = {
|
||||||
version: __EXT_VERSION__,
|
version: __VERSION__,
|
||||||
execute,
|
execute,
|
||||||
stop,
|
stop,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,14 @@
|
|||||||
import { Copy, CornerUpLeft, Eye, EyeOff, HatGlasses, Home, Loader2, Scale } from 'lucide-react'
|
import {
|
||||||
|
ChevronDown,
|
||||||
|
Copy,
|
||||||
|
CornerUpLeft,
|
||||||
|
Eye,
|
||||||
|
EyeOff,
|
||||||
|
HatGlasses,
|
||||||
|
Home,
|
||||||
|
Loader2,
|
||||||
|
Scale,
|
||||||
|
} from 'lucide-react'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { siGithub } from 'simple-icons'
|
import { siGithub } from 'simple-icons'
|
||||||
|
|
||||||
@@ -6,6 +16,7 @@ import { DEMO_API_KEY, DEMO_BASE_URL, DEMO_MODEL, isTestingEndpoint } from '@/ag
|
|||||||
import type { ExtConfig, LanguagePreference } from '@/agent/useAgent'
|
import type { ExtConfig, LanguagePreference } from '@/agent/useAgent'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Input } from '@/components/ui/input'
|
import { Input } from '@/components/ui/input'
|
||||||
|
import { Switch } from '@/components/ui/switch'
|
||||||
|
|
||||||
interface ConfigPanelProps {
|
interface ConfigPanelProps {
|
||||||
config: ExtConfig | null
|
config: ExtConfig | null
|
||||||
@@ -18,18 +29,26 @@ export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) {
|
|||||||
const [baseURL, setBaseURL] = useState(config?.baseURL || DEMO_BASE_URL)
|
const [baseURL, setBaseURL] = useState(config?.baseURL || DEMO_BASE_URL)
|
||||||
const [model, setModel] = useState(config?.model || DEMO_MODEL)
|
const [model, setModel] = useState(config?.model || DEMO_MODEL)
|
||||||
const [language, setLanguage] = useState<LanguagePreference>(config?.language)
|
const [language, setLanguage] = useState<LanguagePreference>(config?.language)
|
||||||
|
const [maxSteps, setMaxSteps] = useState<number | undefined>(config?.maxSteps)
|
||||||
|
const [systemInstruction, setSystemInstruction] = useState(config?.systemInstruction ?? '')
|
||||||
|
const [experimentalLlmsTxt, setExperimentalLlmsTxt] = useState(
|
||||||
|
config?.experimentalLlmsTxt ?? false
|
||||||
|
)
|
||||||
|
const [advancedOpen, setAdvancedOpen] = useState(false)
|
||||||
const [saving, setSaving] = useState(false)
|
const [saving, setSaving] = useState(false)
|
||||||
const [userAuthToken, setUserAuthToken] = useState<string>('')
|
const [userAuthToken, setUserAuthToken] = useState<string>('')
|
||||||
const [copied, setCopied] = useState(false)
|
const [copied, setCopied] = useState(false)
|
||||||
const [showToken, setShowToken] = useState(false)
|
const [showToken, setShowToken] = useState(false)
|
||||||
const [showApiKey, setShowApiKey] = useState(false)
|
const [showApiKey, setShowApiKey] = useState(false)
|
||||||
|
|
||||||
// Update local state when config prop changes
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setApiKey(config?.apiKey || DEMO_API_KEY)
|
setApiKey(config?.apiKey || DEMO_API_KEY)
|
||||||
setBaseURL(config?.baseURL || DEMO_BASE_URL)
|
setBaseURL(config?.baseURL || DEMO_BASE_URL)
|
||||||
setModel(config?.model || DEMO_MODEL)
|
setModel(config?.model || DEMO_MODEL)
|
||||||
setLanguage(config?.language)
|
setLanguage(config?.language)
|
||||||
|
setMaxSteps(config?.maxSteps)
|
||||||
|
setSystemInstruction(config?.systemInstruction ?? '')
|
||||||
|
setExperimentalLlmsTxt(config?.experimentalLlmsTxt ?? false)
|
||||||
}, [config])
|
}, [config])
|
||||||
|
|
||||||
// Poll for user auth token every second until found
|
// Poll for user auth token every second until found
|
||||||
@@ -67,7 +86,15 @@ export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) {
|
|||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
setSaving(true)
|
setSaving(true)
|
||||||
try {
|
try {
|
||||||
await onSave({ apiKey, baseURL, model, language })
|
await onSave({
|
||||||
|
apiKey,
|
||||||
|
baseURL,
|
||||||
|
model,
|
||||||
|
language,
|
||||||
|
maxSteps: maxSteps || undefined,
|
||||||
|
systemInstruction: systemInstruction || undefined,
|
||||||
|
experimentalLlmsTxt,
|
||||||
|
})
|
||||||
} finally {
|
} finally {
|
||||||
setSaving(false)
|
setSaving(false)
|
||||||
}
|
}
|
||||||
@@ -87,28 +114,11 @@ export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Testing API notice */}
|
|
||||||
{isTestingEndpoint(baseURL) && (
|
|
||||||
<div className="p-2.5 rounded-md border border-amber-500/30 bg-amber-500/5 text-[10px] text-muted-foreground leading-relaxed">
|
|
||||||
<Scale className="size-3 inline-block mr-1 -mt-0.5 text-amber-600" />
|
|
||||||
You are using the free testing API. By using this service you agree to the{' '}
|
|
||||||
<a
|
|
||||||
href="https://github.com/alibaba/page-agent/blob/main/docs/terms-and-privacy.md"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="underline hover:text-foreground"
|
|
||||||
>
|
|
||||||
Terms of Use & Privacy Policy
|
|
||||||
</a>
|
|
||||||
. No sensitive data. No guaranteed availability.
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* User Auth Token Section */}
|
{/* User Auth Token Section */}
|
||||||
<div className="flex flex-col gap-1.5 p-3 bg-muted/50 rounded-md border">
|
<div className="flex flex-col gap-1.5 p-3 bg-muted/50 rounded-md border">
|
||||||
<label className="text-xs font-medium text-muted-foreground">User Auth Token</label>
|
<label className="text-xs font-medium text-muted-foreground">User Auth Token</label>
|
||||||
<p className="text-[10px] text-muted-foreground mb-1">
|
<p className="text-[10px] text-muted-foreground mb-1">
|
||||||
Add this token to a website's localStorage to give it authorization to call this extension
|
Give a website the ability to call this extension.
|
||||||
</p>
|
</p>
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
<Input
|
<Input
|
||||||
@@ -153,6 +163,23 @@ export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Testing API notice */}
|
||||||
|
{isTestingEndpoint(baseURL) && (
|
||||||
|
<div className="p-2.5 rounded-md border border-amber-500/30 bg-amber-500/5 text-[11px] text-muted-foreground leading-relaxed">
|
||||||
|
<Scale className="size-3 inline-block mr-1 -mt-0.5 text-amber-600" />
|
||||||
|
You are using the free testing API. By using this service you agree to the{' '}
|
||||||
|
<a
|
||||||
|
href="https://github.com/alibaba/page-agent/blob/main/docs/terms-and-privacy.md"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="underline hover:text-foreground"
|
||||||
|
>
|
||||||
|
Terms of Use & Privacy Policy
|
||||||
|
</a>
|
||||||
|
. No sensitive data. No guaranteed availability.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="flex flex-col gap-1.5">
|
<div className="flex flex-col gap-1.5">
|
||||||
<label className="text-xs text-muted-foreground">Model</label>
|
<label className="text-xs text-muted-foreground">Model</label>
|
||||||
<Input
|
<Input
|
||||||
@@ -197,6 +224,52 @@ export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) {
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Advanced Config */}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setAdvancedOpen(!advancedOpen)}
|
||||||
|
className="flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground cursor-pointer mt-1 font-bold"
|
||||||
|
>
|
||||||
|
Advanced
|
||||||
|
<ChevronDown
|
||||||
|
className="size-3 transition-transform"
|
||||||
|
style={{ transform: advancedOpen ? 'rotate(0deg)' : 'rotate(90deg)' }}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{advancedOpen && (
|
||||||
|
<>
|
||||||
|
<div className="flex flex-col gap-1.5">
|
||||||
|
<label className="text-xs text-muted-foreground">Max Steps</label>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
placeholder="40"
|
||||||
|
min={1}
|
||||||
|
max={200}
|
||||||
|
value={maxSteps ?? ''}
|
||||||
|
onChange={(e) => setMaxSteps(e.target.value ? Number(e.target.value) : undefined)}
|
||||||
|
className="text-xs h-8 [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none [-moz-appearance:textfield]"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-1.5">
|
||||||
|
<label className="text-xs text-muted-foreground">System Instruction</label>
|
||||||
|
<textarea
|
||||||
|
placeholder="Additional instructions for the agent..."
|
||||||
|
value={systemInstruction}
|
||||||
|
onChange={(e) => setSystemInstruction(e.target.value)}
|
||||||
|
rows={3}
|
||||||
|
className="text-xs rounded-md border border-input bg-background px-3 py-2 resize-y min-h-[60px]"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label className="flex items-center justify-between cursor-pointer">
|
||||||
|
<span className="text-xs text-muted-foreground">Experimental llms.txt support</span>
|
||||||
|
<Switch checked={experimentalLlmsTxt} onCheckedChange={setExperimentalLlmsTxt} />
|
||||||
|
</label>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="flex gap-2 mt-2">
|
<div className="flex gap-2 mt-2">
|
||||||
<Button variant="outline" onClick={onClose} className="flex-1 h-8 text-xs cursor-pointer">
|
<Button variant="outline" onClick={onClose} className="flex-1 h-8 text-xs cursor-pointer">
|
||||||
Cancel
|
Cancel
|
||||||
@@ -213,6 +286,10 @@ export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) {
|
|||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
<div className="mt-4 mb-4 pt-4 border-t border-border/50 flex gap-2 justify-between text-[10px] text-muted-foreground">
|
<div className="mt-4 mb-4 pt-4 border-t border-border/50 flex gap-2 justify-between text-[10px] text-muted-foreground">
|
||||||
<div className="flex flex-col justify-between">
|
<div className="flex flex-col justify-between">
|
||||||
|
<span>
|
||||||
|
Version <span className="font-mono">v{__VERSION__}</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
<a
|
<a
|
||||||
href="https://github.com/alibaba/page-agent"
|
href="https://github.com/alibaba/page-agent"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@@ -224,7 +301,9 @@ export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) {
|
|||||||
</svg>
|
</svg>
|
||||||
<span>Source Code</span>
|
<span>Source Code</span>
|
||||||
</a>
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col items-end">
|
||||||
<a
|
<a
|
||||||
href="https://alibaba.github.io/page-agent/"
|
href="https://alibaba.github.io/page-agent/"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@@ -242,18 +321,9 @@ export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) {
|
|||||||
className="flex items-center gap-1 hover:text-foreground"
|
className="flex items-center gap-1 hover:text-foreground"
|
||||||
>
|
>
|
||||||
<HatGlasses className="size-3" />
|
<HatGlasses className="size-3" />
|
||||||
<span>Privacy Policy</span>
|
<span>Privacy</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col items-end">
|
|
||||||
<span>
|
|
||||||
Extension <span className="font-mono">v{__EXT_VERSION__}</span>
|
|
||||||
</span>
|
|
||||||
<span>
|
|
||||||
PageAgent <span className="font-mono">v{__CORE_VERSION__}</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* attribute */}
|
{/* attribute */}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { AlertTriangle, RotateCcw } from 'lucide-react'
|
import { AlertTriangle, Eraser, RotateCcw } from 'lucide-react'
|
||||||
import { Component, type ErrorInfo, type ReactNode } from 'react'
|
import { Component, type ErrorInfo, type ReactNode } from 'react'
|
||||||
|
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
@@ -27,6 +27,11 @@ export class ErrorBoundary extends Component<Props, State> {
|
|||||||
window.location.reload()
|
window.location.reload()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleResetConfig = async () => {
|
||||||
|
await chrome.storage.local.remove(['llmConfig', 'language', 'advancedConfig'])
|
||||||
|
window.location.reload()
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (!this.state.hasError) {
|
if (!this.state.hasError) {
|
||||||
return this.props.children
|
return this.props.children
|
||||||
@@ -39,10 +44,16 @@ export class ErrorBoundary extends Component<Props, State> {
|
|||||||
<p className="text-sm text-muted-foreground mb-4 max-w-xs">
|
<p className="text-sm text-muted-foreground mb-4 max-w-xs">
|
||||||
{this.state.error?.message || 'An unexpected error occurred'}
|
{this.state.error?.message || 'An unexpected error occurred'}
|
||||||
</p>
|
</p>
|
||||||
<Button variant="outline" size="sm" onClick={this.handleReload}>
|
<div className="flex gap-2">
|
||||||
<RotateCcw className="size-3.5 mr-2" />
|
<Button variant="outline" size="sm" onClick={this.handleResetConfig}>
|
||||||
Reload Panel
|
<Eraser className="size-3.5 mr-2" />
|
||||||
</Button>
|
Reset Config
|
||||||
|
</Button>
|
||||||
|
<Button variant="outline" size="sm" onClick={this.handleReload}>
|
||||||
|
<RotateCcw className="size-3.5 mr-2" />
|
||||||
|
Reload Panel
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,20 +43,24 @@ export function MotionOverlay({ active }: { active: boolean }) {
|
|||||||
const motionRef = useRef<Motion | null>(null)
|
const motionRef = useRef<Motion | null>(null)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const mode = document.documentElement.classList.contains('dark') ? 'dark' : 'light'
|
try {
|
||||||
const motion = new Motion({
|
const mode = document.documentElement.classList.contains('dark') ? 'dark' : 'light'
|
||||||
mode,
|
const motion = new Motion({
|
||||||
borderWidth: 4,
|
mode,
|
||||||
borderRadius: 14,
|
borderWidth: 4,
|
||||||
glowWidth: mode === 'dark' ? 120 : 60,
|
borderRadius: 14,
|
||||||
styles: { position: 'absolute', inset: '0' },
|
glowWidth: mode === 'dark' ? 120 : 60,
|
||||||
})
|
styles: { position: 'absolute', inset: '0' },
|
||||||
motionRef.current = motion
|
})
|
||||||
containerRef.current!.appendChild(motion.element)
|
motionRef.current = motion
|
||||||
motion.autoResize(containerRef.current!)
|
containerRef.current!.appendChild(motion.element)
|
||||||
|
motion.autoResize(containerRef.current!)
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('[MotionOverlay] Motion unavailable:', e)
|
||||||
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
motion.dispose()
|
motionRef.current?.dispose()
|
||||||
motionRef.current = null
|
motionRef.current = null
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|||||||
+1
-2
@@ -1,2 +1 @@
|
|||||||
declare const __EXT_VERSION__: string
|
declare const __VERSION__: string
|
||||||
declare const __CORE_VERSION__: string
|
|
||||||
|
|||||||
@@ -19,8 +19,7 @@ export default defineConfig({
|
|||||||
vite: () => ({
|
vite: () => ({
|
||||||
plugins: [tailwindcss()],
|
plugins: [tailwindcss()],
|
||||||
define: {
|
define: {
|
||||||
__EXT_VERSION__: JSON.stringify(pkg.version),
|
__VERSION__: JSON.stringify(pkg.version),
|
||||||
__CORE_VERSION__: JSON.stringify(pkg.dependencies['@page-agent/core']),
|
|
||||||
},
|
},
|
||||||
optimizeDeps: {
|
optimizeDeps: {
|
||||||
force: true,
|
force: true,
|
||||||
@@ -37,6 +36,9 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
zip: {
|
||||||
|
artifactTemplate: 'page-agent-ext-{{version}}-{{browser}}.zip',
|
||||||
|
},
|
||||||
manifest: {
|
manifest: {
|
||||||
default_locale: 'en',
|
default_locale: 'en',
|
||||||
name: '__MSG_extName__',
|
name: '__MSG_extName__',
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@page-agent/llms",
|
"name": "@page-agent/llms",
|
||||||
"version": "1.4.2",
|
"version": "1.5.7",
|
||||||
"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",
|
||||||
@@ -37,7 +37,12 @@
|
|||||||
"postpublish": "node -e \"['LICENSE'].forEach(f=>{try{require('fs').unlinkSync(f)}catch{}})\""
|
"postpublish": "node -e \"['LICENSE'].forEach(f=>{try{require('fs').unlinkSync(f)}catch{}})\""
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chalk": "^5.6.2",
|
"chalk": "^5.6.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"zod": "^3.25.0 || ^4.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
"zod": "^4.3.5"
|
"zod": "^4.3.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* OpenAI Client implementation
|
* OpenAI Client implementation
|
||||||
*/
|
*/
|
||||||
import * as z from 'zod'
|
import * as z from 'zod/v4'
|
||||||
|
|
||||||
import { InvokeError, InvokeErrorType } from './errors'
|
import { InvokeError, InvokeErrorType } from './errors'
|
||||||
import type { InvokeOptions, InvokeResult, LLMClient, LLMConfig, Message, Tool } from './types'
|
import type { InvokeOptions, InvokeResult, LLMClient, LLMConfig, Message, Tool } from './types'
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* Core types for LLM integration
|
* Core types for LLM integration
|
||||||
*/
|
*/
|
||||||
import type * as z from 'zod'
|
import type * as z from 'zod/v4'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Message format - OpenAI standard (industry standard)
|
* Message format - OpenAI standard (industry standard)
|
||||||
|
|||||||
@@ -2,13 +2,11 @@
|
|||||||
* Utility functions for LLM integration
|
* Utility functions for LLM integration
|
||||||
*/
|
*/
|
||||||
import chalk from 'chalk'
|
import chalk from 'chalk'
|
||||||
import * as z from 'zod'
|
import * as z from 'zod/v4'
|
||||||
|
|
||||||
import type { Tool } from './types'
|
import type { Tool } from './types'
|
||||||
|
|
||||||
function debug(message: string) {
|
const debug = console.debug.bind(console, chalk.gray('[LLM]'))
|
||||||
console.debug(chalk.gray('[LLM]'), message)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert Zod schema to OpenAI tool format
|
* Convert Zod schema to OpenAI tool format
|
||||||
@@ -75,6 +73,11 @@ export function modelPatch(body: Record<string, any>) {
|
|||||||
} else if (modelName.startsWith('gpt-51')) {
|
} else if (modelName.startsWith('gpt-51')) {
|
||||||
debug('Applying GPT-51 patch: disable reasoning')
|
debug('Applying GPT-51 patch: disable reasoning')
|
||||||
body.reasoning_effort = 'none'
|
body.reasoning_effort = 'none'
|
||||||
|
} else if (modelName.startsWith('gpt-54')) {
|
||||||
|
debug(
|
||||||
|
'Applying GPT-5.4 patch: skip reasoning_effort because chat/completions rejects it with function tools'
|
||||||
|
)
|
||||||
|
delete body.reasoning_effort
|
||||||
} else if (modelName.startsWith('gpt-5-mini')) {
|
} else if (modelName.startsWith('gpt-5-mini')) {
|
||||||
debug('Applying GPT-5-mini patch: set reasoning effort to low, temperature to 1')
|
debug('Applying GPT-5-mini patch: set reasoning effort to low, temperature to 1')
|
||||||
body.reasoning_effort = 'low'
|
body.reasoning_effort = 'low'
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
outDir: resolve(__dirname, 'dist', 'lib'),
|
outDir: resolve(__dirname, 'dist', 'lib'),
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
external: ['chalk', 'zod'],
|
external: ['chalk', 'zod', 'zod/v4'],
|
||||||
},
|
},
|
||||||
minify: false,
|
minify: false,
|
||||||
sourcemap: true,
|
sourcemap: true,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "page-agent",
|
"name": "page-agent",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "1.4.2",
|
"version": "1.5.7",
|
||||||
"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",
|
||||||
@@ -44,11 +44,16 @@
|
|||||||
"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": {
|
||||||
"@page-agent/core": "1.4.2",
|
"@page-agent/core": "1.5.7",
|
||||||
"@page-agent/llms": "1.4.2",
|
"@page-agent/llms": "1.5.7",
|
||||||
"@page-agent/page-controller": "1.4.2",
|
"@page-agent/page-controller": "1.5.7",
|
||||||
"@page-agent/ui": "1.4.2",
|
"@page-agent/ui": "1.5.7",
|
||||||
"chalk": "^5.6.2",
|
"chalk": "^5.6.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"zod": "^3.25.0 || ^4.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
"zod": "^4.3.5"
|
"zod": "^4.3.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,13 @@
|
|||||||
* Copyright (C) 2025 Alibaba Group Holding Limited
|
* Copyright (C) 2025 Alibaba Group Holding Limited
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*/
|
*/
|
||||||
import { type PageAgentConfig, PageAgentCore, type PageAgentTool, tool } from '@page-agent/core'
|
import { type AgentConfig, PageAgentCore } from '@page-agent/core'
|
||||||
import { PageController } from '@page-agent/page-controller'
|
import { PageController, type PageControllerConfig } from '@page-agent/page-controller'
|
||||||
import { Panel } from '@page-agent/ui'
|
import { Panel } from '@page-agent/ui'
|
||||||
|
|
||||||
export type { PageAgentConfig, PageAgentTool }
|
export * from '@page-agent/core'
|
||||||
export { tool }
|
|
||||||
|
export type PageAgentConfig = AgentConfig & PageControllerConfig
|
||||||
|
|
||||||
export class PageAgent extends PageAgentCore {
|
export class PageAgent extends PageAgentCore {
|
||||||
panel: Panel
|
panel: Panel
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export default defineConfig({
|
|||||||
external: [
|
external: [
|
||||||
'chalk',
|
'chalk',
|
||||||
'zod',
|
'zod',
|
||||||
|
'zod/v4',
|
||||||
// all the internal packages
|
// all the internal packages
|
||||||
/^@page-agent\//,
|
/^@page-agent\//,
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@page-agent/page-controller",
|
"name": "@page-agent/page-controller",
|
||||||
"version": "1.4.2",
|
"version": "1.5.7",
|
||||||
"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",
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import {
|
|||||||
scrollVertically,
|
scrollVertically,
|
||||||
selectOptionElement,
|
selectOptionElement,
|
||||||
} from './actions'
|
} from './actions'
|
||||||
import { VIEWPORT_EXPANSION } from './constants'
|
|
||||||
import * as dom from './dom'
|
import * as dom from './dom'
|
||||||
import type { FlatDomTree, InteractiveElementDomNode } from './dom/dom_tree/type'
|
import type { FlatDomTree, InteractiveElementDomNode } from './dom/dom_tree/type'
|
||||||
import { getPageInfo } from './dom/getPageInfo'
|
import { getPageInfo } from './dom/getPageInfo'
|
||||||
@@ -24,7 +23,6 @@ import { patchReact } from './patches/react'
|
|||||||
* Configuration for PageController
|
* Configuration for PageController
|
||||||
*/
|
*/
|
||||||
export interface PageControllerConfig extends dom.DomConfig {
|
export interface PageControllerConfig extends dom.DomConfig {
|
||||||
viewportExpansion?: number
|
|
||||||
/** Enable visual mask overlay during operations (default: false) */
|
/** Enable visual mask overlay during operations (default: false) */
|
||||||
enableMask?: boolean
|
enableMask?: boolean
|
||||||
}
|
}
|
||||||
@@ -131,7 +129,7 @@ export class PageController extends EventTarget {
|
|||||||
const url = window.location.href
|
const url = window.location.href
|
||||||
const title = document.title
|
const title = document.title
|
||||||
const pi = getPageInfo()
|
const pi = getPageInfo()
|
||||||
const viewportExpansion = this.config.viewportExpansion ?? VIEWPORT_EXPANSION
|
const viewportExpansion = dom.resolveViewportExpansion(this.config.viewportExpansion)
|
||||||
|
|
||||||
await this.updateTree()
|
await this.updateTree()
|
||||||
|
|
||||||
|
|||||||
@@ -101,41 +101,93 @@ const nativeTextAreaValueSetter = Object.getOwnPropertyDescriptor(
|
|||||||
'value'
|
'value'
|
||||||
)!.set!
|
)!.set!
|
||||||
|
|
||||||
/**
|
|
||||||
* create a synthetic keyboard event
|
|
||||||
* with key keycode code
|
|
||||||
*/
|
|
||||||
export async function createSyntheticInputEvent(elem: HTMLElement, key: string) {
|
|
||||||
elem.dispatchEvent(new KeyboardEvent('keydown', { bubbles: true, cancelable: true, key }))
|
|
||||||
await waitFor(0.01)
|
|
||||||
|
|
||||||
if (elem instanceof HTMLInputElement || elem instanceof HTMLTextAreaElement) {
|
|
||||||
elem.dispatchEvent(new Event('beforeinput', { bubbles: true }))
|
|
||||||
await waitFor(0.01)
|
|
||||||
elem.dispatchEvent(new Event('input', { bubbles: true }))
|
|
||||||
await waitFor(0.01)
|
|
||||||
}
|
|
||||||
|
|
||||||
elem.dispatchEvent(new KeyboardEvent('keyup', { bubbles: true, cancelable: true, key }))
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function inputTextElement(element: HTMLElement, text: string) {
|
export async function inputTextElement(element: HTMLElement, text: string) {
|
||||||
if (!(element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement)) {
|
const isContentEditable = element.isContentEditable
|
||||||
throw new Error('Element is not an input or textarea')
|
if (
|
||||||
|
!(element instanceof HTMLInputElement) &&
|
||||||
|
!(element instanceof HTMLTextAreaElement) &&
|
||||||
|
!isContentEditable
|
||||||
|
) {
|
||||||
|
throw new Error('Element is not an input, textarea, or contenteditable')
|
||||||
}
|
}
|
||||||
|
|
||||||
await clickElement(element)
|
await clickElement(element)
|
||||||
|
|
||||||
if (element instanceof HTMLTextAreaElement) {
|
if (isContentEditable) {
|
||||||
|
// Contenteditable support (partial)
|
||||||
|
// Not supported:
|
||||||
|
// - Monaco/CodeMirror: Require direct JS instance access. No universal way to obtain.
|
||||||
|
// - Draft.js: Not responsive to synthetic/execCommand/Range/DataTransfer. Unmaintained.
|
||||||
|
//
|
||||||
|
// Plan A: Dispatch synthetic events
|
||||||
|
// Works: LinkedIn, React contenteditable, Quill.
|
||||||
|
// Fails: Slate.js
|
||||||
|
// Sequence: beforeinput -> mutation -> input -> change -> blur
|
||||||
|
|
||||||
|
// Dispatch beforeinput + mutation + input for clearing
|
||||||
|
if (
|
||||||
|
element.dispatchEvent(
|
||||||
|
new InputEvent('beforeinput', {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
inputType: 'deleteContent',
|
||||||
|
})
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
element.innerText = ''
|
||||||
|
element.dispatchEvent(
|
||||||
|
new InputEvent('input', {
|
||||||
|
bubbles: true,
|
||||||
|
inputType: 'deleteContent',
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dispatch beforeinput + mutation + input for insertion (important for React apps)
|
||||||
|
if (
|
||||||
|
element.dispatchEvent(
|
||||||
|
new InputEvent('beforeinput', {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
inputType: 'insertText',
|
||||||
|
data: text,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
element.innerText = text
|
||||||
|
element.dispatchEvent(
|
||||||
|
new InputEvent('input', {
|
||||||
|
bubbles: true,
|
||||||
|
inputType: 'insertText',
|
||||||
|
data: text,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dispatch change event (for good measure)
|
||||||
|
element.dispatchEvent(new Event('change', { bubbles: true }))
|
||||||
|
|
||||||
|
// Trigger blur for validation
|
||||||
|
element.blur()
|
||||||
|
|
||||||
|
// Plan B: execCommand (deprecated but works better for some editors)
|
||||||
|
// Works: LinkedIn, Quill, Slate.js, react contenteditable components
|
||||||
|
//
|
||||||
|
// document.execCommand('selectAll')
|
||||||
|
// document.execCommand('delete')
|
||||||
|
// document.execCommand('insertText', false, text)
|
||||||
|
} else if (element instanceof HTMLTextAreaElement) {
|
||||||
nativeTextAreaValueSetter.call(element, text)
|
nativeTextAreaValueSetter.call(element, text)
|
||||||
} else {
|
} else {
|
||||||
nativeInputValueSetter.call(element, text)
|
nativeInputValueSetter.call(element, text)
|
||||||
}
|
}
|
||||||
|
|
||||||
const inputEvent = new Event('input', { bubbles: true })
|
// Only dispatch shared input event for non-contenteditable (contenteditable has its own)
|
||||||
element.dispatchEvent(inputEvent)
|
if (!isContentEditable) {
|
||||||
|
element.dispatchEvent(new Event('input', { bubbles: true }))
|
||||||
|
}
|
||||||
|
|
||||||
await waitFor(0.1) // Wait to ensure input event processing completes
|
await waitFor(0.1)
|
||||||
|
|
||||||
blurLastClickedElement()
|
blurLastClickedElement()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (C) 2025 Alibaba Group Holding Limited
|
|
||||||
* All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Viewport expansion for DOM tree extraction.
|
|
||||||
* -1 means full page (no viewport restriction)
|
|
||||||
* 0 means viewport only
|
|
||||||
* positive values expand the viewport by that many pixels
|
|
||||||
*
|
|
||||||
* @note Since isTopElement depends on elementFromPoint,
|
|
||||||
* it returns null when out of viewport, this feature has no practical use, only differ between -1 and 0
|
|
||||||
*/
|
|
||||||
// export const VIEWPORT_EXPANSION = 100
|
|
||||||
export const VIEWPORT_EXPANSION = -1
|
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
* @edit add `data-browser-use-ignore` attribute
|
* @edit add `data-browser-use-ignore` attribute
|
||||||
* @edit improve `sampleRect`, filter out rects with 0 area
|
* @edit improve `sampleRect`, filter out rects with 0 area
|
||||||
* @edit exclude aria-hidden elements
|
* @edit exclude aria-hidden elements
|
||||||
|
* @edit make sure attributes exist for interactive candidates.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export default (
|
export default (
|
||||||
@@ -1446,7 +1447,7 @@ export default (
|
|||||||
/**
|
/**
|
||||||
* @edit add `data-browser-use-ignore` attribute
|
* @edit add `data-browser-use-ignore` attribute
|
||||||
*/
|
*/
|
||||||
if (node.dataset?.browserUseIgnore === 'true') {
|
if (node.dataset?.browserUseIgnore === 'true' || node.dataset?.pageAgentIgnore === 'true') {
|
||||||
return null // Skip this node and its children
|
return null // Skip this node and its children
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1608,6 +1609,18 @@ export default (
|
|||||||
* @edit direct dom ref
|
* @edit direct dom ref
|
||||||
*/
|
*/
|
||||||
nodeData.ref = node
|
nodeData.ref = node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @edit make sure attributes exist for interactive candidates.
|
||||||
|
* @note if the element failed the isInteractiveCandidate, attributes would be empty.
|
||||||
|
*/
|
||||||
|
if (nodeData.isInteractive && Object.keys(nodeData.attributes).length === 0) {
|
||||||
|
const attributeNames = node.getAttributeNames?.() || []
|
||||||
|
for (const name of attributeNames) {
|
||||||
|
const value = node.getAttribute(name)
|
||||||
|
nodeData.attributes[name] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { VIEWPORT_EXPANSION } from '../constants'
|
|
||||||
import domTree from './dom_tree/index.js'
|
import domTree from './dom_tree/index.js'
|
||||||
import {
|
import {
|
||||||
ElementDomNode,
|
ElementDomNode,
|
||||||
@@ -7,7 +6,23 @@ import {
|
|||||||
TextDomNode,
|
TextDomNode,
|
||||||
} from './dom_tree/type'
|
} from './dom_tree/type'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Viewport expansion for DOM tree extraction.
|
||||||
|
* -1 means full page (no viewport restriction)
|
||||||
|
* 0 means viewport only
|
||||||
|
* positive values expand the viewport by that many pixels
|
||||||
|
*
|
||||||
|
* @note Since isTopElement depends on elementFromPoint,
|
||||||
|
* it returns null when out of viewport, this feature has no practical use, only differ between -1 and 0
|
||||||
|
*/
|
||||||
|
const DEFAULT_VIEWPORT_EXPANSION = -1
|
||||||
|
|
||||||
|
export function resolveViewportExpansion(viewportExpansion?: number): number {
|
||||||
|
return viewportExpansion ?? DEFAULT_VIEWPORT_EXPANSION
|
||||||
|
}
|
||||||
|
|
||||||
export interface DomConfig {
|
export interface DomConfig {
|
||||||
|
viewportExpansion?: number
|
||||||
interactiveBlacklist?: (Element | (() => Element))[]
|
interactiveBlacklist?: (Element | (() => Element))[]
|
||||||
interactiveWhitelist?: (Element | (() => Element))[]
|
interactiveWhitelist?: (Element | (() => Element))[]
|
||||||
includeAttributes?: string[]
|
includeAttributes?: string[]
|
||||||
@@ -21,6 +36,8 @@ export interface DomConfig {
|
|||||||
const newElementsCache = new WeakMap<HTMLElement, string>()
|
const newElementsCache = new WeakMap<HTMLElement, string>()
|
||||||
|
|
||||||
export function getFlatTree(config: DomConfig): FlatDomTree {
|
export function getFlatTree(config: DomConfig): FlatDomTree {
|
||||||
|
const viewportExpansion = resolveViewportExpansion(config.viewportExpansion)
|
||||||
|
|
||||||
const interactiveBlacklist = [] as Element[]
|
const interactiveBlacklist = [] as Element[]
|
||||||
for (const item of config.interactiveBlacklist || []) {
|
for (const item of config.interactiveBlacklist || []) {
|
||||||
if (typeof item === 'function') {
|
if (typeof item === 'function') {
|
||||||
@@ -43,7 +60,7 @@ export function getFlatTree(config: DomConfig): FlatDomTree {
|
|||||||
doHighlightElements: true,
|
doHighlightElements: true,
|
||||||
debugMode: true,
|
debugMode: true,
|
||||||
focusHighlightIndex: -1,
|
focusHighlightIndex: -1,
|
||||||
viewportExpansion: VIEWPORT_EXPANSION,
|
viewportExpansion,
|
||||||
interactiveBlacklist,
|
interactiveBlacklist,
|
||||||
interactiveWhitelist,
|
interactiveWhitelist,
|
||||||
highlightOpacity: config.highlightOpacity ?? 0.0,
|
highlightOpacity: config.highlightOpacity ?? 0.0,
|
||||||
@@ -74,6 +91,43 @@ export function getFlatTree(config: DomConfig): FlatDomTree {
|
|||||||
return elements
|
return elements
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const globRegexCache = new Map<string, RegExp>()
|
||||||
|
|
||||||
|
function globToRegex(pattern: string): RegExp {
|
||||||
|
let regex = globRegexCache.get(pattern)
|
||||||
|
if (!regex) {
|
||||||
|
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&')
|
||||||
|
regex = new RegExp(`^${escaped.replace(/\*/g, '.*')}$`)
|
||||||
|
globRegexCache.set(pattern, regex)
|
||||||
|
}
|
||||||
|
return regex
|
||||||
|
}
|
||||||
|
|
||||||
|
function matchAttributes(
|
||||||
|
attrs: Record<string, string>,
|
||||||
|
patterns: string[]
|
||||||
|
): Record<string, string> {
|
||||||
|
const result: Record<string, string> = {}
|
||||||
|
|
||||||
|
for (const pattern of patterns) {
|
||||||
|
if (pattern.includes('*')) {
|
||||||
|
const regex = globToRegex(pattern)
|
||||||
|
for (const key of Object.keys(attrs)) {
|
||||||
|
if (regex.test(key) && attrs[key].trim()) {
|
||||||
|
result[key] = attrs[key].trim()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const value = attrs[pattern]
|
||||||
|
if (value && value.trim()) {
|
||||||
|
result[pattern] = value.trim()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* elementsToString 内部使用的类型
|
* elementsToString 内部使用的类型
|
||||||
*/
|
*/
|
||||||
@@ -140,10 +194,13 @@ export function flatTreeToString(flatTree: FlatDomTree, includeAttributes?: stri
|
|||||||
// for jump check
|
// for jump check
|
||||||
'target',
|
'target',
|
||||||
|
|
||||||
// absolute 定位的下拉菜单
|
// absolute position dropdown menu
|
||||||
'aria-haspopup',
|
'aria-haspopup',
|
||||||
'aria-controls',
|
'aria-controls',
|
||||||
'aria-owns',
|
'aria-owns',
|
||||||
|
|
||||||
|
// content editable
|
||||||
|
'contenteditable',
|
||||||
]
|
]
|
||||||
|
|
||||||
const includeAttrs = [...(includeAttributes || []), ...DEFAULT_INCLUDE_ATTRIBUTES]
|
const includeAttrs = [...(includeAttributes || []), ...DEFAULT_INCLUDE_ATTRIBUTES]
|
||||||
@@ -245,23 +302,15 @@ export function flatTreeToString(flatTree: FlatDomTree, includeAttributes?: stri
|
|||||||
let attributesHtmlStr = ''
|
let attributesHtmlStr = ''
|
||||||
|
|
||||||
if (includeAttrs.length > 0 && node.attributes) {
|
if (includeAttrs.length > 0 && node.attributes) {
|
||||||
const attributesToInclude: Record<string, string> = {}
|
const attributesToInclude = matchAttributes(node.attributes, includeAttrs)
|
||||||
|
|
||||||
// Filter attributes
|
|
||||||
for (const key of includeAttrs) {
|
|
||||||
const value = node.attributes[key]
|
|
||||||
if (value && value.trim() !== '') {
|
|
||||||
attributesToInclude[key] = value.trim()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove duplicate values (for attributes longer than 5 chars)
|
// Remove duplicate values (for attributes longer than 5 chars)
|
||||||
const orderedKeys = includeAttrs.filter((key) => key in attributesToInclude)
|
const keys = Object.keys(attributesToInclude)
|
||||||
if (orderedKeys.length > 1) {
|
if (keys.length > 1) {
|
||||||
const keysToRemove = new Set<string>()
|
const keysToRemove = new Set<string>()
|
||||||
const seenValues: Record<string, string> = {}
|
const seenValues: Record<string, string> = {}
|
||||||
|
|
||||||
for (const key of orderedKeys) {
|
for (const key of keys) {
|
||||||
const value = attributesToInclude[key]
|
const value = attributesToInclude[key]
|
||||||
if (value.length > 5) {
|
if (value.length > 5) {
|
||||||
if (value in seenValues) {
|
if (value in seenValues) {
|
||||||
|
|||||||
@@ -7,3 +7,7 @@
|
|||||||
|
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.wrapper.visible {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,13 +8,7 @@ import cursorStyles from './cursor.module.css'
|
|||||||
export class SimulatorMask {
|
export class SimulatorMask {
|
||||||
shown: boolean = false
|
shown: boolean = false
|
||||||
wrapper = document.createElement('div')
|
wrapper = document.createElement('div')
|
||||||
motion = new Motion({
|
motion: Motion | null = null
|
||||||
mode: isPageDark() ? 'dark' : 'light',
|
|
||||||
styles: {
|
|
||||||
position: 'absolute',
|
|
||||||
inset: '0',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
#cursor = document.createElement('div')
|
#cursor = document.createElement('div')
|
||||||
|
|
||||||
@@ -28,9 +22,19 @@ export class SimulatorMask {
|
|||||||
this.wrapper.id = 'page-agent-runtime_simulator-mask'
|
this.wrapper.id = 'page-agent-runtime_simulator-mask'
|
||||||
this.wrapper.className = styles.wrapper
|
this.wrapper.className = styles.wrapper
|
||||||
this.wrapper.setAttribute('data-browser-use-ignore', 'true')
|
this.wrapper.setAttribute('data-browser-use-ignore', 'true')
|
||||||
|
this.wrapper.setAttribute('data-page-agent-ignore', 'true')
|
||||||
|
|
||||||
this.wrapper.appendChild(this.motion.element)
|
try {
|
||||||
this.motion.autoResize(this.wrapper)
|
const motion = new Motion({
|
||||||
|
mode: isPageDark() ? 'dark' : 'light',
|
||||||
|
styles: { position: 'absolute', inset: '0' },
|
||||||
|
})
|
||||||
|
this.motion = motion
|
||||||
|
this.wrapper.appendChild(motion.element)
|
||||||
|
motion.autoResize(this.wrapper)
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('[SimulatorMask] Motion overlay unavailable:', e)
|
||||||
|
}
|
||||||
|
|
||||||
// Capture all mouse, keyboard, and wheel events
|
// Capture all mouse, keyboard, and wheel events
|
||||||
this.wrapper.addEventListener('click', (e) => {
|
this.wrapper.addEventListener('click', (e) => {
|
||||||
@@ -144,10 +148,10 @@ export class SimulatorMask {
|
|||||||
if (this.shown) return
|
if (this.shown) return
|
||||||
|
|
||||||
this.shown = true
|
this.shown = true
|
||||||
this.motion.start()
|
this.motion?.start()
|
||||||
this.motion.fadeIn()
|
this.motion?.fadeIn()
|
||||||
|
|
||||||
this.wrapper.style.display = 'block'
|
this.wrapper.classList.add(styles.visible)
|
||||||
|
|
||||||
// Initialize cursor position
|
// Initialize cursor position
|
||||||
this.#currentCursorX = window.innerWidth / 2
|
this.#currentCursorX = window.innerWidth / 2
|
||||||
@@ -162,18 +166,18 @@ export class SimulatorMask {
|
|||||||
if (!this.shown) return
|
if (!this.shown) return
|
||||||
|
|
||||||
this.shown = false
|
this.shown = false
|
||||||
this.motion.fadeOut()
|
this.motion?.fadeOut()
|
||||||
this.motion.pause()
|
this.motion?.pause()
|
||||||
|
|
||||||
this.#cursor.classList.remove(cursorStyles.clicking)
|
this.#cursor.classList.remove(cursorStyles.clicking)
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.wrapper.style.display = 'none'
|
this.wrapper.classList.remove(styles.visible)
|
||||||
}, 800) // Match the animation duration
|
}, 800) // Match the animation duration
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose() {
|
dispose() {
|
||||||
this.motion.dispose()
|
this.motion?.dispose()
|
||||||
this.wrapper.remove()
|
this.wrapper.remove()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@page-agent/ui",
|
"name": "@page-agent/ui",
|
||||||
"version": "1.4.2",
|
"version": "1.5.7",
|
||||||
"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",
|
||||||
|
|||||||
@@ -373,6 +373,7 @@ export class Panel {
|
|||||||
wrapper.id = 'page-agent-runtime_agent-panel'
|
wrapper.id = 'page-agent-runtime_agent-panel'
|
||||||
wrapper.className = styles.wrapper
|
wrapper.className = styles.wrapper
|
||||||
wrapper.setAttribute('data-browser-use-ignore', 'true')
|
wrapper.setAttribute('data-browser-use-ignore', 'true')
|
||||||
|
wrapper.setAttribute('data-page-agent-ignore', 'true')
|
||||||
|
|
||||||
wrapper.innerHTML = `
|
wrapper.innerHTML = `
|
||||||
<div class="${styles.background}"></div>
|
<div class="${styles.background}"></div>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ interface CardOptions {
|
|||||||
export function createCard({ icon, content, meta, type }: CardOptions): string {
|
export function createCard({ icon, content, meta, type }: CardOptions): string {
|
||||||
const typeClass = type ? styles[type] : ''
|
const typeClass = type ? styles[type] : ''
|
||||||
const contentHtml = Array.isArray(content)
|
const contentHtml = Array.isArray(content)
|
||||||
? `<div class="${styles.reflectionLines}">${content.join('')}</div>`
|
? `<div class="${styles.reflectionLines}">${content.map((line) => `<span>${escapeHtml(line)}</span>`).join('')}</div>`
|
||||||
: `<span>${escapeHtml(content)}</span>`
|
: `<span>${escapeHtml(content)}</span>`
|
||||||
|
|
||||||
return `
|
return `
|
||||||
@@ -40,13 +40,13 @@ export function createReflectionLines(reflection: {
|
|||||||
}): string[] {
|
}): string[] {
|
||||||
const lines: string[] = []
|
const lines: string[] = []
|
||||||
if (reflection.evaluation_previous_goal) {
|
if (reflection.evaluation_previous_goal) {
|
||||||
lines.push(`<div>🔍 ${escapeHtml(reflection.evaluation_previous_goal)}</div>`)
|
lines.push(`🔍 ${reflection.evaluation_previous_goal}`)
|
||||||
}
|
}
|
||||||
if (reflection.memory) {
|
if (reflection.memory) {
|
||||||
lines.push(`<div>💾 ${escapeHtml(reflection.memory)}</div>`)
|
lines.push(`💾 ${reflection.memory}`)
|
||||||
}
|
}
|
||||||
if (reflection.next_goal) {
|
if (reflection.next_goal) {
|
||||||
lines.push(`<div>🎯 ${escapeHtml(reflection.next_goal)}</div>`)
|
lines.push(`🎯 ${reflection.next_goal}`)
|
||||||
}
|
}
|
||||||
return lines
|
return lines
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ Located in `src/components/ui/`:
|
|||||||
src/
|
src/
|
||||||
├── pages/
|
├── pages/
|
||||||
│ ├── home/
|
│ ├── home/
|
||||||
│ │ ├── HomeContent.tsx # Homepage sections
|
│ │ ├── index.tsx # Homepage
|
||||||
│ │ └── ...Section.tsx
|
│ │ └── ...Section.tsx
|
||||||
│ └── docs/
|
│ └── docs/
|
||||||
│ ├── index.tsx # Docs route switch
|
│ ├── index.tsx # Docs route switch
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@page-agent/website",
|
"name": "@page-agent/website",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.4.2",
|
"version": "1.5.7",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --host 0.0.0.0",
|
"dev": "vite --host 0.0.0.0",
|
||||||
@@ -19,13 +19,13 @@
|
|||||||
"@types/react-dom": "^19.2.1",
|
"@types/react-dom": "^19.2.1",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"lucide-react": "^0.575.0",
|
"lucide-react": "^0.577.0",
|
||||||
"motion": "^12.34.3",
|
"motion": "^12.35.2",
|
||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"react": "^19.2.4",
|
"react": "^19.2.4",
|
||||||
"react-dom": "^19.2.4",
|
"react-dom": "^19.2.4",
|
||||||
"rough-notation": "^0.5.1",
|
"rough-notation": "^0.5.1",
|
||||||
"simple-icons": "^16.9.0",
|
"simple-icons": "^16.11.0",
|
||||||
"sonner": "^2.0.7",
|
"sonner": "^2.0.7",
|
||||||
"tailwind-merge": "^3.5.0",
|
"tailwind-merge": "^3.5.0",
|
||||||
"tailwindcss": "^4.1.14",
|
"tailwindcss": "^4.1.14",
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
User-agent: *
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
Sitemap: https://alibaba.github.io/page-agent/sitemap.xml
|
||||||
+14
-6
@@ -5,10 +5,9 @@
|
|||||||
*/
|
*/
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
|
|
||||||
|
import { Badge } from '@/components/ui/badge'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
import { Badge } from './badge'
|
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Types
|
// Types
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -35,6 +34,8 @@ export interface APIReferenceProps {
|
|||||||
description?: React.ReactNode
|
description?: React.ReactNode
|
||||||
/** Property definitions */
|
/** Property definitions */
|
||||||
properties: PropDefinition[]
|
properties: PropDefinition[]
|
||||||
|
/** Display variant: 'properties' for fields, 'methods' for methods */
|
||||||
|
variant?: 'properties' | 'methods'
|
||||||
/** Additional CSS classes */
|
/** Additional CSS classes */
|
||||||
className?: string
|
className?: string
|
||||||
}
|
}
|
||||||
@@ -43,7 +44,14 @@ export interface APIReferenceProps {
|
|||||||
// Component
|
// Component
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
export function APIReference({ title, description, properties, className }: APIReferenceProps) {
|
export function APIReference({
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
properties,
|
||||||
|
variant = 'properties',
|
||||||
|
className,
|
||||||
|
}: APIReferenceProps) {
|
||||||
|
const isMethodsVariant = variant === 'methods'
|
||||||
return (
|
return (
|
||||||
<div className={cn('my-6', className)}>
|
<div className={cn('my-6', className)}>
|
||||||
{title && (
|
{title && (
|
||||||
@@ -58,10 +66,10 @@ export function APIReference({ title, description, properties, className }: APIR
|
|||||||
<thead>
|
<thead>
|
||||||
<tr className="bg-gray-50 dark:bg-gray-800/50">
|
<tr className="bg-gray-50 dark:bg-gray-800/50">
|
||||||
<th className="px-4 py-3 text-left font-medium text-gray-600 dark:text-gray-300">
|
<th className="px-4 py-3 text-left font-medium text-gray-600 dark:text-gray-300">
|
||||||
Property
|
{isMethodsVariant ? 'Method' : 'Property'}
|
||||||
</th>
|
</th>
|
||||||
<th className="px-4 py-3 text-left font-medium text-gray-600 dark:text-gray-300">
|
<th className="px-4 py-3 text-left font-medium text-gray-600 dark:text-gray-300">
|
||||||
Type
|
{isMethodsVariant ? 'Return Type' : 'Type'}
|
||||||
</th>
|
</th>
|
||||||
<th className="px-4 py-3 text-left font-medium text-gray-600 dark:text-gray-300 hidden md:table-cell">
|
<th className="px-4 py-3 text-left font-medium text-gray-600 dark:text-gray-300 hidden md:table-cell">
|
||||||
Default
|
Default
|
||||||
@@ -120,7 +128,7 @@ function PropRow({ name, type, required, defaultValue, description, status }: Pr
|
|||||||
|
|
||||||
{/* Type */}
|
{/* Type */}
|
||||||
<td className="px-4 py-3 align-top">
|
<td className="px-4 py-3 align-top">
|
||||||
<code className="font-mono text-xs text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-800 px-1.5 py-0.5 rounded whitespace-nowrap">
|
<code className="font-mono text-xs text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-800 px-1.5 py-0.5 rounded wrap-break-word">
|
||||||
{type}
|
{type}
|
||||||
</code>
|
</code>
|
||||||
</td>
|
</td>
|
||||||
@@ -42,7 +42,7 @@ const CodeEditor: React.FC<CodeEditorProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`group relative ${containerClasses} rounded-xl border shadow-2xl overflow-hidden ${className}`}
|
className={`group relative ${containerClasses} rounded-xl border shadow-2xl my-4 overflow-hidden ${className}`}
|
||||||
>
|
>
|
||||||
{/* 编辑器顶部栏 */}
|
{/* 编辑器顶部栏 */}
|
||||||
{showHeader && (
|
{showHeader && (
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { siGithub } from 'simple-icons'
|
import { siGithub, siX } from 'simple-icons'
|
||||||
|
|
||||||
import { useLanguage } from '@/i18n/context'
|
import { useLanguage } from '@/i18n/context'
|
||||||
|
|
||||||
@@ -15,6 +15,7 @@ export default function Footer() {
|
|||||||
<p className="text-gray-600 dark:text-gray-300 text-sm">
|
<p className="text-gray-600 dark:text-gray-300 text-sm">
|
||||||
© 2026 page-agent. All rights reserved.
|
© 2026 page-agent. All rights reserved.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="flex items-center space-x-6">
|
<div className="flex items-center space-x-6">
|
||||||
<a
|
<a
|
||||||
href="https://github.com/alibaba/page-agent/blob/main/docs/terms-and-privacy.md"
|
href="https://github.com/alibaba/page-agent/blob/main/docs/terms-and-privacy.md"
|
||||||
@@ -24,6 +25,22 @@ export default function Footer() {
|
|||||||
>
|
>
|
||||||
{isZh ? '使用条款与隐私' : 'Terms & Privacy'}
|
{isZh ? '使用条款与隐私' : 'Terms & Privacy'}
|
||||||
</a>
|
</a>
|
||||||
|
<a
|
||||||
|
href="https://x.com/simonluvramen"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition-colors duration-200"
|
||||||
|
aria-label="X (Twitter)"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
role="img"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
className="w-4 h-4 fill-current"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<path d={siX.path} />
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
<a
|
<a
|
||||||
href="https://github.com/alibaba/page-agent"
|
href="https://github.com/alibaba/page-agent"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { useState } from 'react'
|
|||||||
import { siGithub } from 'simple-icons'
|
import { siGithub } from 'simple-icons'
|
||||||
import { Link } from 'wouter'
|
import { Link } from 'wouter'
|
||||||
|
|
||||||
|
import { formatStars, useGitHubStars } from '@/hooks/useGitHubStars'
|
||||||
import { useLanguage } from '@/i18n/context'
|
import { useLanguage } from '@/i18n/context'
|
||||||
|
|
||||||
import LanguageSwitcher from './LanguageSwitcher'
|
import LanguageSwitcher from './LanguageSwitcher'
|
||||||
@@ -12,6 +13,7 @@ import { HyperText } from './ui/hyper-text'
|
|||||||
export default function Header() {
|
export default function Header() {
|
||||||
const { isZh } = useLanguage()
|
const { isZh } = useLanguage()
|
||||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
|
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
|
||||||
|
const stars = useGitHubStars()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -34,8 +36,11 @@ export default function Header() {
|
|||||||
className="w-10 h-10 rounded-xl group-hover:scale-110 transition-transform duration-200"
|
className="w-10 h-10 rounded-xl group-hover:scale-110 transition-transform duration-200"
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<span className="text-base sm:text-xl font-bold text-gray-900 dark:text-white block leading-tight">
|
<span className="text-base sm:text-xl font-bold text-gray-900 dark:text-white leading-tight flex items-baseline gap-1.5">
|
||||||
page-agent
|
page-agent
|
||||||
|
<span className="hidden sm:inline text-[10px] font-mono font-normal text-gray-400 dark:text-gray-500 tabular-nums before:content-['v']">
|
||||||
|
{import.meta.env.VERSION}
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<HyperText
|
<HyperText
|
||||||
as="p"
|
as="p"
|
||||||
@@ -66,7 +71,7 @@ export default function Header() {
|
|||||||
href="https://github.com/alibaba/page-agent"
|
href="https://github.com/alibaba/page-agent"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
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="flex items-center gap-1 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="GitHub"
|
aria-label="GitHub"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
@@ -77,6 +82,9 @@ export default function Header() {
|
|||||||
>
|
>
|
||||||
<path d={siGithub.path} />
|
<path d={siGithub.path} />
|
||||||
</svg>
|
</svg>
|
||||||
|
{stars !== null && (
|
||||||
|
<span className="text-sm tabular-nums">★ {formatStars(stars)}</span>
|
||||||
|
)}
|
||||||
</a>
|
</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
@@ -86,9 +94,6 @@ export default function Header() {
|
|||||||
role="navigation"
|
role="navigation"
|
||||||
aria-label={isZh ? '文档' : 'Docs'}
|
aria-label={isZh ? '文档' : 'Docs'}
|
||||||
>
|
>
|
||||||
<span className="text-xs font-mono text-gray-400 dark:text-gray-500 tabular-nums before:content-['v']">
|
|
||||||
{import.meta.env.VERSION}
|
|
||||||
</span>
|
|
||||||
<Link
|
<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"
|
||||||
@@ -112,6 +117,9 @@ export default function Header() {
|
|||||||
<path d={siGithub.path} />
|
<path d={siGithub.path} />
|
||||||
</svg>
|
</svg>
|
||||||
GitHub
|
GitHub
|
||||||
|
{stars !== null && (
|
||||||
|
<span className="text-sm font-medium tabular-nums ">★ {formatStars(stars)}</span>
|
||||||
|
)}
|
||||||
</a>
|
</a>
|
||||||
<ThemeSwitcher />
|
<ThemeSwitcher />
|
||||||
<LanguageSwitcher />
|
<LanguageSwitcher />
|
||||||
@@ -161,6 +169,11 @@ export default function Header() {
|
|||||||
<path d={siGithub.path} />
|
<path d={siGithub.path} />
|
||||||
</svg>
|
</svg>
|
||||||
GitHub
|
GitHub
|
||||||
|
{stars !== null && (
|
||||||
|
<span className="text-xs tabular-nums text-gray-400 dark:text-gray-500">
|
||||||
|
★ {formatStars(stars)}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</a>
|
</a>
|
||||||
<div className="flex items-center gap-3 px-3 py-2">
|
<div className="flex items-center gap-3 px-3 py-2">
|
||||||
<ThemeSwitcher />
|
<ThemeSwitcher />
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
// Demo build (auto-init with demo LLM, for quick testing)
|
// Demo build (auto-init with demo LLM, for quick testing)
|
||||||
export const CDN_DEMO_URL =
|
export const CDN_DEMO_URL =
|
||||||
'https://cdn.jsdelivr.net/npm/page-agent@1.4.2/dist/iife/page-agent.demo.js'
|
'https://cdn.jsdelivr.net/npm/page-agent@1.5.7/dist/iife/page-agent.demo.js'
|
||||||
export const CDN_DEMO_CN_URL =
|
export const CDN_DEMO_CN_URL =
|
||||||
'https://registry.npmmirror.com/page-agent/1.4.2/files/dist/iife/page-agent.demo.js'
|
'https://registry.npmmirror.com/page-agent/1.5.7/files/dist/iife/page-agent.demo.js'
|
||||||
|
|
||||||
// Demo LLM for website testing (homepage quick trial uses flash)
|
// Demo LLM for website testing (homepage quick trial uses flash)
|
||||||
export const DEMO_MODEL = 'qwen3.5-flash'
|
export const DEMO_MODEL = 'qwen3.5-flash'
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
|
const STATS_URL = 'https://page-agent.github.io/gh-stats/stats.json'
|
||||||
|
|
||||||
|
let cached: number | null = null
|
||||||
|
|
||||||
|
export function useGitHubStars() {
|
||||||
|
const [stars, setStars] = useState(cached)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (cached !== null) return
|
||||||
|
fetch(STATS_URL)
|
||||||
|
.then((r) => r.json())
|
||||||
|
.then((data) => {
|
||||||
|
cached = data.stargazers_count ?? null
|
||||||
|
setStars(cached)
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return stars
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatStars(n: number): string {
|
||||||
|
if (n >= 1000) return `${(n / 1000).toFixed(1).replace(/\.0$/, '')}k`
|
||||||
|
return String(n)
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { useEffect } from 'react'
|
||||||
|
|
||||||
|
const DEFAULT_TITLE = 'PageAgent - The GUI Agent Living in Your Webpage'
|
||||||
|
|
||||||
|
export function useDocumentTitle(title?: string) {
|
||||||
|
useEffect(() => {
|
||||||
|
document.title = title ? `${title} - PageAgent` : DEFAULT_TITLE
|
||||||
|
}, [title])
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import { Link, useLocation } from 'wouter'
|
|||||||
|
|
||||||
import { SparklesText } from '@/components/ui/sparkles-text'
|
import { SparklesText } from '@/components/ui/sparkles-text'
|
||||||
import { useLanguage } from '@/i18n/context'
|
import { useLanguage } from '@/i18n/context'
|
||||||
|
import { useDocumentTitle } from '@/lib/useDocumentTitle'
|
||||||
|
|
||||||
interface DocsLayoutProps {
|
interface DocsLayoutProps {
|
||||||
children: ReactNode
|
children: ReactNode
|
||||||
@@ -34,10 +35,6 @@ export default function DocsLayout({ children }: DocsLayoutProps) {
|
|||||||
title: isZh ? '故障排查' : 'Troubleshooting',
|
title: isZh ? '故障排查' : 'Troubleshooting',
|
||||||
path: '/introduction/troubleshooting',
|
path: '/introduction/troubleshooting',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: '🚧 ' + (isZh ? '最佳实践' : 'Best Practices'),
|
|
||||||
path: '/integration/best-practices',
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -50,7 +47,7 @@ export default function DocsLayout({ children }: DocsLayoutProps) {
|
|||||||
{ title: isZh ? 'Chrome 扩展' : 'Chrome Extension', path: '/features/chrome-extension' },
|
{ title: isZh ? 'Chrome 扩展' : 'Chrome Extension', path: '/features/chrome-extension' },
|
||||||
{
|
{
|
||||||
title: isZh ? '接入第三方 Agent' : 'Third-party Agent',
|
title: isZh ? '接入第三方 Agent' : 'Third-party Agent',
|
||||||
path: '/integration/third-party-agent',
|
path: '/features/third-party-agent',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -59,15 +56,22 @@ export default function DocsLayout({ children }: DocsLayoutProps) {
|
|||||||
items: [
|
items: [
|
||||||
{ title: 'PageAgent', path: '/advanced/page-agent' },
|
{ title: 'PageAgent', path: '/advanced/page-agent' },
|
||||||
{ title: 'PageAgentCore', path: '/advanced/page-agent-core' },
|
{ title: 'PageAgentCore', path: '/advanced/page-agent-core' },
|
||||||
|
{ title: 'PageController', path: '/advanced/page-controller' },
|
||||||
{ title: isZh ? '自定义 UI' : 'Custom UI', path: '/advanced/custom-ui' },
|
{ title: isZh ? '自定义 UI' : 'Custom UI', path: '/advanced/custom-ui' },
|
||||||
{
|
{
|
||||||
title: '🚧 ' + (isZh ? '安全与权限' : 'Security & Permissions'),
|
title: '🚧 ' + (isZh ? '安全与权限' : 'Security & Permissions'),
|
||||||
path: '/integration/security-permissions',
|
path: '/advanced/security-permissions',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const activeTitle = navigationSections
|
||||||
|
.flatMap((s) => s.items)
|
||||||
|
.find((item) => item.path === location)?.title
|
||||||
|
|
||||||
|
useDocumentTitle(activeTitle)
|
||||||
|
|
||||||
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-225">
|
<div className="flex gap-8 min-w-225">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
import { APIDivider, APIReference } from '@/components/APIReference'
|
||||||
import CodeEditor from '@/components/CodeEditor'
|
import CodeEditor from '@/components/CodeEditor'
|
||||||
import { Heading } from '@/components/Heading'
|
import { Heading } from '@/components/Heading'
|
||||||
import { APIDivider, APIReference } from '@/components/ui/api-reference'
|
|
||||||
import { useLanguage } from '@/i18n/context'
|
import { useLanguage } from '@/i18n/context'
|
||||||
|
|
||||||
export default function CustomUIDocs() {
|
export default function CustomUIDocs() {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
import { Link } from 'wouter'
|
||||||
|
|
||||||
|
import { APIDivider, APIReference, TypeRef } from '@/components/APIReference'
|
||||||
import CodeEditor from '@/components/CodeEditor'
|
import CodeEditor from '@/components/CodeEditor'
|
||||||
import { Heading } from '@/components/Heading'
|
import { Heading } from '@/components/Heading'
|
||||||
import { APIDivider, APIReference, TypeRef } from '@/components/ui/api-reference'
|
|
||||||
import { useLanguage } from '@/i18n/context'
|
import { useLanguage } from '@/i18n/context'
|
||||||
|
|
||||||
export default function PageAgentCoreDocs() {
|
export default function PageAgentCoreDocs() {
|
||||||
@@ -50,19 +52,13 @@ const agent = new PageAgentCore({
|
|||||||
baseURL: 'https://api.openai.com/v1',
|
baseURL: 'https://api.openai.com/v1',
|
||||||
apiKey: 'your-api-key',
|
apiKey: 'your-api-key',
|
||||||
model: 'gpt-5.2',
|
model: 'gpt-5.2',
|
||||||
language: 'en-US',
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Listen to events for UI display
|
// Listen to events for UI display
|
||||||
|
|
||||||
agent.addEventListener('statuschange', () => {
|
agent.addEventListener('statuschange', () => {
|
||||||
console.log('Status:', agent.status)
|
console.log('Status:', agent.status)
|
||||||
})
|
})
|
||||||
|
|
||||||
agent.addEventListener('historychange', () => {
|
|
||||||
console.log('History:', agent.history)
|
|
||||||
})
|
|
||||||
|
|
||||||
agent.addEventListener('activity', (e) => {
|
agent.addEventListener('activity', (e) => {
|
||||||
const activity = (e as CustomEvent).detail
|
const activity = (e as CustomEvent).detail
|
||||||
console.log('Activity:', activity.type)
|
console.log('Activity:', activity.type)
|
||||||
@@ -75,14 +71,54 @@ const result = await agent.execute('Fill in the form with test data')`}
|
|||||||
|
|
||||||
<APIDivider title={isZh ? '配置' : 'Configuration'} />
|
<APIDivider title={isZh ? '配置' : 'Configuration'} />
|
||||||
|
|
||||||
{/* LLM Configuration */}
|
{/* Configuration */}
|
||||||
<section className="mb-10">
|
<section className="mb-10">
|
||||||
<Heading id="llmconfig">LLMConfig</Heading>
|
<Heading id="configuration">PageAgentCoreConfig</Heading>
|
||||||
<p className="text-gray-600 dark:text-gray-400 mb-4">
|
<p className="text-gray-600 dark:text-gray-400 mb-4">
|
||||||
{isZh
|
{isZh
|
||||||
? '配置与大语言模型的连接参数。支持 OpenAI 兼容的 API。'
|
? 'PageAgentCoreConfig = AgentConfig & { pageController: PageController }。AgentConfig 包含以下配置项:'
|
||||||
: 'Configure connection parameters for the language model. Supports OpenAI-compatible APIs.'}
|
: 'PageAgentCoreConfig = AgentConfig & { pageController: PageController }. AgentConfig contains the following options:'}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
{/* PageController */}
|
||||||
|
<h3 className="text-lg font-semibold mt-6 mb-3 text-gray-800 dark:text-gray-200">
|
||||||
|
PageController
|
||||||
|
</h3>
|
||||||
|
<APIReference
|
||||||
|
properties={[
|
||||||
|
{
|
||||||
|
name: 'pageController',
|
||||||
|
type: 'PageController',
|
||||||
|
required: true,
|
||||||
|
description: isZh ? (
|
||||||
|
<>
|
||||||
|
<Link
|
||||||
|
href="/advanced/page-controller"
|
||||||
|
className="text-blue-600 dark:text-blue-400 hover:underline"
|
||||||
|
>
|
||||||
|
PageController
|
||||||
|
</Link>{' '}
|
||||||
|
实例,用于 DOM 操作和元素交互。
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Link
|
||||||
|
href="/advanced/page-controller"
|
||||||
|
className="text-blue-600 dark:text-blue-400 hover:underline"
|
||||||
|
>
|
||||||
|
PageController
|
||||||
|
</Link>{' '}
|
||||||
|
instance for DOM operations and element interaction.
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* LLM Config */}
|
||||||
|
<h3 className="text-lg font-semibold mt-6 mb-3 text-gray-800 dark:text-gray-200">
|
||||||
|
{isZh ? 'LLM 配置' : 'LLM Config'}
|
||||||
|
</h3>
|
||||||
<APIReference
|
<APIReference
|
||||||
properties={[
|
properties={[
|
||||||
{
|
{
|
||||||
@@ -110,7 +146,6 @@ const result = await agent.execute('Fill in the form with test data')`}
|
|||||||
{
|
{
|
||||||
name: 'temperature',
|
name: 'temperature',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
defaultValue: '0',
|
|
||||||
description: isZh
|
description: isZh
|
||||||
? '模型温度参数,控制输出随机性'
|
? '模型温度参数,控制输出随机性'
|
||||||
: 'Model temperature, controls output randomness',
|
: 'Model temperature, controls output randomness',
|
||||||
@@ -130,16 +165,11 @@ const result = await agent.execute('Fill in the form with test data')`}
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* Agent Configuration */}
|
{/* Agent Config */}
|
||||||
<section className="mb-10">
|
<h3 className="text-lg font-semibold mt-6 mb-3 text-gray-800 dark:text-gray-200">
|
||||||
<Heading id="agentconfig">AgentConfig</Heading>
|
{isZh ? 'Agent 配置' : 'Agent Config'}
|
||||||
<p className="text-gray-600 dark:text-gray-400 mb-4">
|
</h3>
|
||||||
{isZh
|
|
||||||
? '配置 Agent 的行为、生命周期钩子和扩展能力。'
|
|
||||||
: 'Configure agent behavior, lifecycle hooks, and extension capabilities.'}
|
|
||||||
</p>
|
|
||||||
<APIReference
|
<APIReference
|
||||||
properties={[
|
properties={[
|
||||||
{
|
{
|
||||||
@@ -151,7 +181,7 @@ const result = await agent.execute('Fill in the form with test data')`}
|
|||||||
{
|
{
|
||||||
name: 'maxSteps',
|
name: 'maxSteps',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
defaultValue: '20',
|
defaultValue: '40',
|
||||||
description: isZh ? '每个任务的最大步骤数' : 'Maximum number of steps per task',
|
description: isZh ? '每个任务的最大步骤数' : 'Maximum number of steps per task',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -166,8 +196,8 @@ const result = await agent.execute('Fill in the form with test data')`}
|
|||||||
name: 'instructions',
|
name: 'instructions',
|
||||||
type: 'InstructionsConfig',
|
type: 'InstructionsConfig',
|
||||||
description: isZh
|
description: isZh
|
||||||
? '指导 Agent 行为的指令配置'
|
? '指导 Agent 行为的指令配置,见下方类型定义'
|
||||||
: 'Instructions to guide agent behavior',
|
: 'Instructions to guide agent behavior, see type definition below',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'transformPageContent',
|
name: 'transformPageContent',
|
||||||
@@ -177,7 +207,15 @@ const result = await agent.execute('Fill in the form with test data')`}
|
|||||||
: 'Transform page content before sending to LLM, useful for data masking',
|
: 'Transform page content before sending to LLM, useful for data masking',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'experimentalScriptExecutionTool',
|
name: 'customSystemPrompt',
|
||||||
|
type: 'string',
|
||||||
|
status: 'experimental',
|
||||||
|
description: isZh
|
||||||
|
? '完全覆盖默认系统提示词。谨慎使用。'
|
||||||
|
: 'Completely override the default system prompt. Use with caution.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'experimentalScript\nExecutionTool',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
defaultValue: 'false',
|
defaultValue: 'false',
|
||||||
status: 'experimental',
|
status: 'experimental',
|
||||||
@@ -185,134 +223,58 @@ const result = await agent.execute('Fill in the form with test data')`}
|
|||||||
? '启用实验性 JavaScript 执行工具'
|
? '启用实验性 JavaScript 执行工具'
|
||||||
: 'Enable experimental JavaScript execution tool',
|
: 'Enable experimental JavaScript execution tool',
|
||||||
},
|
},
|
||||||
]}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<h3 className="text-lg font-semibold mt-6 mb-3">InstructionsConfig</h3>
|
|
||||||
<APIReference
|
|
||||||
properties={[
|
|
||||||
{
|
{
|
||||||
name: 'system',
|
name: 'experimentalLlmsTxt',
|
||||||
type: 'string',
|
type: 'boolean',
|
||||||
|
defaultValue: 'false',
|
||||||
|
status: 'experimental',
|
||||||
description: isZh
|
description: isZh
|
||||||
? '全局系统级指令,应用于所有任务'
|
? '从当前站点根目录获取 /llms.txt 并作为上下文提供给 LLM'
|
||||||
: 'Global system-level instructions, applied to all tasks',
|
: 'Fetch /llms.txt from site origin and include as LLM context',
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'getPageInstructions',
|
|
||||||
type: '(url: string) => string | undefined | null',
|
|
||||||
description: isZh
|
|
||||||
? '动态页面级指令回调,在每个步骤前调用'
|
|
||||||
: 'Dynamic page-level instructions callback, called before each step',
|
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* Lifecycle Hooks */}
|
{/* Lifecycle Hooks */}
|
||||||
<section className="mb-10">
|
<h3 className="text-lg font-semibold mt-6 mb-3 text-gray-800 dark:text-gray-200">
|
||||||
<Heading id="lifecycle-hooks">{isZh ? '生命周期钩子' : 'Lifecycle Hooks'}</Heading>
|
{isZh ? '生命周期钩子' : 'Lifecycle Hooks'}
|
||||||
|
<span className="ml-2 text-xs font-normal text-amber-600 dark:text-amber-400">
|
||||||
|
experimental
|
||||||
|
</span>
|
||||||
|
</h3>
|
||||||
<div className="bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 rounded-lg p-4 mb-4">
|
<div className="bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 rounded-lg p-4 mb-4">
|
||||||
<p className="text-amber-800 dark:text-amber-200 text-sm">
|
<p className="text-amber-800 dark:text-amber-200 text-sm">
|
||||||
<strong>⚠️ {isZh ? '警告' : 'Warning'}:</strong>{' '}
|
|
||||||
{isZh
|
{isZh
|
||||||
? '这些接口高度实验性,可能在未来版本中发生变化。建议优先使用事件系统(Events)来监听 Agent 状态。'
|
? '这些接口高度实验性,可能在未来版本中发生变化。'
|
||||||
: 'These APIs are highly experimental and may change in future versions. Prefer using the Events system for monitoring agent state.'}
|
: 'These APIs are highly experimental and may change in future versions. '}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-gray-600 dark:text-gray-400 mb-4">
|
|
||||||
{isZh
|
|
||||||
? '所有生命周期钩子都接收 agent 实例作为第一个参数,便于在回调中访问 Agent 状态和方法。'
|
|
||||||
: 'All lifecycle hooks receive the agent instance as first parameter, making it easy to access agent state and methods in callbacks.'}
|
|
||||||
</p>
|
|
||||||
<APIReference
|
<APIReference
|
||||||
properties={[
|
properties={[
|
||||||
{
|
{
|
||||||
name: 'onBeforeStep',
|
name: 'onBeforeStep',
|
||||||
type: '(agent: PageAgentCore, stepCount: number) => void | Promise<void>',
|
type: '(agent, stepCount) => void | Promise<void>',
|
||||||
description: isZh ? '每个步骤执行前调用' : 'Called before each step execution',
|
description: isZh ? '每个步骤执行前调用' : 'Called before each step execution',
|
||||||
status: 'experimental',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'onAfterStep',
|
name: 'onAfterStep',
|
||||||
type: '(agent: PageAgentCore, history: HistoricalEvent[]) => void | Promise<void>',
|
type: '(agent, history) => void | Promise<void>',
|
||||||
description: isZh ? '每个步骤执行后调用' : 'Called after each step execution',
|
description: isZh ? '每个步骤执行后调用' : 'Called after each step execution',
|
||||||
status: 'experimental',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'onBeforeTask',
|
name: 'onBeforeTask',
|
||||||
type: '(agent: PageAgentCore) => void | Promise<void>',
|
type: '(agent) => void | Promise<void>',
|
||||||
description: isZh ? '任务开始前调用' : 'Called before task starts',
|
description: isZh ? '任务开始前调用' : 'Called before task starts',
|
||||||
status: 'experimental',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'onAfterTask',
|
name: 'onAfterTask',
|
||||||
type: '(agent: PageAgentCore, result: ExecutionResult) => void | Promise<void>',
|
type: '(agent, result) => void | Promise<void>',
|
||||||
description: isZh ? '任务结束后调用' : 'Called after task ends',
|
description: isZh ? '任务结束后调用' : 'Called after task ends',
|
||||||
status: 'experimental',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'onDispose',
|
name: 'onDispose',
|
||||||
type: '(agent: PageAgentCore, reason?: string) => void',
|
type: '(agent, reason?) => void',
|
||||||
description: isZh ? 'Agent 销毁时调用' : 'Called when agent is disposed',
|
description: isZh ? 'Agent 销毁时调用' : 'Called when agent is disposed',
|
||||||
status: 'experimental',
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* PageController Configuration */}
|
|
||||||
<section className="mb-10">
|
|
||||||
<Heading id="pagecontrollerconfig">PageControllerConfig</Heading>
|
|
||||||
<p className="text-gray-600 dark:text-gray-400 mb-4">
|
|
||||||
{isZh
|
|
||||||
? '配置 DOM 提取、元素交互和视觉反馈。'
|
|
||||||
: 'Configure DOM extraction, element interaction, and visual feedback.'}
|
|
||||||
</p>
|
|
||||||
<APIReference
|
|
||||||
properties={[
|
|
||||||
{
|
|
||||||
name: 'pageController',
|
|
||||||
type: 'PageController',
|
|
||||||
status: 'experimental',
|
|
||||||
description: isZh
|
|
||||||
? '自定义 PageController 实例。如不提供,将创建默认实例。'
|
|
||||||
: 'Custom PageController instance. If not provided, a default one will be created.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'enableMask',
|
|
||||||
type: 'boolean',
|
|
||||||
defaultValue: 'true',
|
|
||||||
description: isZh
|
|
||||||
? '启用视觉遮罩覆盖层,阻止用户在自动化期间操作'
|
|
||||||
: 'Enable visual mask overlay that blocks user interaction during automation',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'viewportExpansion',
|
|
||||||
type: 'number',
|
|
||||||
defaultValue: '0',
|
|
||||||
description: isZh
|
|
||||||
? '视口扩展像素数,-1 表示提取整个页面'
|
|
||||||
: 'Viewport expansion in pixels, -1 means extract entire page',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'interactiveBlacklist',
|
|
||||||
type: '(Element | (() => Element))[]',
|
|
||||||
description: isZh ? '要排除的交互元素列表' : 'Elements to exclude from interaction',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'interactiveWhitelist',
|
|
||||||
type: '(Element | (() => Element))[]',
|
|
||||||
description: isZh
|
|
||||||
? '要强制包含的交互元素列表'
|
|
||||||
: 'Elements to force include for interaction',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'includeAttributes',
|
|
||||||
type: 'string[]',
|
|
||||||
description: isZh
|
|
||||||
? '在 DOM 提取中包含的额外属性'
|
|
||||||
: 'Additional attributes to include in DOM extraction',
|
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
@@ -369,16 +331,24 @@ const result = await agent.execute('Fill in the form with test data')`}
|
|||||||
<section className="mb-10">
|
<section className="mb-10">
|
||||||
<Heading id="methods">{isZh ? '方法' : 'Methods'}</Heading>
|
<Heading id="methods">{isZh ? '方法' : 'Methods'}</Heading>
|
||||||
<APIReference
|
<APIReference
|
||||||
|
variant="methods"
|
||||||
properties={[
|
properties={[
|
||||||
{
|
{
|
||||||
name: 'execute(task: string)',
|
name: 'execute(task)',
|
||||||
type: 'Promise<ExecutionResult>',
|
type: 'Promise<ExecutionResult>',
|
||||||
description: isZh
|
description: isZh
|
||||||
? '执行任务并返回结果。包含 success、data 和 history 字段。'
|
? '执行任务并返回结果。包含 success、data 和 history 字段。'
|
||||||
: 'Execute a task and return result. Contains success, data, and history fields.',
|
: 'Execute a task and return result. Contains success, data, and history fields.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'dispose(reason?: string)',
|
name: 'stop()',
|
||||||
|
type: 'void',
|
||||||
|
description: isZh
|
||||||
|
? '停止当前任务。Agent 仍可复用。'
|
||||||
|
: 'Stop the current task. Agent remains reusable.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'dispose()',
|
||||||
type: 'void',
|
type: 'void',
|
||||||
description: isZh
|
description: isZh
|
||||||
? '销毁 Agent 并清理资源'
|
? '销毁 Agent 并清理资源'
|
||||||
@@ -443,11 +413,8 @@ const result = await agent.execute('Fill in the form with test data')`}
|
|||||||
<CodeEditor
|
<CodeEditor
|
||||||
language="typescript"
|
language="typescript"
|
||||||
code={`interface ExecutionResult {
|
code={`interface ExecutionResult {
|
||||||
/** Whether the task completed successfully */
|
|
||||||
success: boolean
|
success: boolean
|
||||||
/** Result description from the agent */
|
|
||||||
data: string
|
data: string
|
||||||
/** Full execution history */
|
|
||||||
history: HistoricalEvent[]
|
history: HistoricalEvent[]
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
@@ -467,50 +434,21 @@ const result = await agent.execute('Fill in the form with test data')`}
|
|||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<APIDivider title={isZh ? '无头模式' : 'Headless Mode'} />
|
{/* InstructionsConfig */}
|
||||||
|
|
||||||
{/* Headless Usage */}
|
|
||||||
<section className="mb-10">
|
<section className="mb-10">
|
||||||
<Heading id="headless-mode">{isZh ? '无头模式' : 'Headless Mode'}</Heading>
|
<Heading id="instructionsconfig">InstructionsConfig</Heading>
|
||||||
<p className="text-gray-600 dark:text-gray-400 mb-4">
|
|
||||||
{isZh
|
|
||||||
? '在非 DOM 环境中,你必须实现自定义的 PageController(例如远程操作页面或 Puppeteer)。'
|
|
||||||
: 'In non-DOM environments, you must implement a custom PageController (e.g., remote page control or Puppeteer).'}
|
|
||||||
</p>
|
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
language="typescript"
|
language="typescript"
|
||||||
code={`import { PageAgentCore } from '@page-agent/core'
|
code={`interface InstructionsConfig {
|
||||||
import type { PageController } from '@page-agent/page-controller'
|
/** Global system-level instructions, applied to all tasks */
|
||||||
|
system?: string
|
||||||
|
|
||||||
class MyRemotePageController implements PageController {
|
/**
|
||||||
// Implement required methods for DOM extraction and interaction
|
* Dynamic page-level instructions callback.
|
||||||
}
|
* Called before each step to get instructions for the current page.
|
||||||
|
*/
|
||||||
const agent = new PageAgentCore({
|
getPageInstructions?: (url: string) => string | undefined
|
||||||
pageController: new MyRemotePageController(),
|
}`}
|
||||||
baseURL: 'https://api.openai.com/v1',
|
|
||||||
apiKey: 'your-api-key',
|
|
||||||
model: 'gpt-5.2',
|
|
||||||
language: 'en-US',
|
|
||||||
})
|
|
||||||
|
|
||||||
// Listen to events for UI display
|
|
||||||
|
|
||||||
agent.addEventListener('statuschange', () => {
|
|
||||||
console.log('Status:', agent.status)
|
|
||||||
})
|
|
||||||
|
|
||||||
agent.addEventListener('historychange', () => {
|
|
||||||
console.log('History:', agent.history)
|
|
||||||
})
|
|
||||||
|
|
||||||
agent.addEventListener('activity', (e) => {
|
|
||||||
const activity = (e as CustomEvent).detail
|
|
||||||
console.log('Activity:', activity.type)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Execute task
|
|
||||||
const result = await agent.execute('Fill in the form with test data')`}
|
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { Link } from 'wouter'
|
|||||||
|
|
||||||
import CodeEditor from '@/components/CodeEditor'
|
import CodeEditor from '@/components/CodeEditor'
|
||||||
import { Heading } from '@/components/Heading'
|
import { Heading } from '@/components/Heading'
|
||||||
import { APIReference, TypeRef } from '@/components/ui/api-reference'
|
|
||||||
import { useLanguage } from '@/i18n/context'
|
import { useLanguage } from '@/i18n/context'
|
||||||
|
|
||||||
export default function PageAgentDocs() {
|
export default function PageAgentDocs() {
|
||||||
@@ -14,8 +13,8 @@ export default function PageAgentDocs() {
|
|||||||
|
|
||||||
<p className="text-xl text-gray-600 dark:text-gray-300 mb-8 leading-relaxed">
|
<p className="text-xl text-gray-600 dark:text-gray-300 mb-8 leading-relaxed">
|
||||||
{isZh
|
{isZh
|
||||||
? 'PageAgent 是带有内置 UI 面板的完整 Agent 类。它继承自 PageAgentCore,并自动创建交互面板。'
|
? 'PageAgent 是带有内置 UI 面板的完整 Agent 类。它继承自 PageAgentCore,并自动创建交互面板和 PageController。'
|
||||||
: 'PageAgent is the complete Agent class with built-in UI panel. It extends PageAgentCore and automatically creates an interactive panel.'}
|
: 'PageAgent is the complete Agent class with built-in UI panel. It extends PageAgentCore and automatically creates an interactive panel and PageController.'}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{/* When to use */}
|
{/* When to use */}
|
||||||
@@ -29,6 +28,11 @@ export default function PageAgentDocs() {
|
|||||||
: 'In most cases, you should use PageAgent. It provides a complete out-of-the-box experience:'}
|
: 'In most cases, you should use PageAgent. It provides a complete out-of-the-box experience:'}
|
||||||
</p>
|
</p>
|
||||||
<ul className="list-disc list-inside text-gray-600 dark:text-gray-400 space-y-2 mb-6">
|
<ul className="list-disc list-inside text-gray-600 dark:text-gray-400 space-y-2 mb-6">
|
||||||
|
<li>
|
||||||
|
{isZh
|
||||||
|
? '自动创建 PageController,处理 DOM 提取和元素操作'
|
||||||
|
: 'Automatically creates PageController for DOM extraction and element actions'}
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
{isZh
|
{isZh
|
||||||
? '内置 UI 面板,显示任务进度、Agent 思考过程和操作结果'
|
? '内置 UI 面板,显示任务进度、Agent 思考过程和操作结果'
|
||||||
@@ -74,9 +78,8 @@ console.log(result.history) // Full execution history`}
|
|||||||
<CodeEditor
|
<CodeEditor
|
||||||
language="typescript"
|
language="typescript"
|
||||||
code={`class PageAgent extends PageAgentCore {
|
code={`class PageAgent extends PageAgentCore {
|
||||||
/** The UI panel instance */
|
|
||||||
panel: Panel
|
panel: Panel
|
||||||
|
pageController: PageController
|
||||||
constructor(config: PageAgentConfig)
|
constructor(config: PageAgentConfig)
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
@@ -90,7 +93,21 @@ console.log(result.history) // Full execution history`}
|
|||||||
>
|
>
|
||||||
PageAgentCore
|
PageAgentCore
|
||||||
</Link>
|
</Link>
|
||||||
,所有核心方法和事件都可用。详细的 API 参考请查看 PageAgentCore 文档。
|
,所有核心方法和事件都可用。配置项合并了{' '}
|
||||||
|
<Link
|
||||||
|
href="/advanced/page-agent-core#configuration"
|
||||||
|
className="text-blue-600 dark:text-blue-400 hover:underline"
|
||||||
|
>
|
||||||
|
AgentConfig
|
||||||
|
</Link>{' '}
|
||||||
|
和{' '}
|
||||||
|
<Link
|
||||||
|
href="/advanced/page-controller#configuration"
|
||||||
|
className="text-blue-600 dark:text-blue-400 hover:underline"
|
||||||
|
>
|
||||||
|
PageControllerConfig
|
||||||
|
</Link>
|
||||||
|
。
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
@@ -101,71 +118,34 @@ console.log(result.history) // Full execution history`}
|
|||||||
>
|
>
|
||||||
PageAgentCore
|
PageAgentCore
|
||||||
</Link>
|
</Link>
|
||||||
. All core methods and events are available. See PageAgentCore docs for detailed API
|
. All core methods and events are available. Config merges{' '}
|
||||||
reference.
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* Configuration */}
|
|
||||||
<section className="mb-10">
|
|
||||||
<Heading id="configuration">{isZh ? '配置' : 'Configuration'}</Heading>
|
|
||||||
<p className="text-gray-600 dark:text-gray-400 mb-4">
|
|
||||||
{isZh
|
|
||||||
? 'PageAgent 使用与 PageAgentCore 相同的配置接口。'
|
|
||||||
: 'PageAgent uses the same configuration interface as PageAgentCore.'}
|
|
||||||
</p>
|
|
||||||
<p className="text-gray-600 dark:text-gray-400 mb-4">
|
|
||||||
{isZh ? (
|
|
||||||
<>
|
|
||||||
完整配置请参考{' '}
|
|
||||||
<Link
|
<Link
|
||||||
href="/advanced/page-agent-core"
|
href="/advanced/page-agent-core#configuration"
|
||||||
className="text-blue-600 dark:text-blue-400 hover:underline"
|
className="text-blue-600 dark:text-blue-400 hover:underline"
|
||||||
>
|
>
|
||||||
PageAgentCore 配置文档
|
AgentConfig
|
||||||
</Link>
|
|
||||||
。
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
See{' '}
|
|
||||||
<Link
|
|
||||||
href="/advanced/page-agent-core"
|
|
||||||
className="text-blue-600 dark:text-blue-400 hover:underline"
|
|
||||||
>
|
|
||||||
PageAgentCore configuration docs
|
|
||||||
</Link>{' '}
|
</Link>{' '}
|
||||||
for complete reference.
|
and{' '}
|
||||||
|
<Link
|
||||||
|
href="/advanced/page-controller#configuration"
|
||||||
|
className="text-blue-600 dark:text-blue-400 hover:underline"
|
||||||
|
>
|
||||||
|
PageControllerConfig
|
||||||
|
</Link>
|
||||||
|
.
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Panel Property */}
|
{/* Panel */}
|
||||||
<section className="mb-10">
|
<section className="mb-10">
|
||||||
<Heading id="panel-property">{isZh ? 'Panel 属性' : 'Panel Property'}</Heading>
|
<Heading id="panel">{isZh ? 'UI 面板' : 'UI Panel'}</Heading>
|
||||||
<p className="text-gray-600 dark:text-gray-400 mb-4">
|
<p className="text-gray-600 dark:text-gray-400 mb-4">
|
||||||
{isZh
|
{isZh
|
||||||
? 'PageAgent 自动创建一个 Panel 实例。你可以通过 panel 属性访问它来控制 UI:'
|
? 'PageAgent 自动创建一个 Panel 实例。你可以通过 panel 属性控制 UI:'
|
||||||
: 'PageAgent automatically creates a Panel instance. You can access it via the panel property to control the UI:'}
|
: 'PageAgent automatically creates a Panel instance. You can control the UI via the panel property:'}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<APIReference
|
|
||||||
properties={[
|
|
||||||
{
|
|
||||||
name: 'panel',
|
|
||||||
type: 'Panel',
|
|
||||||
required: true,
|
|
||||||
description: isZh
|
|
||||||
? '内置的 UI 面板实例,用于显示任务进度和接收用户输入。'
|
|
||||||
: 'The built-in UI panel instance for displaying task progress and receiving user input.',
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<h3 className="text-lg font-semibold mt-6 mb-3">{isZh ? 'Panel 方法' : 'Panel Methods'}</h3>
|
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
language="typescript"
|
language="typescript"
|
||||||
code={`// Show/hide the panel
|
code={`// Show/hide the panel
|
||||||
@@ -212,16 +192,16 @@ agent.panel.dispose()`}
|
|||||||
</tr>
|
</tr>
|
||||||
<tr className="bg-white dark:bg-gray-900">
|
<tr className="bg-white dark:bg-gray-900">
|
||||||
<td className="px-4 py-3 text-gray-600 dark:text-gray-400">
|
<td className="px-4 py-3 text-gray-600 dark:text-gray-400">
|
||||||
{isZh ? 'Headless 模式' : 'Headless Mode'}
|
{isZh ? '自动创建 PageController' : 'Auto-creates PageController'}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-3 text-center text-gray-400 dark:text-gray-600">-</td>
|
|
||||||
<td className="px-4 py-3 text-center text-green-600 dark:text-green-400">✓</td>
|
<td className="px-4 py-3 text-center text-green-600 dark:text-green-400">✓</td>
|
||||||
|
<td className="px-4 py-3 text-center text-gray-400 dark:text-gray-600">-</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr className="bg-white dark:bg-gray-900">
|
<tr className="bg-white dark:bg-gray-900">
|
||||||
<td className="px-4 py-3 text-gray-600 dark:text-gray-400">
|
<td className="px-4 py-3 text-gray-600 dark:text-gray-400">
|
||||||
{isZh ? '自定义 PageController' : 'Custom PageController'}
|
{isZh ? 'Headless 模式' : 'Headless Mode'}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-3 text-center text-green-600 dark:text-green-400">✓</td>
|
<td className="px-4 py-3 text-center text-gray-400 dark:text-gray-600">-</td>
|
||||||
<td className="px-4 py-3 text-center text-green-600 dark:text-green-400">✓</td>
|
<td className="px-4 py-3 text-center text-green-600 dark:text-green-400">✓</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr className="bg-white dark:bg-gray-900">
|
<tr className="bg-white dark:bg-gray-900">
|
||||||
|
|||||||
@@ -0,0 +1,286 @@
|
|||||||
|
import { Link } from 'wouter'
|
||||||
|
|
||||||
|
import { APIDivider, APIReference } from '@/components/APIReference'
|
||||||
|
import CodeEditor from '@/components/CodeEditor'
|
||||||
|
import { Heading } from '@/components/Heading'
|
||||||
|
import { useLanguage } from '@/i18n/context'
|
||||||
|
|
||||||
|
export default function PageControllerDocs() {
|
||||||
|
const { isZh } = useLanguage()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1 className="text-4xl font-bold mb-6">PageController</h1>
|
||||||
|
|
||||||
|
<p className="text-xl text-gray-600 dark:text-gray-300 mb-8 leading-relaxed">
|
||||||
|
{isZh
|
||||||
|
? 'PageController 负责 DOM 提取和元素交互,独立于 LLM。它将页面状态结构化为 LLM 可消费的格式,并执行元素级操作。'
|
||||||
|
: 'PageController handles DOM extraction and element interaction, independent of LLM. It structures page state into LLM-consumable format and executes element-level actions.'}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* Basic Usage */}
|
||||||
|
<section className="mb-10">
|
||||||
|
<Heading id="basic-usage">{isZh ? '基本用法' : 'Basic Usage'}</Heading>
|
||||||
|
<p className="text-gray-600 dark:text-gray-400 mb-4">
|
||||||
|
{isZh
|
||||||
|
? 'PageAgent 接受 PageController 配置项:'
|
||||||
|
: 'PageAgent accepts PageController options:'}
|
||||||
|
</p>
|
||||||
|
<CodeEditor
|
||||||
|
language="typescript"
|
||||||
|
code={`import { PageAgent } from 'page-agent'
|
||||||
|
|
||||||
|
const agent = new PageAgent({
|
||||||
|
baseURL: 'https://api.openai.com/v1',
|
||||||
|
apiKey: 'your-api-key',
|
||||||
|
model: 'gpt-5.2',
|
||||||
|
|
||||||
|
// PageController options
|
||||||
|
enableMask: true,
|
||||||
|
viewportExpansion: 0,
|
||||||
|
})`}
|
||||||
|
/>
|
||||||
|
<p className="text-gray-600 dark:text-gray-400 mt-4">
|
||||||
|
{isZh
|
||||||
|
? 'PageAgentCore 接受 PageController 实例:'
|
||||||
|
: 'PageAgentCore accepts a PageController instance:'}
|
||||||
|
</p>
|
||||||
|
<CodeEditor
|
||||||
|
language="typescript"
|
||||||
|
code={`import { PageAgentCore } from '@page-agent/core'
|
||||||
|
import { PageController } from '@page-agent/page-controller'
|
||||||
|
|
||||||
|
const pageController = new PageController({
|
||||||
|
enableMask: true,
|
||||||
|
viewportExpansion: -1, // extract full page
|
||||||
|
})
|
||||||
|
|
||||||
|
const agent = new PageAgentCore({
|
||||||
|
pageController,
|
||||||
|
baseURL: 'https://api.openai.com/v1',
|
||||||
|
apiKey: 'your-api-key',
|
||||||
|
model: 'gpt-5.2',
|
||||||
|
})`}
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<APIDivider title={isZh ? '配置' : 'Configuration'} />
|
||||||
|
|
||||||
|
{/* Configuration */}
|
||||||
|
<section className="mb-10">
|
||||||
|
<Heading id="configuration">PageControllerConfig</Heading>
|
||||||
|
<APIReference
|
||||||
|
properties={[
|
||||||
|
{
|
||||||
|
name: 'enableMask',
|
||||||
|
type: 'boolean',
|
||||||
|
defaultValue: 'false',
|
||||||
|
description: isZh
|
||||||
|
? '启用视觉遮罩覆盖层,在自动化期间阻止用户操作页面。通过 PageAgent 创建时默认为 true。'
|
||||||
|
: 'Enable visual mask overlay that blocks user interaction during automation. Defaults to true when created via PageAgent.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'viewportExpansion',
|
||||||
|
type: 'number',
|
||||||
|
defaultValue: '0',
|
||||||
|
description: isZh
|
||||||
|
? '向视口外扩展提取的像素数。设为 -1 表示提取整个页面。'
|
||||||
|
: 'Pixels to expand extraction beyond viewport. Set to -1 to extract the entire page.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'interactiveBlacklist',
|
||||||
|
type: '(Element | (() => Element))[]',
|
||||||
|
description: isZh
|
||||||
|
? '要排除的交互元素列表。支持元素引用或返回元素的函数(延迟求值)。'
|
||||||
|
: 'Elements to exclude from interaction. Supports element references or functions returning elements (lazy evaluation).',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'interactiveWhitelist',
|
||||||
|
type: '(Element | (() => Element))[]',
|
||||||
|
description: isZh
|
||||||
|
? '要强制包含的交互元素列表。支持元素引用或返回元素的函数。'
|
||||||
|
: 'Elements to force include for interaction. Supports element references or functions returning elements.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'includeAttributes',
|
||||||
|
type: 'string[]',
|
||||||
|
description: isZh
|
||||||
|
? '在 DOM 提取中包含的额外 HTML 属性。支持通配符 *(如 data-* 匹配所有 data- 开头的属性)。默认已包含常见属性如 role, aria-label 等。'
|
||||||
|
: 'Additional HTML attributes to include in DOM extraction. Supports wildcard * (e.g. data-* matches all data- prefixed attributes). Common attributes like role, aria-label are included by default.',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<APIDivider title={isZh ? '方法' : 'Methods'} />
|
||||||
|
|
||||||
|
{/* Methods */}
|
||||||
|
<section className="mb-10">
|
||||||
|
<Heading id="methods">{isZh ? '方法' : 'Methods'}</Heading>
|
||||||
|
|
||||||
|
<h3 className="text-lg font-semibold mt-6 mb-3">{isZh ? '状态查询' : 'State Queries'}</h3>
|
||||||
|
<APIReference
|
||||||
|
variant="methods"
|
||||||
|
properties={[
|
||||||
|
{
|
||||||
|
name: 'getBrowserState()',
|
||||||
|
type: 'Promise<BrowserState>',
|
||||||
|
description: isZh
|
||||||
|
? '获取结构化的浏览器状态(URL、标题、简化 HTML 等),自动调用 updateTree() 刷新 DOM。这是 Agent 在每步使用的主要方法。'
|
||||||
|
: 'Get structured browser state (URL, title, simplified HTML, etc.), automatically calls updateTree() to refresh DOM. This is the primary method the agent uses each step.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'updateTree()',
|
||||||
|
type: 'Promise<string>',
|
||||||
|
description: isZh
|
||||||
|
? '刷新 DOM 树并返回简化 HTML。通常不需要手动调用 —— getBrowserState() 会自动调用。'
|
||||||
|
: 'Refresh DOM tree and return simplified HTML. Usually not needed manually — getBrowserState() calls it automatically.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'getCurrentUrl()',
|
||||||
|
type: 'Promise<string>',
|
||||||
|
description: isZh ? '获取当前页面 URL。' : 'Get current page URL.',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<h3 className="text-lg font-semibold mt-6 mb-3">{isZh ? '元素操作' : 'Element Actions'}</h3>
|
||||||
|
<APIReference
|
||||||
|
variant="methods"
|
||||||
|
properties={[
|
||||||
|
{
|
||||||
|
name: 'clickElement(index)',
|
||||||
|
type: 'Promise<ActionResult>',
|
||||||
|
description: isZh
|
||||||
|
? '按索引点击元素。索引来自简化 HTML 中的 [N] 标记。'
|
||||||
|
: 'Click element by index. Index comes from [N] markers in simplified HTML.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'inputText(index, text)',
|
||||||
|
type: 'Promise<ActionResult>',
|
||||||
|
description: isZh ? '向输入框元素填入文本。' : 'Input text into a form element.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'selectOption(index, optionText)',
|
||||||
|
type: 'Promise<ActionResult>',
|
||||||
|
description: isZh ? '在下拉框中选择选项。' : 'Select option in a dropdown element.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'scroll(options)',
|
||||||
|
type: 'Promise<ActionResult>',
|
||||||
|
description: isZh
|
||||||
|
? '垂直滚动页面或指定元素。'
|
||||||
|
: 'Scroll page or specific element vertically.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'scrollHorizontally(options)',
|
||||||
|
type: 'Promise<ActionResult>',
|
||||||
|
description: isZh
|
||||||
|
? '水平滚动页面或指定元素。'
|
||||||
|
: 'Scroll page or specific element horizontally.',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<h3 className="text-lg font-semibold mt-6 mb-3">{isZh ? '遮罩控制' : 'Mask Control'}</h3>
|
||||||
|
<APIReference
|
||||||
|
variant="methods"
|
||||||
|
properties={[
|
||||||
|
{
|
||||||
|
name: 'showMask()',
|
||||||
|
type: 'Promise<void>',
|
||||||
|
description: isZh
|
||||||
|
? '显示视觉遮罩。需要 enableMask: true。'
|
||||||
|
: 'Show visual mask overlay. Requires enableMask: true.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'hideMask()',
|
||||||
|
type: 'Promise<void>',
|
||||||
|
description: isZh ? '隐藏视觉遮罩。' : 'Hide visual mask overlay.',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<h3 className="text-lg font-semibold mt-6 mb-3">{isZh ? '生命周期' : 'Lifecycle'}</h3>
|
||||||
|
<APIReference
|
||||||
|
variant="methods"
|
||||||
|
properties={[
|
||||||
|
{
|
||||||
|
name: 'dispose()',
|
||||||
|
type: 'void',
|
||||||
|
description: isZh
|
||||||
|
? '清理所有资源(DOM 高亮、遮罩等)。Agent 销毁时自动调用。'
|
||||||
|
: 'Clean up all resources (DOM highlights, mask, etc.). Called automatically when agent disposes.',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<APIDivider title={isZh ? '类型定义' : 'Type Definitions'} />
|
||||||
|
|
||||||
|
{/* BrowserState */}
|
||||||
|
<section className="mb-10">
|
||||||
|
<Heading id="browser-state">BrowserState</Heading>
|
||||||
|
<p className="text-gray-600 dark:text-gray-400 mb-4">
|
||||||
|
{isZh
|
||||||
|
? 'getBrowserState() 返回的结构化浏览器状态,直接用于构建 LLM prompt。'
|
||||||
|
: 'Structured browser state returned by getBrowserState(), used directly to build LLM prompts.'}
|
||||||
|
</p>
|
||||||
|
<CodeEditor
|
||||||
|
language="typescript"
|
||||||
|
code={`interface BrowserState {
|
||||||
|
url: string
|
||||||
|
title: string
|
||||||
|
header: string // page info + scroll position
|
||||||
|
content: string // simplified HTML of interactive elements
|
||||||
|
footer: string // scroll hint
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* ActionResult */}
|
||||||
|
<section className="mb-10">
|
||||||
|
<Heading id="action-result">ActionResult</Heading>
|
||||||
|
<CodeEditor
|
||||||
|
language="typescript"
|
||||||
|
code={`interface ActionResult {
|
||||||
|
success: boolean
|
||||||
|
message: string
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Custom PageController */}
|
||||||
|
<section className="mb-10">
|
||||||
|
<Heading id="custom-implementation">
|
||||||
|
{isZh ? '自定义实现' : 'Custom Implementation'}
|
||||||
|
</Heading>
|
||||||
|
<p className="text-gray-600 dark:text-gray-400 mb-4">
|
||||||
|
{isZh
|
||||||
|
? '在非浏览器环境(如 Puppeteer、Playwright),你可以实现自定义 PageController。需要实现 Agent 使用的核心方法:'
|
||||||
|
: 'In non-browser environments (e.g. Puppeteer, Playwright), you can implement a custom PageController. Implement the core methods used by the agent:'}
|
||||||
|
</p>
|
||||||
|
<CodeEditor
|
||||||
|
language="typescript"
|
||||||
|
code={`import { PageAgentCore } from '@page-agent/core'
|
||||||
|
import type { PageController } from '@page-agent/page-controller'
|
||||||
|
|
||||||
|
class PuppeteerPageController implements PageController {
|
||||||
|
async getBrowserState() { /* ... */ }
|
||||||
|
async clickElement(index: number) { /* ... */ }
|
||||||
|
async inputText(index: number, text: string) { /* ... */ }
|
||||||
|
async scroll(options: { down: boolean; numPages: number }) { /* ... */ }
|
||||||
|
// ... other methods
|
||||||
|
}
|
||||||
|
|
||||||
|
const agent = new PageAgentCore({
|
||||||
|
pageController: new PuppeteerPageController(),
|
||||||
|
baseURL: 'https://api.openai.com/v1',
|
||||||
|
apiKey: 'your-api-key',
|
||||||
|
model: 'gpt-5.2',
|
||||||
|
})`}
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import BetaNotice from '@/components/BetaNotice'
|
|
||||||
import CodeEditor from '@/components/CodeEditor'
|
import CodeEditor from '@/components/CodeEditor'
|
||||||
import { Heading } from '@/components/Heading'
|
import { Heading } from '@/components/Heading'
|
||||||
import { useLanguage } from '@/i18n/context'
|
import { useLanguage } from '@/i18n/context'
|
||||||
@@ -12,115 +11,100 @@ export default function CustomTools() {
|
|||||||
|
|
||||||
<p className="text-xl text-gray-600 dark:text-gray-300 mb-8 leading-relaxed">
|
<p className="text-xl text-gray-600 dark:text-gray-300 mb-8 leading-relaxed">
|
||||||
{isZh
|
{isZh
|
||||||
? '通过注册自定义工具,扩展 AI Agent 的能力边界。使用 Zod 定义严格的输入接口,让 AI 安全调用你的业务逻辑。'
|
? '通过注册自定义工具,扩展 AI Agent 的能力边界。使用 Zod 定义输入接口,让 AI 安全调用你的业务逻辑。'
|
||||||
: 'Extend AI Agent capabilities by registering custom tools. Use Zod to define strict input schemas for safe business logic calls.'}
|
: 'Extend AI Agent capabilities by registering custom tools. Define input schemas with Zod for safe business logic invocation.'}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="space-y-8">
|
<div className="space-y-8">
|
||||||
<section>
|
<section>
|
||||||
<Heading id="tool-registration" className="text-2xl font-bold mb-4">
|
<Heading id="zod-version" className="text-2xl font-bold mb-4">
|
||||||
{isZh ? '工具注册' : 'Tool Registration'}
|
{isZh ? 'Zod 版本' : 'Zod Version'}
|
||||||
</Heading>
|
</Heading>
|
||||||
<p className="text-gray-600 dark:text-gray-300 mb-4">
|
<p className="text-gray-600 dark:text-gray-300 mb-4">
|
||||||
{isZh
|
{isZh
|
||||||
? '每个自定义工具需要定义四个核心属性:name、description、input schema 和 execute 函数。'
|
? 'Page Agent 使用 Zod 定义工具的输入 schema。支持 Zod 3 (>=3.25.0) 和 Zod 4,请从 zod/v4 子路径导入。不支持 Zod Mini。'
|
||||||
: 'Each custom tool requires four core properties: name, description, input schema, and execute function.'}
|
: 'Page Agent uses Zod for tool input schemas. Both Zod 3 (>=3.25.0) and Zod 4 are supported. Always import from the zod/v4 subpath. Zod Mini is not supported.'}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
code={`import * as zod from 'zod'
|
code={`// Zod 3 (>=3.25.0) or Zod 4
|
||||||
import { PageAgent, tool } from 'page-agent'
|
import { z } from 'zod/v4'`}
|
||||||
|
|
||||||
// override internal tool
|
|
||||||
const customTools = {
|
|
||||||
ask_user: tool({
|
|
||||||
description:
|
|
||||||
'Ask the user or parent model a question and wait for their answer. Use this if you need more information or clarification.',
|
|
||||||
inputSchema: zod.object({
|
|
||||||
question: zod.string(),
|
|
||||||
}),
|
|
||||||
execute: async function (this: PageAgent, input) {
|
|
||||||
const answer = await do_some_thing(input.question)
|
|
||||||
return "✅ Received user answer: " + answer
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove internal tool
|
|
||||||
const customTools = {
|
|
||||||
scroll: null, // never scroll
|
|
||||||
}
|
|
||||||
|
|
||||||
const pageAgent = new PageAgent({customTools})
|
|
||||||
`}
|
|
||||||
language="javascript"
|
language="javascript"
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<Heading id="page-filter" className="text-2xl font-bold mb-4">
|
<Heading id="define-tools" className="text-2xl font-bold mb-4">
|
||||||
{isZh ? '页面过滤器' : 'Page Filter'}
|
{isZh ? '定义工具' : 'Define Tools'}
|
||||||
</Heading>
|
</Heading>
|
||||||
|
|
||||||
<BetaNotice />
|
|
||||||
|
|
||||||
<p className="text-gray-600 dark:text-gray-300 mb-4">
|
<p className="text-gray-600 dark:text-gray-300 mb-4">
|
||||||
{isZh
|
{isZh
|
||||||
? '通过 pageFilter 属性控制工具在哪些页面可见,提升安全性和用户体验。'
|
? '使用 tool() 辅助函数定义自定义工具,每个工具包含 description、inputSchema 和 execute 三个属性。'
|
||||||
: 'Control tool visibility on specific pages via the pageFilter property to enhance security and UX.'}
|
: 'Use the tool() helper to define custom tools with description, inputSchema, and execute.'}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
code={`pageAgent.registerTool({
|
code={`import { z } from 'zod/v4'
|
||||||
name: 'approveOrder',
|
import { PageAgent, tool } from 'page-agent'
|
||||||
description: '审批订单',
|
|
||||||
input: z.object({
|
const pageAgent = new PageAgent({
|
||||||
orderId: z.string(),
|
customTools: {
|
||||||
approved: z.boolean()
|
|
||||||
}),
|
//
|
||||||
execute: async (params) => {
|
add_to_cart: tool({
|
||||||
// 审批逻辑
|
description: 'Add a product to the shopping cart by its product ID.',
|
||||||
|
inputSchema: z.object({
|
||||||
|
productId: z.string(),
|
||||||
|
quantity: z.number().min(1).default(1),
|
||||||
|
}),
|
||||||
|
execute: async function (input) {
|
||||||
|
await fetch('/api/cart', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(input),
|
||||||
|
})
|
||||||
|
return \`Added \${input.quantity}x \${input.productId} to cart.\`
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
//
|
||||||
|
search_knowledge_base: tool({
|
||||||
|
description: 'Search the internal knowledge base and return relevant articles.',
|
||||||
|
inputSchema: z.object({
|
||||||
|
query: z.string(),
|
||||||
|
limit: z.number().max(10).default(3),
|
||||||
|
}),
|
||||||
|
execute: async function (input) {
|
||||||
|
const res = await fetch(
|
||||||
|
\`/api/kb?q=\${encodeURIComponent(input.query)}&limit=\${input.limit}\`
|
||||||
|
)
|
||||||
|
const articles = await res.json()
|
||||||
|
return JSON.stringify(articles)
|
||||||
|
},
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
// 可选:页面过滤器
|
|
||||||
pageFilter: {
|
|
||||||
// 只在订单管理页面显示
|
|
||||||
include: ['/admin/orders', '/admin/orders/*'],
|
|
||||||
// 排除特定页面
|
|
||||||
exclude: ['/admin/orders/archived']
|
|
||||||
}
|
|
||||||
})`}
|
})`}
|
||||||
language="javascript"
|
language="javascript"
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<Heading id="best-practices" className="text-2xl font-bold mb-4">
|
<Heading id="override-remove" className="text-2xl font-bold mb-4">
|
||||||
{isZh ? '最佳实践' : 'Best Practices'}
|
{isZh ? '覆盖与移除内置工具' : 'Override & Remove Built-in Tools'}
|
||||||
</Heading>
|
</Heading>
|
||||||
<div className="space-y-4">
|
<p className="text-gray-600 dark:text-gray-300 mb-4">
|
||||||
<div className="p-4 bg-yellow-50 dark:bg-yellow-900/20 rounded-lg">
|
{isZh
|
||||||
<h3 className="text-lg font-semibold text-yellow-900 dark:text-yellow-300 mb-2">
|
? '使用相同的名称可以覆盖内置工具的行为,设置为 null 则完全移除该工具。'
|
||||||
{isZh ? '⚡ 性能优化' : '⚡ Performance Optimization'}
|
: 'Use the same name to override a built-in tool, or set it to null to remove it entirely.'}
|
||||||
</h3>
|
</p>
|
||||||
<ul className="text-gray-600 dark:text-gray-300 space-y-1 text-sm">
|
|
||||||
<li>
|
<CodeEditor
|
||||||
{isZh
|
code={`const pageAgent = new PageAgent({
|
||||||
? '• 使用 pageFilter 减少不必要的工具加载'
|
customTools: {
|
||||||
: '• Use pageFilter to reduce unnecessary tool loading'}
|
scroll: null, // remove scroll tool
|
||||||
</li>
|
execute_javascript: null, // remove script execution
|
||||||
<li>
|
},
|
||||||
{isZh
|
})`}
|
||||||
? '• 在 execute 函数中实现适当的缓存机制'
|
language="javascript"
|
||||||
: '• Implement appropriate caching in execute functions'}
|
/>
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
{isZh
|
|
||||||
? '• 避免在工具中执行耗时的同步操作'
|
|
||||||
: '• Avoid long-running sync operations in tools'}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,11 +10,11 @@ const BASELINE = new Set([
|
|||||||
'gemini-3-flash',
|
'gemini-3-flash',
|
||||||
'deepseek-3.2',
|
'deepseek-3.2',
|
||||||
'qwen3.5-plus',
|
'qwen3.5-plus',
|
||||||
|
'qwen3.5-flash',
|
||||||
])
|
])
|
||||||
|
|
||||||
// Models grouped by brand, newest first
|
// Models grouped by brand, newest first
|
||||||
const MODEL_GROUPS: Record<string, string[]> = {
|
const MODEL_GROUPS: Record<string, string[]> = {
|
||||||
OpenAI: ['gpt-5.2', 'gpt-5.1', 'gpt-5', 'gpt-5-mini', 'gpt-4.1', 'gpt-4.1-mini'],
|
|
||||||
Qwen: [
|
Qwen: [
|
||||||
'qwen3.5-plus',
|
'qwen3.5-plus',
|
||||||
'qwen3.5-flash',
|
'qwen3.5-flash',
|
||||||
@@ -23,6 +23,7 @@ const MODEL_GROUPS: Record<string, string[]> = {
|
|||||||
'qwen-3-plus',
|
'qwen-3-plus',
|
||||||
'qwen3:14b (ollama)',
|
'qwen3:14b (ollama)',
|
||||||
],
|
],
|
||||||
|
OpenAI: ['gpt-5.4', 'gpt-5.2', 'gpt-5.1', 'gpt-5', 'gpt-5-mini', 'gpt-4.1', 'gpt-4.1-mini'],
|
||||||
DeepSeek: ['deepseek-3.2'],
|
DeepSeek: ['deepseek-3.2'],
|
||||||
Google: ['gemini-3-pro', 'gemini-3-flash', 'gemini-2.5'],
|
Google: ['gemini-3-pro', 'gemini-3-flash', 'gemini-2.5'],
|
||||||
Anthropic: [
|
Anthropic: [
|
||||||
@@ -67,11 +68,6 @@ export default function Models() {
|
|||||||
<Heading id="tested-models" className="text-2xl font-semibold mb-3">
|
<Heading id="tested-models" className="text-2xl font-semibold mb-3">
|
||||||
{isZh ? '已测试模型' : 'Tested Models'}
|
{isZh ? '已测试模型' : 'Tested Models'}
|
||||||
</Heading>
|
</Heading>
|
||||||
<p className="text-sm text-gray-600 dark:text-gray-400 mb-4">
|
|
||||||
{isZh
|
|
||||||
? '推荐使用 ToolCall 能力强的轻量级模型。'
|
|
||||||
: 'Recommended: Fast, lightweight models with strong ToolCall capabilities.'}
|
|
||||||
</p>
|
|
||||||
<div className="bg-linear-to-br from-emerald-50 to-cyan-50 dark:from-emerald-950/30 dark:to-cyan-950/30 rounded-xl p-6 border border-emerald-200/50 dark:border-emerald-800/50">
|
<div className="bg-linear-to-br from-emerald-50 to-cyan-50 dark:from-emerald-950/30 dark:to-cyan-950/30 rounded-xl p-6 border border-emerald-200/50 dark:border-emerald-800/50">
|
||||||
<div className="grid grid-cols-[5rem_1fr] gap-x-3 gap-y-3 items-start">
|
<div className="grid grid-cols-[5rem_1fr] gap-x-3 gap-y-3 items-start">
|
||||||
{Object.entries(MODEL_GROUPS).map(([brand, models]) => (
|
{Object.entries(MODEL_GROUPS).map(([brand, models]) => (
|
||||||
@@ -95,6 +91,11 @@ export default function Models() {
|
|||||||
<h2 className="text-2xl font-semibold mb-4">{isZh ? '提示' : 'Tips'}</h2>
|
<h2 className="text-2xl font-semibold mb-4">{isZh ? '提示' : 'Tips'}</h2>
|
||||||
<div className="p-4 bg-blue-50 dark:bg-blue-950/20 rounded-lg border border-blue-200 dark:border-blue-800">
|
<div className="p-4 bg-blue-50 dark:bg-blue-950/20 rounded-lg border border-blue-200 dark:border-blue-800">
|
||||||
<ul className="text-sm text-gray-700 dark:text-gray-300 space-y-2 list-disc pl-5">
|
<ul className="text-sm text-gray-700 dark:text-gray-300 space-y-2 list-disc pl-5">
|
||||||
|
<li>
|
||||||
|
{isZh
|
||||||
|
? '⭐ 推荐使用 ToolCall 能力强的轻量级模型'
|
||||||
|
: '⭐ Recommended: Fast, lightweight models with strong ToolCall capabilities'}
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
{isZh
|
{isZh
|
||||||
? 'ToolCall 能力较弱的模型可能返回错误的格式,常见错误能够自动恢复,建议设置较高的 temperature'
|
? 'ToolCall 能力较弱的模型可能返回错误的格式,常见错误能够自动恢复,建议设置较高的 temperature'
|
||||||
@@ -109,39 +110,6 @@ export default function Models() {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Security Section */}
|
|
||||||
<section className="mb-10">
|
|
||||||
<h2 className="text-2xl font-semibold mb-4">
|
|
||||||
{isZh ? '🔐 生产环境鉴权建议' : '🔐 Production Authentication'}
|
|
||||||
</h2>
|
|
||||||
<div className="bg-yellow-50 dark:bg-yellow-950/20 border-l-4 border-yellow-500 p-5 rounded-r-lg mb-4">
|
|
||||||
<p className="text-sm font-semibold text-yellow-900 dark:text-yellow-200">
|
|
||||||
{isZh
|
|
||||||
? '⚠️ 永远不要把真实的 LLM API Key 发布到前端代码'
|
|
||||||
: '⚠️ Never commit real LLM API Keys to your frontend code'}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="bg-gray-50 dark:bg-gray-900/30 rounded-lg p-5 border border-gray-200 dark:border-gray-800">
|
|
||||||
<h3 className="font-semibold text-gray-900 dark:text-gray-100 mb-2">
|
|
||||||
{isZh ? '后端代理转发' : 'Backend Proxy Pattern'}
|
|
||||||
</h3>
|
|
||||||
<p className="text-sm text-gray-600 dark:text-gray-400 mb-3">
|
|
||||||
{isZh
|
|
||||||
? '在后端搭建一个 LLM 流量转发接口,该接口使用与你网站上其他接口相同的鉴权方式,例如:'
|
|
||||||
: 'Set up a backend LLM proxy endpoint that uses the same authentication method as other APIs in your website, such as:'}
|
|
||||||
</p>
|
|
||||||
<ul className="text-sm text-gray-600 dark:text-gray-400 space-y-1">
|
|
||||||
<li>{isZh ? '• Session/Cookie 会话认证' : '• Session/Cookie-based authentication'}</li>
|
|
||||||
<li>
|
|
||||||
{isZh ? '• OIDC (OpenID Connect) 单点登录' : '• OIDC (OpenID Connect) single sign-on'}
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
{isZh ? '• 临时 Access Key 或 JWT Token' : '• Temporary Access Key or JWT Token'}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* Configuration Section */}
|
{/* Configuration Section */}
|
||||||
<section className="mb-10">
|
<section className="mb-10">
|
||||||
<Heading id="configuration">{isZh ? '配置方式' : 'Configuration'}</Heading>
|
<Heading id="configuration">{isZh ? '配置方式' : 'Configuration'}</Heading>
|
||||||
@@ -169,9 +137,24 @@ const pageAgent = new PageAgent({
|
|||||||
<Heading id="free-testing-api">{isZh ? '免费测试接口' : 'Free Testing API'}</Heading>
|
<Heading id="free-testing-api">{isZh ? '免费测试接口' : 'Free Testing API'}</Heading>
|
||||||
<p className="text-sm text-gray-600 dark:text-gray-400 mb-4">
|
<p className="text-sm text-gray-600 dark:text-gray-400 mb-4">
|
||||||
{isZh
|
{isZh
|
||||||
? '以下免费测试接口仅供 PageAgent.js 和 PageAgent Extension 的技术评估使用。有速率限制,可能随时变更。请勿用于生产环境。'
|
? '以下免费测试接口仅供 PageAgent.js 和 PageAgent Extension 的技术评估和测试使用。'
|
||||||
: 'The following free testing endpoints are provided for technical evaluation of PageAgent.js and PageAgent Extension only. Rate-limited, subject to change. Not for production use.'}
|
: 'The following free testing endpoint is provided for testing and technical evaluation.'}
|
||||||
</p>
|
</p>
|
||||||
|
<div className="my-4 p-4 bg-amber-50 dark:bg-amber-950/20 rounded-lg border border-amber-200 dark:border-amber-800">
|
||||||
|
<p className="text-xs text-gray-600 dark:text-gray-400">
|
||||||
|
{isZh
|
||||||
|
? '⚠️ 仅供技术评估和研发用途,禁止用于生产环境。数据通过中国大陆服务器处理。请勿输入任何个人身份信息或敏感数据。使用即表示您同意'
|
||||||
|
: '⚠️ Strictly for technical evaluation and R&D only. Data is processed via servers in Mainland China. Do not input any PII or sensitive data. By using this API you agree to the'}{' '}
|
||||||
|
<a
|
||||||
|
href="https://github.com/alibaba/page-agent/blob/main/docs/terms-and-privacy.md#2-testing-api-and-demo-disclaimer--terms-of-use"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="text-blue-500 hover:underline"
|
||||||
|
>
|
||||||
|
{isZh ? '使用条款' : 'Terms of Use'}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
<div className="bg-gray-50 dark:bg-gray-900/30 rounded-lg p-5 border border-gray-200 dark:border-gray-800">
|
<div className="bg-gray-50 dark:bg-gray-900/30 rounded-lg p-5 border border-gray-200 dark:border-gray-800">
|
||||||
<h3 className="font-semibold text-gray-900 dark:text-gray-100 mb-2">
|
<h3 className="font-semibold text-gray-900 dark:text-gray-100 mb-2">
|
||||||
Qwen (Alibaba Cloud China)
|
Qwen (Alibaba Cloud China)
|
||||||
@@ -197,21 +180,6 @@ LLM_MODEL_NAME="qwen3.5-plus"
|
|||||||
LLM_API_KEY="NA"`}
|
LLM_API_KEY="NA"`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-4 p-4 bg-amber-50 dark:bg-amber-950/20 rounded-lg border border-amber-200 dark:border-amber-800">
|
|
||||||
<p className="text-xs text-gray-600 dark:text-gray-400">
|
|
||||||
{isZh
|
|
||||||
? '⚠️ 仅供技术评估和研发用途,禁止用于生产环境。数据通过中国大陆服务器处理。请勿输入任何个人身份信息或敏感数据。使用即表示您同意'
|
|
||||||
: '⚠️ Strictly for technical evaluation and R&D only. Data is processed via servers in Mainland China. Do not input any PII or sensitive data. By using this API you agree to the'}{' '}
|
|
||||||
<a
|
|
||||||
href="https://github.com/alibaba/page-agent/blob/main/docs/terms-and-privacy.md#2-testing-api-and-demo-disclaimer--terms-of-use"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="text-blue-500 hover:underline"
|
|
||||||
>
|
|
||||||
{isZh ? '使用条款' : 'Terms of Use'}
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Ollama Section */}
|
{/* Ollama Section */}
|
||||||
@@ -247,16 +215,76 @@ LLM_MODEL_NAME="qwen3:14b"`}
|
|||||||
{isZh
|
{isZh
|
||||||
? '确保上下文长度大于输入 token 数,否则 Ollama 会静默截断 prompt。普通页面约需 15k token,随步骤增加。默认 4k 上下文长度无法正常工作'
|
? '确保上下文长度大于输入 token 数,否则 Ollama 会静默截断 prompt。普通页面约需 15k token,随步骤增加。默认 4k 上下文长度无法正常工作'
|
||||||
: 'Ensure context length exceeds input tokens, or Ollama will silently truncate prompts. ~15k tokens for a typical page, increases with steps. Default 4k context length will NOT work'}
|
: 'Ensure context length exceeds input tokens, or Ollama will silently truncate prompts. ~15k tokens for a typical page, increases with steps. Default 4k context length will NOT work'}
|
||||||
<ul className="text-sm text-gray-700 dark:text-gray-300 space-y-2 list-disc pl-5">
|
|
||||||
<li>
|
|
||||||
<code className="text-xs bg-gray-200 dark:bg-gray-700 px-1 rounded">
|
|
||||||
$env:OLLAMA_CONTEXT_LENGTH=64000; ollama serve
|
|
||||||
</code>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-4">
|
||||||
|
<h3 className="font-semibold text-gray-900 dark:text-gray-100 mb-3">
|
||||||
|
{isZh ? '建议启动参数' : 'Recommended Startup'}
|
||||||
|
</h3>
|
||||||
|
<p className="text-sm text-gray-600 dark:text-gray-400 mb-3">
|
||||||
|
{isZh
|
||||||
|
? '启动 Ollama 时建议配置以下环境变量:扩大上下文窗口、允许跨域访问、监听所有网络接口。'
|
||||||
|
: 'Start Ollama with these environment variables: larger context window, allow cross-origin access, and listen on all interfaces.'}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<p className="text-xs font-medium text-gray-500 dark:text-gray-400">macOS / Linux</p>
|
||||||
|
<CodeEditor
|
||||||
|
code={`OLLAMA_CONTEXT_LENGTH=64000 OLLAMA_HOST=0.0.0.0:11434 OLLAMA_ORIGINS="*" ollama serve`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<p className="text-xs font-medium text-gray-500 dark:text-gray-400 pt-2">
|
||||||
|
Windows (PowerShell)
|
||||||
|
</p>
|
||||||
|
<CodeEditor
|
||||||
|
code={`$env:OLLAMA_CONTEXT_LENGTH=64000; $env:OLLAMA_HOST="0.0.0.0:11434"; $env:OLLAMA_ORIGINS="*"; ollama serve`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Production Authentication */}
|
||||||
|
<section className="mb-10">
|
||||||
|
<Heading id="production-authentication" className="text-2xl font-semibold mb-4">
|
||||||
|
{isZh ? '🔐 生产环境鉴权' : '🔐 Production Authentication'}
|
||||||
|
</Heading>
|
||||||
|
<p className="text-sm text-gray-600 dark:text-gray-400 mb-3">
|
||||||
|
{isZh
|
||||||
|
? '如果你只是将它用作个人助手,可以直接连接你的 LLM 服务。'
|
||||||
|
: 'If you only use it as a personal assistant, you can connect to your LLM service directly.'}
|
||||||
|
</p>
|
||||||
|
<p className="text-sm text-gray-600 dark:text-gray-400 mb-3">
|
||||||
|
{isZh ? (
|
||||||
|
<>
|
||||||
|
如果你计划将它集成到你的 Web 应用中,建议搭建一个后端代理来转发 LLM 请求,并使用{' '}
|
||||||
|
<code>customFetch</code> 携带 Cookie 或其他鉴权信息:
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
If you plan to integrate it into your web app, it's better to have a backend proxy for
|
||||||
|
the LLM and use <code>customFetch</code> to authenticate the request with cookies or
|
||||||
|
other methods:
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<CodeEditor
|
||||||
|
code={`const agent = new PageAgent({
|
||||||
|
baseURL: '/api/llm-proxy',
|
||||||
|
apiKey: 'NA',
|
||||||
|
model: 'gpt-5.1',
|
||||||
|
customFetch: (url, init) =>
|
||||||
|
fetch(url, { ...init, credentials: 'include' }),
|
||||||
|
});`}
|
||||||
|
/>
|
||||||
|
<div className="mt-4 bg-yellow-50 dark:bg-yellow-950/20 border-l-4 border-yellow-500 p-4 rounded-r-lg">
|
||||||
|
<p className="text-sm font-semibold text-yellow-900 dark:text-yellow-200">
|
||||||
|
{isZh
|
||||||
|
? '⚠️ 永远不要把真实的 LLM API Key 提交到前端代码中'
|
||||||
|
: '⚠️ NEVER commit real LLM API keys to your frontend code'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
-21
@@ -105,27 +105,6 @@ const pageAgentTool = {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg p-4 mb-6">
|
|
||||||
<h3 className="text-lg font-semibold text-yellow-900 dark:text-yellow-100 mb-2">
|
|
||||||
{isZh ? '⚠️ 注意事项' : '⚠️ Notes'}
|
|
||||||
</h3>
|
|
||||||
<ul className="text-yellow-800 dark:text-yellow-200 space-y-1 text-sm">
|
|
||||||
<li>
|
|
||||||
•{' '}
|
|
||||||
{isZh
|
|
||||||
? '确保目标网站允许自动化操作'
|
|
||||||
: 'Ensure target website allows automated operations'}
|
|
||||||
</li>
|
|
||||||
<li>• {isZh ? '实现适当的频率限制' : 'Implement appropriate rate limiting'}</li>
|
|
||||||
<li>
|
|
||||||
•{' '}
|
|
||||||
{isZh
|
|
||||||
? '敏感操作建议要求人工确认'
|
|
||||||
: 'Recommend human confirmation for sensitive operations'}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -6,15 +6,15 @@ import CustomUIDocs from './advanced/custom-ui/page'
|
|||||||
import PageAgentCoreDocs from './advanced/page-agent-core/page'
|
import PageAgentCoreDocs from './advanced/page-agent-core/page'
|
||||||
// Advanced
|
// Advanced
|
||||||
import PageAgentDocs from './advanced/page-agent/page'
|
import PageAgentDocs from './advanced/page-agent/page'
|
||||||
|
import PageControllerDocs from './advanced/page-controller/page'
|
||||||
|
import SecurityPermissions from './advanced/security-permissions/page'
|
||||||
// Features
|
// Features
|
||||||
import ChromeExtension from './features/chrome-extension/page'
|
import ChromeExtension from './features/chrome-extension/page'
|
||||||
import Instructions from './features/custom-instructions/page'
|
import Instructions from './features/custom-instructions/page'
|
||||||
import CustomTools from './features/custom-tools/page'
|
import CustomTools from './features/custom-tools/page'
|
||||||
import DataMasking from './features/data-masking/page'
|
import DataMasking from './features/data-masking/page'
|
||||||
import Models from './features/models/page'
|
import Models from './features/models/page'
|
||||||
import BestPractices from './integration/best-practices/page'
|
import ThirdPartyAgent from './features/third-party-agent/page'
|
||||||
import SecurityPermissions from './integration/security-permissions/page'
|
|
||||||
import ThirdPartyAgent from './integration/third-party-agent/page'
|
|
||||||
import Limitations from './introduction/limitations/page'
|
import Limitations from './introduction/limitations/page'
|
||||||
// Introduction
|
// Introduction
|
||||||
import Overview from './introduction/overview/page'
|
import Overview from './introduction/overview/page'
|
||||||
@@ -80,6 +80,11 @@ export default function DocsRouter() {
|
|||||||
<ChromeExtension />
|
<ChromeExtension />
|
||||||
</DocsPage>
|
</DocsPage>
|
||||||
</Route>
|
</Route>
|
||||||
|
<Route path="/features/third-party-agent">
|
||||||
|
<DocsPage>
|
||||||
|
<ThirdPartyAgent />
|
||||||
|
</DocsPage>
|
||||||
|
</Route>
|
||||||
|
|
||||||
{/* Advanced */}
|
{/* Advanced */}
|
||||||
<Route path="/advanced/page-agent">
|
<Route path="/advanced/page-agent">
|
||||||
@@ -92,26 +97,21 @@ export default function DocsRouter() {
|
|||||||
<PageAgentCoreDocs />
|
<PageAgentCoreDocs />
|
||||||
</DocsPage>
|
</DocsPage>
|
||||||
</Route>
|
</Route>
|
||||||
|
<Route path="/advanced/page-controller">
|
||||||
|
<DocsPage>
|
||||||
|
<PageControllerDocs />
|
||||||
|
</DocsPage>
|
||||||
|
</Route>
|
||||||
<Route path="/advanced/custom-ui">
|
<Route path="/advanced/custom-ui">
|
||||||
<DocsPage>
|
<DocsPage>
|
||||||
<CustomUIDocs />
|
<CustomUIDocs />
|
||||||
</DocsPage>
|
</DocsPage>
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/integration/security-permissions">
|
<Route path="/advanced/security-permissions">
|
||||||
<DocsPage>
|
<DocsPage>
|
||||||
<SecurityPermissions />
|
<SecurityPermissions />
|
||||||
</DocsPage>
|
</DocsPage>
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/integration/best-practices">
|
|
||||||
<DocsPage>
|
|
||||||
<BestPractices />
|
|
||||||
</DocsPage>
|
|
||||||
</Route>
|
|
||||||
<Route path="/integration/third-party-agent">
|
|
||||||
<DocsPage>
|
|
||||||
<ThirdPartyAgent />
|
|
||||||
</DocsPage>
|
|
||||||
</Route>
|
|
||||||
|
|
||||||
{/* Default redirect or 404 */}
|
{/* Default redirect or 404 */}
|
||||||
<Route path="/docs">
|
<Route path="/docs">
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
import BetaNotice from '@/components/BetaNotice'
|
|
||||||
import CodeEditor from '@/components/CodeEditor'
|
|
||||||
import { useLanguage } from '@/i18n/context'
|
|
||||||
|
|
||||||
export default function BestPractices() {
|
|
||||||
const { isZh } = useLanguage()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h1 className="text-4xl font-bold mb-6">{isZh ? '最佳实践' : 'Best Practices'}</h1>
|
|
||||||
<BetaNotice />
|
|
||||||
<p className="text-xl text-gray-600 dark:text-gray-300 mb-6 leading-relaxed">
|
|
||||||
{isZh
|
|
||||||
? '使用 page-agent 的最佳实践和常见问题解决方案。'
|
|
||||||
: 'Best practices and common solutions for using page-agent.'}
|
|
||||||
</p>
|
|
||||||
<CodeEditor code={`// TODO`} />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -83,7 +83,7 @@ export default function LimitationsPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Interaction Limitations */}
|
{/* Interaction Limitations */}
|
||||||
<Heading id="interaction-capabilities" className="text-2xl font-bold mb-3">
|
<Heading id="interaction-capabilities" className="text-2xl font-bold mb-3 mt-6">
|
||||||
{isZh ? '交互能力' : 'Interaction Capabilities'}
|
{isZh ? '交互能力' : 'Interaction Capabilities'}
|
||||||
</Heading>
|
</Heading>
|
||||||
<div className="bg-gray-50 dark:bg-gray-800 rounded-lg p-6 mb-6">
|
<div className="bg-gray-50 dark:bg-gray-800 rounded-lg p-6 mb-6">
|
||||||
@@ -128,33 +128,25 @@ export default function LimitationsPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Understanding Limitations */}
|
{/* Understanding Limitations */}
|
||||||
<Heading id="understanding" className="text-2xl font-bold mb-3">
|
<Heading id="understanding" className="text-2xl font-bold mb-3 mt-6">
|
||||||
{isZh ? '理解能力' : 'Understanding'}
|
{isZh ? '理解能力' : 'Understanding'}
|
||||||
</Heading>
|
</Heading>
|
||||||
<div className="bg-amber-50 dark:bg-amber-900/20 border-l-4 border-amber-400 p-4 mb-6">
|
|
||||||
<p className="text-amber-800 dark:text-amber-200 mb-2 font-medium">
|
|
||||||
{isZh
|
|
||||||
? 'Page Agent 不使用多模态模型,不截图,没有视觉能力。仅通过 DOM 结构理解页面。'
|
|
||||||
: 'Page Agent does not use multimodal models, does not take screenshots, and has no visual capability. It reads pages through DOM structure only.'}
|
|
||||||
</p>
|
|
||||||
<p className="text-amber-700 dark:text-amber-300 text-sm">
|
|
||||||
{isZh
|
|
||||||
? '图片、Canvas、WebGL、SVG 等视觉内容无法被识别。页面的语义化程度和可访问性直接影响 AI 的理解准确性。'
|
|
||||||
: 'Images, Canvas, WebGL, SVG and other visual content cannot be recognized. Page semantic quality and accessibility directly affect AI accuracy.'}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Website Quality */}
|
<p className="mb-2 font-medium">
|
||||||
<Heading id="page-quality-matters" className="text-2xl font-bold mb-3">
|
{isZh
|
||||||
{isZh ? '网页质量影响' : 'Page Quality Matters'}
|
? 'Page Agent 不使用多模态模型,不截图,没有视觉能力。仅通过 DOM 结构理解页面。'
|
||||||
</Heading>
|
: 'Page Agent does not use multimodal models, does not take screenshots, and has no visual capability. It reads pages through DOM structure only.'}
|
||||||
<div className="bg-gray-50 dark:bg-gray-800 rounded-lg p-6">
|
</p>
|
||||||
<p className="text-gray-600 dark:text-gray-300 text-sm">
|
<p className="mb-2 font-medium">
|
||||||
{isZh
|
{isZh
|
||||||
? '反常识的交互逻辑、纯视觉的操作提示、快速出现消失的元素等都会降低自动化成功率。语义化的 HTML 和良好的可访问性会显著提升效果。'
|
? '图片、Canvas、WebGL、SVG 等视觉内容无法被识别。页面的语义化程度和可访问性直接影响 AI 的理解准确性。'
|
||||||
: 'Counter-intuitive interactions, visual-only cues, and rapidly appearing/disappearing elements reduce automation success. Semantic HTML and good accessibility significantly improve results.'}
|
: 'Images, Canvas, WebGL, SVG and other visual content cannot be recognized. Page semantic quality and accessibility directly affect AI accuracy.'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
<p className="mb-2 font-medium">
|
||||||
|
{isZh
|
||||||
|
? '反常识的交互逻辑、纯视觉的操作提示、快速出现消失的元素等都会降低自动化成功率。语义化的 HTML 和良好的可访问性会显著提升效果。'
|
||||||
|
: 'Counter-intuitive interactions, visual-only cues, and rapidly appearing/disappearing elements reduce automation success. Semantic HTML and good accessibility significantly improve results.'}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
import { Suspense, lazy } from 'react'
|
import { Suspense, lazy } from 'react'
|
||||||
|
|
||||||
import HeroSection from './home/HeroSection'
|
import { useDocumentTitle } from '@/lib/useDocumentTitle'
|
||||||
|
|
||||||
const FeaturesSection = lazy(() => import('./home/FeaturesSection'))
|
import HeroSection from './HeroSection'
|
||||||
const ScenariosSection = lazy(() => import('./home/ScenariosSection'))
|
|
||||||
const OneMoreThingSection = lazy(() => import('./home/OneMoreThingSection'))
|
const FeaturesSection = lazy(() => import('./FeaturesSection'))
|
||||||
|
const ScenariosSection = lazy(() => import('./ScenariosSection'))
|
||||||
|
const OneMoreThingSection = lazy(() => import('./OneMoreThingSection'))
|
||||||
|
|
||||||
export default function HomePage() {
|
export default function HomePage() {
|
||||||
|
useDocumentTitle()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<HeroSection />
|
<HeroSection />
|
||||||
@@ -3,9 +3,9 @@ import { Route, Switch, useLocation } from 'wouter'
|
|||||||
|
|
||||||
import Footer from './components/Footer'
|
import Footer from './components/Footer'
|
||||||
import Header from './components/Header'
|
import Header from './components/Header'
|
||||||
import HomePage from './pages/Home'
|
import HomePage from './pages/home'
|
||||||
|
|
||||||
const docsImport = () => import('./pages/docs/index')
|
const docsImport = () => import('./pages/docs')
|
||||||
const DocsPages = lazy(docsImport)
|
const DocsPages = lazy(docsImport)
|
||||||
|
|
||||||
function ScrollToTop() {
|
function ScrollToTop() {
|
||||||
@@ -18,8 +18,10 @@ function ScrollToTop() {
|
|||||||
|
|
||||||
export default function Router() {
|
export default function Router() {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const id = requestIdleCallback(() => docsImport())
|
const schedule = globalThis.requestIdleCallback ?? ((cb: () => void) => setTimeout(cb, 1))
|
||||||
return () => cancelIdleCallback(id)
|
const cancel = globalThis.cancelIdleCallback ?? clearTimeout
|
||||||
|
const id = schedule(() => docsImport())
|
||||||
|
return () => cancel(id)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import tailwindcss from '@tailwindcss/vite'
|
import tailwindcss from '@tailwindcss/vite'
|
||||||
import react from '@vitejs/plugin-react-swc'
|
import react from '@vitejs/plugin-react-swc'
|
||||||
import { config as dotenvConfig } from 'dotenv'
|
import { config as dotenvConfig } from 'dotenv'
|
||||||
import { copyFileSync, mkdirSync, readFileSync } from 'node:fs'
|
import { copyFileSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
||||||
import process from 'node:process'
|
import process from 'node:process'
|
||||||
import { dirname, join, resolve } from 'path'
|
import { dirname, join, resolve } from 'path'
|
||||||
import { fileURLToPath } from 'url'
|
import { fileURLToPath } from 'url'
|
||||||
@@ -27,14 +27,16 @@ const SPA_ROUTES = [
|
|||||||
'docs/features/custom-instructions',
|
'docs/features/custom-instructions',
|
||||||
'docs/features/models',
|
'docs/features/models',
|
||||||
'docs/features/chrome-extension',
|
'docs/features/chrome-extension',
|
||||||
|
'docs/features/third-party-agent',
|
||||||
'docs/advanced/page-agent',
|
'docs/advanced/page-agent',
|
||||||
'docs/advanced/page-agent-core',
|
'docs/advanced/page-agent-core',
|
||||||
|
'docs/advanced/page-controller',
|
||||||
'docs/advanced/custom-ui',
|
'docs/advanced/custom-ui',
|
||||||
'docs/integration/security-permissions',
|
'docs/advanced/security-permissions',
|
||||||
'docs/integration/best-practices',
|
|
||||||
'docs/integration/third-party-agent',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const SITE_URL = 'https://alibaba.github.io/page-agent'
|
||||||
|
|
||||||
function spaRoutes() {
|
function spaRoutes() {
|
||||||
return {
|
return {
|
||||||
name: 'spa-routes',
|
name: 'spa-routes',
|
||||||
@@ -47,6 +49,19 @@ function spaRoutes() {
|
|||||||
copyFileSync(src, join(dir, 'index.html'))
|
copyFileSync(src, join(dir, 'index.html'))
|
||||||
}
|
}
|
||||||
console.log(` ✓ Copied index.html to ${SPA_ROUTES.length} SPA routes`)
|
console.log(` ✓ Copied index.html to ${SPA_ROUTES.length} SPA routes`)
|
||||||
|
|
||||||
|
const today = new Date().toISOString().split('T')[0]
|
||||||
|
const urls = ['', ...SPA_ROUTES]
|
||||||
|
.map(
|
||||||
|
(route) =>
|
||||||
|
` <url>\n <loc>${SITE_URL}/${route}</loc>\n <lastmod>${today}</lastmod>\n </url>`
|
||||||
|
)
|
||||||
|
.join('\n')
|
||||||
|
writeFileSync(
|
||||||
|
join(dist, 'sitemap.xml'),
|
||||||
|
`<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n${urls}\n</urlset>\n`
|
||||||
|
)
|
||||||
|
console.log(` ✓ Generated sitemap.xml with ${SPA_ROUTES.length + 1} URLs`)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-14
@@ -4,7 +4,7 @@
|
|||||||
*
|
*
|
||||||
* Usage:
|
* Usage:
|
||||||
* node scripts/sync-version.js # Sync current version from root
|
* node scripts/sync-version.js # Sync current version from root
|
||||||
* node scripts/sync-version.js 0.1.0 # Set and sync new version
|
* node scripts/sync-version.js 0.1.0 # Set root version, then sync all packages
|
||||||
*/
|
*/
|
||||||
import chalk from 'chalk'
|
import chalk from 'chalk'
|
||||||
import { existsSync, readFileSync, readdirSync, writeFileSync } from 'fs'
|
import { existsSync, readFileSync, readdirSync, writeFileSync } from 'fs'
|
||||||
@@ -15,19 +15,18 @@ import { fileURLToPath } from 'url'
|
|||||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||||
const rootDir = join(__dirname, '..')
|
const rootDir = join(__dirname, '..')
|
||||||
|
|
||||||
// Parse arguments
|
|
||||||
const versionArg = process.argv[2]
|
const versionArg = process.argv[2]
|
||||||
|
|
||||||
if (!versionArg) {
|
|
||||||
console.log(chalk.yellow('⚠️ No version specified.\n'))
|
|
||||||
exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read root package.json
|
// Read root package.json
|
||||||
const rootPkgPath = join(rootDir, 'package.json')
|
const rootPkgPath = join(rootDir, 'package.json')
|
||||||
const rootPkg = JSON.parse(readFileSync(rootPkgPath, 'utf-8'))
|
const rootPkg = JSON.parse(readFileSync(rootPkgPath, 'utf-8'))
|
||||||
const oldVersion = rootPkg.version
|
const oldVersion = rootPkg.version
|
||||||
const newVersion = versionArg
|
const newVersion = versionArg ?? rootPkg.version
|
||||||
|
|
||||||
|
if (!newVersion) {
|
||||||
|
console.log(chalk.yellow('⚠️ No version found in root package.json.\n'))
|
||||||
|
exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
console.log(chalk.cyan.bold('\n📦 Syncing version\n'))
|
console.log(chalk.cyan.bold('\n📦 Syncing version\n'))
|
||||||
|
|
||||||
@@ -35,7 +34,10 @@ console.log(chalk.cyan.bold('\n📦 Syncing version\n'))
|
|||||||
if (versionArg) {
|
if (versionArg) {
|
||||||
rootPkg.version = newVersion
|
rootPkg.version = newVersion
|
||||||
writeFileSync(rootPkgPath, JSON.stringify(rootPkg, null, ' ') + '\n')
|
writeFileSync(rootPkgPath, JSON.stringify(rootPkg, null, ' ') + '\n')
|
||||||
console.log(chalk.green('✓') + ` ${chalk.bold('root')} → ${chalk.yellow(newVersion)}`)
|
console.log(
|
||||||
|
chalk.green('✓') +
|
||||||
|
` ${chalk.bold('root')}: ${chalk.dim(oldVersion)} → ${chalk.yellow(newVersion)}`
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
console.log(chalk.dim(' root:') + ` ${chalk.yellow(newVersion)} ${chalk.dim('(source)')}`)
|
console.log(chalk.dim(' root:') + ` ${chalk.yellow(newVersion)} ${chalk.dim('(source)')}`)
|
||||||
}
|
}
|
||||||
@@ -78,11 +80,8 @@ for (const pkg of packages) {
|
|||||||
const pkgJson = JSON.parse(readFileSync(pkgPath, 'utf-8'))
|
const pkgJson = JSON.parse(readFileSync(pkgPath, 'utf-8'))
|
||||||
let pkgChanged = false
|
let pkgChanged = false
|
||||||
|
|
||||||
// Skip extension package version (it has its own versioning)
|
// Update package version
|
||||||
const isExtension = pkg === 'extension'
|
if (pkgJson.version !== newVersion) {
|
||||||
|
|
||||||
// Update package version (skip extension)
|
|
||||||
if (!isExtension && pkgJson.version !== newVersion) {
|
|
||||||
pkgJson.version = newVersion
|
pkgJson.version = newVersion
|
||||||
pkgChanged = true
|
pkgChanged = true
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user