Compare commits
30 Commits
EXT_v0.1.7
..
v1.4.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 0ffd221a61 | |||
| ee208b9779 | |||
| 6ad20c8f45 | |||
| ff4caaec22 | |||
| 91ae86f56e | |||
| c2daf13ead | |||
| ce2258fe16 | |||
| 68d68182e2 | |||
| 5f74f98a97 | |||
| 07b4dc32f7 | |||
| 9d5c8b2302 | |||
| 0c61ead38f | |||
| d92ad8f959 | |||
| a51772e191 | |||
| 89c83f454d | |||
| 7f67463a24 | |||
| 2311c1918a | |||
| b45a2af9bf | |||
| b4377ed152 | |||
| b7be29d792 | |||
| cbe223dac5 | |||
| 22fe448d95 | |||
| 49bdc70c78 | |||
| d6bd6a7170 | |||
| 2b99620de3 | |||
| d913dd7bd0 | |||
| 8cb57befb3 | |||
| b1be05309b | |||
| e5cf60df1b | |||
| 58a3d5fbc6 |
@@ -9,7 +9,7 @@ body:
|
||||
Thanks for your interest in improving the project! Before submitting, please read our guidelines.
|
||||
感谢您对改进项目的兴趣!提交前请阅读我们的指南。
|
||||
|
||||
- [Code of Conduct](https://github.com/alibaba/page-agent/blob/main/CODE_OF_CONDUCT.md)
|
||||
- [Code of Conduct](https://github.com/alibaba/page-agent/blob/main/docs/CODE_OF_CONDUCT.md)
|
||||
- [Contributing Guide](https://github.com/alibaba/page-agent/blob/main/CONTRIBUTING.md)
|
||||
|
||||
- type: textarea
|
||||
|
||||
@@ -10,5 +10,5 @@ contact_links:
|
||||
url: https://github.com/alibaba/page-agent/blob/main/CONTRIBUTING.md
|
||||
about: How to contribute code and ideas. 如何进行贡献与提交代码。
|
||||
- name: Code of Conduct / 行为准则
|
||||
url: https://github.com/alibaba/page-agent/blob/main/CODE_OF_CONDUCT.md
|
||||
url: https://github.com/alibaba/page-agent/blob/main/docs/CODE_OF_CONDUCT.md
|
||||
about: Community expectations and standards. 社区行为期望与标准。
|
||||
|
||||
@@ -9,7 +9,7 @@ body:
|
||||
Thanks for your interest in improving the project! Before submitting, please read our guidelines.
|
||||
感谢您对改进项目的兴趣!提交前请阅读我们的指南。
|
||||
|
||||
- [Code of Conduct](https://github.com/alibaba/page-agent/blob/main/CODE_OF_CONDUCT.md)
|
||||
- [Code of Conduct](https://github.com/alibaba/page-agent/blob/main/docs/CODE_OF_CONDUCT.md)
|
||||
- [Contributing Guide](https://github.com/alibaba/page-agent/blob/main/CONTRIBUTING.md)
|
||||
|
||||
- type: textarea
|
||||
|
||||
@@ -22,4 +22,4 @@ Closes #(issue)
|
||||
|
||||
## Requirements / 要求
|
||||
|
||||
- [ ] I have read and follow the [Code of Conduct](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) . / 我已阅读并遵守行为准则。
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20, 24, 25]
|
||||
node-version: [24]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
@@ -29,11 +29,6 @@ dist-ssr
|
||||
.env
|
||||
.env.*
|
||||
|
||||
# Copied files for npm publish (generated by prepublishOnly)
|
||||
packages/page-agent/README.md
|
||||
packages/page-agent/README-zh.md
|
||||
packages/page-agent/LICENSE
|
||||
|
||||
# extension
|
||||
.output
|
||||
.wxt
|
||||
|
||||
@@ -9,26 +9,33 @@
|
||||
|
||||
The GUI Agent Living in Your Webpage. Control web interfaces with natural language.
|
||||
|
||||
🌐 **English** | [中文](./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>
|
||||
|
||||
<video id="demo-video" src="https://github.com/user-attachments/assets/34d8444d-cbfb-44a3-a24e-fd5c167bb0bf" controls crossorigin muted></video>
|
||||
<video id="demo-video" src="https://github.com/user-attachments/assets/11aed429-b69c-49d5-8982-fa99b4a0e9a8" controls crossorigin muted></video>
|
||||
|
||||
---
|
||||
|
||||
## ✨ Features
|
||||
|
||||
- **🎯 Easy integration**
|
||||
- No need for `browser extension` / `python` / `headless browser`.
|
||||
- Just in-page javascript. Everything happens in your web page.
|
||||
- The best tool for your agent to control web pages.
|
||||
- **🎯 Easy integration**
|
||||
- No need for `browser extension` / `python` / `headless browser`.
|
||||
- Just in-page javascript. Everything happens in your web page.
|
||||
- The best tool for your agent to control web pages.
|
||||
- **📖 Text-based DOM manipulation**
|
||||
- No screenshots. No OCR or multi-modal LLMs needed.
|
||||
- No special permissions required.
|
||||
- No screenshots. No OCR or multi-modal LLMs needed.
|
||||
- No special permissions required.
|
||||
- **🧠 Bring your own LLMs**
|
||||
- **🎨 Pretty UI with human-in-the-loop**
|
||||
- **🐙 Optional [chrome extension](https://alibaba.github.io/page-agent/#/docs/features/chrome-extension) for multi-page tasks.**
|
||||
- **🐙 Optional [chrome extension](https://alibaba.github.io/page-agent/docs/features/chrome-extension) for multi-page tasks.**
|
||||
|
||||
## 💡 Use Cases
|
||||
|
||||
- **SaaS AI Copilot** — Ship an AI copilot in your product in lines of code. No backend rewrite needed.
|
||||
- **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.
|
||||
- **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).
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
@@ -42,10 +49,10 @@ Fastest way to try PageAgent with our free Demo LLM:
|
||||
|
||||
| Mirrors | URL |
|
||||
| ------- | ---------------------------------------------------------------------------------- |
|
||||
| Global | https://cdn.jsdelivr.net/npm/page-agent@1.3.0/dist/iife/page-agent.demo.js |
|
||||
| China | https://registry.npmmirror.com/page-agent/1.3.0/files/dist/iife/page-agent.demo.js |
|
||||
| Global | https://cdn.jsdelivr.net/npm/page-agent@1.4.0/dist/iife/page-agent.demo.js |
|
||||
| China | https://registry.npmmirror.com/page-agent/1.4.0/files/dist/iife/page-agent.demo.js |
|
||||
|
||||
> **⚠️ For technical evaluation only.** Demo LLM has rate limits and usage restrictions. Slow. May change without notice.
|
||||
> **⚠️ 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
|
||||
|
||||
@@ -57,8 +64,8 @@ npm install page-agent
|
||||
import { PageAgent } from 'page-agent'
|
||||
|
||||
const agent = new PageAgent({
|
||||
model: 'deepseek-chat',
|
||||
baseURL: 'https://api.deepseek.com',
|
||||
model: 'qwen3.5-plus',
|
||||
baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
|
||||
apiKey: 'YOUR_API_KEY',
|
||||
language: 'en-US',
|
||||
})
|
||||
@@ -66,28 +73,13 @@ const agent = new PageAgent({
|
||||
await agent.execute('Click the login button')
|
||||
```
|
||||
|
||||
For more programmatic usage, see [📖 Documentations](https://alibaba.github.io/page-agent/#/docs/introduction/overview).
|
||||
|
||||
## 🏗️ Structure
|
||||
|
||||
PageAgent adopts a simplified monorepo structure:
|
||||
|
||||
```
|
||||
packages/
|
||||
├── core/ # ** Core agent logic (npm: @page-agent/core) **
|
||||
├── llms/ # LLM client (npm: @page-agent/llms)
|
||||
├── page-controller/ # DOM operations (npm: @page-agent/page-controller)
|
||||
├── ui/ # Panel UI (npm: @page-agent/ui)
|
||||
├── page-agent/ # Entry class and iife builds(npm: page-agent)
|
||||
├── extension/ # Chrome extension for multi-page tasks
|
||||
└── website/ # Website & Documentation site
|
||||
```
|
||||
For more programmatic usage, see [📖 Documentations](https://alibaba.github.io/page-agent/docs/introduction/overview).
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
We welcome contributions from the community! Follow our instructions in [CONTRIBUTING.md](CONTRIBUTING.md) for environment setup and local development.
|
||||
|
||||
Please read [Code of Conduct](CODE_OF_CONDUCT.md) before contributing.
|
||||
Please read [Code of Conduct](docs/CODE_OF_CONDUCT.md) before contributing.
|
||||
|
||||
## 👏 Acknowledgments
|
||||
|
||||
|
||||
@@ -146,7 +146,7 @@ interface PageAgentConfig {
|
||||
- Single-page application only (cannot navigate across pages)
|
||||
- No visual recognition (relies on DOM structure)
|
||||
- Limited interaction support (no hover, drag-drop, canvas operations)
|
||||
- See [Limitations](https://alibaba.github.io/page-agent/#/docs/introduction/limitations) for details
|
||||
- See [Limitations](https://alibaba.github.io/page-agent/docs/introduction/limitations) for details
|
||||
|
||||
### Acknowledgments
|
||||
|
||||
@@ -9,26 +9,33 @@
|
||||
|
||||
纯 JS 实现的 GUI agent。使用自然语言操作你的 Web 应用。无须后端、客户端、浏览器插件。
|
||||
|
||||
🌐 [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>
|
||||
|
||||
<video id="demo-video" src="https://github.com/user-attachments/assets/34d8444d-cbfb-44a3-a24e-fd5c167bb0bf" controls crossorigin muted></video>
|
||||
<video id="demo-video" src="https://github.com/user-attachments/assets/11aed429-b69c-49d5-8982-fa99b4a0e9a8" controls crossorigin muted></video>
|
||||
|
||||
---
|
||||
|
||||
## ✨ Features
|
||||
|
||||
- **🎯 轻松集成**
|
||||
- 无需 `浏览器插件` / `Python` / `无头浏览器`。
|
||||
- 纯页面内 JavaScript,一切都在你的网页中完成。
|
||||
- The best tool for your agent to control web pages.
|
||||
- **🎯 轻松集成**
|
||||
- 无需 `浏览器插件` / `Python` / `无头浏览器`。
|
||||
- 纯页面内 JavaScript,一切都在你的网页中完成。
|
||||
- The best tool for your agent to control web pages.
|
||||
- **📖 基于文本的 DOM 操作**
|
||||
- 无需截图,无需 OCR 或多模态模型。
|
||||
- 无需特殊权限。
|
||||
- 无需截图,无需 OCR 或多模态模型。
|
||||
- 无需特殊权限。
|
||||
- **🧠 用你自己的 LLM**
|
||||
- **🎨 精美 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 副驾驶,不需要重写后端。
|
||||
- **智能表单填写** — 把 20 次点击变成一句话。ERP、CRM、管理后台的最佳拍档。
|
||||
- **无障碍增强** — 用自然语言让任何网页无障碍。语音指令、屏幕阅读器,零门槛。
|
||||
- **跨页面 Agent** — 通过可选的 [Chrome 扩展](https://alibaba.github.io/page-agent/docs/features/chrome-extension),让你的 Agent 跨标签页工作。
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
@@ -42,10 +49,10 @@
|
||||
|
||||
| Mirrors | URL |
|
||||
| ------- | ---------------------------------------------------------------------------------- |
|
||||
| Global | https://cdn.jsdelivr.net/npm/page-agent@1.3.0/dist/iife/page-agent.demo.js |
|
||||
| China | https://registry.npmmirror.com/page-agent/1.3.0/files/dist/iife/page-agent.demo.js |
|
||||
| Global | https://cdn.jsdelivr.net/npm/page-agent@1.4.0/dist/iife/page-agent.demo.js |
|
||||
| China | https://registry.npmmirror.com/page-agent/1.4.0/files/dist/iife/page-agent.demo.js |
|
||||
|
||||
> **⚠️ 仅用于技术评估。** Demo LLM 有速率和使用限制,速度较慢,可能随时变更。
|
||||
> **⚠️ 仅用于技术评估。** 该 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 安装
|
||||
|
||||
@@ -57,8 +64,8 @@ npm install page-agent
|
||||
import { PageAgent } from 'page-agent'
|
||||
|
||||
const agent = new PageAgent({
|
||||
model: 'deepseek-chat',
|
||||
baseURL: 'https://api.deepseek.com',
|
||||
model: 'qwen3.5-plus',
|
||||
baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
|
||||
apiKey: 'YOUR_API_KEY',
|
||||
language: 'zh-CN',
|
||||
})
|
||||
@@ -66,26 +73,11 @@ const agent = new PageAgent({
|
||||
await agent.execute('点击登录按钮')
|
||||
```
|
||||
|
||||
更多编程用法,请参阅 [📖 文档](https://alibaba.github.io/page-agent/#/docs/introduction/overview)。
|
||||
|
||||
## 🏗️ 架构设计
|
||||
|
||||
PageAgent adopts a simplified monorepo structure:
|
||||
|
||||
```
|
||||
packages/
|
||||
├── core/ # ** Core agent logic (npm: @page-agent/core) **
|
||||
├── llms/ # LLM 客户端 (npm: @page-agent/llms)
|
||||
├── page-controller/ # DOM 操作 (npm: @page-agent/page-controller)
|
||||
├── ui/ # 面板 UI (npm: @page-agent/ui)
|
||||
├── page-agent/ # 入口类 & iife 包 (npm: page-agent)
|
||||
├── extension/ # Chrome 扩展,支持跨页面任务
|
||||
└── website/ # 网站 & 文档站点
|
||||
```
|
||||
更多编程用法,请参阅 [📖 文档](https://alibaba.github.io/page-agent/docs/introduction/overview)。
|
||||
|
||||
## 🤝 贡献
|
||||
|
||||
欢迎社区贡献!请参阅 [CONTRIBUTING.md](CONTRIBUTING.md) 了解环境配置和本地开发说明。
|
||||
欢迎社区贡献!请参阅 [CONTRIBUTING.md](../CONTRIBUTING.md) 了解环境配置和本地开发说明。
|
||||
|
||||
请在贡献前阅读[行为准则](CODE_OF_CONDUCT.md)。
|
||||
|
||||
@@ -114,7 +106,7 @@ file and in the node_modules directory after installation.
|
||||
|
||||
## 📄 许可证
|
||||
|
||||
[MIT License](LICENSE)
|
||||
[MIT License](../LICENSE)
|
||||
|
||||
---
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
# Terms of Use & Privacy
|
||||
|
||||
**Last updated:** February 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.
|
||||
|
||||
---
|
||||
|
||||
## 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.
|
||||
|
||||
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
|
||||
- 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
|
||||
|
||||
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.
|
||||
|
||||
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:
|
||||
|
||||
**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.
|
||||
|
||||
**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.
|
||||
|
||||
**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.
|
||||
|
||||
**No Guaranteed Availability**: This testing API may be rate-limited, degraded, or discontinued at any time without prior notice.
|
||||
|
||||
**"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.
|
||||
|
||||
**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).
|
||||
|
||||
**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.
|
||||
|
||||
---
|
||||
|
||||
## 3. Browser Extension (Page Agent Ext)
|
||||
|
||||
### Data Processing
|
||||
|
||||
The extension performs DOM analysis and automation actions **locally in your browser**. Your browsing history, passwords, and form data are not accessed or collected by the extension developer.
|
||||
|
||||
Data is transmitted to external servers **only when you initiate an automation task**. When this occurs:
|
||||
|
||||
- Your task instructions (natural language commands)
|
||||
- Simplified page structure (cleaned DOM) of all pages under the extension's control
|
||||
|
||||
are sent to the LLM API endpoint configured in **your settings**.
|
||||
|
||||
> **Note:** The DOM cleaning process simplifies page structure for AI readability but **does not guarantee removal of sensitive information** (e.g., visible text, form values, or personal data on the page). Please be mindful of the page content when initiating tasks.
|
||||
|
||||
**If you configure a third-party LLM provider** (e.g., OpenAI, Anthropic, or others), data is sent directly to that provider. Their privacy policies apply.
|
||||
|
||||
**If you use 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.
|
||||
|
||||
### Data Storage
|
||||
|
||||
- **Local storage only**: Your configuration (API endpoint, API key, model selection) is stored in your browser via `chrome.storage.local`
|
||||
- **No cloud sync**: Configuration is not synced to any external server
|
||||
- **No analytics**: The extension does not include any analytics or tracking code
|
||||
|
||||
### Your Control
|
||||
|
||||
- The extension is open source and can be audited by anyone
|
||||
- You choose which LLM provider to use
|
||||
- You may configure your own API endpoint at any time
|
||||
- You can clear all stored data by removing the extension
|
||||
|
||||
---
|
||||
|
||||
## Changes
|
||||
|
||||
We may update these terms as the project evolves.
|
||||
|
||||
## Contact
|
||||
|
||||
https://github.com/alibaba/page-agent/issues
|
||||
Generated
+529
-431
File diff suppressed because it is too large
Load Diff
+11
-11
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "root",
|
||||
"private": true,
|
||||
"version": "1.3.0",
|
||||
"version": "1.4.0",
|
||||
"type": "module",
|
||||
"workspaces": [
|
||||
"packages/page-controller",
|
||||
@@ -35,29 +35,29 @@
|
||||
"prepare": "husky"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^20.3.1",
|
||||
"@commitlint/config-conventional": "^20.3.1",
|
||||
"@commitlint/cli": "^20.4.2",
|
||||
"@commitlint/config-conventional": "^20.4.2",
|
||||
"@eslint/js": "^9.39.2",
|
||||
"@microsoft/api-extractor": "^7.56.3",
|
||||
"@tailwindcss/vite": "^4.1.18",
|
||||
"@microsoft/api-extractor": "^7.57.3",
|
||||
"@tailwindcss/vite": "^4.2.1",
|
||||
"@trivago/prettier-plugin-sort-imports": "^6.0.2",
|
||||
"@types/node": "^25.2.2",
|
||||
"@types/node": "^25.3.0",
|
||||
"@vitejs/plugin-react-swc": "^4.1.0",
|
||||
"chalk": "^5.6.2",
|
||||
"concurrently": "^9.2.1",
|
||||
"dotenv": "^17.2.4",
|
||||
"dotenv": "^17.3.1",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-react-dom": "^2.12.2",
|
||||
"eslint-plugin-react-dom": "^2.13.0",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
"eslint-plugin-react-refresh": "^0.5.0",
|
||||
"eslint-plugin-react-x": "^2.12.2",
|
||||
"eslint-plugin-react-refresh": "^0.5.2",
|
||||
"eslint-plugin-react-x": "^2.13.0",
|
||||
"globals": "^17.0.0",
|
||||
"husky": "^9.1.7",
|
||||
"lint-staged": "^16.2.4",
|
||||
"prettier": "^3.8.0",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.55.0",
|
||||
"typescript-eslint": "^8.56.1",
|
||||
"unplugin-dts": "^1.0.0-beta.6",
|
||||
"vite": "^7.3.1",
|
||||
"vite-plugin-css-injected-by-js": "^3.5.2",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@page-agent/core",
|
||||
"private": false,
|
||||
"version": "1.3.0",
|
||||
"version": "1.4.0",
|
||||
"type": "module",
|
||||
"main": "./dist/esm/page-agent-core.js",
|
||||
"module": "./dist/esm/page-agent-core.js",
|
||||
@@ -45,7 +45,7 @@
|
||||
"dependencies": {
|
||||
"chalk": "^5.6.2",
|
||||
"zod": "^4.3.5",
|
||||
"@page-agent/llms": "1.3.0",
|
||||
"@page-agent/page-controller": "1.3.0"
|
||||
"@page-agent/llms": "1.4.0",
|
||||
"@page-agent/page-controller": "1.4.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import { InvokeError, LLM, type Tool } from '@page-agent/llms'
|
||||
import type { BrowserState, PageController } from '@page-agent/page-controller'
|
||||
import chalk from 'chalk'
|
||||
import * as zod from 'zod'
|
||||
import * as z from 'zod'
|
||||
|
||||
import { type PageAgentConfig, type SupportedLanguage } from './config'
|
||||
import { DEFAULT_MAX_STEPS } from './config/constants'
|
||||
@@ -248,16 +248,16 @@ export class PageAgentCore extends EventTarget {
|
||||
{ role: 'user' as const, content: await this.#assembleUserPrompt() },
|
||||
]
|
||||
|
||||
const tools = { AgentOutput: this.#packMacroTool() }
|
||||
const macroTool = { AgentOutput: this.#packMacroTool() }
|
||||
|
||||
// invoke LLM
|
||||
|
||||
console.log(chalk.blue.bold('🧠 Thinking...'))
|
||||
this.#emitActivity({ type: 'thinking' })
|
||||
|
||||
const result = await this.#llm.invoke(messages, tools, this.#abortController.signal, {
|
||||
const result = await this.#llm.invoke(messages, macroTool, this.#abortController.signal, {
|
||||
toolChoiceName: 'AgentOutput',
|
||||
normalizeResponse,
|
||||
normalizeResponse: (res) => normalizeResponse(res, this.tools),
|
||||
})
|
||||
|
||||
// assemble history
|
||||
@@ -358,24 +358,22 @@ export class PageAgentCore extends EventTarget {
|
||||
const tools = this.tools
|
||||
|
||||
const actionSchemas = Array.from(tools.entries()).map(([toolName, tool]) => {
|
||||
return zod.object({ [toolName]: tool.inputSchema }).describe(tool.description)
|
||||
return z.object({ [toolName]: tool.inputSchema }).describe(tool.description)
|
||||
})
|
||||
|
||||
const actionSchema = zod.union(
|
||||
actionSchemas as unknown as [zod.ZodType, zod.ZodType, ...zod.ZodType[]]
|
||||
)
|
||||
const actionSchema = z.union(actionSchemas as unknown as [z.ZodType, z.ZodType, ...z.ZodType[]])
|
||||
|
||||
const macroToolSchema = zod.object({
|
||||
// thinking: zod.string().optional(),
|
||||
evaluation_previous_goal: zod.string().optional(),
|
||||
memory: zod.string().optional(),
|
||||
next_goal: zod.string().optional(),
|
||||
const macroToolSchema = z.object({
|
||||
// thinking: z.string().optional(),
|
||||
evaluation_previous_goal: z.string().optional(),
|
||||
memory: z.string().optional(),
|
||||
next_goal: z.string().optional(),
|
||||
action: actionSchema,
|
||||
})
|
||||
|
||||
return {
|
||||
description: 'You MUST call this tool every step!',
|
||||
inputSchema: macroToolSchema as zod.ZodType<MacroToolInput>,
|
||||
inputSchema: macroToolSchema as z.ZodType<MacroToolInput>,
|
||||
execute: async (input: MacroToolInput): Promise<MacroToolResult> => {
|
||||
// abort
|
||||
if (this.#abortController.signal.aborted) throw new Error('AbortError')
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Internal tools for PageAgent.
|
||||
* @note Adapted from browser-use
|
||||
*/
|
||||
import * as zod from 'zod'
|
||||
import * as z from 'zod'
|
||||
|
||||
import type { PageAgentCore } from '../PageAgentCore'
|
||||
import { waitFor } from '../utils'
|
||||
@@ -13,7 +13,7 @@ import { waitFor } from '../utils'
|
||||
export interface PageAgentTool<TParams = any> {
|
||||
// name: string
|
||||
description: string
|
||||
inputSchema: zod.ZodType<TParams>
|
||||
inputSchema: z.ZodType<TParams>
|
||||
execute: (this: PageAgentCore, args: TParams) => Promise<string>
|
||||
}
|
||||
|
||||
@@ -32,9 +32,9 @@ tools.set(
|
||||
tool({
|
||||
description:
|
||||
'Complete task. Text is your final response to the user — keep it concise unless the user explicitly asks for detail.',
|
||||
inputSchema: zod.object({
|
||||
text: zod.string(),
|
||||
success: zod.boolean().default(true),
|
||||
inputSchema: z.object({
|
||||
text: z.string(),
|
||||
success: z.boolean().default(true),
|
||||
}),
|
||||
execute: async function (this: PageAgentCore, input) {
|
||||
// @note main loop will handle this one
|
||||
@@ -47,8 +47,8 @@ tools.set(
|
||||
'wait',
|
||||
tool({
|
||||
description: 'Wait for x seconds. Can be used to wait until the page or data is fully loaded.',
|
||||
inputSchema: zod.object({
|
||||
seconds: zod.number().min(1).max(10).default(1),
|
||||
inputSchema: z.object({
|
||||
seconds: z.number().min(1).max(10).default(1),
|
||||
}),
|
||||
execute: async function (this: PageAgentCore, input) {
|
||||
// try to subtract LLM calling time from the actual wait time
|
||||
@@ -67,8 +67,8 @@ tools.set(
|
||||
tool({
|
||||
description:
|
||||
'Ask the user a question and wait for their answer. Use this if you need more information or clarification.',
|
||||
inputSchema: zod.object({
|
||||
question: zod.string(),
|
||||
inputSchema: z.object({
|
||||
question: z.string(),
|
||||
}),
|
||||
execute: async function (this: PageAgentCore, input) {
|
||||
if (!this.onAskUser) {
|
||||
@@ -84,8 +84,8 @@ tools.set(
|
||||
'click_element_by_index',
|
||||
tool({
|
||||
description: 'Click element by index',
|
||||
inputSchema: zod.object({
|
||||
index: zod.int().min(0),
|
||||
inputSchema: z.object({
|
||||
index: z.int().min(0),
|
||||
}),
|
||||
execute: async function (this: PageAgentCore, input) {
|
||||
const result = await this.pageController.clickElement(input.index)
|
||||
@@ -98,9 +98,9 @@ tools.set(
|
||||
'input_text',
|
||||
tool({
|
||||
description: 'Click and type text into an interactive input element',
|
||||
inputSchema: zod.object({
|
||||
index: zod.int().min(0),
|
||||
text: zod.string(),
|
||||
inputSchema: z.object({
|
||||
index: z.int().min(0),
|
||||
text: z.string(),
|
||||
}),
|
||||
execute: async function (this: PageAgentCore, input) {
|
||||
const result = await this.pageController.inputText(input.index, input.text)
|
||||
@@ -114,9 +114,9 @@ tools.set(
|
||||
tool({
|
||||
description:
|
||||
'Select dropdown option for interactive element index by the text of the option you want to select',
|
||||
inputSchema: zod.object({
|
||||
index: zod.int().min(0),
|
||||
text: zod.string(),
|
||||
inputSchema: z.object({
|
||||
index: z.int().min(0),
|
||||
text: z.string(),
|
||||
}),
|
||||
execute: async function (this: PageAgentCore, input) {
|
||||
const result = await this.pageController.selectOption(input.index, input.text)
|
||||
@@ -132,11 +132,11 @@ tools.set(
|
||||
'scroll',
|
||||
tool({
|
||||
description: 'Scroll the page vertically. Use index for scroll elements (dropdowns/custom UI).',
|
||||
inputSchema: zod.object({
|
||||
down: zod.boolean().default(true),
|
||||
num_pages: zod.number().min(0).max(10).optional().default(0.1),
|
||||
pixels: zod.number().int().min(0).optional(),
|
||||
index: zod.number().int().min(0).optional(),
|
||||
inputSchema: z.object({
|
||||
down: z.boolean().default(true),
|
||||
num_pages: z.number().min(0).max(10).optional().default(0.1),
|
||||
pixels: z.number().int().min(0).optional(),
|
||||
index: z.number().int().min(0).optional(),
|
||||
}),
|
||||
execute: async function (this: PageAgentCore, input) {
|
||||
const result = await this.pageController.scroll({
|
||||
@@ -156,10 +156,10 @@ tools.set(
|
||||
tool({
|
||||
description:
|
||||
'Scroll the page horizontally, or within a specific element by index. Useful for wide tables.',
|
||||
inputSchema: zod.object({
|
||||
right: zod.boolean().default(true),
|
||||
pixels: zod.number().int().min(0),
|
||||
index: zod.number().int().min(0).optional(),
|
||||
inputSchema: z.object({
|
||||
right: z.boolean().default(true),
|
||||
pixels: z.number().int().min(0),
|
||||
index: z.number().int().min(0).optional(),
|
||||
}),
|
||||
execute: async function (this: PageAgentCore, input) {
|
||||
const result = await this.pageController.scrollHorizontally(input)
|
||||
@@ -173,8 +173,8 @@ tools.set(
|
||||
tool({
|
||||
description:
|
||||
'Execute JavaScript code on the current page. Supports async/await syntax. Use with caution!',
|
||||
inputSchema: zod.object({
|
||||
script: zod.string(),
|
||||
inputSchema: z.object({
|
||||
script: z.string(),
|
||||
}),
|
||||
execute: async function (this: PageAgentCore, input) {
|
||||
const result = await this.pageController.executeJavascript(input.script)
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { InvokeError, InvokeErrorType } from '@page-agent/llms'
|
||||
import chalk from 'chalk'
|
||||
import * as z from 'zod'
|
||||
|
||||
import type { PageAgentTool } from '../tools'
|
||||
|
||||
/**
|
||||
* Normalize LLM response and fix common format issues.
|
||||
@@ -9,9 +13,10 @@ import chalk from 'chalk'
|
||||
* - Arguments wrapped as double JSON string
|
||||
* - Nested function call format
|
||||
* - Missing action field (fallback to wait)
|
||||
* - Primitive action input for single-field tools (e.g. `{"click_element_by_index": 2}`)
|
||||
* - etc.
|
||||
*/
|
||||
export function normalizeResponse(response: any): any {
|
||||
export function normalizeResponse(response: any, tools?: Map<string, PageAgentTool>): any {
|
||||
let resolvedArguments = null as any
|
||||
|
||||
const choice = (response as { choices?: Choice[] }).choices?.[0]
|
||||
@@ -78,6 +83,11 @@ export function normalizeResponse(response: any): any {
|
||||
resolvedArguments.action = safeJsonParse(resolvedArguments.action)
|
||||
}
|
||||
|
||||
// validate and fix action input using tool schemas
|
||||
if (resolvedArguments.action && tools) {
|
||||
resolvedArguments.action = validateAction(resolvedArguments.action, tools)
|
||||
}
|
||||
|
||||
// fix incomplete formats
|
||||
if (!resolvedArguments.action) {
|
||||
console.log(chalk.yellow(`[normalizeResponse] #5: fixing tool_call`))
|
||||
@@ -108,6 +118,55 @@ export function normalizeResponse(response: any): any {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate action against tool schemas. Provides clear error messages
|
||||
* instead of letting the union schema produce unreadable errors.
|
||||
*
|
||||
* Also coerces primitive inputs for single-field tools:
|
||||
* e.g. `{"click_element_by_index": 2}` → `{"click_element_by_index": {"index": 2}}`
|
||||
*/
|
||||
function validateAction(action: any, tools: Map<string, PageAgentTool>): any {
|
||||
if (typeof action !== 'object' || action === null) return action
|
||||
|
||||
const toolName = Object.keys(action)[0]
|
||||
if (!toolName) return action
|
||||
|
||||
const tool = tools.get(toolName)
|
||||
if (!tool) {
|
||||
const available = Array.from(tools.keys()).join(', ')
|
||||
throw new InvokeError(
|
||||
InvokeErrorType.INVALID_TOOL_ARGS,
|
||||
`Unknown action "${toolName}". Available: ${available}`
|
||||
)
|
||||
}
|
||||
|
||||
let value = action[toolName]
|
||||
const schema = tool.inputSchema
|
||||
|
||||
// coerce primitive input for single-field tools
|
||||
if (schema instanceof z.ZodObject && value !== null && typeof value !== 'object') {
|
||||
const requiredKey = Object.keys(schema.shape).find(
|
||||
(k) => !(schema.shape as Record<string, z.ZodType>)[k].safeParse(undefined).success
|
||||
)
|
||||
if (requiredKey) {
|
||||
console.log(
|
||||
chalk.yellow(`[normalizeResponse] coercing primitive action input for "${toolName}"`)
|
||||
)
|
||||
value = { [requiredKey]: value }
|
||||
}
|
||||
}
|
||||
|
||||
const result = schema.safeParse(value)
|
||||
if (!result.success) {
|
||||
throw new InvokeError(
|
||||
InvokeErrorType.INVALID_TOOL_ARGS,
|
||||
`Invalid input for action "${toolName}": ${z.prettifyError(result.error)}`
|
||||
)
|
||||
}
|
||||
|
||||
return { [toolName]: result.data }
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely parse JSON, return original input if not json.
|
||||
*/
|
||||
|
||||
@@ -1,60 +1,5 @@
|
||||
# Privacy Policy for Page Agent Extension
|
||||
|
||||
**Last updated:** February 2026
|
||||
This document has moved. Please see our full **[Terms of Use & Privacy](../../docs/terms-and-privacy.md)**.
|
||||
|
||||
## Overview
|
||||
|
||||
Page Agent Extension is a browser automation tool that uses AI to help you interact with web pages. This policy explains what data is processed and under what circumstances.
|
||||
|
||||
## Data Processing
|
||||
|
||||
### Local Processing
|
||||
|
||||
The extension performs DOM analysis and automation actions **locally in your browser**. Your browsing history, passwords, and form data are not accessed or collected by the extension developer.
|
||||
|
||||
### Data Transmission
|
||||
|
||||
Data is transmitted to external servers **only when you initiate an automation task**. When this occurs:
|
||||
|
||||
- Your task instructions (natural language commands)
|
||||
- Simplified page structure (cleaned DOM) of all pages under the extension's control
|
||||
|
||||
are sent to the LLM API endpoint configured in **your settings**.
|
||||
|
||||
> **Note:** The DOM cleaning process simplifies page structure for AI readability but **does not guarantee removal of sensitive information** (e.g., visible text, form values, or personal data on the page). Please be mindful of the page content when initiating tasks.
|
||||
|
||||
**If you configure a third-party LLM provider** (e.g., OpenAI, Anthropic, or others), data is sent directly to that provider. Their privacy policies apply.
|
||||
|
||||
**If you use our testing endpoint**, your requests are proxied to [DeepSeek](https://deepseek.com) for AI processing. Regarding this test endpoint:
|
||||
|
||||
- This endpoint is provided for evaluation purposes only and is not recommended for production or daily use
|
||||
- The free model and their service providers may change at any time without prior notice
|
||||
- We do **not** store your task content, page content, or visited URLs
|
||||
- Minimal logging (timestamps, request metadata, IP addresses) may be collected for abuse prevention and service stability
|
||||
- DeepSeek's [Privacy Policy](https://cdn.deepseek.com/policies/en-US/deepseek-privacy-policy.html) applies to their processing of your requests
|
||||
|
||||
## Data Storage
|
||||
|
||||
- **Local storage only**: Your configuration (API endpoint, API key, model selection) is stored in your browser via `chrome.storage.local`
|
||||
- **No cloud sync**: Configuration is not synced to any external server
|
||||
- **No analytics**: The extension does not include any analytics or tracking code
|
||||
|
||||
## Your Control
|
||||
|
||||
- The extension is open source and can be audited by anyone
|
||||
- You choose which LLM provider to use
|
||||
- You may configure your own API endpoint at any time
|
||||
- You can clear all stored data by removing the extension
|
||||
|
||||
## Changes to This Policy
|
||||
|
||||
We may update this policy as the extension evolves. Significant changes will be noted in the extension's release notes.
|
||||
|
||||
## Contact
|
||||
|
||||
For questions about this privacy policy:
|
||||
https://github.com/alibaba/page-agent/issues
|
||||
|
||||
---
|
||||
|
||||
Source code: https://github.com/alibaba/page-agent
|
||||
Online: https://github.com/alibaba/page-agent/blob/main/docs/terms-and-privacy.md
|
||||
|
||||
@@ -22,7 +22,7 @@ Page Agent Ext performs DOM analysis locally in your browser. When you initiate
|
||||
- Your API Key — Configure your own LLM API (OpenAI, Anthropic, etc.). Data goes directly to your provider
|
||||
- Test API — A free test endpoint is available for evaluation; we recommend your own key for regular use
|
||||
|
||||
Privacy policy: https://github.com/alibaba/page-agent/blob/main/packages/extension/PRIVACY.md
|
||||
Terms of Use & Privacy: https://github.com/alibaba/page-agent/blob/main/docs/terms-and-privacy.md
|
||||
|
||||
🌟 Open Source:
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ Page Agent Ext 在浏览器本地进行 DOM 分析。当你发起任务时,经
|
||||
- 你的 API Key — 配置你自己的 LLM API(OpenAI、Anthropic 等),数据直接发送到你的服务商
|
||||
- 测试 API — 提供免费测试端点供体验,日常使用建议配置自己的 Key
|
||||
|
||||
隐私政策:https://github.com/alibaba/page-agent/blob/main/packages/extension/PRIVACY.md
|
||||
使用条款与隐私:https://github.com/alibaba/page-agent/blob/main/docs/terms-and-privacy.md
|
||||
|
||||
🌟 开源项目:
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@page-agent/ext",
|
||||
"private": true,
|
||||
"version": "0.1.7",
|
||||
"version": "0.1.8",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "wxt",
|
||||
@@ -17,31 +17,31 @@
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"@radix-ui/react-switch": "^1.2.6",
|
||||
"@radix-ui/react-tooltip": "^1.2.8",
|
||||
"@types/chrome": "^0.1.34",
|
||||
"@types/react": "^19.2.13",
|
||||
"@types/chrome": "^0.1.37",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.1",
|
||||
"@wxt-dev/module-react": "^1.1.5",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"idb": "^8.0.3",
|
||||
"lucide-react": "^0.563.0",
|
||||
"motion": "^12.34.0",
|
||||
"lucide-react": "^0.575.0",
|
||||
"motion": "^12.34.3",
|
||||
"next-themes": "^0.4.6",
|
||||
"react": "^19.2.4",
|
||||
"react-dom": "^19.2.4",
|
||||
"rough-notation": "^0.5.1",
|
||||
"simple-icons": "^16.8.0",
|
||||
"simple-icons": "^16.9.0",
|
||||
"sonner": "^2.0.7",
|
||||
"tailwind-merge": "^3.4.0",
|
||||
"tailwind-merge": "^3.5.0",
|
||||
"tailwindcss": "^4.1.14",
|
||||
"tw-animate-css": "^1.4.0",
|
||||
"wxt": "^0.20.14"
|
||||
"wxt": "^0.20.18"
|
||||
},
|
||||
"dependencies": {
|
||||
"@page-agent/core": "1.3.0",
|
||||
"@page-agent/llms": "1.3.0",
|
||||
"@page-agent/page-controller": "1.3.0",
|
||||
"@page-agent/ui": "1.3.0",
|
||||
"@page-agent/core": "1.4.0",
|
||||
"@page-agent/llms": "1.4.0",
|
||||
"@page-agent/page-controller": "1.4.0",
|
||||
"@page-agent/ui": "1.4.0",
|
||||
"ai-motion": "^0.4.8",
|
||||
"chalk": "^5.6.2",
|
||||
"zod": "^4.3.5"
|
||||
|
||||
@@ -1,13 +1,30 @@
|
||||
import type { LLMConfig } from '@page-agent/llms'
|
||||
|
||||
// Demo LLM for testing
|
||||
export const DEMO_MODEL = 'PAGE-AGENT-FREE-TESTING-RANDOM'
|
||||
export const DEMO_BASE_URL =
|
||||
'https://hwcxiuzfylggtcktqgij.supabase.co/functions/v1/llm-testing-proxy'
|
||||
export const DEMO_API_KEY = 'PAGE-AGENT-FREE-TESTING-RANDOM'
|
||||
export const DEMO_MODEL = 'qwen3.5-plus'
|
||||
export const DEMO_BASE_URL = 'https://page-ag-testing-ohftxirgbn.cn-shanghai.fcapp.run'
|
||||
export const DEMO_API_KEY = 'NA'
|
||||
|
||||
export const DEMO_CONFIG: LLMConfig = {
|
||||
apiKey: DEMO_API_KEY,
|
||||
baseURL: DEMO_BASE_URL,
|
||||
model: DEMO_MODEL,
|
||||
}
|
||||
|
||||
/** Legacy testing endpoints that should be auto-migrated to DEMO_BASE_URL */
|
||||
export const LEGACY_TESTING_ENDPOINTS = [
|
||||
'https://hwcxiuzfylggtcktqgij.supabase.co/functions/v1/llm-testing-proxy',
|
||||
]
|
||||
|
||||
export function isTestingEndpoint(url: string): boolean {
|
||||
const normalized = url.replace(/\/+$/, '')
|
||||
return normalized === DEMO_BASE_URL || LEGACY_TESTING_ENDPOINTS.some((ep) => normalized === ep)
|
||||
}
|
||||
|
||||
export function migrateLegacyEndpoint(config: LLMConfig): LLMConfig {
|
||||
const normalized = config.baseURL.replace(/\/+$/, '')
|
||||
if (LEGACY_TESTING_ENDPOINTS.some((ep) => normalized === ep)) {
|
||||
return { ...DEMO_CONFIG }
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
@@ -6,14 +6,14 @@
|
||||
* - switch_to_tab: Switch to an existing tab
|
||||
* - close_tab: Close a tab (optionally switch to another)
|
||||
*/
|
||||
import * as zod from 'zod'
|
||||
import * as z from 'zod'
|
||||
|
||||
import type { TabsController } from './TabsController'
|
||||
|
||||
/** Tool definition compatible with PageAgentCore customTools */
|
||||
interface TabTool {
|
||||
description: string
|
||||
inputSchema: zod.ZodType
|
||||
inputSchema: z.ZodType
|
||||
execute: (input: unknown) => Promise<string>
|
||||
}
|
||||
|
||||
@@ -26,8 +26,8 @@ export function createTabTools(tabsController: TabsController): Record<string, T
|
||||
open_new_tab: {
|
||||
description:
|
||||
'Open a new browser tab with the specified URL. The new tab becomes the current tab for all subsequent page operations.',
|
||||
inputSchema: zod.object({
|
||||
url: zod.string().describe('The URL to open in the new tab'),
|
||||
inputSchema: z.object({
|
||||
url: z.string().describe('The URL to open in the new tab'),
|
||||
}),
|
||||
execute: async (input: unknown) => {
|
||||
const { url } = input as { url: string }
|
||||
@@ -42,8 +42,8 @@ export function createTabTools(tabsController: TabsController): Record<string, T
|
||||
switch_to_tab: {
|
||||
description:
|
||||
'Switch to an existing tab by its ID. After switching, all page operations will target the new current tab. You can only switch to tabs in the tab list shown in browser state.',
|
||||
inputSchema: zod.object({
|
||||
tab_id: zod.number().int().describe('The tab ID to switch to'),
|
||||
inputSchema: z.object({
|
||||
tab_id: z.number().int().describe('The tab ID to switch to'),
|
||||
}),
|
||||
execute: async (input: unknown) => {
|
||||
const { tab_id } = input as { tab_id: number }
|
||||
@@ -58,8 +58,8 @@ export function createTabTools(tabsController: TabsController): Record<string, T
|
||||
close_tab: {
|
||||
description:
|
||||
'Close a tab by its ID. Cannot close the initial tab. Optionally specify which tab to switch to after closing.',
|
||||
inputSchema: zod.object({
|
||||
tab_id: zod.number().int().describe('The tab ID to close'),
|
||||
inputSchema: z.object({
|
||||
tab_id: z.number().int().describe('The tab ID to close'),
|
||||
}),
|
||||
execute: async (input: unknown) => {
|
||||
const { tab_id } = input as { tab_id: number }
|
||||
|
||||
@@ -11,7 +11,7 @@ import type { LLMConfig } from '@page-agent/llms'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
|
||||
import { MultiPageAgent } from './MultiPageAgent'
|
||||
import { DEMO_CONFIG } from './constants'
|
||||
import { DEMO_CONFIG, migrateLegacyEndpoint } from './constants'
|
||||
|
||||
/** Language preference: undefined means follow system */
|
||||
export type LanguagePreference = SupportedLanguage | undefined
|
||||
@@ -41,13 +41,19 @@ export function useAgent(): UseAgentResult {
|
||||
|
||||
useEffect(() => {
|
||||
chrome.storage.local.get(['llmConfig', 'language']).then((result) => {
|
||||
const llmConfig = (result.llmConfig as LLMConfig) ?? DEMO_CONFIG
|
||||
let llmConfig = (result.llmConfig as LLMConfig) ?? DEMO_CONFIG
|
||||
const language = (result.language as SupportedLanguage) || undefined
|
||||
const full = { ...llmConfig, language }
|
||||
if (!result.llmConfig) {
|
||||
|
||||
// Auto-migrate legacy testing endpoints
|
||||
const migrated = migrateLegacyEndpoint(llmConfig)
|
||||
if (migrated !== llmConfig) {
|
||||
llmConfig = migrated
|
||||
chrome.storage.local.set({ llmConfig: migrated })
|
||||
} else if (!result.llmConfig) {
|
||||
chrome.storage.local.set({ llmConfig: DEMO_CONFIG })
|
||||
}
|
||||
setConfig(full)
|
||||
|
||||
setConfig({ ...llmConfig, language })
|
||||
})
|
||||
}, [])
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Copy, CornerUpLeft, Eye, EyeOff, HatGlasses, Home, Loader2, Scale } fro
|
||||
import { useEffect, useState } from 'react'
|
||||
import { siGithub } from 'simple-icons'
|
||||
|
||||
import { DEMO_API_KEY, DEMO_BASE_URL, DEMO_MODEL } from '@/agent/constants'
|
||||
import { DEMO_API_KEY, DEMO_BASE_URL, DEMO_MODEL, isTestingEndpoint } from '@/agent/constants'
|
||||
import type { ExtConfig, LanguagePreference } from '@/agent/useAgent'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
@@ -87,6 +87,23 @@ export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) {
|
||||
</Button>
|
||||
</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 */}
|
||||
<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>
|
||||
@@ -219,7 +236,7 @@ export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) {
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://github.com/alibaba/page-agent/blob/main/packages/extension/PRIVACY.md"
|
||||
href="https://github.com/alibaba/page-agent/blob/main/docs/terms-and-privacy.md"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center gap-1 hover:text-foreground"
|
||||
|
||||
@@ -125,7 +125,7 @@ export function EmptyState() {
|
||||
</svg>
|
||||
</a>
|
||||
<a
|
||||
href="https://alibaba.github.io/page-agent/#/docs/features/chrome-extension"
|
||||
href="https://alibaba.github.io/page-agent/docs/features/chrome-extension"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="hover:text-foreground transition-colors"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@page-agent/llms",
|
||||
"version": "1.3.0",
|
||||
"version": "1.4.0",
|
||||
"type": "module",
|
||||
"main": "./dist/lib/page-agent-llms.js",
|
||||
"module": "./dist/lib/page-agent-llms.js",
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
/**
|
||||
* OpenAI Client implementation
|
||||
*/
|
||||
import * as z from 'zod'
|
||||
|
||||
import { InvokeError, InvokeErrorType } from './errors'
|
||||
import type { InvokeOptions, InvokeResult, LLMClient, LLMConfig, Message, Tool } from './types'
|
||||
import { modelPatch, zodToOpenAITool } from './utils'
|
||||
@@ -182,7 +184,7 @@ export class OpenAIClient implements LLMClient {
|
||||
// Validate with schema
|
||||
const validation = tool.inputSchema.safeParse(parsedArgs)
|
||||
if (!validation.success) {
|
||||
console.error(validation.error)
|
||||
console.error(z.prettifyError(validation.error))
|
||||
throw new InvokeError(
|
||||
InvokeErrorType.INVALID_TOOL_ARGS,
|
||||
'Tool arguments validation failed',
|
||||
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
/// <reference types="vite/client" />
|
||||
@@ -1,16 +1,17 @@
|
||||
import { OpenAIClient } from './OpenAIClient'
|
||||
import { DEFAULT_TEMPERATURE, LLM_MAX_RETRIES } from './constants'
|
||||
import { InvokeError } from './errors'
|
||||
import { InvokeError, InvokeErrorType } from './errors'
|
||||
import type { InvokeOptions, InvokeResult, LLMClient, LLMConfig, Message, Tool } from './types'
|
||||
|
||||
export type { InvokeError, InvokeOptions, InvokeResult, LLMClient, LLMConfig, Message, Tool }
|
||||
export { InvokeError, InvokeErrorType }
|
||||
export type { InvokeOptions, InvokeResult, LLMClient, LLMConfig, Message, Tool }
|
||||
|
||||
export function parseLLMConfig(config: LLMConfig): Required<LLMConfig> {
|
||||
// Runtime validation as defensive programming (types already guarantee these)
|
||||
if (!config.baseURL || !config.apiKey || !config.model) {
|
||||
throw new Error(
|
||||
'[PageAgent] LLM configuration required. Please provide: baseURL, apiKey, model. ' +
|
||||
'See: https://alibaba.github.io/page-agent/#/docs/features/models'
|
||||
'See: https://alibaba.github.io/page-agent/docs/features/models'
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Core types for LLM integration
|
||||
*/
|
||||
import type { z } from 'zod'
|
||||
import type * as z from 'zod'
|
||||
|
||||
/**
|
||||
* Message format - OpenAI standard (industry standard)
|
||||
|
||||
@@ -38,6 +38,7 @@ export function modelPatch(body: Record<string, any>) {
|
||||
if (modelName.startsWith('qwen')) {
|
||||
debug('Applying Qwen patch: use higher temperature for auto fixing')
|
||||
body.temperature = Math.max(body.temperature || 0, 1.0)
|
||||
body.enable_thinking = false
|
||||
}
|
||||
|
||||
if (modelName.startsWith('claude')) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "page-agent",
|
||||
"private": false,
|
||||
"version": "1.3.0",
|
||||
"version": "1.4.0",
|
||||
"type": "module",
|
||||
"main": "./dist/esm/page-agent.js",
|
||||
"module": "./dist/esm/page-agent.js",
|
||||
@@ -44,10 +44,10 @@
|
||||
"postpublish": "node -e \"['README.md','LICENSE'].forEach(f=>{try{require('fs').unlinkSync(f)}catch{}})\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@page-agent/core": "1.3.0",
|
||||
"@page-agent/llms": "1.3.0",
|
||||
"@page-agent/page-controller": "1.3.0",
|
||||
"@page-agent/ui": "1.3.0",
|
||||
"@page-agent/core": "1.4.0",
|
||||
"@page-agent/llms": "1.4.0",
|
||||
"@page-agent/page-controller": "1.4.0",
|
||||
"@page-agent/ui": "1.4.0",
|
||||
"chalk": "^5.6.2",
|
||||
"zod": "^4.3.5"
|
||||
}
|
||||
|
||||
@@ -13,9 +13,9 @@ window.PageAgent = PageAgent
|
||||
|
||||
console.log('🚀 page-agent.js loaded!')
|
||||
|
||||
const DEMO_MODEL = 'PAGE-AGENT-FREE-TESTING-RANDOM'
|
||||
const DEMO_BASE_URL = 'https://hwcxiuzfylggtcktqgij.supabase.co/functions/v1/llm-testing-proxy'
|
||||
const DEMO_API_KEY = 'PAGE-AGENT-FREE-TESTING-RANDOM'
|
||||
const DEMO_MODEL = 'qwen3.5-plus'
|
||||
const DEMO_BASE_URL = 'https://page-ag-testing-ohftxirgbn.cn-shanghai.fcapp.run'
|
||||
const DEMO_API_KEY = 'NA'
|
||||
|
||||
// in case document.x is not ready yet
|
||||
setTimeout(() => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@page-agent/page-controller",
|
||||
"version": "1.3.0",
|
||||
"version": "1.4.0",
|
||||
"type": "module",
|
||||
"main": "./dist/lib/page-controller.js",
|
||||
"module": "./dist/lib/page-controller.js",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@page-agent/ui",
|
||||
"version": "1.3.0",
|
||||
"version": "1.4.0",
|
||||
"type": "module",
|
||||
"main": "./dist/lib/page-agent-ui.js",
|
||||
"module": "./dist/lib/page-agent-ui.js",
|
||||
|
||||
+40
-24
@@ -5,9 +5,9 @@
|
||||
- **React** with TypeScript
|
||||
- **Vite** for dev server and build
|
||||
- **Tailwind CSS** for styling
|
||||
- **shadcn/ui** (new-york style) for UI components
|
||||
- **shadcn/ui** (new-york style) for UI components — **do NOT hand-edit `src/components/ui/` files**
|
||||
- **Magic UI** for animations and effects
|
||||
- **wouter** with hash routing for static hosting
|
||||
- **wouter** with browser routing (`base: "/page-agent"`)
|
||||
- **lucide-react** for icons
|
||||
|
||||
## Component Guidelines
|
||||
@@ -53,58 +53,74 @@ Located in `src/components/ui/`:
|
||||
### Styling Rules
|
||||
|
||||
1. **Prefer Tailwind classes** over custom CSS
|
||||
2. Use CSS modules only for complex component-specific styles
|
||||
3. Support dark mode via `dark:` classes
|
||||
4. Use CSS variables from `src/index.css` for theme colors
|
||||
2. Support dark mode via `dark:` classes
|
||||
3. Use CSS variables from `src/index.css` for theme colors
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
src/
|
||||
├── pages/
|
||||
│ ├── Home.tsx # Homepage
|
||||
│ ├── home/
|
||||
│ │ ├── HomeContent.tsx # Homepage sections
|
||||
│ │ └── ...Section.tsx
|
||||
│ └── docs/
|
||||
│ ├── Layout.tsx # Documentation sidebar
|
||||
│ ├── index.tsx # Docs route switch
|
||||
│ ├── Layout.tsx # Sidebar navigation
|
||||
│ └── [section]/[topic]/page.tsx
|
||||
├── components/
|
||||
│ ├── ui/ # shadcn/ui + Magic UI components
|
||||
│ ├── ui/ # shadcn/ui + Magic UI (DO NOT hand-edit)
|
||||
│ ├── Heading.tsx # Anchor heading for doc pages
|
||||
│ ├── Header.tsx # Site header
|
||||
│ └── Footer.tsx # Site footer
|
||||
├── i18n/ # Internationalization
|
||||
├── router.tsx # Central routing
|
||||
├── router.tsx # Root layout + routing
|
||||
└── main.tsx # App entry
|
||||
```
|
||||
|
||||
## Routing
|
||||
|
||||
Uses wouter browser routing with base path for GitHub Pages deployment at `https://alibaba.github.io/page-agent/`.
|
||||
|
||||
```tsx
|
||||
// main.tsx
|
||||
<Router base="/page-agent">
|
||||
<PagesRouter />
|
||||
</Router>
|
||||
```
|
||||
|
||||
**Key rules:**
|
||||
|
||||
- Header and Footer live in `router.tsx` **outside** `<Switch>`, so they always see the root router context (`base="/page-agent"`)
|
||||
- Docs pages are nested via `<Route path="/docs" nest>`, which creates a child context (`base="/page-agent/docs"`)
|
||||
- Inside the docs nest, Link hrefs are relative to `/docs` (e.g. `href="/features/models"`, NOT `href="/docs/features/models"`)
|
||||
- **Never use `~` prefix** in Link hrefs — it bypasses the base path entirely
|
||||
- Doc page headings use `<Heading id="slug" level={2}>` for anchor links
|
||||
|
||||
### SPA on GitHub Pages
|
||||
|
||||
Instead of `404.html` redirects, the build copies `index.html` into every route directory. Add new routes to the `SPA_ROUTES` array in `vite.config.js`.
|
||||
|
||||
## Adding New Pages
|
||||
|
||||
### Documentation Page
|
||||
|
||||
1. Create `src/pages/docs/<section>/<slug>/page.tsx`
|
||||
2. Add route to `src/router.tsx` with `<Header /> + <DocsLayout>` wrapper
|
||||
3. Add navigation item to `pages/docs/Layout.tsx`
|
||||
|
||||
## Routing
|
||||
|
||||
Uses hash-based routing for static hosting:
|
||||
|
||||
```tsx
|
||||
import { Router } from 'wouter'
|
||||
import { useHashLocation } from 'wouter/use-hash-location'
|
||||
|
||||
;<Router hook={useHashLocation}>{/* routes */}</Router>
|
||||
```
|
||||
2. Add route in `src/pages/docs/index.tsx`
|
||||
3. Add navigation item in `src/pages/docs/Layout.tsx`
|
||||
4. Add path to `SPA_ROUTES` in `vite.config.js`
|
||||
|
||||
## Configuration Files
|
||||
|
||||
| File | Purpose |
|
||||
| ----------------- | ----------------------- |
|
||||
| `components.json` | shadcn/ui configuration |
|
||||
| `vite.config.js` | Vite build settings |
|
||||
| `vite.config.js` | Vite build + SPA routes |
|
||||
| `tsconfig.json` | TypeScript config |
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
npm start # Dev server (from root)
|
||||
npm start # Dev server (from root)
|
||||
npm run build:website # Build website (from root)
|
||||
```
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@page-agent/website",
|
||||
"private": true,
|
||||
"version": "1.3.0",
|
||||
"version": "1.4.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --host 0.0.0.0",
|
||||
@@ -15,19 +15,19 @@
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"@radix-ui/react-switch": "^1.2.6",
|
||||
"@radix-ui/react-tooltip": "^1.2.8",
|
||||
"@types/react": "^19.2.13",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.1",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"lucide-react": "^0.563.0",
|
||||
"motion": "^12.34.0",
|
||||
"lucide-react": "^0.575.0",
|
||||
"motion": "^12.34.3",
|
||||
"next-themes": "^0.4.6",
|
||||
"react": "^19.2.4",
|
||||
"react-dom": "^19.2.4",
|
||||
"rough-notation": "^0.5.1",
|
||||
"simple-icons": "^16.8.0",
|
||||
"simple-icons": "^16.9.0",
|
||||
"sonner": "^2.0.7",
|
||||
"tailwind-merge": "^3.4.0",
|
||||
"tailwind-merge": "^3.5.0",
|
||||
"tailwindcss": "^4.1.14",
|
||||
"tw-animate-css": "^1.4.0",
|
||||
"wouter": "^3.9.0"
|
||||
|
||||
@@ -16,6 +16,14 @@ export default function Footer() {
|
||||
© 2026 page-agent. All rights reserved.
|
||||
</p>
|
||||
<div className="flex items-center space-x-6">
|
||||
<a
|
||||
href="https://github.com/alibaba/page-agent/blob/main/docs/terms-and-privacy.md"
|
||||
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 text-sm"
|
||||
>
|
||||
{isZh ? '使用条款与隐私' : 'Terms & Privacy'}
|
||||
</a>
|
||||
<a
|
||||
href="https://github.com/alibaba/page-agent"
|
||||
target="_blank"
|
||||
|
||||
@@ -23,7 +23,7 @@ export default function Header() {
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
{/* Logo */}
|
||||
<Link
|
||||
href="~/"
|
||||
href="/"
|
||||
className="flex items-center gap-2 sm:gap-3 group shrink-0"
|
||||
aria-label={isZh ? 'page-agent 首页' : 'page-agent home'}
|
||||
onClick={() => setMobileMenuOpen(false)}
|
||||
@@ -44,7 +44,7 @@ export default function Header() {
|
||||
animateOnHover={true}
|
||||
aria-hidden="true"
|
||||
>
|
||||
GUI Agent in your webpage
|
||||
AI Agent In Your Webpage
|
||||
</HyperText>
|
||||
</div>
|
||||
</Link>
|
||||
@@ -56,7 +56,7 @@ export default function Header() {
|
||||
aria-label="Mobile navigation"
|
||||
>
|
||||
<Link
|
||||
href="~/docs/introduction/overview"
|
||||
href="/docs/introduction/overview"
|
||||
className="p-2 rounded-lg text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-blue-600 dark:hover:text-blue-400 transition-colors duration-200 shrink-0"
|
||||
aria-label={isZh ? '文档' : 'Docs'}
|
||||
>
|
||||
@@ -90,7 +90,7 @@ export default function Header() {
|
||||
{import.meta.env.VERSION}
|
||||
</span>
|
||||
<Link
|
||||
href="~/docs/introduction/overview"
|
||||
href="/docs/introduction/overview"
|
||||
className="flex items-center gap-1.5 text-gray-600 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 transition-colors duration-200"
|
||||
>
|
||||
<BookOpen className="w-4 h-4" />
|
||||
@@ -138,7 +138,7 @@ export default function Header() {
|
||||
role="navigation"
|
||||
>
|
||||
<Link
|
||||
href="~/docs/introduction/overview"
|
||||
href="/docs/introduction/overview"
|
||||
className="flex items-center gap-2 px-3 py-2 rounded-lg text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-blue-600 dark:hover:text-blue-400 transition-colors duration-200"
|
||||
onClick={() => setMobileMenuOpen(false)}
|
||||
>
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { ComponentPropsWithoutRef, useEffect, useRef } from 'react'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
type Level = 2 | 3
|
||||
|
||||
interface HeadingProps extends Omit<ComponentPropsWithoutRef<'h2'>, 'children'> {
|
||||
id: string
|
||||
level?: Level
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
const levelStyles = {
|
||||
2: { tag: 'h2', className: 'text-2xl font-semibold mb-4' },
|
||||
3: { tag: 'h3', className: 'text-xl font-semibold mb-3' },
|
||||
} as const
|
||||
|
||||
export function Heading({ id, level = 2, className, children, ...props }: HeadingProps) {
|
||||
const ref = useRef<HTMLHeadingElement>(null)
|
||||
const { tag: Tag, className: defaultClassName } = levelStyles[level]
|
||||
|
||||
useEffect(() => {
|
||||
if (window.location.hash === `#${id}`) {
|
||||
ref.current?.scrollIntoView({ behavior: 'smooth' })
|
||||
}
|
||||
}, [id])
|
||||
|
||||
return (
|
||||
<Tag
|
||||
ref={ref}
|
||||
id={id}
|
||||
className={cn('group relative scroll-mt-20', defaultClassName, className)}
|
||||
{...props}
|
||||
>
|
||||
<a
|
||||
href={`#${id}`}
|
||||
className="absolute -left-5 top-1/2 -translate-y-1/2 opacity-0 group-hover:opacity-100 text-gray-400 hover:text-blue-500 transition-opacity no-underline"
|
||||
aria-label={`Link to ${id}`}
|
||||
>
|
||||
#
|
||||
</a>
|
||||
{children}
|
||||
</Tag>
|
||||
)
|
||||
}
|
||||
@@ -85,7 +85,7 @@ export function MagicCard({
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
<div className="bg-background absolute inset-px rounded-[inherit]" />
|
||||
<div className="absolute inset-px rounded-[inherit] bg-white dark:bg-neutral-900" />
|
||||
<motion.div
|
||||
className="pointer-events-none absolute inset-px rounded-[inherit] opacity-0 transition-opacity duration-300 group-hover:opacity-100"
|
||||
style={{
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import { ComponentPropsWithoutRef } from 'react'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
interface MarqueeProps extends ComponentPropsWithoutRef<'div'> {
|
||||
/**
|
||||
* Optional CSS class name to apply custom styles
|
||||
*/
|
||||
className?: string
|
||||
/**
|
||||
* Whether to reverse the animation direction
|
||||
* @default false
|
||||
*/
|
||||
reverse?: boolean
|
||||
/**
|
||||
* Whether to pause the animation on hover
|
||||
* @default false
|
||||
*/
|
||||
pauseOnHover?: boolean
|
||||
/**
|
||||
* Content to be displayed in the marquee
|
||||
*/
|
||||
children: React.ReactNode
|
||||
/**
|
||||
* Whether to animate vertically instead of horizontally
|
||||
* @default false
|
||||
*/
|
||||
vertical?: boolean
|
||||
/**
|
||||
* Number of times to repeat the content
|
||||
* @default 4
|
||||
*/
|
||||
repeat?: number
|
||||
}
|
||||
|
||||
export function Marquee({
|
||||
className,
|
||||
reverse = false,
|
||||
pauseOnHover = false,
|
||||
children,
|
||||
vertical = false,
|
||||
repeat = 4,
|
||||
...props
|
||||
}: MarqueeProps) {
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
className={cn(
|
||||
'group flex [gap:var(--gap)] overflow-hidden p-2 [--duration:40s] [--gap:1rem]',
|
||||
{
|
||||
'flex-row': !vertical,
|
||||
'flex-col': vertical,
|
||||
},
|
||||
className
|
||||
)}
|
||||
>
|
||||
{Array(repeat)
|
||||
.fill(0)
|
||||
.map((_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className={cn('flex shrink-0 justify-around [gap:var(--gap)]', {
|
||||
'animate-marquee flex-row': !vertical,
|
||||
'animate-marquee-vertical flex-col': vertical,
|
||||
'group-hover:[animation-play-state:paused]': pauseOnHover,
|
||||
'[animation-direction:reverse]': reverse,
|
||||
})}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
// Demo build (auto-init with demo LLM, for quick testing)
|
||||
export const CDN_DEMO_URL =
|
||||
'https://cdn.jsdelivr.net/npm/page-agent@1.3.0/dist/iife/page-agent.demo.js'
|
||||
'https://cdn.jsdelivr.net/npm/page-agent@1.4.0/dist/iife/page-agent.demo.js'
|
||||
export const CDN_DEMO_CN_URL =
|
||||
'https://registry.npmmirror.com/page-agent/1.3.0/files/dist/iife/page-agent.demo.js'
|
||||
'https://registry.npmmirror.com/page-agent/1.4.0/files/dist/iife/page-agent.demo.js'
|
||||
|
||||
// Demo LLM for website testing
|
||||
export const DEMO_MODEL = 'PAGE-AGENT-FREE-TESTING-RANDOM'
|
||||
export const DEMO_BASE_URL =
|
||||
'https://hwcxiuzfylggtcktqgij.supabase.co/functions/v1/llm-testing-proxy'
|
||||
export const DEMO_API_KEY = 'PAGE-AGENT-FREE-TESTING-RANDOM'
|
||||
// Demo LLM for website testing (homepage quick trial uses flash)
|
||||
export const DEMO_MODEL = 'qwen3.5-flash'
|
||||
export const DEMO_BASE_URL = 'https://page-ag-testing-ohftxirgbn.cn-shanghai.fcapp.run'
|
||||
export const DEMO_API_KEY = 'NA'
|
||||
|
||||
@@ -377,6 +377,24 @@ td {
|
||||
background-position: bottom center;
|
||||
}
|
||||
}
|
||||
--animate-marquee: marquee var(--duration) infinite linear;
|
||||
--animate-marquee-vertical: marquee-vertical var(--duration) linear infinite;
|
||||
@keyframes marquee {
|
||||
from {
|
||||
transform: translateX(0);
|
||||
}
|
||||
to {
|
||||
transform: translateX(calc(-100% - var(--gap)));
|
||||
}
|
||||
}
|
||||
@keyframes marquee-vertical {
|
||||
from {
|
||||
transform: translateY(0);
|
||||
}
|
||||
to {
|
||||
transform: translateY(calc(-100% - var(--gap)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* shadcn dark mode */
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import { Router } from 'wouter'
|
||||
import { useHashLocation } from 'wouter/use-hash-location'
|
||||
|
||||
import { LanguageProvider } from './i18n/context'
|
||||
import { default as PagesRouter } from './router'
|
||||
|
||||
import './index.css'
|
||||
|
||||
// Redirect legacy hash routes (e.g. /#/docs/foo) to clean paths
|
||||
const { hash } = window.location
|
||||
if (hash.length > 1 && hash.includes('/')) {
|
||||
const path = hash.replace(/^#\/?/, '/')
|
||||
history.replaceState(null, '', '/page-agent' + path)
|
||||
}
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<LanguageProvider>
|
||||
<Router hook={useHashLocation}>
|
||||
<Router base="/page-agent">
|
||||
<PagesRouter />
|
||||
</Router>
|
||||
</LanguageProvider>
|
||||
|
||||
@@ -1,586 +1,15 @@
|
||||
/* eslint-disable react-dom/no-dangerously-set-innerhtml */
|
||||
import { Bot, Box, MessageSquare, PlayCircle, Shield, Sparkles, Users, Zap } from 'lucide-react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Link, useSearchParams } from 'wouter'
|
||||
|
||||
import Footer from '../components/Footer'
|
||||
import Header from '../components/Header'
|
||||
import { AnimatedGradientText } from '../components/ui/animated-gradient-text'
|
||||
import { Highlighter } from '../components/ui/highlighter'
|
||||
import { NeonGradientCard } from '../components/ui/neon-gradient-card'
|
||||
import { Particles } from '../components/ui/particles'
|
||||
import { SparklesText } from '../components/ui/sparkles-text'
|
||||
import {
|
||||
CDN_DEMO_CN_URL,
|
||||
CDN_DEMO_URL,
|
||||
DEMO_API_KEY,
|
||||
DEMO_BASE_URL,
|
||||
DEMO_MODEL,
|
||||
} from '../constants'
|
||||
import { useLanguage } from '../i18n/context'
|
||||
|
||||
function getInjection(useCN?: boolean) {
|
||||
const cdn = useCN ? CDN_DEMO_CN_URL : CDN_DEMO_URL
|
||||
|
||||
const injection = encodeURI(
|
||||
`javascript:(function(){var s=document.createElement('script');s.src=\`${cdn}?t=\${Math.random()}\`;s.setAttribute('crossorigin', true);s.type="text/javascript";s.onload=()=>console.log('PageAgent script loaded!');document.body.appendChild(s);})();`
|
||||
)
|
||||
|
||||
return `
|
||||
<a
|
||||
href=${injection}
|
||||
class="inline-flex items-center text-xs px-3 py-2 bg-blue-500 text-white font-medium rounded-lg hover:shadow-md transform hover:scale-105 transition-all duration-200 cursor-move border-2 border-dashed border-green-300"
|
||||
draggable="true"
|
||||
onclick="return false;"
|
||||
title="Drag me to your bookmarks bar!"
|
||||
>
|
||||
✨PageAgent
|
||||
</a>
|
||||
`
|
||||
}
|
||||
import FeaturesSection from './home/FeaturesSection'
|
||||
import HeroSection from './home/HeroSection'
|
||||
import OneMoreThingSection from './home/OneMoreThingSection'
|
||||
import ScenariosSection from './home/ScenariosSection'
|
||||
|
||||
export default function HomePage() {
|
||||
const { language, isZh } = useLanguage()
|
||||
|
||||
const defaultTask = isZh
|
||||
? '从导航栏中进入文档页,打开"快速开始"相关的文档,帮我总结成 markdown'
|
||||
: 'Goto docs in navigation bar, find Quick-Start section, and summarize in markdown'
|
||||
|
||||
const [task, setTask] = useState(() => defaultTask)
|
||||
|
||||
// Update task when language changes
|
||||
useEffect(() => {
|
||||
setTask(defaultTask)
|
||||
}, [defaultTask])
|
||||
|
||||
const [params, setParams] = useSearchParams()
|
||||
const isOther = params.has('try_other')
|
||||
|
||||
const [activeTab, setActiveTab] = useState<'try' | 'other'>(isOther ? 'other' : 'try')
|
||||
const [cdnSource, setCdnSource] = useState<'international' | 'china'>('international')
|
||||
|
||||
const handleExecute = async () => {
|
||||
if (!task.trim()) return
|
||||
|
||||
const win = window as any
|
||||
|
||||
// Lazy load PageAgent only when user clicks execute
|
||||
if (!win.pageAgent || win.pageAgent.disposed) {
|
||||
const { PageAgent } = await import('page-agent')
|
||||
win.pageAgent = new PageAgent({
|
||||
// 把 react 根元素排除掉,挂了很多冒泡时间导致假阳
|
||||
interactiveBlacklist: [document.getElementById('root')!],
|
||||
language: language,
|
||||
|
||||
instructions: {
|
||||
system: 'You are a helpful assistant on PageAgent website.',
|
||||
getPageInstructions: (url) => {
|
||||
const hint = url.includes('page-agent') ? 'This is PageAgent demo page.' : undefined
|
||||
console.log('[instructions] getPageInstructions:', url, '->', hint)
|
||||
return hint
|
||||
},
|
||||
},
|
||||
|
||||
model:
|
||||
import.meta.env.DEV && import.meta.env.LLM_MODEL_NAME
|
||||
? import.meta.env.LLM_MODEL_NAME
|
||||
: DEMO_MODEL,
|
||||
baseURL:
|
||||
import.meta.env.DEV && import.meta.env.LLM_BASE_URL
|
||||
? import.meta.env.LLM_BASE_URL
|
||||
: DEMO_BASE_URL,
|
||||
apiKey:
|
||||
import.meta.env.DEV && import.meta.env.LLM_API_KEY
|
||||
? import.meta.env.LLM_API_KEY
|
||||
: DEMO_API_KEY,
|
||||
|
||||
// enableAskUser: false,
|
||||
// promptForNextTask: false,
|
||||
// enablePanel: false,
|
||||
})
|
||||
}
|
||||
|
||||
const result = await win.pageAgent.execute(task)
|
||||
console.log(result)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-linear-to-br from-blue-50 to-purple-50 dark:from-gray-900 dark:to-gray-800">
|
||||
<Header />
|
||||
|
||||
{/* Hero Section */}
|
||||
<main id="main-content">
|
||||
<section
|
||||
className="relative px-6 py-22 pb-18 lg:py-28 overflow-hidden"
|
||||
aria-labelledby="hero-heading"
|
||||
>
|
||||
<div className="max-w-7xl mx-auto text-center">
|
||||
{/* Background Pattern + Particles */}
|
||||
<div className="absolute inset-0 opacity-30" aria-hidden="true">
|
||||
<div className="absolute inset-0 bg-linear-to-r from-blue-400/20 to-purple-400/20 rounded-3xl transform rotate-1"></div>
|
||||
<div className="absolute inset-0 bg-linear-to-l from-purple-400/20 to-blue-400/20 rounded-3xl transform -rotate-1"></div>
|
||||
</div>
|
||||
<Particles
|
||||
className="absolute inset-0"
|
||||
quantity={80}
|
||||
staticity={30}
|
||||
ease={80}
|
||||
color="#6366f1"
|
||||
/>
|
||||
|
||||
<div className="relative z-10">
|
||||
<div className="inline-flex items-center px-4 py-2 mb-8 text-sm font-medium bg-white/90 dark:bg-gray-800/90 rounded-full shadow-lg border border-gray-200 dark:border-gray-700">
|
||||
<span
|
||||
className="w-2 h-2 bg-blue-500 rounded-full mr-2 animate-pulse"
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
<AnimatedGradientText colorFrom="#3b82f6" colorTo="#8b5cf6">
|
||||
GUI Agent in your webpage
|
||||
</AnimatedGradientText>
|
||||
</div>
|
||||
|
||||
<h1
|
||||
id="hero-heading"
|
||||
className="text-5xl lg:text-7xl font-bold mb-8 bg-linear-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent pb-1"
|
||||
>
|
||||
{isZh ? '让你的 Web 应用' : 'The AI Operator'}
|
||||
<br />
|
||||
{isZh ? '拥有 AI 操作员' : 'Living in Your Web App'}
|
||||
</h1>
|
||||
|
||||
<p className="text-xl lg:text-2xl text-gray-600 dark:text-gray-300 mb-12 max-w-4xl mx-auto leading-relaxed">
|
||||
<Highlighter action="underline" color="#8b5cf6" strokeWidth={2}>
|
||||
<span className="bg-linear-to-r from-blue-500 to-purple-500 bg-clip-text text-transparent font-bold">
|
||||
{isZh ? '🪄一行代码' : '🪄One line of code'}
|
||||
</span>
|
||||
</Highlighter>
|
||||
{isZh
|
||||
? ',为你的网站添加 GUI Agent。'
|
||||
: ' adds intelligent GUI Agents to your website.'}
|
||||
<br />
|
||||
{isZh
|
||||
? '用户/答疑机器人给出文字指示,AI 帮你操作页面。'
|
||||
: 'Users give natural language commands, AI handles the rest.'}
|
||||
</p>
|
||||
|
||||
{/* Try It Now Section - Tab Card */}
|
||||
<div className="mb-12">
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<NeonGradientCard
|
||||
borderSize={2}
|
||||
borderRadius={20}
|
||||
neonColors={{ firstColor: '#ff00aa', secondColor: '#00FFF1' }}
|
||||
>
|
||||
{/* Tab Headers */}
|
||||
<div className="flex border-b border-gray-200 dark:border-gray-700">
|
||||
<button
|
||||
onClick={() => setActiveTab('try')}
|
||||
className={`flex-1 px-4 py-4 text-lg font-medium transition-colors duration-200 rounded-tl-2xl ${
|
||||
activeTab === 'try'
|
||||
? 'bg-linear-to-r from-blue-50 to-purple-50 dark:from-blue-900/30 dark:to-purple-900/30 text-blue-700 dark:text-blue-300 border-b-2 border-blue-500'
|
||||
: 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:bg-gray-50 dark:hover:bg-gray-700'
|
||||
}`}
|
||||
>
|
||||
{isZh ? '🚀 立即尝试' : '🚀 Try It Now'}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('other')}
|
||||
className={`flex-1 px-4 py-4 text-lg font-medium transition-colors duration-200 rounded-tr-2xl ${
|
||||
activeTab === 'other'
|
||||
? 'bg-linear-to-r from-green-50 to-blue-50 dark:from-green-900/30 dark:to-blue-900/30 text-green-700 dark:text-green-300 border-b-2 border-green-500'
|
||||
: 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:bg-gray-50 dark:hover:bg-gray-700'
|
||||
}`}
|
||||
>
|
||||
{isZh ? '🌐 其他网页尝试' : '🌐 Try on Other Sites'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Tab Content */}
|
||||
<div className="p-4">
|
||||
{activeTab === 'try' && (
|
||||
<div className="space-y-4">
|
||||
<div className="relative">
|
||||
<input
|
||||
value={task}
|
||||
onChange={(e) => setTask(e.target.value)}
|
||||
placeholder={
|
||||
isZh
|
||||
? '输入您想要 AI 执行的任务...'
|
||||
: 'Describe what you want AI to do...'
|
||||
}
|
||||
className="w-full px-4 py-3 pr-20 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-500 dark:placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-none text-sm mb-0"
|
||||
data-page-agent-not-interactive
|
||||
/>
|
||||
<button
|
||||
onClick={handleExecute}
|
||||
// disabled
|
||||
// disabled={!task.trim()}
|
||||
className="absolute right-2 top-2 px-5 py-1.5 bg-linear-to-r from-blue-600 to-purple-600 text-white font-medium rounded-md hover:shadow-md transform hover:scale-105 transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none text-sm"
|
||||
data-page-agent-not-interactive
|
||||
>
|
||||
{isZh ? '执行' : 'Run'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === 'other' && (
|
||||
<div className="grid md:grid-cols-2 gap-6">
|
||||
{/* 左侧:操作步骤 */}
|
||||
<div className="space-y-4">
|
||||
{/* Keyboard Shortcut Hint */}
|
||||
<div className="bg-blue-50 dark:bg-gray-700 p-4 rounded-lg">
|
||||
<p className="text-gray-700 dark:text-gray-300 text-sm mb-3">
|
||||
<span className="font-semibold">
|
||||
{isZh ? '步骤 1:' : 'Step 1:'}
|
||||
</span>{' '}
|
||||
{isZh ? '显示收藏夹栏' : 'Show your bookmarks bar'}
|
||||
</p>
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<kbd className="px-2 py-1 bg-white dark:bg-gray-600 border border-gray-300 dark:border-gray-500 rounded text-xs font-mono">
|
||||
Ctrl + Shift + B
|
||||
</kbd>
|
||||
<span className="text-gray-500 dark:text-gray-400">
|
||||
{isZh ? '或' : 'or'}
|
||||
</span>
|
||||
<kbd className="px-2 py-1 bg-white dark:bg-gray-600 border border-gray-300 dark:border-gray-500 rounded text-xs font-mono">
|
||||
⌘ + Shift + B
|
||||
</kbd>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Draggable Bookmarklet */}
|
||||
<div className="bg-green-50 dark:bg-gray-700 p-4 rounded-lg">
|
||||
<p className="text-gray-700 dark:text-gray-300 text-sm mb-3">
|
||||
<span className="font-semibold">
|
||||
{isZh ? '步骤 2:' : 'Step 2:'}
|
||||
</span>{' '}
|
||||
{isZh
|
||||
? '拖拽下面按钮到收藏夹栏'
|
||||
: 'Drag this button to your bookmarks'}
|
||||
</p>
|
||||
<div className="flex items-center justify-center gap-3">
|
||||
<select
|
||||
value={cdnSource}
|
||||
onChange={(e) =>
|
||||
setCdnSource(e.target.value as 'international' | 'china')
|
||||
}
|
||||
className="px-2 py-1.5 text-xs border border-gray-300 dark:border-gray-500 rounded bg-white dark:bg-gray-600 text-gray-700 dark:text-gray-200"
|
||||
>
|
||||
<option value="international">
|
||||
{isZh ? '国际' : 'International'}
|
||||
</option>
|
||||
<option value="china">
|
||||
{isZh ? '国内镜像' : 'China Mirror'}
|
||||
</option>
|
||||
</select>
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: getInjection(cdnSource === 'china'),
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Usage Instructions */}
|
||||
<div className="bg-purple-50 dark:bg-gray-700 p-4 rounded-lg">
|
||||
<p className="text-gray-700 dark:text-gray-300 text-sm">
|
||||
<span className="font-semibold">
|
||||
{isZh ? '步骤 3:' : 'Step 3:'}
|
||||
</span>{' '}
|
||||
{isZh
|
||||
? '在其他网站点击收藏夹中的按钮即可使用'
|
||||
: 'Click the bookmark on any site to activate'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 右侧:注意事项 */}
|
||||
<div className="bg-yellow-50 dark:bg-gray-700 p-4 rounded-lg">
|
||||
<h4 className="font-semibold text-gray-900 dark:text-white mb-3 text-sm">
|
||||
{isZh ? '⚠️ 注意' : '⚠️ Heads Up'}
|
||||
</h4>
|
||||
<ul className="space-y-2 text-sm text-gray-700 dark:text-gray-300">
|
||||
<li className="flex items-start text-left">
|
||||
<span className="w-1.5 h-1.5 bg-yellow-500 rounded-full mt-2 mr-2 shrink-0 "></span>
|
||||
{isZh
|
||||
? '仅做技术评估,链接定期失效'
|
||||
: 'Demo only—link may expire without notice'}
|
||||
</li>
|
||||
<li className="flex items-start text-left">
|
||||
<span className="w-1.5 h-1.5 bg-yellow-500 rounded-full mt-2 mr-2 shrink-0 "></span>
|
||||
{isZh
|
||||
? '使用 DeepSeek 模型,参考 DeepSeek 用户协议和隐私政策'
|
||||
: 'This free demo uses DeepSeek API (see their terms and privacy policy)'}
|
||||
</li>
|
||||
<li className="flex items-start text-left">
|
||||
<span className="w-1.5 h-1.5 bg-yellow-500 rounded-full mt-2 mr-2 shrink-0 "></span>
|
||||
{isZh
|
||||
? '部分网站屏蔽了链接嵌入,将无反应'
|
||||
: 'Some sites block script injection (CSP policies)'}
|
||||
</li>
|
||||
<li className="flex items-start text-left">
|
||||
<span className="w-1.5 h-1.5 bg-yellow-500 rounded-full mt-2 mr-2 shrink-0 "></span>
|
||||
{isZh
|
||||
? '仅支持单页应用,页面跳转后需要重新注入'
|
||||
: 'Works on single-page apps only—reload required after navigation'}
|
||||
</li>
|
||||
<li className="flex items-start text-left">
|
||||
<span className="w-1.5 h-1.5 bg-yellow-500 rounded-full mt-2 mr-2 shrink-0 "></span>
|
||||
{isZh
|
||||
? '仅识别文本,不识别图像,不支持拖拽等复杂交互'
|
||||
: 'Text-only understanding—no image recognition or drag-and-drop'}
|
||||
</li>
|
||||
<li className="flex items-start text-left">
|
||||
<span className="w-1.5 h-1.5 bg-yellow-500 rounded-full mt-2 mr-2 shrink-0 "></span>
|
||||
{isZh ? '详细使用限制参照' : 'Full limitations in'}{' '}
|
||||
<Link
|
||||
href="/docs/introduction/limitations"
|
||||
className="text-blue-600 dark:text-blue-400 hover:underline"
|
||||
>
|
||||
{isZh ? '《文档》' : 'Docs'}
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</NeonGradientCard>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul
|
||||
className="flex flex-wrap justify-center gap-6 text-sm text-gray-500 dark:text-gray-400"
|
||||
role="list"
|
||||
>
|
||||
<li className="flex items-center">
|
||||
<span
|
||||
className="w-2 h-2 bg-green-500 rounded-full mr-2"
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
{isZh ? '纯前端方案' : 'Pure Front-end Solution'}
|
||||
</li>
|
||||
<li className="flex items-center">
|
||||
<span
|
||||
className="w-2 h-2 bg-green-500 rounded-full mr-2"
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
{isZh ? '支持私有模型' : 'Your Own Models'}
|
||||
</li>
|
||||
<li className="flex items-center">
|
||||
<span
|
||||
className="w-2 h-2 bg-green-500 rounded-full mr-2"
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
{isZh ? '无痛脱敏' : 'Built-in Privacy'}
|
||||
</li>
|
||||
<li className="flex items-center">
|
||||
<span
|
||||
className="w-2 h-2 bg-green-500 rounded-full mr-2"
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
{isZh ? 'MIT 开源' : 'MIT Open Source'}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Features Section */}
|
||||
<section
|
||||
className="px-6 py-20 bg-white/50 dark:bg-gray-800/50 backdrop-blur-sm"
|
||||
aria-labelledby="features-heading"
|
||||
>
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-8" role="list">
|
||||
{/* Feature 1 */}
|
||||
<article
|
||||
className="group p-8 bg-linear-to-br from-blue-100 to-purple-100 dark:from-gray-700 dark:to-gray-800 rounded-2xl shadow-lg hover:shadow-xl transition-all duration-300 border border-gray-100 dark:border-gray-700"
|
||||
role="listitem"
|
||||
>
|
||||
<div
|
||||
className="w-14 h-14 bg-linear-to-br from-blue-500 to-blue-600 rounded-xl flex items-center justify-center mb-6 group-hover:scale-110 group-hover:rotate-3 transition-all duration-300 shadow-lg"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<Box className="w-7 h-7 text-white" strokeWidth={2.5} />
|
||||
</div>
|
||||
<h3 className="text-xl font-bold mb-4 text-gray-900 dark:text-white">
|
||||
{isZh ? '纯页面内方案' : 'In-page Solution'}
|
||||
</h3>
|
||||
<p className="text-gray-600 dark:text-gray-300 leading-relaxed">
|
||||
{isZh
|
||||
? '完全运行在你的页面内。不需要浏览器插件、不需要无头浏览器,不需要后端。'
|
||||
: 'Runs entirely within your page. No browser extensions, no headless browsers, and no backend required.'}
|
||||
</p>
|
||||
</article>
|
||||
|
||||
{/* Feature 2 */}
|
||||
<article
|
||||
className="group p-8 bg-linear-to-br from-purple-100 to-pink-100 dark:from-gray-700 dark:to-gray-800 rounded-2xl shadow-lg hover:shadow-xl transition-all duration-300 border border-gray-100 dark:border-gray-700"
|
||||
role="listitem"
|
||||
>
|
||||
<div
|
||||
className="w-14 h-14 bg-linear-to-br from-green-500 to-green-600 rounded-xl flex items-center justify-center mb-6 group-hover:scale-110 group-hover:rotate-3 transition-all duration-300 shadow-lg"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<Zap className="w-7 h-7 text-white fill-white" strokeWidth={2.5} />
|
||||
</div>
|
||||
<h3 className="text-xl font-bold mb-4 text-gray-900 dark:text-white">
|
||||
{isZh ? '零后端部署' : 'Zero Backend Setup'}
|
||||
</h3>
|
||||
<p className="text-gray-600 dark:text-gray-300 leading-relaxed">
|
||||
{isZh
|
||||
? '前端脚本引入,自定义 LLM 接入点。从 OpenAI 到 qwen3,完全由你掌控。'
|
||||
: 'Just drop in a script. Works with any LLM provider—OpenAI, Anthropic, or your own models.'}
|
||||
</p>
|
||||
</article>
|
||||
|
||||
{/* Feature 3 */}
|
||||
<article
|
||||
className="group p-8 bg-linear-to-br from-orange-100 to-red-100 dark:from-gray-700 dark:to-gray-800 rounded-2xl shadow-lg hover:shadow-xl transition-all duration-300 border border-gray-100 dark:border-gray-700"
|
||||
role="listitem"
|
||||
>
|
||||
<div
|
||||
className="w-14 h-14 bg-linear-to-br from-purple-500 to-purple-600 rounded-xl flex items-center justify-center mb-6 group-hover:scale-110 group-hover:rotate-3 transition-all duration-300 shadow-lg"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<MessageSquare className="w-7 h-7 text-white" strokeWidth={2.5} />
|
||||
</div>
|
||||
<h3 className="text-xl font-bold mb-4 text-gray-900 dark:text-white">
|
||||
{isZh ? '普惠智能交互' : 'Natural Language UI'}
|
||||
</h3>
|
||||
<p className="text-gray-600 dark:text-gray-300 leading-relaxed">
|
||||
{isZh
|
||||
? '为复杂 B端系统、管理后台提供自然语言入口。让每个用户都能轻松上手。'
|
||||
: 'Transform complex admin panels into chat interfaces. Make powerful tools accessible to everyone, not just experts.'}
|
||||
</p>
|
||||
</article>
|
||||
|
||||
{/* Feature 4 */}
|
||||
<article
|
||||
className="group p-8 bg-linear-to-br from-green-100 to-blue-100 dark:from-gray-700 dark:to-gray-800 rounded-2xl shadow-lg hover:shadow-xl transition-all duration-300 border border-gray-100 dark:border-gray-700"
|
||||
role="listitem"
|
||||
>
|
||||
<div
|
||||
className="w-14 h-14 bg-linear-to-br from-orange-500 to-orange-600 rounded-xl flex items-center justify-center mb-6 group-hover:scale-110 group-hover:rotate-3 transition-all duration-300 shadow-lg"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<Shield className="w-7 h-7 text-white" strokeWidth={2.5} />
|
||||
</div>
|
||||
<h3 className="text-xl font-bold mb-4 text-gray-900 dark:text-white">
|
||||
{isZh ? '安全可控集成' : 'Secure by Design'}
|
||||
</h3>
|
||||
<p className="text-gray-600 dark:text-gray-300 leading-relaxed">
|
||||
{isZh
|
||||
? '支持操作黑白名单、数据脱敏保护。注入自定义知识库,让 AI 按你的规则工作。'
|
||||
: 'Control what AI can access with allowlists, data masking, and custom knowledge injection. Your rules, your data.'}
|
||||
</p>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Use Cases Section */}
|
||||
<section className="px-6 py-20" aria-labelledby="use-cases-heading">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<div className="text-center mb-16">
|
||||
<SparklesText
|
||||
className="text-4xl lg:text-5xl mb-6"
|
||||
colors={{ first: '#3b82f6', second: '#8b5cf6' }}
|
||||
>
|
||||
{isZh ? '应用场景' : 'Where It Shines'}
|
||||
</SparklesText>
|
||||
<p className="text-xl text-gray-600 dark:text-gray-300 max-w-3xl mx-auto">
|
||||
{isZh
|
||||
? '从简单的表单填写到复杂的业务流程,AI 都能理解并执行'
|
||||
: 'From simple forms to complex workflows, AI understands and executes'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid lg:grid-cols-2 gap-12" role="list">
|
||||
{/* Use Case 1 */}
|
||||
<div className="group bg-linear-to-br from-blue-100 to-purple-100 dark:from-gray-700 dark:to-gray-800 p-8 rounded-2xl hover:shadow-xl transition-all duration-300">
|
||||
<div className="flex items-start space-x-4">
|
||||
<div className="w-12 h-12 bg-linear-to-br from-blue-500 to-purple-500 rounded-xl flex items-center justify-center shrink-0 group-hover:scale-110 transition-transform duration-300 shadow-md">
|
||||
<Bot className="w-6 h-6 text-white" strokeWidth={2.5} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-xl font-bold mb-2 text-gray-900 dark:text-white">
|
||||
{isZh ? '对接答疑机器人' : 'Supercharge Support Bots'}
|
||||
</h3>
|
||||
<p className="text-gray-600 dark:text-gray-300">
|
||||
{isZh
|
||||
? '把你的答疑助手变成全能Agent。客服机器人不再只说「请先点击设置按钮然后点击...」,而是直接帮用户现场操作。'
|
||||
: 'Stop telling users where to click—let AI do it for them. Turn your chatbot from a guide into an operator that actually completes tasks.'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Use Case 2 */}
|
||||
<div className="group bg-linear-to-br from-green-100 to-blue-100 dark:from-gray-700 dark:to-gray-800 p-8 rounded-2xl hover:shadow-xl transition-all duration-300">
|
||||
<div className="flex items-start space-x-4">
|
||||
<div className="w-12 h-12 bg-linear-to-br from-green-500 to-blue-500 rounded-xl flex items-center justify-center shrink-0 group-hover:scale-110 transition-transform duration-300 shadow-md">
|
||||
<Sparkles className="w-6 h-6 text-white" strokeWidth={2.5} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-xl font-bold mb-2 text-gray-900 dark:text-white">
|
||||
{isZh ? '交互升级/智能化改造' : 'Modernize Legacy Apps'}
|
||||
</h3>
|
||||
<p className="text-gray-600 dark:text-gray-300">
|
||||
{isZh
|
||||
? '一行代码,老应用变身Agent,产品专家帮用户操作复杂 B 端软件。降低人工支持成本,提高用户满意度。'
|
||||
: 'Add AI superpowers to old software without rebuilding. One script tag transforms complex enterprise tools into chat-driven interfaces.'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Use Case 3 */}
|
||||
<div className="group bg-linear-to-br from-purple-100 to-pink-100 dark:from-gray-700 dark:to-gray-800 p-8 rounded-2xl hover:shadow-xl transition-all duration-300">
|
||||
<div className="flex items-start space-x-4">
|
||||
<div className="w-12 h-12 bg-linear-to-br from-purple-500 to-pink-500 rounded-xl flex items-center justify-center shrink-0 group-hover:scale-110 transition-transform duration-300 shadow-md">
|
||||
<PlayCircle className="w-6 h-6 text-white fill-white/30" strokeWidth={2.5} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-xl font-bold mb-2 text-gray-900 dark:text-white">
|
||||
{isZh ? '产品教学' : 'Interactive Walkthroughs'}
|
||||
</h3>
|
||||
<p className="text-gray-600 dark:text-gray-300">
|
||||
{isZh
|
||||
? '向用户演示交互过程,边做边教。例如让AI演示「如何提交报销申请」的完整操作流程。'
|
||||
: "Show, don't tell. Let AI demonstrate workflows in real-time—perfect for onboarding or training new users on complex systems."}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Use Case 4 */}
|
||||
<div className="group bg-linear-to-br from-orange-100 to-red-100 dark:from-gray-700 dark:to-gray-800 p-8 rounded-2xl hover:shadow-xl transition-all duration-300">
|
||||
<div className="flex items-start space-x-4">
|
||||
<div className="w-12 h-12 bg-linear-to-br from-orange-500 to-red-500 rounded-xl flex items-center justify-center shrink-0 group-hover:scale-110 transition-transform duration-300 shadow-md">
|
||||
<Users className="w-6 h-6 text-white" strokeWidth={2.5} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-xl font-bold mb-2 text-gray-900 dark:text-white">
|
||||
{isZh ? '无障碍支持' : 'Accessibility First'}
|
||||
</h3>
|
||||
<p className="text-gray-600 dark:text-gray-300">
|
||||
{isZh
|
||||
? '为视障用户、老年用户提供自然语言交互,对接屏幕阅读器或语音助理,让软件人人可用。'
|
||||
: 'Make web apps accessible through natural language. Perfect for screen readers, voice control, or users who find traditional interfaces challenging.'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
<>
|
||||
<HeroSection />
|
||||
<FeaturesSection />
|
||||
<ScenariosSection />
|
||||
<OneMoreThingSection />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import CodeEditor from '@/components/CodeEditor'
|
||||
import { Heading } from '@/components/Heading'
|
||||
import { APIDivider, APIReference } from '@/components/ui/api-reference'
|
||||
import { useLanguage } from '@/i18n/context'
|
||||
|
||||
@@ -17,7 +18,7 @@ export default function CustomUIDocs() {
|
||||
|
||||
{/* Architecture */}
|
||||
<section className="mb-10">
|
||||
<h2 className="text-2xl font-semibold mb-4">{isZh ? '架构' : 'Architecture'}</h2>
|
||||
<Heading id="architecture">{isZh ? '架构' : 'Architecture'}</Heading>
|
||||
<p className="text-gray-600 dark:text-gray-400 mb-4">
|
||||
{isZh
|
||||
? 'PageAgent 由三个独立模块组成,可自由组合:'
|
||||
@@ -45,7 +46,7 @@ export default function CustomUIDocs() {
|
||||
|
||||
{/* Two Event Streams */}
|
||||
<section className="mb-10">
|
||||
<h2 className="text-2xl font-semibold mb-4">{isZh ? '两个事件流' : 'Two Event Streams'}</h2>
|
||||
<Heading id="two-event-streams">{isZh ? '两个事件流' : 'Two Event Streams'}</Heading>
|
||||
<p className="text-gray-600 dark:text-gray-400 mb-4">
|
||||
{isZh
|
||||
? 'PageAgentCore 提供两种不同性质的事件流,方便 UI 渲染:'
|
||||
@@ -120,7 +121,7 @@ export default function CustomUIDocs() {
|
||||
|
||||
{/* All Events */}
|
||||
<section className="mb-10">
|
||||
<h2 className="text-2xl font-semibold mb-4">{isZh ? '所有事件' : 'All Events'}</h2>
|
||||
<Heading id="all-events">{isZh ? '所有事件' : 'All Events'}</Heading>
|
||||
<APIReference
|
||||
properties={[
|
||||
{
|
||||
@@ -155,7 +156,7 @@ export default function CustomUIDocs() {
|
||||
|
||||
{/* HistoricalEvent Types */}
|
||||
<section className="mb-10">
|
||||
<h2 className="text-2xl font-semibold mb-4">HistoricalEvent</h2>
|
||||
<Heading id="historicalevent">HistoricalEvent</Heading>
|
||||
<p className="text-gray-600 dark:text-gray-400 mb-4">
|
||||
{isZh ? 'agent.history 数组中的事件类型:' : 'Event types in agent.history array:'}
|
||||
</p>
|
||||
@@ -172,7 +173,7 @@ export default function CustomUIDocs() {
|
||||
|
||||
{/* AgentActivity Types */}
|
||||
<section className="mb-10">
|
||||
<h2 className="text-2xl font-semibold mb-4">AgentActivity</h2>
|
||||
<Heading id="agentactivity">AgentActivity</Heading>
|
||||
<p className="text-gray-600 dark:text-gray-400 mb-4">
|
||||
{isZh ? 'activity 事件的 detail 类型:' : 'The detail type of activity events:'}
|
||||
</p>
|
||||
@@ -191,9 +192,7 @@ export default function CustomUIDocs() {
|
||||
|
||||
{/* React Hooks Example */}
|
||||
<section className="mb-10">
|
||||
<h2 className="text-2xl font-semibold mb-4">
|
||||
{isZh ? '使用 React Hooks' : 'Using React Hooks'}
|
||||
</h2>
|
||||
<Heading id="using-react-hooks">{isZh ? '使用 React Hooks' : 'Using React Hooks'}</Heading>
|
||||
<p className="text-gray-600 dark:text-gray-400 mb-4">
|
||||
{isZh ? '监听事件并更新 React 状态:' : 'Listen to events and update React state:'}
|
||||
</p>
|
||||
@@ -229,9 +228,9 @@ export default function CustomUIDocs() {
|
||||
|
||||
{/* Assembly Example */}
|
||||
<section className="mb-10">
|
||||
<h2 className="text-2xl font-semibold mb-4">
|
||||
<Heading id="assembling-core-controller-custom-ui">
|
||||
{isZh ? '组装 Core + Controller + 自定义 UI' : 'Assembling Core + Controller + Custom UI'}
|
||||
</h2>
|
||||
</Heading>
|
||||
<p className="text-gray-600 dark:text-gray-400 mb-4">
|
||||
{isZh
|
||||
? '参考内置 PageAgent 的实现方式,用自定义 UI 替换 Panel:'
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import CodeEditor from '@/components/CodeEditor'
|
||||
import { Heading } from '@/components/Heading'
|
||||
import { APIDivider, APIReference, TypeRef } from '@/components/ui/api-reference'
|
||||
import { useLanguage } from '@/i18n/context'
|
||||
|
||||
@@ -17,9 +18,9 @@ export default function PageAgentCoreDocs() {
|
||||
|
||||
{/* When to use */}
|
||||
<section className="mb-10">
|
||||
<h2 className="text-2xl font-semibold mb-4">
|
||||
<Heading id="when-to-use-pageagentcore">
|
||||
{isZh ? '何时使用 PageAgentCore' : 'When to Use PageAgentCore'}
|
||||
</h2>
|
||||
</Heading>
|
||||
<ul className="list-disc list-inside text-gray-600 dark:text-gray-400 space-y-2">
|
||||
<li>{isZh ? '需要自定义 UI 界面' : 'Need a custom UI interface'}</li>
|
||||
<li>{isZh ? '在自动化测试中无头运行' : 'Running headless in automated tests'}</li>
|
||||
@@ -38,7 +39,7 @@ export default function PageAgentCoreDocs() {
|
||||
|
||||
{/* Basic Usage */}
|
||||
<section className="mb-10">
|
||||
<h2 className="text-2xl font-semibold mb-4">{isZh ? '基本用法' : 'Basic Usage'}</h2>
|
||||
<Heading id="basic-usage">{isZh ? '基本用法' : 'Basic Usage'}</Heading>
|
||||
<CodeEditor
|
||||
language="typescript"
|
||||
code={`import { PageAgentCore } from '@page-agent/core'
|
||||
@@ -76,7 +77,7 @@ const result = await agent.execute('Fill in the form with test data')`}
|
||||
|
||||
{/* LLM Configuration */}
|
||||
<section className="mb-10">
|
||||
<h2 className="text-2xl font-semibold mb-4">LLMConfig</h2>
|
||||
<Heading id="llmconfig">LLMConfig</Heading>
|
||||
<p className="text-gray-600 dark:text-gray-400 mb-4">
|
||||
{isZh
|
||||
? '配置与大语言模型的连接参数。支持 OpenAI 兼容的 API。'
|
||||
@@ -133,7 +134,7 @@ const result = await agent.execute('Fill in the form with test data')`}
|
||||
|
||||
{/* Agent Configuration */}
|
||||
<section className="mb-10">
|
||||
<h2 className="text-2xl font-semibold mb-4">AgentConfig</h2>
|
||||
<Heading id="agentconfig">AgentConfig</Heading>
|
||||
<p className="text-gray-600 dark:text-gray-400 mb-4">
|
||||
{isZh
|
||||
? '配置 Agent 的行为、生命周期钩子和扩展能力。'
|
||||
@@ -210,7 +211,7 @@ const result = await agent.execute('Fill in the form with test data')`}
|
||||
|
||||
{/* Lifecycle Hooks */}
|
||||
<section className="mb-10">
|
||||
<h2 className="text-2xl font-semibold mb-4">{isZh ? '生命周期钩子' : 'Lifecycle Hooks'}</h2>
|
||||
<Heading id="lifecycle-hooks">{isZh ? '生命周期钩子' : 'Lifecycle Hooks'}</Heading>
|
||||
<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">
|
||||
<strong>⚠️ {isZh ? '警告' : 'Warning'}:</strong>{' '}
|
||||
@@ -262,7 +263,7 @@ const result = await agent.execute('Fill in the form with test data')`}
|
||||
|
||||
{/* PageController Configuration */}
|
||||
<section className="mb-10">
|
||||
<h2 className="text-2xl font-semibold mb-4">PageControllerConfig</h2>
|
||||
<Heading id="pagecontrollerconfig">PageControllerConfig</Heading>
|
||||
<p className="text-gray-600 dark:text-gray-400 mb-4">
|
||||
{isZh
|
||||
? '配置 DOM 提取、元素交互和视觉反馈。'
|
||||
@@ -321,7 +322,7 @@ const result = await agent.execute('Fill in the form with test data')`}
|
||||
|
||||
{/* Properties */}
|
||||
<section className="mb-10">
|
||||
<h2 className="text-2xl font-semibold mb-4">{isZh ? '属性' : 'Properties'}</h2>
|
||||
<Heading id="properties">{isZh ? '属性' : 'Properties'}</Heading>
|
||||
<APIReference
|
||||
properties={[
|
||||
{
|
||||
@@ -366,7 +367,7 @@ const result = await agent.execute('Fill in the form with test data')`}
|
||||
|
||||
{/* Methods */}
|
||||
<section className="mb-10">
|
||||
<h2 className="text-2xl font-semibold mb-4">{isZh ? '方法' : 'Methods'}</h2>
|
||||
<Heading id="methods">{isZh ? '方法' : 'Methods'}</Heading>
|
||||
<APIReference
|
||||
properties={[
|
||||
{
|
||||
@@ -389,7 +390,7 @@ const result = await agent.execute('Fill in the form with test data')`}
|
||||
|
||||
{/* Events */}
|
||||
<section className="mb-10">
|
||||
<h2 className="text-2xl font-semibold mb-4">{isZh ? '事件' : 'Events'}</h2>
|
||||
<Heading id="events">{isZh ? '事件' : 'Events'}</Heading>
|
||||
<p className="text-gray-600 dark:text-gray-400 mb-4">
|
||||
{isZh ? (
|
||||
<>
|
||||
@@ -438,7 +439,7 @@ const result = await agent.execute('Fill in the form with test data')`}
|
||||
|
||||
{/* ExecutionResult */}
|
||||
<section className="mb-10">
|
||||
<h2 className="text-2xl font-semibold mb-4">ExecutionResult</h2>
|
||||
<Heading id="executionresult">ExecutionResult</Heading>
|
||||
<CodeEditor
|
||||
language="typescript"
|
||||
code={`interface ExecutionResult {
|
||||
@@ -454,7 +455,7 @@ const result = await agent.execute('Fill in the form with test data')`}
|
||||
|
||||
{/* AgentActivity */}
|
||||
<section className="mb-10">
|
||||
<h2 className="text-2xl font-semibold mb-4">AgentActivity</h2>
|
||||
<Heading id="agentactivity">AgentActivity</Heading>
|
||||
<CodeEditor
|
||||
language="typescript"
|
||||
code={`type AgentActivity =
|
||||
@@ -470,7 +471,7 @@ const result = await agent.execute('Fill in the form with test data')`}
|
||||
|
||||
{/* Headless Usage */}
|
||||
<section className="mb-10">
|
||||
<h2 className="text-2xl font-semibold mb-4">{isZh ? '无头模式' : 'Headless Mode'}</h2>
|
||||
<Heading id="headless-mode">{isZh ? '无头模式' : 'Headless Mode'}</Heading>
|
||||
<p className="text-gray-600 dark:text-gray-400 mb-4">
|
||||
{isZh
|
||||
? '在非 DOM 环境中,你必须实现自定义的 PageController(例如远程操作页面或 Puppeteer)。'
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Link } from 'wouter'
|
||||
|
||||
import CodeEditor from '@/components/CodeEditor'
|
||||
import { Heading } from '@/components/Heading'
|
||||
import { APIReference, TypeRef } from '@/components/ui/api-reference'
|
||||
import { useLanguage } from '@/i18n/context'
|
||||
|
||||
@@ -19,9 +20,9 @@ export default function PageAgentDocs() {
|
||||
|
||||
{/* When to use */}
|
||||
<section className="mb-10">
|
||||
<h2 className="text-2xl font-semibold mb-4">
|
||||
<Heading id="when-to-use-pageagent">
|
||||
{isZh ? '何时使用 PageAgent' : 'When to Use PageAgent'}
|
||||
</h2>
|
||||
</Heading>
|
||||
<p className="text-gray-600 dark:text-gray-400 mb-4">
|
||||
{isZh
|
||||
? '在大多数场景下,你应该使用 PageAgent。它提供了开箱即用的完整体验:'
|
||||
@@ -43,7 +44,7 @@ export default function PageAgentDocs() {
|
||||
|
||||
{/* Basic Usage */}
|
||||
<section className="mb-10">
|
||||
<h2 className="text-2xl font-semibold mb-4">{isZh ? '基本用法' : 'Basic Usage'}</h2>
|
||||
<Heading id="basic-usage">{isZh ? '基本用法' : 'Basic Usage'}</Heading>
|
||||
<CodeEditor
|
||||
language="typescript"
|
||||
code={`import { PageAgent } from 'page-agent'
|
||||
@@ -69,7 +70,7 @@ console.log(result.history) // Full execution history`}
|
||||
|
||||
{/* Class Definition */}
|
||||
<section className="mb-10">
|
||||
<h2 className="text-2xl font-semibold mb-4">{isZh ? '类定义' : 'Class Definition'}</h2>
|
||||
<Heading id="class-definition">{isZh ? '类定义' : 'Class Definition'}</Heading>
|
||||
<CodeEditor
|
||||
language="typescript"
|
||||
code={`class PageAgent extends PageAgentCore {
|
||||
@@ -109,7 +110,7 @@ console.log(result.history) // Full execution history`}
|
||||
|
||||
{/* Configuration */}
|
||||
<section className="mb-10">
|
||||
<h2 className="text-2xl font-semibold mb-4">{isZh ? '配置' : 'Configuration'}</h2>
|
||||
<Heading id="configuration">{isZh ? '配置' : 'Configuration'}</Heading>
|
||||
<p className="text-gray-600 dark:text-gray-400 mb-4">
|
||||
{isZh
|
||||
? 'PageAgent 使用与 PageAgentCore 相同的配置接口。'
|
||||
@@ -144,7 +145,7 @@ console.log(result.history) // Full execution history`}
|
||||
|
||||
{/* Panel Property */}
|
||||
<section className="mb-10">
|
||||
<h2 className="text-2xl font-semibold mb-4">{isZh ? 'Panel 属性' : 'Panel Property'}</h2>
|
||||
<Heading id="panel-property">{isZh ? 'Panel 属性' : 'Panel Property'}</Heading>
|
||||
<p className="text-gray-600 dark:text-gray-400 mb-4">
|
||||
{isZh
|
||||
? 'PageAgent 自动创建一个 Panel 实例。你可以通过 panel 属性访问它来控制 UI:'
|
||||
@@ -185,9 +186,9 @@ agent.panel.dispose()`}
|
||||
|
||||
{/* Comparison with PageAgentCore */}
|
||||
<section className="mb-10">
|
||||
<h2 className="text-2xl font-semibold mb-4">
|
||||
<Heading id="pageagent-vs-pageagentcore">
|
||||
{isZh ? 'PageAgent vs PageAgentCore' : 'PageAgent vs PageAgentCore'}
|
||||
</h2>
|
||||
</Heading>
|
||||
<div className="overflow-hidden rounded-lg border border-gray-200 dark:border-gray-700">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { siChromewebstore, siGithub } from 'simple-icons'
|
||||
|
||||
import CodeEditor from '@/components/CodeEditor'
|
||||
import { Heading } from '@/components/Heading'
|
||||
import { useLanguage } from '@/i18n/context'
|
||||
|
||||
export default function ChromeExtension() {
|
||||
@@ -22,7 +23,9 @@ export default function ChromeExtension() {
|
||||
<div className="space-y-8 mt-8">
|
||||
{/* Features */}
|
||||
<section>
|
||||
<h2 className="text-2xl font-bold mb-4">{isZh ? '核心特性' : 'Key Features'}</h2>
|
||||
<Heading id="key-features" className="text-2xl font-bold mb-4">
|
||||
{isZh ? '核心特性' : 'Key Features'}
|
||||
</Heading>
|
||||
<div className="grid md:grid-cols-3 gap-4">
|
||||
<div className="p-4 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
||||
<h3 className="font-semibold mb-2">🔓 {isZh ? '多页任务' : 'Multi-Page Tasks'}</h3>
|
||||
@@ -57,7 +60,9 @@ export default function ChromeExtension() {
|
||||
|
||||
{/* Install */}
|
||||
<section>
|
||||
<h2 className="text-2xl font-bold mb-4">{isZh ? '获取扩展' : 'Get the Extension'}</h2>
|
||||
<Heading id="get-the-extension" className="text-2xl font-bold mb-4">
|
||||
{isZh ? '获取扩展' : 'Get the Extension'}
|
||||
</Heading>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<a
|
||||
href={chromeWebStoreUrl}
|
||||
@@ -86,9 +91,9 @@ export default function ChromeExtension() {
|
||||
|
||||
{/* Relationship with PageAgent.js */}
|
||||
<section>
|
||||
<h2 className="text-2xl font-bold mb-4">
|
||||
<Heading id="how-it-relates-to-page-agent-js" className="text-2xl font-bold mb-4">
|
||||
{isZh ? '与 PageAgent.js 的关系' : 'How It Relates to PageAgent.js'}
|
||||
</h2>
|
||||
</Heading>
|
||||
<div className="p-5 bg-gray-50 dark:bg-gray-800 rounded-lg space-y-3 text-gray-600 dark:text-gray-300">
|
||||
<p>
|
||||
{isZh
|
||||
@@ -105,9 +110,9 @@ export default function ChromeExtension() {
|
||||
|
||||
{/* Third-party Integration */}
|
||||
<section>
|
||||
<h2 className="text-2xl font-bold mb-4">
|
||||
<Heading id="third-party-integration" className="text-2xl font-bold mb-4">
|
||||
{isZh ? '第三方接入' : 'Third-Party Integration'}
|
||||
</h2>
|
||||
</Heading>
|
||||
<p className="text-gray-600 dark:text-gray-300 mb-4">
|
||||
{isZh
|
||||
? '通过页面 JavaScript 调用 `window.PAGE_AGENT_EXT`,你的应用可以发起跨页面任务并控制浏览器行为。'
|
||||
@@ -145,7 +150,9 @@ localStorage.setItem('PageAgentExtUserAuthToken', '<your-token-from-extension>')
|
||||
|
||||
{/* API Reference */}
|
||||
<section>
|
||||
<h2 className="text-2xl font-bold mb-4">{isZh ? 'API 参考' : 'API Reference'}</h2>
|
||||
<Heading id="api-reference" className="text-2xl font-bold mb-4">
|
||||
{isZh ? 'API 参考' : 'API Reference'}
|
||||
</Heading>
|
||||
|
||||
{/* AI Assistant Instructions */}
|
||||
<section className="p-4 bg-purple-50 dark:bg-purple-900/20 rounded-lg">
|
||||
@@ -170,9 +177,9 @@ localStorage.setItem('PageAgentExtUserAuthToken', '<your-token-from-extension>')
|
||||
</section>
|
||||
|
||||
{/* TypeScript Declaration */}
|
||||
<h2 className="text-2xl font-bold mb-4">
|
||||
<Heading id="typescript-declaration" className="text-2xl font-bold mb-4">
|
||||
{isZh ? 'TypeScript 类型声明' : 'TypeScript Declaration'}
|
||||
</h2>
|
||||
</Heading>
|
||||
<p className="text-gray-600 dark:text-gray-300 mb-4">
|
||||
{isZh
|
||||
? '推荐把 `execute` 的类型声明加入你的项目,获得完整类型提示。'
|
||||
@@ -304,11 +311,14 @@ window.PAGE_AGENT_EXT.stop()`
|
||||
|
||||
{/* Integration Guide */}
|
||||
<section>
|
||||
<h2 className="text-2xl font-bold mb-4">
|
||||
<Heading
|
||||
id="integrate-multipageagent-into-your-extension"
|
||||
className="text-2xl font-bold mb-4"
|
||||
>
|
||||
{isZh
|
||||
? '将 MultiPageAgent 集成你自己的插件'
|
||||
: 'Integrate MultiPageAgent into Your Extension'}
|
||||
</h2>
|
||||
</Heading>
|
||||
<p>@TODO</p>
|
||||
<p className="text-gray-600 dark:text-gray-300 mb-4">
|
||||
{isZh
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import CodeEditor from '@/components/CodeEditor'
|
||||
import { Heading } from '@/components/Heading'
|
||||
import { useLanguage } from '@/i18n/context'
|
||||
|
||||
export default function Instructions() {
|
||||
@@ -16,9 +17,9 @@ export default function Instructions() {
|
||||
|
||||
{/* System Instructions */}
|
||||
<section className="mb-12">
|
||||
<h2 className="text-3xl font-bold mb-6">
|
||||
<Heading id="system-instructions" className="text-3xl font-bold mb-6">
|
||||
{isZh ? '系统级指导 (System Instructions)' : 'System Instructions'}
|
||||
</h2>
|
||||
</Heading>
|
||||
|
||||
<p className="text-gray-600 dark:text-gray-300 mb-6">
|
||||
{isZh
|
||||
@@ -46,9 +47,9 @@ Guidelines:
|
||||
|
||||
{/* Page Instructions */}
|
||||
<section className="mb-12">
|
||||
<h2 className="text-3xl font-bold mb-6">
|
||||
<Heading id="page-instructions" className="text-3xl font-bold mb-6">
|
||||
{isZh ? '页面级指导 (Page Instructions)' : 'Page Instructions'}
|
||||
</h2>
|
||||
</Heading>
|
||||
|
||||
<p className="text-gray-600 dark:text-gray-300 mb-6">
|
||||
{isZh
|
||||
@@ -90,7 +91,9 @@ This is the product listing page.
|
||||
|
||||
{/* How It Works */}
|
||||
<section className="mb-12">
|
||||
<h2 className="text-3xl font-bold mb-6">{isZh ? '工作原理' : 'How It Works'}</h2>
|
||||
<Heading id="how-it-works" className="text-3xl font-bold mb-6">
|
||||
{isZh ? '工作原理' : 'How It Works'}
|
||||
</Heading>
|
||||
|
||||
<p className="text-gray-600 dark:text-gray-300 mb-4">
|
||||
{isZh
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import BetaNotice from '@/components/BetaNotice'
|
||||
import CodeEditor from '@/components/CodeEditor'
|
||||
import { Heading } from '@/components/Heading'
|
||||
import { useLanguage } from '@/i18n/context'
|
||||
|
||||
export default function CustomTools() {
|
||||
@@ -17,7 +18,9 @@ export default function CustomTools() {
|
||||
|
||||
<div className="space-y-8">
|
||||
<section>
|
||||
<h2 className="text-2xl font-bold mb-4">{isZh ? '工具注册' : 'Tool Registration'}</h2>
|
||||
<Heading id="tool-registration" className="text-2xl font-bold mb-4">
|
||||
{isZh ? '工具注册' : 'Tool Registration'}
|
||||
</Heading>
|
||||
<p className="text-gray-600 dark:text-gray-300 mb-4">
|
||||
{isZh
|
||||
? '每个自定义工具需要定义四个核心属性:name、description、input schema 和 execute 函数。'
|
||||
@@ -55,7 +58,9 @@ const pageAgent = new PageAgent({customTools})
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className="text-2xl font-bold mb-4">{isZh ? '页面过滤器' : 'Page Filter'}</h2>
|
||||
<Heading id="page-filter" className="text-2xl font-bold mb-4">
|
||||
{isZh ? '页面过滤器' : 'Page Filter'}
|
||||
</Heading>
|
||||
|
||||
<BetaNotice />
|
||||
|
||||
@@ -89,7 +94,9 @@ const pageAgent = new PageAgent({customTools})
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className="text-2xl font-bold mb-4">{isZh ? '最佳实践' : 'Best Practices'}</h2>
|
||||
<Heading id="best-practices" className="text-2xl font-bold mb-4">
|
||||
{isZh ? '最佳实践' : 'Best Practices'}
|
||||
</Heading>
|
||||
<div className="space-y-4">
|
||||
<div className="p-4 bg-yellow-50 dark:bg-yellow-900/20 rounded-lg">
|
||||
<h3 className="text-lg font-semibold text-yellow-900 dark:text-yellow-300 mb-2">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import CodeEditor from '@/components/CodeEditor'
|
||||
import { Heading } from '@/components/Heading'
|
||||
import { useLanguage } from '@/i18n/context'
|
||||
|
||||
export default function DataMasking() {
|
||||
@@ -15,7 +16,9 @@ export default function DataMasking() {
|
||||
</p>
|
||||
|
||||
<section className="mb-12">
|
||||
<h2 className="text-3xl font-bold mb-6">{isZh ? '接口定义' : 'API Definition'}</h2>
|
||||
<Heading id="api-definition" className="text-3xl font-bold mb-6">
|
||||
{isZh ? '接口定义' : 'API Definition'}
|
||||
</Heading>
|
||||
|
||||
<CodeEditor
|
||||
className="mb-6"
|
||||
@@ -30,9 +33,9 @@ export default function DataMasking() {
|
||||
</section>
|
||||
|
||||
<section className="mb-12">
|
||||
<h2 className="text-3xl font-bold mb-6">
|
||||
<Heading id="common-masking-patterns" className="text-3xl font-bold mb-6">
|
||||
{isZh ? '常用脱敏规则' : 'Common Masking Patterns'}
|
||||
</h2>
|
||||
</Heading>
|
||||
|
||||
<p className="text-gray-600 dark:text-gray-300 mb-6">
|
||||
{isZh
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Fragment } from 'react'
|
||||
|
||||
import CodeEditor from '@/components/CodeEditor'
|
||||
import { Heading } from '@/components/Heading'
|
||||
import { useLanguage } from '@/i18n/context'
|
||||
|
||||
const BASELINE = new Set([
|
||||
@@ -8,15 +9,22 @@ const BASELINE = new Set([
|
||||
'claude-haiku-4.5',
|
||||
'gemini-3-flash',
|
||||
'deepseek-3.2',
|
||||
'qwen3-coder-next',
|
||||
'qwen3.5-plus',
|
||||
])
|
||||
|
||||
// Models grouped by brand, newest first
|
||||
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'],
|
||||
Google: ['gemini-3-pro', 'gemini-3-flash', 'gemini-2.5'],
|
||||
Qwen: ['qwen3-coder-next', 'qwen-3-max', 'qwen-3-plus', 'qwen3:14b (ollama)'],
|
||||
Qwen: [
|
||||
'qwen3.5-plus',
|
||||
'qwen3.5-flash',
|
||||
'qwen3-coder-next',
|
||||
'qwen-3-max',
|
||||
'qwen-3-plus',
|
||||
'qwen3:14b (ollama)',
|
||||
],
|
||||
DeepSeek: ['deepseek-3.2'],
|
||||
Google: ['gemini-3-pro', 'gemini-3-flash', 'gemini-2.5'],
|
||||
Anthropic: [
|
||||
'claude-opus-4.6',
|
||||
'claude-opus-4.5',
|
||||
@@ -26,7 +34,7 @@ const MODEL_GROUPS: Record<string, string[]> = {
|
||||
],
|
||||
xAI: ['grok-4.1-fast', 'grok-4', 'grok-code-fast'],
|
||||
MoonshotAI: ['kimi-k2.5'],
|
||||
'Z.AI': ['glm-4.7'],
|
||||
'Z.AI': ['glm-5', 'glm-4.7'],
|
||||
}
|
||||
|
||||
const ModelBadge = ({ model, baseline }: { model: string; baseline?: boolean }) => (
|
||||
@@ -56,7 +64,9 @@ export default function Models() {
|
||||
|
||||
{/* Models Section */}
|
||||
<section className="mb-10">
|
||||
<h2 className="text-2xl font-semibold mb-3">{isZh ? '已测试模型' : 'Tested Models'}</h2>
|
||||
<Heading id="tested-models" className="text-2xl font-semibold mb-3">
|
||||
{isZh ? '已测试模型' : 'Tested Models'}
|
||||
</Heading>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400 mb-4">
|
||||
{isZh
|
||||
? '推荐使用 ToolCall 能力强的轻量级模型。'
|
||||
@@ -134,13 +144,13 @@ export default function Models() {
|
||||
|
||||
{/* Configuration Section */}
|
||||
<section className="mb-10">
|
||||
<h2 className="text-2xl font-semibold mb-4">{isZh ? '配置方式' : 'Configuration'}</h2>
|
||||
<Heading id="configuration">{isZh ? '配置方式' : 'Configuration'}</Heading>
|
||||
<CodeEditor
|
||||
code={`// OpenAI-compatible services (e.g., Alibaba Bailian)
|
||||
const pageAgent = new PageAgent({
|
||||
baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
|
||||
apiKey: 'your-api-key',
|
||||
model: 'qwen-plus'
|
||||
model: 'qwen3.5-plus'
|
||||
});
|
||||
|
||||
// Self-hosted models (e.g., Ollama)
|
||||
@@ -150,19 +160,63 @@ const pageAgent = new PageAgent({
|
||||
model: 'qwen3:14b'
|
||||
});
|
||||
|
||||
// Free testing endpoint
|
||||
// Note: Rate-limited, content-filtered, subject to change. Replace with your own.
|
||||
// Note: Uses official DeepSeek-chat (3.2). See DeepSeek website for terms & privacy.
|
||||
const DEMO_MODEL = 'PAGE-AGENT-FREE-TESTING-RANDOM'
|
||||
const DEMO_BASE_URL = 'https://hwcxiuzfylggtcktqgij.supabase.co/functions/v1/llm-testing-proxy'
|
||||
const DEMO_API_KEY = 'PAGE-AGENT-FREE-TESTING-RANDOM'
|
||||
`}
|
||||
/>
|
||||
</section>
|
||||
|
||||
{/* Free Testing API Section */}
|
||||
<section className="mb-10">
|
||||
<Heading id="free-testing-api">{isZh ? '免费测试接口' : 'Free Testing API'}</Heading>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400 mb-4">
|
||||
{isZh
|
||||
? '以下免费测试接口仅供 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.'}
|
||||
</p>
|
||||
<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">
|
||||
Qwen (Alibaba Cloud China)
|
||||
</h3>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mb-3">
|
||||
{isZh
|
||||
? '通过阿里云函数计算(中国大陆)转发至百炼 Qwen 模型'
|
||||
: 'Proxied via Alibaba Cloud FC (Mainland China) to BaiLian Qwen models'}
|
||||
{' · '}
|
||||
<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>
|
||||
<CodeEditor
|
||||
code={`# qwen3.5-plus (default for demos) or qwen3.5-flash (lighter)
|
||||
LLM_BASE_URL="https://page-ag-testing-ohftxirgbn.cn-shanghai.fcapp.run"
|
||||
LLM_MODEL_NAME="qwen3.5-plus"
|
||||
LLM_API_KEY="NA"`}
|
||||
/>
|
||||
</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>
|
||||
|
||||
{/* Ollama Section */}
|
||||
<section className="mb-10">
|
||||
<h2 className="text-2xl font-semibold mb-4">Ollama</h2>
|
||||
<Heading id="ollama">Ollama</Heading>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400 mb-4">
|
||||
{isZh
|
||||
? '已在 Ollama 0.15 + qwen3:14b (RTX3090 24GB) 上测试通过。'
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Suspense } from 'react'
|
||||
import { Route, Switch } from 'wouter'
|
||||
|
||||
import Header from '../../components/Header'
|
||||
import DocsLayout from './Layout'
|
||||
import CustomUIDocs from './advanced/custom-ui/page'
|
||||
import PageAgentCoreDocs from './advanced/page-agent-core/page'
|
||||
@@ -24,12 +23,9 @@ import Troubleshooting from './introduction/troubleshooting/page'
|
||||
|
||||
function DocsPage({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<div className="min-h-screen bg-white dark:bg-gray-900">
|
||||
<Header />
|
||||
<DocsLayout>
|
||||
<Suspense>{children}</Suspense>
|
||||
</DocsLayout>
|
||||
</div>
|
||||
<DocsLayout>
|
||||
<Suspense>{children}</Suspense>
|
||||
</DocsLayout>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import BetaNotice from '@/components/BetaNotice'
|
||||
import { Heading } from '@/components/Heading'
|
||||
import { useLanguage } from '@/i18n/context'
|
||||
|
||||
export default function SecurityPermissions() {
|
||||
@@ -18,9 +19,9 @@ export default function SecurityPermissions() {
|
||||
|
||||
<div className="space-y-6">
|
||||
<section>
|
||||
<h2 className="text-2xl font-bold mb-3">
|
||||
<Heading id="element-interaction-allowlist-blocklist" className="text-2xl font-bold mb-3">
|
||||
{isZh ? '元素操作黑白名单' : 'Element Interaction Allowlist/Blocklist'}
|
||||
</h2>
|
||||
</Heading>
|
||||
<div className="space-y-3">
|
||||
<div className="p-4 bg-red-50 dark:bg-red-900/20 rounded-lg">
|
||||
<h3 className="text-lg font-semibold text-red-900 dark:text-red-300">
|
||||
@@ -46,9 +47,9 @@ export default function SecurityPermissions() {
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className="text-2xl font-bold mb-3">
|
||||
<Heading id="instruction-safety-constraints" className="text-2xl font-bold mb-3">
|
||||
{isZh ? 'Instruction 安全约束' : 'Instruction Safety Constraints'}
|
||||
</h2>
|
||||
</Heading>
|
||||
<div className="p-4 bg-yellow-50 dark:bg-yellow-900/20 rounded-lg">
|
||||
<h3 className="text-lg font-semibold mb-2 text-yellow-900 dark:text-yellow-300">
|
||||
⚠️ {isZh ? '高危操作控制' : 'High-Risk Operation Control'}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import CodeEditor from '@/components/CodeEditor'
|
||||
import { Heading } from '@/components/Heading'
|
||||
import { useLanguage } from '@/i18n/context'
|
||||
|
||||
export default function ThirdPartyAgentPage() {
|
||||
@@ -26,7 +27,9 @@ export default function ThirdPartyAgentPage() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h2 className="text-2xl font-bold mb-4">{isZh ? '集成方式' : 'Integration Method'}</h2>
|
||||
<Heading id="integration-method" className="text-2xl font-bold mb-4">
|
||||
{isZh ? '集成方式' : 'Integration Method'}
|
||||
</Heading>
|
||||
|
||||
<div className="space-y-4 mb-6">
|
||||
<div className="p-4 bg-green-50 dark:bg-green-900/20 rounded-lg">
|
||||
@@ -57,7 +60,9 @@ const pageAgentTool = {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 className="text-2xl font-bold mb-4">{isZh ? '应用场景' : 'Use Cases'}</h2>
|
||||
<Heading id="use-cases" className="text-2xl font-bold mb-4">
|
||||
{isZh ? '应用场景' : 'Use Cases'}
|
||||
</Heading>
|
||||
<div className="grid md:grid-cols-2 gap-4 mb-6">
|
||||
<div className="bg-linear-to-br from-blue-50 to-purple-50 dark:from-gray-800 dark:to-gray-700 p-4 rounded-lg">
|
||||
<h4 className="font-semibold mb-2 text-gray-900 dark:text-white">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Link } from 'wouter'
|
||||
|
||||
import { Heading } from '@/components/Heading'
|
||||
import { useLanguage } from '@/i18n/context'
|
||||
|
||||
export default function LimitationsPage() {
|
||||
@@ -20,9 +21,9 @@ export default function LimitationsPage() {
|
||||
|
||||
<div className="prose prose-lg dark:prose-invert max-w-none">
|
||||
{/* PageAgent.js vs PageAgentExt */}
|
||||
<h2 className="text-2xl font-bold mb-3">
|
||||
<Heading id="pageagent-js-vs-pageagentext" className="text-2xl font-bold mb-3">
|
||||
{isZh ? 'PageAgent.js vs PageAgentExt' : 'PageAgent.js vs PageAgentExt'}
|
||||
</h2>
|
||||
</Heading>
|
||||
<p className="text-gray-600 dark:text-gray-300 mb-4">
|
||||
{isZh
|
||||
? 'PageAgent.js 是核心库,运行在页面内。PageAgentExt 是可选的浏览器扩展,提供额外的浏览器级控制能力。'
|
||||
@@ -37,7 +38,7 @@ export default function LimitationsPage() {
|
||||
<th className="text-left py-3 pl-4 font-semibold">
|
||||
PageAgentExt{' '}
|
||||
<Link
|
||||
href="/docs/features/chrome-extension"
|
||||
href="/features/chrome-extension"
|
||||
className="text-xs font-normal text-blue-600 dark:text-blue-400 hover:underline"
|
||||
>
|
||||
{isZh ? '了解更多' : 'learn more'}
|
||||
@@ -82,9 +83,9 @@ export default function LimitationsPage() {
|
||||
</div>
|
||||
|
||||
{/* Interaction Limitations */}
|
||||
<h2 className="text-2xl font-bold mb-3">
|
||||
<Heading id="interaction-capabilities" className="text-2xl font-bold mb-3">
|
||||
{isZh ? '交互能力' : 'Interaction Capabilities'}
|
||||
</h2>
|
||||
</Heading>
|
||||
<div className="bg-gray-50 dark:bg-gray-800 rounded-lg p-6 mb-6">
|
||||
<div className="grid md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
@@ -127,7 +128,9 @@ export default function LimitationsPage() {
|
||||
</div>
|
||||
|
||||
{/* Understanding Limitations */}
|
||||
<h2 className="text-2xl font-bold mb-3">{isZh ? '理解能力' : 'Understanding'}</h2>
|
||||
<Heading id="understanding" className="text-2xl font-bold mb-3">
|
||||
{isZh ? '理解能力' : 'Understanding'}
|
||||
</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
|
||||
@@ -142,9 +145,9 @@ export default function LimitationsPage() {
|
||||
</div>
|
||||
|
||||
{/* Website Quality */}
|
||||
<h2 className="text-2xl font-bold mb-3">
|
||||
<Heading id="page-quality-matters" className="text-2xl font-bold mb-3">
|
||||
{isZh ? '网页质量影响' : 'Page Quality Matters'}
|
||||
</h2>
|
||||
</Heading>
|
||||
<div className="bg-gray-50 dark:bg-gray-800 rounded-lg p-6">
|
||||
<p className="text-gray-600 dark:text-gray-300 text-sm">
|
||||
{isZh
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Heading } from '@/components/Heading'
|
||||
import { useLanguage } from '@/i18n/context'
|
||||
|
||||
export default function Overview() {
|
||||
@@ -48,9 +49,9 @@ export default function Overview() {
|
||||
</div>
|
||||
|
||||
<section>
|
||||
<h2 className="text-2xl font-bold mb-4">
|
||||
<Heading id="what-is-page-agent" className="text-2xl font-bold mb-4">
|
||||
{isZh ? '什么是 page-agent?' : 'What is page-agent?'}
|
||||
</h2>
|
||||
</Heading>
|
||||
|
||||
<p className="text-gray-600 dark:text-gray-300 mb-8 leading-relaxed ">
|
||||
{isZh
|
||||
@@ -60,7 +61,9 @@ export default function Overview() {
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className="text-2xl font-bold mb-3">{isZh ? '核心特性' : 'Core Features'}</h2>
|
||||
<Heading id="core-features" className="text-2xl font-bold mb-3">
|
||||
{isZh ? '核心特性' : 'Core Features'}
|
||||
</Heading>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-4 mb-8" role="list">
|
||||
<div className="p-4 bg-blue-50 dark:bg-blue-900/20 rounded-lg">
|
||||
@@ -108,9 +111,9 @@ export default function Overview() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 className="text-2xl font-bold mb-4">
|
||||
<Heading id="vs-browser-use" className="text-2xl font-bold mb-4">
|
||||
{isZh ? '与 browser-use 的区别' : 'vs. browser-use'}
|
||||
</h2>
|
||||
</Heading>
|
||||
|
||||
<div className="overflow-x-auto mb-8">
|
||||
<table className="w-full border-collapse border border-gray-300 dark:border-gray-600">
|
||||
@@ -174,7 +177,9 @@ export default function Overview() {
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h2 className="text-2xl font-bold mb-4">{isZh ? '应用场景' : 'Use Cases'}</h2>
|
||||
<Heading id="use-cases" className="text-2xl font-bold mb-4">
|
||||
{isZh ? '应用场景' : 'Use Cases'}
|
||||
</Heading>
|
||||
|
||||
<ul className="space-y-4 mb-8">
|
||||
<li className="flex items-start space-x-3">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import CodeEditor from '@/components/CodeEditor'
|
||||
import { Heading } from '@/components/Heading'
|
||||
import { CDN_DEMO_CN_URL, CDN_DEMO_URL } from '@/constants'
|
||||
import { useLanguage } from '@/i18n/context'
|
||||
|
||||
@@ -13,7 +14,9 @@ export default function QuickStart() {
|
||||
{isZh ? '几分钟内完成 page-agent 的集成。' : 'Integrate page-agent in minutes.'}
|
||||
</p>
|
||||
|
||||
<h2 className="text-2xl font-bold mb-3">{isZh ? '安装步骤' : 'Installation Steps'}</h2>
|
||||
<Heading id="installation-steps" className="text-2xl font-bold mb-3">
|
||||
{isZh ? '安装步骤' : 'Installation Steps'}
|
||||
</Heading>
|
||||
|
||||
<div className="space-y-4 mb-6">
|
||||
{/* Demo CDN - One Line */}
|
||||
@@ -23,7 +26,32 @@ export default function QuickStart() {
|
||||
</h3>
|
||||
<div className="bg-yellow-50 dark:bg-yellow-900/20 p-2 rounded mb-3 text-sm">
|
||||
<span className="text-yellow-800 dark:text-yellow-200">
|
||||
⚠️ {isZh ? '仅用于技术评估' : 'For evaluation only'}
|
||||
⚠️{' '}
|
||||
{isZh ? (
|
||||
<>
|
||||
该 Demo CDN 使用了免费的测试 LLM API,使用即表示您同意其
|
||||
<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="underline"
|
||||
>
|
||||
使用条款
|
||||
</a>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
This demo CDN uses our free testing LLM API. By using it 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="underline"
|
||||
>
|
||||
Terms of Use
|
||||
</a>
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<CodeEditor
|
||||
@@ -71,8 +99,8 @@ import { PageAgent } from 'page-agent'`}
|
||||
</h3>
|
||||
<CodeEditor
|
||||
code={`const agent = new PageAgent({
|
||||
model: 'deepseek-chat',
|
||||
baseURL: 'https://api.deepseek.com',
|
||||
model: 'qwen3.5-plus',
|
||||
baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
|
||||
apiKey: 'YOUR_API_KEY',
|
||||
language: '${isZh ? 'zh-CN' : 'en-US'}'
|
||||
})`}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useEffect, useRef, useState } from 'react'
|
||||
import { Link } from 'wouter'
|
||||
|
||||
import CodeEditor from '@/components/CodeEditor'
|
||||
import { Heading } from '@/components/Heading'
|
||||
import { useLanguage } from '@/i18n/context'
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -369,10 +370,13 @@ export default function TroubleshootingPage() {
|
||||
{/* Main content */}
|
||||
<div className="flex-1 min-w-0 space-y-12">
|
||||
{SECTIONS.map((section) => (
|
||||
<section key={section.id} id={section.id} className="scroll-mt-24">
|
||||
<h2 className="text-2xl font-bold mb-4 text-gray-900 dark:text-white">
|
||||
<section key={section.id} className="scroll-mt-24">
|
||||
<Heading
|
||||
id={section.id}
|
||||
className="text-2xl font-bold mb-4 text-gray-900 dark:text-white"
|
||||
>
|
||||
{isZh ? section.title.zh : section.title.en}
|
||||
</h2>
|
||||
</Heading>
|
||||
|
||||
{/* Symptom callout */}
|
||||
<div
|
||||
|
||||
@@ -0,0 +1,232 @@
|
||||
import { Bot, Box, MessageSquare, Shield, Sparkles, Users } from 'lucide-react'
|
||||
|
||||
import { BlurFade } from '../../components/ui/blur-fade'
|
||||
import { Highlighter } from '../../components/ui/highlighter'
|
||||
import { MagicCard } from '../../components/ui/magic-card'
|
||||
import { Particles } from '../../components/ui/particles'
|
||||
import { useLanguage } from '../../i18n/context'
|
||||
|
||||
// Word-cloud style: each item has a position (%), size, opacity, and color for a scattered look
|
||||
const LLM_CLOUD: {
|
||||
name: string
|
||||
color: string
|
||||
x: number
|
||||
y: number
|
||||
size: number
|
||||
opacity: number
|
||||
}[] = [
|
||||
{ name: 'OpenAI', color: '#10b981', x: 18, y: 22, size: 1.5, opacity: 1 },
|
||||
{ name: 'Claude', color: '#f97316', x: 62, y: 15, size: 1.35, opacity: 0.95 },
|
||||
{ name: 'Qwen', color: '#8b5cf6', x: 38, y: 50, size: 1.8, opacity: 0.9 },
|
||||
{ name: 'Gemini', color: '#3b82f6', x: 68, y: 48, size: 1.2, opacity: 0.85 },
|
||||
{ name: 'DeepSeek', color: '#06b6d4', x: 10, y: 65, size: 1.1, opacity: 0.8 },
|
||||
{ name: 'Grok', color: '#f43f5e', x: 52, y: 78, size: 1.0, opacity: 0.75 },
|
||||
{ name: 'Ollama', color: '#9ca3af', x: 82, y: 25, size: 1.1, opacity: 0.8 },
|
||||
{ name: 'Kimi', color: '#14b8a6', x: 30, y: 82, size: 0.85, opacity: 0.6 },
|
||||
{ name: 'GLM', color: '#f59e0b', x: 70, y: 72, size: 0.85, opacity: 0.55 },
|
||||
{ name: 'LLaMA', color: '#60a5fa', x: 88, y: 70, size: 0.8, opacity: 0.45 },
|
||||
]
|
||||
|
||||
const CARD_HEIGHT = 'h-72'
|
||||
|
||||
export default function FeaturesSection() {
|
||||
const { isZh } = useLanguage()
|
||||
|
||||
return (
|
||||
<section className="px-6 py-14" aria-labelledby="features-heading">
|
||||
<div className="max-w-6xl mx-auto">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 auto-rows-[18rem]">
|
||||
{/* Row 1: Zero Infrastructure (2col) + Privacy (1col) */}
|
||||
<BlurFade inView className="col-span-1 md:col-span-2">
|
||||
<MagicCard
|
||||
className="h-full rounded-2xl"
|
||||
gradientFrom="#3b82f6"
|
||||
gradientTo="#06b6d4"
|
||||
gradientColor="#3b82f6"
|
||||
gradientOpacity={0.15}
|
||||
>
|
||||
<div className={`flex ${CARD_HEIGHT} flex-col`}>
|
||||
<div className="flex-1 p-7 flex flex-col justify-center">
|
||||
<div className="space-y-2.5 mb-5">
|
||||
{[
|
||||
'pip install browser-use playwright',
|
||||
'docker run -p 3000:3000 playwright-mcp',
|
||||
'const browser = await chromium.launch()',
|
||||
].map((cmd) => (
|
||||
<div
|
||||
key={cmd}
|
||||
className="font-mono text-sm text-white-400 dark:text-gray-300 truncate"
|
||||
>
|
||||
<Highlighter
|
||||
action="strike-through"
|
||||
color="#ef4444aa"
|
||||
strokeWidth={1.5}
|
||||
// multiline={false}
|
||||
isView
|
||||
>
|
||||
{cmd}
|
||||
</Highlighter>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="bg-emerald-50 dark:bg-emerald-950/30 border border-emerald-200/60 dark:border-emerald-700/40 rounded-xl px-5 py-3 font-mono text-sm text-emerald-700 dark:text-emerald-400 flex items-center gap-2.5">
|
||||
<span className="text-emerald-500 text-xs shrink-0">✓</span>
|
||||
{'<script src="page-agent.js"></script>'}
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-7 pb-5">
|
||||
<div className="flex items-center gap-2.5 mb-1">
|
||||
<Box className="w-5 h-5 text-blue-500" />
|
||||
<h3 className="font-semibold text-lg text-gray-900 dark:text-white">
|
||||
{isZh ? '零基建集成' : 'Zero Infrastructure'}
|
||||
</h3>
|
||||
</div>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-300 leading-relaxed">
|
||||
{isZh
|
||||
? '无需 Python、无头浏览器、服务端部署。一行 script 标签搞定。'
|
||||
: "No Python. No headless browser. No server. One script tag — that's it."}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</MagicCard>
|
||||
</BlurFade>
|
||||
|
||||
<BlurFade inView delay={0.1} className="col-span-1">
|
||||
<MagicCard
|
||||
className="h-full rounded-2xl"
|
||||
gradientFrom="#8b5cf6"
|
||||
gradientTo="#a855f7"
|
||||
gradientColor="#8b5cf6"
|
||||
gradientOpacity={0.12}
|
||||
>
|
||||
<div className={`flex ${CARD_HEIGHT} flex-col`}>
|
||||
<div className="flex-1 relative overflow-hidden">
|
||||
<Particles
|
||||
className="absolute inset-0"
|
||||
quantity={40}
|
||||
staticity={50}
|
||||
ease={80}
|
||||
color="#8b5cf6"
|
||||
/>
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<div className="w-16 h-16 rounded-2xl bg-purple-500/10 dark:bg-purple-500/20 backdrop-blur-sm flex items-center justify-center ring-1 ring-purple-500/20">
|
||||
<Shield className="w-8 h-8 text-purple-500" strokeWidth={1.5} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-6 pb-5">
|
||||
<h3 className="font-semibold text-lg text-gray-900 dark:text-white mb-1">
|
||||
{isZh ? '隐私优先' : 'Privacy by Default'}
|
||||
</h3>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-300 leading-relaxed">
|
||||
{isZh
|
||||
? '浏览器内运行,数据完全由你掌控。'
|
||||
: 'Runs in the browser. You control your data, always.'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</MagicCard>
|
||||
</BlurFade>
|
||||
|
||||
{/* Row 2: Human-in-the-Loop (1col) + LLM (2col) */}
|
||||
<BlurFade inView delay={0.15} className="col-span-1">
|
||||
<MagicCard
|
||||
className="h-full rounded-2xl"
|
||||
gradientFrom="#3b82f6"
|
||||
gradientTo="#8b5cf6"
|
||||
gradientColor="#6366f1"
|
||||
gradientOpacity={0.12}
|
||||
>
|
||||
<div className={`flex ${CARD_HEIGHT} flex-col`}>
|
||||
<div className="flex-1 p-5 flex flex-col justify-center max-w-xs mx-auto w-full">
|
||||
<div className="flex gap-2 mb-2.5">
|
||||
<div className="shrink-0 w-6 h-6 rounded-full bg-purple-100 dark:bg-purple-900/50 flex items-center justify-center">
|
||||
<Bot className="w-3.5 h-3.5 text-purple-600 dark:text-purple-400" />
|
||||
</div>
|
||||
<div className="bg-gray-100 dark:bg-white/10 rounded-2xl rounded-tl-md px-3.5 py-2 text-sm text-gray-700 dark:text-gray-200">
|
||||
{isZh ? '找到 3 条匹配记录。选择哪一条?' : 'Found 3 matches. Which one?'}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2 justify-end mb-2.5">
|
||||
<div className="bg-blue-500 rounded-2xl rounded-tr-md px-3.5 py-2 text-sm text-white">
|
||||
{isZh ? '第二条' : 'The second one.'}
|
||||
</div>
|
||||
<div className="shrink-0 w-6 h-6 rounded-full bg-blue-100 dark:bg-blue-900/50 flex items-center justify-center">
|
||||
<Users className="w-3.5 h-3.5 text-blue-600 dark:text-blue-400" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<div className="shrink-0 w-6 h-6 rounded-full bg-emerald-100 dark:bg-emerald-900/50 flex items-center justify-center text-emerald-600 dark:text-emerald-400 text-xs font-bold">
|
||||
✓
|
||||
</div>
|
||||
<div className="bg-gray-100 dark:bg-white/10 rounded-2xl rounded-tl-md px-3.5 py-2 text-sm text-gray-700 dark:text-gray-200">
|
||||
{isZh ? '已选择并提交!' : 'Done! Selected and submitted.'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-5 pb-5">
|
||||
<div className="flex items-center gap-2.5 mb-1">
|
||||
<MessageSquare className="w-5 h-5 text-blue-500" />
|
||||
<h3 className="font-semibold text-lg text-gray-900 dark:text-white">
|
||||
{isZh ? '人机协同' : 'Human-in-the-Loop'}
|
||||
</h3>
|
||||
</div>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-300 leading-relaxed">
|
||||
{isZh
|
||||
? '内置协作面板,AI 操作前先确认——不是盲目自动化。'
|
||||
: 'Built-in collaborative panel. Agent asks before acting — not blind automation.'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</MagicCard>
|
||||
</BlurFade>
|
||||
|
||||
<BlurFade inView delay={0.2} className="col-span-1 md:col-span-2">
|
||||
<MagicCard
|
||||
className="h-full rounded-2xl"
|
||||
gradientFrom="#f59e0b"
|
||||
gradientTo="#f97316"
|
||||
gradientColor="#f59e0b"
|
||||
gradientOpacity={0.12}
|
||||
>
|
||||
<div className={`flex ${CARD_HEIGHT} flex-col`}>
|
||||
<div className="flex-1 overflow-hidden relative">
|
||||
{LLM_CLOUD.map((item) => (
|
||||
<span
|
||||
key={item.name}
|
||||
className="absolute font-semibold whitespace-nowrap select-none"
|
||||
style={{
|
||||
left: `${item.x}%`,
|
||||
top: `${item.y}%`,
|
||||
fontSize: `${item.size}rem`,
|
||||
color: item.color,
|
||||
opacity: item.opacity,
|
||||
transform: 'translate(-50%, -50%)',
|
||||
textShadow: `0 0 80px ${item.color}99`,
|
||||
}}
|
||||
>
|
||||
{item.name}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
<div className="px-7 pb-5">
|
||||
<div className="flex items-center gap-2.5 mb-1">
|
||||
<Sparkles className="w-5 h-5 text-amber-500" />
|
||||
<h3 className="font-semibold text-lg text-gray-900 dark:text-white">
|
||||
{isZh ? '兼容多种 LLM' : 'Bring Your Own LLMs'}
|
||||
</h3>
|
||||
</div>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-300 leading-relaxed">
|
||||
{isZh
|
||||
? 'OpenAI、Claude、DeepSeek、Qwen 等,或通过 Ollama 完全离线。'
|
||||
: 'OpenAI, Claude, DeepSeek, Qwen, and more — or fully offline via Ollama.'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</MagicCard>
|
||||
</BlurFade>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,403 @@
|
||||
/* eslint-disable react-dom/no-dangerously-set-innerhtml */
|
||||
import { PageAgent } from 'page-agent'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Link, useSearchParams } from 'wouter'
|
||||
|
||||
import { AnimatedGradientText } from '../../components/ui/animated-gradient-text'
|
||||
import { Highlighter } from '../../components/ui/highlighter'
|
||||
import { NeonGradientCard } from '../../components/ui/neon-gradient-card'
|
||||
import { Particles } from '../../components/ui/particles'
|
||||
import {
|
||||
CDN_DEMO_CN_URL,
|
||||
CDN_DEMO_URL,
|
||||
DEMO_API_KEY,
|
||||
DEMO_BASE_URL,
|
||||
DEMO_MODEL,
|
||||
} from '../../constants'
|
||||
import { useLanguage } from '../../i18n/context'
|
||||
|
||||
function getInjection(useCN?: boolean) {
|
||||
const cdn = useCN ? CDN_DEMO_CN_URL : CDN_DEMO_URL
|
||||
|
||||
const injection = encodeURI(
|
||||
`javascript:(function(){var s=document.createElement('script');s.src=\`${cdn}?t=\${Math.random()}\`;s.setAttribute('crossorigin', true);s.type="text/javascript";s.onload=()=>console.log('PageAgent script loaded!');document.body.appendChild(s);})();`
|
||||
)
|
||||
|
||||
return `
|
||||
<a
|
||||
href=${injection}
|
||||
class="inline-flex items-center text-xs px-3 py-2 bg-blue-500 text-white font-medium rounded-lg hover:shadow-md transform hover:scale-105 transition-all duration-200 cursor-move border-2 border-dashed border-green-300"
|
||||
draggable="true"
|
||||
onclick="return false;"
|
||||
title="Drag me to your bookmarks bar!"
|
||||
>
|
||||
✨PageAgent
|
||||
</a>
|
||||
`
|
||||
}
|
||||
|
||||
export default function HeroSection() {
|
||||
const { language, isZh } = useLanguage()
|
||||
|
||||
const defaultTask = isZh
|
||||
? '从导航栏中进入文档页,打开"快速开始"相关的文档,帮我总结成 markdown'
|
||||
: 'Goto docs in navigation bar, find Quick-Start section, and summarize in markdown'
|
||||
|
||||
const [task, setTask] = useState(() => defaultTask)
|
||||
|
||||
useEffect(() => {
|
||||
setTask(defaultTask)
|
||||
}, [defaultTask])
|
||||
|
||||
const [params] = useSearchParams()
|
||||
const isOther = params.has('try_other')
|
||||
|
||||
const [activeTab, setActiveTab] = useState<'try' | 'other'>(isOther ? 'other' : 'try')
|
||||
const [cdnSource, setCdnSource] = useState<'international' | 'china'>('international')
|
||||
|
||||
const handleExecute = async () => {
|
||||
if (!task.trim()) return
|
||||
|
||||
const win = window as any
|
||||
|
||||
if (!win.pageAgent || win.pageAgent.disposed) {
|
||||
win.pageAgent = new PageAgent({
|
||||
interactiveBlacklist: [document.getElementById('root')!],
|
||||
language: language,
|
||||
|
||||
instructions: {
|
||||
system: 'You are a helpful assistant on PageAgent website.',
|
||||
getPageInstructions: (url: string) => {
|
||||
const hint = url.includes('page-agent') ? 'This is PageAgent demo page.' : undefined
|
||||
console.log('[instructions] getPageInstructions:', url, '->', hint)
|
||||
return hint
|
||||
},
|
||||
},
|
||||
|
||||
model:
|
||||
import.meta.env.DEV && import.meta.env.LLM_MODEL_NAME
|
||||
? import.meta.env.LLM_MODEL_NAME
|
||||
: DEMO_MODEL,
|
||||
baseURL:
|
||||
import.meta.env.DEV && import.meta.env.LLM_BASE_URL
|
||||
? import.meta.env.LLM_BASE_URL
|
||||
: DEMO_BASE_URL,
|
||||
apiKey:
|
||||
import.meta.env.DEV && import.meta.env.LLM_API_KEY
|
||||
? import.meta.env.LLM_API_KEY
|
||||
: DEMO_API_KEY,
|
||||
})
|
||||
}
|
||||
|
||||
const result = await win.pageAgent.execute(task)
|
||||
console.log(result)
|
||||
}
|
||||
|
||||
return (
|
||||
<section
|
||||
className="relative px-6 pt-24 py-20 pb-18 lg:py-22 lg:pt-28 overflow-hidden"
|
||||
aria-labelledby="hero-heading"
|
||||
>
|
||||
<div className="max-w-7xl mx-auto text-center">
|
||||
{/* Background Pattern + Particles */}
|
||||
<div className="absolute inset-0 opacity-30" aria-hidden="true">
|
||||
<div className="absolute inset-0 bg-linear-to-r from-blue-400/20 to-purple-400/20 rounded-3xl transform rotate-1"></div>
|
||||
<div className="absolute inset-0 bg-linear-to-l from-purple-400/20 to-blue-400/20 rounded-3xl transform -rotate-1"></div>
|
||||
</div>
|
||||
<Particles
|
||||
className="absolute inset-0"
|
||||
quantity={80}
|
||||
staticity={30}
|
||||
ease={80}
|
||||
color="#6366f1"
|
||||
/>
|
||||
|
||||
<div className="relative z-10">
|
||||
<div className="inline-flex items-center px-4 py-2 mb-8 text-sm font-medium bg-white/90 dark:bg-gray-800/90 rounded-full shadow-lg border border-gray-200 dark:border-gray-700">
|
||||
<span
|
||||
className="w-2 h-2 bg-blue-500 rounded-full mr-2 animate-pulse"
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
<AnimatedGradientText colorFrom="#3b82f6" colorTo="#8b5cf6">
|
||||
AI Agent In Your Webpage
|
||||
</AnimatedGradientText>
|
||||
</div>
|
||||
|
||||
<h1
|
||||
id="hero-heading"
|
||||
className="text-5xl lg:text-7xl font-bold mb-14 mt-8 bg-linear-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent pb-1"
|
||||
>
|
||||
{isZh ? (
|
||||
<>
|
||||
<span className="text-6xl lg:text-7xl">你网站里的 AI 操作员</span>
|
||||
<span className="block text-xl lg:text-2xl mt-5 font-medium bg-linear-to-r from-blue-400 to-purple-400 bg-clip-text text-transparent">
|
||||
The AI Operator Living in Your Web Page
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
The AI Operator
|
||||
<br />
|
||||
Living in Your Web Page
|
||||
</>
|
||||
)}
|
||||
</h1>
|
||||
|
||||
<p className="text-xl lg:text-2xl text-gray-600 dark:text-gray-300 mb-12 max-w-4xl mx-auto leading-relaxed">
|
||||
<Highlighter action="underline" color="#8b5cf6" strokeWidth={2}>
|
||||
<span className="bg-linear-to-r from-blue-500 to-purple-500 bg-clip-text text-transparent font-bold">
|
||||
{isZh ? '🪄一行代码' : '🪄One line of code'}
|
||||
</span>
|
||||
</Highlighter>
|
||||
{isZh
|
||||
? ',让你的网站变身 AI 原生应用。'
|
||||
: ', turns your website into an AI-native app.'}
|
||||
<br />
|
||||
{isZh
|
||||
? '用户/答疑机器人给出文字指示,AI 帮你操作页面。'
|
||||
: 'Users give natural language commands, AI handles the rest.'}
|
||||
</p>
|
||||
|
||||
{/* Try It Now Section - Tab Card */}
|
||||
<div className="mb-12">
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<NeonGradientCard
|
||||
borderSize={2}
|
||||
borderRadius={20}
|
||||
neonColors={{ firstColor: '#ff00aa', secondColor: '#00FFF1' }}
|
||||
>
|
||||
{/* Tab Headers */}
|
||||
<div className="flex border-b border-gray-200 dark:border-gray-700">
|
||||
<button
|
||||
onClick={() => setActiveTab('try')}
|
||||
className={`cursor-pointer flex-1 px-4 py-4 text-lg font-medium transition-colors duration-200 rounded-tl-2xl ${
|
||||
activeTab === 'try'
|
||||
? 'bg-linear-to-r from-blue-50 to-purple-50 dark:from-blue-900/30 dark:to-purple-900/30 text-blue-700 dark:text-blue-300 border-b-2 border-blue-500'
|
||||
: 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:bg-gray-50 dark:hover:bg-gray-700'
|
||||
}`}
|
||||
>
|
||||
{isZh ? '🚀 立即尝试' : '🚀 Try It Now'}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('other')}
|
||||
className={`cursor-pointer flex-1 px-4 py-4 text-lg font-medium transition-colors duration-200 rounded-tr-2xl ${
|
||||
activeTab === 'other'
|
||||
? 'bg-linear-to-r from-green-50 to-blue-50 dark:from-green-900/30 dark:to-blue-900/30 text-green-700 dark:text-green-300 border-b-2 border-green-500'
|
||||
: 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:bg-gray-50 dark:hover:bg-gray-700'
|
||||
}`}
|
||||
>
|
||||
{isZh ? '🌐 其他网页尝试' : '🌐 Try on Other Sites'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Tab Content */}
|
||||
<div className="p-4">
|
||||
{activeTab === 'try' && (
|
||||
<div className="space-y-4">
|
||||
<div className="relative">
|
||||
<input
|
||||
value={task}
|
||||
onChange={(e) => setTask(e.target.value)}
|
||||
placeholder={
|
||||
isZh
|
||||
? '输入您想要 AI 执行的任务...'
|
||||
: 'Describe what you want AI to do...'
|
||||
}
|
||||
className="w-full px-4 py-3 pr-20 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-500 dark:placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-none text-sm mb-0"
|
||||
data-page-agent-not-interactive
|
||||
/>
|
||||
<button
|
||||
onClick={handleExecute}
|
||||
className="absolute right-2 top-2 px-5 py-1.5 bg-linear-to-r from-blue-600 to-purple-600 text-white font-medium rounded-md hover:shadow-md transform hover:scale-105 transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none text-sm"
|
||||
data-page-agent-not-interactive
|
||||
>
|
||||
{isZh ? '执行' : 'Run'}
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 text-left">
|
||||
{isZh ? (
|
||||
<>
|
||||
使用免费测试 LLM API,点击执行即表示您同意
|
||||
<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="underline"
|
||||
>
|
||||
使用条款
|
||||
</a>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
Powered by free testing LLM API. By clicking Run 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="underline"
|
||||
>
|
||||
Terms of Use
|
||||
</a>
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === 'other' && (
|
||||
<div className="grid md:grid-cols-2 gap-6">
|
||||
{/* 左侧:操作步骤 */}
|
||||
<div className="space-y-4">
|
||||
<div className="bg-blue-50 dark:bg-gray-700 p-4 rounded-lg">
|
||||
<p className="text-gray-700 dark:text-gray-300 text-sm mb-3">
|
||||
<span className="font-semibold">{isZh ? '步骤 1:' : 'Step 1:'}</span>{' '}
|
||||
{isZh ? '显示收藏夹栏' : 'Show your bookmarks bar'}
|
||||
</p>
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<kbd className="px-2 py-1 bg-white dark:bg-gray-600 border border-gray-300 dark:border-gray-500 rounded text-xs font-mono">
|
||||
Ctrl + Shift + B
|
||||
</kbd>
|
||||
<span className="text-gray-500 dark:text-gray-400">
|
||||
{isZh ? '或' : 'or'}
|
||||
</span>
|
||||
<kbd className="px-2 py-1 bg-white dark:bg-gray-600 border border-gray-300 dark:border-gray-500 rounded text-xs font-mono">
|
||||
⌘ + Shift + B
|
||||
</kbd>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-green-50 dark:bg-gray-700 p-4 rounded-lg">
|
||||
<p className="text-gray-700 dark:text-gray-300 text-sm mb-3">
|
||||
<span className="font-semibold">{isZh ? '步骤 2:' : 'Step 2:'}</span>{' '}
|
||||
{isZh ? '拖拽下面按钮到收藏夹栏' : 'Drag this button to your bookmarks'}
|
||||
</p>
|
||||
<div className="flex items-center justify-center gap-3">
|
||||
<select
|
||||
value={cdnSource}
|
||||
onChange={(e) =>
|
||||
setCdnSource(e.target.value as 'international' | 'china')
|
||||
}
|
||||
className="px-2 py-1.5 text-xs border border-gray-300 dark:border-gray-500 rounded bg-white dark:bg-gray-600 text-gray-700 dark:text-gray-200"
|
||||
>
|
||||
<option value="international">jsdelivr CDN</option>
|
||||
<option value="china">npmmirror CDN</option>
|
||||
</select>
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: getInjection(cdnSource === 'china'),
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-purple-50 dark:bg-gray-700 p-4 rounded-lg">
|
||||
<p className="text-gray-700 dark:text-gray-300 text-sm">
|
||||
<span className="font-semibold">{isZh ? '步骤 3:' : 'Step 3:'}</span>{' '}
|
||||
{isZh
|
||||
? '在其他网站点击收藏夹中的按钮即可使用'
|
||||
: 'Click the bookmark on any site to activate'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 右侧:注意事项 */}
|
||||
<div className="bg-yellow-50 dark:bg-gray-700 p-4 rounded-lg">
|
||||
<h4 className="font-semibold text-gray-900 dark:text-white mb-3 text-sm">
|
||||
{isZh ? '⚠️ 注意' : '⚠️ Heads Up'}
|
||||
</h4>
|
||||
<ul className="space-y-2 text-sm text-gray-700 dark:text-gray-300">
|
||||
<li className="flex items-start text-left">
|
||||
<span className="w-1.5 h-1.5 bg-yellow-500 rounded-full mt-2 mr-2 shrink-0 "></span>
|
||||
{isZh ? (
|
||||
<span>
|
||||
使用免费测试 LLM API,使用即表示同意
|
||||
<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-yellow-700 dark:text-yellow-300 underline"
|
||||
>
|
||||
使用条款
|
||||
</a>
|
||||
</span>
|
||||
) : (
|
||||
<span>
|
||||
Uses free testing LLM API. By using 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-yellow-700 dark:text-yellow-300 underline"
|
||||
>
|
||||
Terms of Use
|
||||
</a>
|
||||
</span>
|
||||
)}
|
||||
</li>
|
||||
<li className="flex items-start text-left">
|
||||
<span className="w-1.5 h-1.5 bg-yellow-500 rounded-full mt-2 mr-2 shrink-0 "></span>
|
||||
{isZh
|
||||
? '数据通过中国大陆服务器处理'
|
||||
: 'Data processed via servers in Mainland China'}
|
||||
</li>
|
||||
<li className="flex items-start text-left">
|
||||
<span className="w-1.5 h-1.5 bg-yellow-500 rounded-full mt-2 mr-2 shrink-0 "></span>
|
||||
{isZh
|
||||
? '部分网站屏蔽了链接嵌入,将无反应'
|
||||
: 'Some sites block script injection (CSP policies)'}
|
||||
</li>
|
||||
<li className="flex items-start text-left">
|
||||
<span className="w-1.5 h-1.5 bg-yellow-500 rounded-full mt-2 mr-2 shrink-0 "></span>
|
||||
{isZh ? '支持单页应用' : 'Works on single-page apps'}
|
||||
</li>
|
||||
<li className="flex items-start text-left">
|
||||
<span className="w-1.5 h-1.5 bg-yellow-500 rounded-full mt-2 mr-2 shrink-0 "></span>
|
||||
{isZh
|
||||
? '仅识别文本,不识别图像,不支持拖拽等复杂交互'
|
||||
: 'Text-only understanding—no image recognition or drag-and-drop'}
|
||||
</li>
|
||||
<li className="flex items-start text-left">
|
||||
<span className="w-1.5 h-1.5 bg-yellow-500 rounded-full mt-2 mr-2 shrink-0 "></span>
|
||||
{isZh ? '详细使用限制参照' : 'Full limitations in'}
|
||||
<Link
|
||||
href="/docs/introduction/limitations"
|
||||
className="text-blue-600 dark:text-blue-400 hover:underline pl-1"
|
||||
>
|
||||
{isZh ? '《文档》' : 'Docs'}
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</NeonGradientCard>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul
|
||||
className="flex flex-wrap justify-center gap-6 text-sm text-gray-500 dark:text-gray-400"
|
||||
role="list"
|
||||
>
|
||||
<li className="flex items-center">
|
||||
<span className="w-2 h-2 bg-green-500 rounded-full mr-2" aria-hidden="true"></span>
|
||||
{isZh ? '纯前端方案' : 'Pure Front-end Solution'}
|
||||
</li>
|
||||
<li className="flex items-center">
|
||||
<span className="w-2 h-2 bg-green-500 rounded-full mr-2" aria-hidden="true"></span>
|
||||
{isZh ? '支持私有模型' : 'Your Own Models'}
|
||||
</li>
|
||||
<li className="flex items-center">
|
||||
<span className="w-2 h-2 bg-green-500 rounded-full mr-2" aria-hidden="true"></span>
|
||||
{isZh ? '无痛脱敏' : 'Built-in Privacy'}
|
||||
</li>
|
||||
<li className="flex items-center">
|
||||
<span className="w-2 h-2 bg-green-500 rounded-full mr-2" aria-hidden="true"></span>
|
||||
{isZh ? 'MIT 开源' : 'MIT Open Source'}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
import { ExternalLink } from 'lucide-react'
|
||||
import { siGooglechrome } from 'simple-icons'
|
||||
import { Link } from 'wouter'
|
||||
|
||||
import { BlurFade } from '../../components/ui/blur-fade'
|
||||
import { MagicCard } from '../../components/ui/magic-card'
|
||||
import { useLanguage } from '../../i18n/context'
|
||||
|
||||
export default function OneMoreThingSection() {
|
||||
const { isZh } = useLanguage()
|
||||
|
||||
return (
|
||||
<section className="px-6 py-14" aria-labelledby="one-more-thing-heading">
|
||||
<div className="max-w-4xl mx-auto text-center">
|
||||
<BlurFade inView>
|
||||
<h2
|
||||
id="one-more-thing-heading"
|
||||
className="text-4xl lg:text-5xl font-bold mb-6 bg-linear-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent"
|
||||
>
|
||||
One More Thing
|
||||
</h2>
|
||||
<p className="text-xl text-gray-600 dark:text-gray-300 mb-4 max-w-2xl mx-auto">
|
||||
{isZh
|
||||
? '想要多页面控制?试试可选的浏览器扩展。'
|
||||
: 'Need multi-page control? Try the optional browser extension.'}
|
||||
</p>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400 mb-12 max-w-2xl mx-auto">
|
||||
{'* '}
|
||||
{isZh
|
||||
? 'PageAgent.js 本身无需任何扩展即可工作,扩展是额外的能力增强。'
|
||||
: 'PageAgent.js works without any extension — this is a power-up, not a dependency.'}
|
||||
</p>
|
||||
</BlurFade>
|
||||
|
||||
<div className="flex flex-col sm:flex-row items-center justify-center gap-4 mb-12">
|
||||
<a
|
||||
href="https://chromewebstore.google.com/detail/page-agent-ext/akldabonmimlicnjlflnapfeklbfemhj"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="group inline-flex items-center gap-3 px-8 py-4 bg-linear-to-r from-blue-600 to-blue-700 hover:from-blue-700 hover:to-blue-800 text-white font-medium rounded-2xl shadow-lg hover:shadow-xl transition-all duration-300 hover:scale-105"
|
||||
>
|
||||
<img
|
||||
src="https://img.alicdn.com/imgextra/i3/O1CN01JpW0Vo1sR3FpiZKFM_!!6000000005762-55-tps-192-192.svg"
|
||||
alt="Chrome Web Store"
|
||||
className="w-7 h-7"
|
||||
/>
|
||||
<span>{isZh ? '从 Chrome 应用商店安装' : 'Install from Chrome Web Store'}</span>
|
||||
<ExternalLink className="w-4 h-4 opacity-50 group-hover:opacity-100 transition-opacity" />
|
||||
</a>
|
||||
<Link
|
||||
href="/docs/features/chrome-extension"
|
||||
className="inline-flex items-center gap-3 px-8 py-4 bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 text-gray-900 dark:text-white font-medium rounded-2xl transition-all duration-300 hover:scale-105"
|
||||
>
|
||||
<svg className="w-5 h-5" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path d={siGooglechrome.path} fill="currentColor" />
|
||||
</svg>
|
||||
<span>{isZh ? '查看文档' : 'Read the Docs'}</span>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="grid sm:grid-cols-3 gap-5 text-left max-w-3xl mx-auto">
|
||||
{[
|
||||
{
|
||||
title: isZh ? '多页面任务' : 'Multi-Page Tasks',
|
||||
desc: isZh
|
||||
? '跨多个页面和标签页连续执行任务,不再受限于单页上下文'
|
||||
: 'Run tasks across multiple pages and tabs without being limited to a single page context',
|
||||
},
|
||||
{
|
||||
title: isZh ? '页面内发起控制' : 'Control from Your Page',
|
||||
desc: isZh
|
||||
? '在页面 JS 中发起任务,驱动整个浏览器完成跨标签操作'
|
||||
: 'Trigger tasks from page JS to drive the entire browser across tabs',
|
||||
},
|
||||
{
|
||||
title: isZh ? '外部发起任务' : 'External Triggers',
|
||||
desc: isZh
|
||||
? '页面 JS、本地 Agent 或云端 Agent 均可通过扩展发起任务'
|
||||
: 'Page JS, local agents, or cloud agents can trigger tasks through the extension',
|
||||
},
|
||||
].map((item) => (
|
||||
<MagicCard
|
||||
key={item.title}
|
||||
className="rounded-xl"
|
||||
gradientColor="#8b5cf620"
|
||||
gradientOpacity={0.15}
|
||||
>
|
||||
<div className="p-5">
|
||||
<h3 className="font-semibold text-gray-900 dark:text-white mb-1">{item.title}</h3>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-300">{item.desc}</p>
|
||||
</div>
|
||||
</MagicCard>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
import { Bot, Users, Zap } from 'lucide-react'
|
||||
|
||||
import { BlurFade } from '../../components/ui/blur-fade'
|
||||
import { SparklesText } from '../../components/ui/sparkles-text'
|
||||
import { useLanguage } from '../../i18n/context'
|
||||
|
||||
export default function ScenariosSection() {
|
||||
const { isZh } = useLanguage()
|
||||
|
||||
return (
|
||||
<section className="px-6 py-16" aria-labelledby="scenarios-heading">
|
||||
<div className="max-w-6xl mx-auto">
|
||||
<BlurFade inView>
|
||||
<div className="text-center mb-12">
|
||||
<SparklesText
|
||||
className="text-4xl lg:text-5xl mb-6"
|
||||
colors={{ first: '#3b82f6', second: '#8b5cf6' }}
|
||||
>
|
||||
{isZh ? '应用场景' : 'Built For'}
|
||||
</SparklesText>
|
||||
</div>
|
||||
</BlurFade>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
{/* SaaS AI Copilot */}
|
||||
<BlurFade inView delay={0.05}>
|
||||
<div className="group relative overflow-hidden rounded-2xl bg-linear-to-b from-blue-50 to-white dark:from-blue-950/40 dark:to-gray-800 border border-blue-200/80 dark:border-blue-800/50 shadow-sm hover:shadow-xl hover:-translate-y-1 transition-all duration-500">
|
||||
<div className="p-6 pb-4">
|
||||
<div className="rounded-xl bg-gray-950 p-4 font-mono text-xs leading-6 text-gray-300 overflow-hidden shadow-inner">
|
||||
<div>
|
||||
<span className="text-purple-400">import</span> {'{ PageAgent }'}{' '}
|
||||
<span className="text-purple-400">from</span>{' '}
|
||||
<span className="text-emerald-400">'page-agent'</span>
|
||||
</div>
|
||||
<div className="mt-2">
|
||||
<span className="text-purple-400">const</span>{' '}
|
||||
<span className="text-blue-300">copilot</span> ={' '}
|
||||
<span className="text-purple-400">new</span>{' '}
|
||||
<span className="text-yellow-300">PageAgent</span>
|
||||
{'({'}
|
||||
</div>
|
||||
<div className="pl-4">
|
||||
<span className="text-blue-300">model</span>:{' '}
|
||||
<span className="text-emerald-400">'gpt-5.1'</span>,
|
||||
</div>
|
||||
<div className="pl-4">
|
||||
<span className="text-blue-300">apiKey</span>:{' '}
|
||||
<span className="text-emerald-400">process.env.KEY</span>,
|
||||
</div>
|
||||
<div>{'})'}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-6 pt-2">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Bot className="w-5 h-5 text-blue-500" />
|
||||
<h3 className="font-semibold text-lg text-gray-900 dark:text-white">
|
||||
{isZh ? 'SaaS AI 副驾驶' : 'SaaS AI Copilot'}
|
||||
</h3>
|
||||
</div>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-300 leading-relaxed">
|
||||
{isZh
|
||||
? '几小时内为你的产品加上 AI 副驾驶,不需要重写后端。'
|
||||
: 'Ship an AI copilot in your product in hours, not months. No backend rewrite needed.'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</BlurFade>
|
||||
|
||||
{/* Smart Form Filling */}
|
||||
<BlurFade inView delay={0.1}>
|
||||
<div className="group relative overflow-hidden rounded-2xl bg-linear-to-b from-amber-50 to-white dark:from-amber-950/40 dark:to-gray-800 border border-amber-200/80 dark:border-amber-800/50 shadow-sm hover:shadow-xl hover:-translate-y-1 transition-all duration-500">
|
||||
<div className="p-6 pb-4">
|
||||
<div className="rounded-xl bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700 p-4 shadow-inner space-y-2.5">
|
||||
<div className="flex items-center gap-2 text-xs text-gray-500 dark:text-gray-400 bg-amber-50 dark:bg-amber-900/30 rounded-lg px-3 py-2 border border-amber-200/50 dark:border-amber-700/40">
|
||||
<span>🪄</span>
|
||||
<span className="italic">
|
||||
{isZh
|
||||
? '"填写上周五出差的报销单"'
|
||||
: '"Fill the expense report for Friday\'s trip"'}
|
||||
</span>
|
||||
</div>
|
||||
{[
|
||||
{ label: isZh ? '姓名' : 'Name', value: 'John Smith' },
|
||||
{ label: isZh ? '金额' : 'Amount', value: '$342.50' },
|
||||
{ label: isZh ? '类目' : 'Category', value: 'Travel' },
|
||||
].map((field) => (
|
||||
<div key={field.label} className="flex items-center gap-2">
|
||||
<span className="text-xs text-gray-400 dark:text-gray-500 w-12 shrink-0">
|
||||
{field.label}
|
||||
</span>
|
||||
<div className="flex-1 h-7 bg-gray-50 dark:bg-gray-800 rounded border border-gray-200 dark:border-gray-600 px-2 flex items-center text-xs text-gray-600 dark:text-gray-300">
|
||||
{field.value}
|
||||
</div>
|
||||
<span className="text-emerald-500 text-xs">✓</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-6 pt-2">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Zap className="w-5 h-5 text-amber-500" />
|
||||
<h3 className="font-semibold text-lg text-gray-900 dark:text-white">
|
||||
{isZh ? '智能表单填写' : 'Smart Form Filling'}
|
||||
</h3>
|
||||
</div>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-300 leading-relaxed">
|
||||
{isZh
|
||||
? '把 20 次点击变成一句话。ERP、CRM、管理后台的最佳拍档。'
|
||||
: 'Turn 20-click workflows into one sentence. Perfect for ERP, CRM, and admin systems.'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</BlurFade>
|
||||
|
||||
{/* Accessibility */}
|
||||
<BlurFade inView delay={0.15}>
|
||||
<div className="group relative overflow-hidden rounded-2xl bg-linear-to-b from-purple-50 to-white dark:from-purple-950/40 dark:to-gray-800 border border-purple-200/80 dark:border-purple-800/50 shadow-sm hover:shadow-xl hover:-translate-y-1 transition-all duration-500">
|
||||
<div className="p-6 pb-4 flex flex-col items-center justify-center">
|
||||
<div className="w-full rounded-xl bg-purple-50 dark:bg-purple-900/30 p-5 space-y-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 rounded-full bg-purple-500/10 dark:bg-purple-500/20 flex items-center justify-center text-base">
|
||||
🎤
|
||||
</div>
|
||||
<div className="text-sm text-purple-700 dark:text-purple-300 italic">
|
||||
{isZh ? '"点击提交按钮"' : '"Click the submit button"'}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 pl-11">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<div className="w-1.5 h-1.5 bg-purple-400 rounded-full animate-pulse"></div>
|
||||
<div className="w-1.5 h-1.5 bg-purple-400 rounded-full animate-pulse [animation-delay:0.2s]"></div>
|
||||
<div className="w-1.5 h-1.5 bg-purple-400 rounded-full animate-pulse [animation-delay:0.4s]"></div>
|
||||
</div>
|
||||
<span className="text-xs text-purple-500 dark:text-purple-400">
|
||||
{isZh ? 'AI 正在执行...' : 'AI executing...'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 pl-11 text-sm text-emerald-600 dark:text-emerald-400">
|
||||
<span>✓</span> {isZh ? '按钮已点击' : 'Button clicked'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-6 pt-2">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Users className="w-5 h-5 text-purple-500" />
|
||||
<h3 className="font-semibold text-lg text-gray-900 dark:text-white">
|
||||
{isZh ? '无障碍增强' : 'Accessibility'}
|
||||
</h3>
|
||||
</div>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-300 leading-relaxed">
|
||||
{isZh
|
||||
? '用自然语言让任何网页无障碍。语音指令、屏幕阅读器,零门槛。'
|
||||
: 'Make any web app accessible through natural language. Voice, screen readers, zero barrier.'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</BlurFade>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -1,35 +1,52 @@
|
||||
import { Suspense, lazy } from 'react'
|
||||
import { Route, Switch } from 'wouter'
|
||||
import { Suspense, useLayoutEffect } from 'react'
|
||||
import { Route, Switch, useLocation } from 'wouter'
|
||||
|
||||
import Footer from './components/Footer'
|
||||
import Header from './components/Header'
|
||||
import HomePage from './pages/Home'
|
||||
import DocsPages from './pages/docs/index'
|
||||
|
||||
// const DocsPages = lazy(() => import('./pages/docs/index'))
|
||||
function ScrollToTop() {
|
||||
const [pathname] = useLocation()
|
||||
useLayoutEffect(() => {
|
||||
window.scrollTo(0, 0)
|
||||
}, [pathname])
|
||||
return null
|
||||
}
|
||||
|
||||
export default function Router() {
|
||||
return (
|
||||
<Suspense>
|
||||
<Switch>
|
||||
{/* Home page */}
|
||||
<Route path="/">
|
||||
<HomePage />
|
||||
</Route>
|
||||
<div className="flex min-h-screen flex-col">
|
||||
<Header />
|
||||
<Suspense>
|
||||
<ScrollToTop />
|
||||
<Switch>
|
||||
<Route path="/">
|
||||
<main
|
||||
id="main-content"
|
||||
className="flex-1 bg-linear-to-br from-blue-50 to-purple-50 dark:from-gray-900 dark:to-gray-800"
|
||||
>
|
||||
<HomePage />
|
||||
</main>
|
||||
</Route>
|
||||
|
||||
{/* All docs pages */}
|
||||
<Route path="/docs" nest>
|
||||
<DocsPages />
|
||||
</Route>
|
||||
|
||||
{/* 404 */}
|
||||
<Route>
|
||||
<div className="min-h-screen bg-white dark:bg-gray-900 flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<h1 className="text-4xl font-bold mb-4 text-gray-900 dark:text-white">404</h1>
|
||||
<p className="text-xl text-gray-600 dark:text-gray-300">页面未找到</p>
|
||||
<Route path="/docs" nest>
|
||||
<div className="flex-1 bg-white dark:bg-gray-900">
|
||||
<DocsPages />
|
||||
</div>
|
||||
</div>
|
||||
</Route>
|
||||
</Switch>
|
||||
</Suspense>
|
||||
</Route>
|
||||
|
||||
<Route>
|
||||
<div className="flex-1 bg-white dark:bg-gray-900 flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<h1 className="text-4xl font-bold mb-4 text-gray-900 dark:text-white">404</h1>
|
||||
<p className="text-xl text-gray-600 dark:text-gray-300">Page not found</p>
|
||||
</div>
|
||||
</div>
|
||||
</Route>
|
||||
</Switch>
|
||||
</Suspense>
|
||||
<Footer />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import tailwindcss from '@tailwindcss/vite'
|
||||
import react from '@vitejs/plugin-react-swc'
|
||||
import { config as dotenvConfig } from 'dotenv'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { copyFileSync, mkdirSync, readFileSync } from 'node:fs'
|
||||
import process from 'node:process'
|
||||
import { dirname, resolve } from 'path'
|
||||
import { dirname, join, resolve } from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
import { defineConfig } from 'vite'
|
||||
|
||||
@@ -15,11 +15,47 @@ const pageAgentPkg = JSON.parse(
|
||||
// Load .env from repo root
|
||||
dotenvConfig({ path: resolve(__dirname, '../../.env') })
|
||||
|
||||
// All SPA routes that need index.html copies for direct access on static hosts
|
||||
const SPA_ROUTES = [
|
||||
'docs',
|
||||
'docs/introduction/overview',
|
||||
'docs/introduction/quick-start',
|
||||
'docs/introduction/limitations',
|
||||
'docs/introduction/troubleshooting',
|
||||
'docs/features/custom-tools',
|
||||
'docs/features/data-masking',
|
||||
'docs/features/custom-instructions',
|
||||
'docs/features/models',
|
||||
'docs/features/chrome-extension',
|
||||
'docs/advanced/page-agent',
|
||||
'docs/advanced/page-agent-core',
|
||||
'docs/advanced/custom-ui',
|
||||
'docs/integration/security-permissions',
|
||||
'docs/integration/best-practices',
|
||||
'docs/integration/third-party-agent',
|
||||
]
|
||||
|
||||
function spaRoutes() {
|
||||
return {
|
||||
name: 'spa-routes',
|
||||
closeBundle() {
|
||||
const dist = resolve(__dirname, 'dist')
|
||||
const src = join(dist, 'index.html')
|
||||
for (const route of SPA_ROUTES) {
|
||||
const dir = join(dist, route)
|
||||
mkdirSync(dir, { recursive: true })
|
||||
copyFileSync(src, join(dir, 'index.html'))
|
||||
}
|
||||
console.log(` ✓ Copied index.html to ${SPA_ROUTES.length} SPA routes`)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Website Config (React Documentation Site)
|
||||
export default defineConfig(({ mode }) => ({
|
||||
base: './',
|
||||
base: '/page-agent/',
|
||||
clearScreen: false,
|
||||
plugins: [react(), tailwindcss()],
|
||||
plugins: [react(), tailwindcss(), spaRoutes()],
|
||||
build: {
|
||||
chunkSizeWarningLimit: 2000,
|
||||
cssCodeSplit: true,
|
||||
|
||||
@@ -111,7 +111,7 @@ const CDN_DEMO_URL_NEW = `https://cdn.jsdelivr.net/npm/page-agent@${newVersion}/
|
||||
const CDN_DEMO_CN_URL_OLD = `https://registry.npmmirror.com/page-agent/${oldVersion}/files/dist/iife/page-agent.demo.js`
|
||||
const CDN_DEMO_CN_URL_NEW = `https://registry.npmmirror.com/page-agent/${newVersion}/files/dist/iife/page-agent.demo.js`
|
||||
|
||||
const filesToUpdateCdn = ['README.md', 'README-zh.md', 'packages/website/src/constants.ts']
|
||||
const filesToUpdateCdn = ['README.md', 'docs/README-zh.md', 'packages/website/src/constants.ts']
|
||||
|
||||
for (const relPath of filesToUpdateCdn) {
|
||||
const filePath = join(rootDir, relPath)
|
||||
|
||||
Reference in New Issue
Block a user