Compare commits
59 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| af36f43104 | |||
| b5f260207c | |||
| 9075204c29 | |||
| ce3a80bc53 | |||
| 64c36f67bb | |||
| 78d4919289 | |||
| 1af2607cca | |||
| ab713f3d3a | |||
| 05cc4f36c8 | |||
| 54e9bdad7d | |||
| 0fc88c0080 | |||
| 02f97ef2fa | |||
| 0853ffbbc6 | |||
| 60ac6da09f | |||
| 11a9fb1181 | |||
| 3762d0f1e0 | |||
| ebbe92f7ca | |||
| 9f239b2de3 | |||
| 2410c27b55 | |||
| 6d6c82648c | |||
| f41119c1ad | |||
| 9e6e1b996c | |||
| 1ab23e181f | |||
| a1b485691f | |||
| 694e05b800 | |||
| b761795c1a | |||
| 8cc54983aa | |||
| 59a599285e | |||
| 2f8c2b395c | |||
| 28f836674c | |||
| 6f01632231 | |||
| 41123ac561 | |||
| c8bf0dd20f | |||
| ec2b744bab | |||
| eab9cf64e8 | |||
| 42130d6f1d | |||
| c0ce6e7d14 | |||
| 8f5f98f889 | |||
| ee4719cdcf | |||
| b4c82b7833 | |||
| aefc3cfb89 | |||
| 7f2b8c2766 | |||
| 62e24a3050 | |||
| 4f6249a252 | |||
| 62519416bb | |||
| a1e70e663d | |||
| 338e317be1 | |||
| 9a66d5205f | |||
| 7d45bf6598 | |||
| 78ee72eea4 | |||
| 3b71c535b1 | |||
| 6615f7b300 | |||
| 38caae5e23 | |||
| 1751ba325d | |||
| 066b045822 | |||
| 3b21c42cdb | |||
| 8b1e91d665 | |||
| aa5909264a | |||
| b6232d4e21 |
@@ -6,7 +6,7 @@ on:
|
|||||||
- 'v*'
|
- 'v*'
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
id-token: write # Required for OIDC
|
id-token: write # Required for OIDC
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|||||||
Vendored
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"recommendations": ["dbaeumer.vscode-eslint", "prettier.prettier-vscode"]
|
"recommendations": ["dbaeumer.vscode-eslint", "prettier.prettier-vscode"]
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+27
-26
@@ -1,28 +1,29 @@
|
|||||||
{
|
{
|
||||||
"editor.fontLigatures": true,
|
"editor.fontLigatures": true,
|
||||||
"cSpell.words": [
|
"cSpell.words": [
|
||||||
"deepseek",
|
"deepseek",
|
||||||
"HITL",
|
"HITL",
|
||||||
"innerhtml",
|
"innerhtml",
|
||||||
"llms",
|
"llms",
|
||||||
"opensource",
|
"onwarn",
|
||||||
"qwen",
|
"opensource",
|
||||||
"retryable",
|
"qwen",
|
||||||
"shadcn",
|
"retryable",
|
||||||
"wouter"
|
"shadcn",
|
||||||
],
|
"wouter"
|
||||||
"markdownlint.config": {
|
],
|
||||||
// "comment": "Relaxed rules",
|
"markdownlint.config": {
|
||||||
"default": true,
|
// "comment": "Relaxed rules",
|
||||||
"whitespace": false,
|
"default": true,
|
||||||
"line_length": false,
|
"whitespace": false,
|
||||||
"ul-indent": false,
|
"line_length": false,
|
||||||
"no-inline-html": false,
|
"ul-indent": false,
|
||||||
"no-bare-urls": false,
|
"no-inline-html": false,
|
||||||
"fenced-code-language": false,
|
"no-bare-urls": false,
|
||||||
"first-line-h1": false,
|
"fenced-code-language": false,
|
||||||
"block-spacing": false,
|
"first-line-h1": false,
|
||||||
"blanks-around-lists": false,
|
"block-spacing": false,
|
||||||
"ol-prefix": false
|
"blanks-around-lists": false,
|
||||||
}
|
"ol-prefix": false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,9 +9,10 @@ This is a **monorepo** with npm workspaces:
|
|||||||
|
|
||||||
Internal packages:
|
Internal packages:
|
||||||
|
|
||||||
|
- **CDN** (`packages/cdn/`) - IIFE builds for script tag usage (npm: `@page-agent/cdn`)
|
||||||
- **LLMs** (`packages/llms/`) - LLM client with reflection-before-action mental model
|
- **LLMs** (`packages/llms/`) - LLM client with reflection-before-action mental model
|
||||||
- **Page Controller** (`packages/page-controller/`) - DOM operations, independent of LLM
|
- **Page Controller** (`packages/page-controller/`) - DOM operations and visual feedback (SimulatorMask), independent of LLM
|
||||||
- **UI** (`packages/ui/`) - Panel, SimulatorMask, i18n. Decoupled from PageAgent
|
- **UI** (`packages/ui/`) - Panel and i18n. Decoupled from PageAgent
|
||||||
|
|
||||||
## Development Commands
|
## Development Commands
|
||||||
|
|
||||||
@@ -31,6 +32,7 @@ Simple monorepo solution: TypeScript references + Vite aliases. Update tsconfig
|
|||||||
```
|
```
|
||||||
packages/
|
packages/
|
||||||
├── page-agent/ # npm: "page-agent" ⭐ MAIN
|
├── page-agent/ # npm: "page-agent" ⭐ MAIN
|
||||||
|
├── cdn/ # npm: "@page-agent/cdn" (IIFE builds)
|
||||||
├── website/ # @page-agent/website (private)
|
├── website/ # @page-agent/website (private)
|
||||||
├── llms/ # @page-agent/llms
|
├── llms/ # @page-agent/llms
|
||||||
├── page-controller/ # @page-agent/page-controller
|
├── page-controller/ # @page-agent/page-controller
|
||||||
@@ -43,8 +45,8 @@ packages/
|
|||||||
|
|
||||||
- **Page Agent**: Core lib. Imports from `@page-agent/llms`, `@page-agent/page-controller`, `@page-agent/ui`
|
- **Page Agent**: Core lib. Imports from `@page-agent/llms`, `@page-agent/page-controller`, `@page-agent/ui`
|
||||||
- **LLMs**: LLM client with MacroToolInput contract. No dependency on page-agent
|
- **LLMs**: LLM client with MacroToolInput contract. No dependency on page-agent
|
||||||
- **UI**: Panel, Mask, i18n. No dependency on page-agent
|
- **UI**: Panel and i18n. No dependency on page-agent
|
||||||
- **Page Controller**: Pure DOM operations. No LLM or UI dependency
|
- **Page Controller**: DOM operations with optional visual feedback (SimulatorMask). No LLM dependency. Enable mask via `enableMask: true` config
|
||||||
|
|
||||||
### PageController ↔ PageAgent Communication
|
### PageController ↔ PageAgent Communication
|
||||||
|
|
||||||
@@ -69,41 +71,43 @@ const pageInfo = await this.pageController.getPageInfo()
|
|||||||
3. **LLM Processing**: AI returns action plans (page-agent)
|
3. **LLM Processing**: AI returns action plans (page-agent)
|
||||||
4. **Indexed Operations**: PageAgent calls PageController by element index
|
4. **Indexed Operations**: PageAgent calls PageController by element index
|
||||||
|
|
||||||
### CDN Auto-Injection
|
### CDN Builds (`packages/cdn/`)
|
||||||
|
|
||||||
Library auto-initializes via script tag:
|
Two IIFE builds for script tag usage:
|
||||||
|
|
||||||
```html
|
| Build | File | Description |
|
||||||
<script src="page-agent.js?model=gpt-4"></script>
|
| ----- | -------------------- | -------------------------------- |
|
||||||
```
|
| Demo | `page-agent.demo.js` | Auto-init with built-in test API |
|
||||||
|
| Full | `page-agent.js` | Exposes `PageAgent` class only |
|
||||||
|
|
||||||
Query params configure `PageAgentConfig` in `src/umd.ts`.
|
Demo build supports query params (e.g., `?model=gpt-4&lang=en-US`).
|
||||||
|
|
||||||
## Key Files Reference
|
## Key Files Reference
|
||||||
|
|
||||||
### Page Agent (`packages/page-agent/`)
|
### Page Agent (`packages/page-agent/`)
|
||||||
|
|
||||||
| File | Description |
|
| File | Description |
|
||||||
|------|-------------|
|
| ------------------ | --------------------------------------- |
|
||||||
| `src/PageAgent.ts` | ⭐ Main AI agent class |
|
| `src/PageAgent.ts` | ⭐ Main AI agent class |
|
||||||
| `src/umd.ts` | CDN/UMD entry with auto-init |
|
| `src/umd.ts` | CDN/UMD entry with auto-init |
|
||||||
| `src/tools/` | Tool definitions calling PageController |
|
| `src/tools/` | Tool definitions calling PageController |
|
||||||
|
|
||||||
### LLMs (`packages/llms/`)
|
### LLMs (`packages/llms/`)
|
||||||
|
|
||||||
| File | Description |
|
| File | Description |
|
||||||
|------|-------------|
|
| ---------------------------- | ------------------------------------- |
|
||||||
| `src/index.ts` | ⭐ LLM class with retry logic |
|
| `src/index.ts` | ⭐ LLM class with retry logic |
|
||||||
| `src/types.ts` | MacroToolInput, AgentBrain, LLMConfig |
|
| `src/types.ts` | MacroToolInput, AgentBrain, LLMConfig |
|
||||||
| `src/OpenAILenientClient.ts` | OpenAI-compatible client |
|
| `src/OpenAILenientClient.ts` | OpenAI-compatible client |
|
||||||
|
|
||||||
### Page Controller (`packages/page-controller/`)
|
### Page Controller (`packages/page-controller/`)
|
||||||
|
|
||||||
| File | Description |
|
| File | Description |
|
||||||
|------|-------------|
|
| --------------------------- | ---------------------------------------------------------- |
|
||||||
| `src/PageController.ts` | ⭐ Main controller class |
|
| `src/PageController.ts` | ⭐ Main controller class with optional mask support |
|
||||||
| `src/actions.ts` | Element interactions (click, input, scroll) |
|
| `src/SimulatorMask.ts` | Visual overlay blocking user interaction during automation |
|
||||||
| `src/dom/dom_tree/index.js` | Core DOM extraction engine |
|
| `src/actions.ts` | Element interactions (click, input, scroll) |
|
||||||
|
| `src/dom/dom_tree/index.js` | Core DOM extraction engine |
|
||||||
|
|
||||||
## Adding New Features
|
## Adding New Features
|
||||||
|
|
||||||
|
|||||||
+60
-50
@@ -7,16 +7,16 @@ Thank you for your interest in contributing to Page-Agent! We welcome contributi
|
|||||||
### Development Setup
|
### Development Setup
|
||||||
|
|
||||||
1. **Prerequisites**
|
1. **Prerequisites**
|
||||||
- `node.js >= 20` with `npm >= 10`
|
- `node.js >= 20` with `npm >= 10`
|
||||||
- An editor that supports `ts/eslint/prettier`
|
- An editor that supports `ts/eslint/prettier`
|
||||||
- Make sure `eslint`, `prettier` and `commitlint` work well
|
- Make sure `eslint`, `prettier` and `commitlint` work well
|
||||||
|
|
||||||
2. **Setup**
|
2. **Setup**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install
|
npm ci
|
||||||
npm start # Start demo and documentation site
|
npm start # Start demo and documentation site
|
||||||
```
|
```
|
||||||
|
|
||||||
### Project Structure
|
### Project Structure
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ We use a simplified monorepo solution with `native npm-workspace + ts reference
|
|||||||
|
|
||||||
- When developing. Use alias so that we don't have to pre-build.
|
- When developing. Use alias so that we don't have to pre-build.
|
||||||
- When bundling. Use external and disable ts `paths` alias to leave deps out.
|
- When bundling. Use external and disable ts `paths` alias to leave deps out.
|
||||||
- When bundling `UMD` and `Website`. Bundle everything including local packages.
|
- When bundling `IIFE` and `Website`. Bundle everything including local packages.
|
||||||
|
|
||||||
## 🤝 How to Contribute
|
## 🤝 How to Contribute
|
||||||
|
|
||||||
@@ -44,40 +44,39 @@ We use a simplified monorepo solution with `native npm-workspace + ts reference
|
|||||||
|
|
||||||
1. **Fork and Clone**
|
1. **Fork and Clone**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/your-username/page-agent.git
|
git clone https://github.com/your-username/page-agent.git
|
||||||
cd page-agent
|
cd page-agent
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Create Feature Branch**
|
2. **Create Feature Branch**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git checkout -b feat/your-feature-name
|
git checkout -b feat/your-feature-name
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **Make Changes**
|
3. **Make Changes**
|
||||||
- Follow existing code style and patterns
|
- Follow existing code style and patterns
|
||||||
- Add tests for new functionality
|
- Add tests for new functionality
|
||||||
- Update documentation as needed
|
- Update documentation as needed
|
||||||
|
|
||||||
4. **Test Your Changes**
|
4. **Test Your Changes**
|
||||||
|
- Test in our demo website
|
||||||
```bash
|
- Test it on other websites if applicable
|
||||||
# TODO
|
- `@TODO: test suite`
|
||||||
```
|
|
||||||
|
|
||||||
5. **Commit and Push**
|
5. **Commit and Push**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git add .
|
git add .
|
||||||
git commit -m "feat: add awesome feature"
|
git commit -m "feat: add awesome feature"
|
||||||
git push origin feat/your-feature-name
|
git push origin feat/your-feature-name
|
||||||
```
|
```
|
||||||
|
|
||||||
6. **Create Pull Request**
|
6. **Create Pull Request**
|
||||||
- Provide clear description of changes
|
- Provide clear description of changes
|
||||||
- Link related issues
|
- Link related issues
|
||||||
- Include screenshots for UI changes
|
- Include screenshots for UI changes
|
||||||
|
|
||||||
## 📝 Code Style
|
## 📝 Code Style
|
||||||
|
|
||||||
@@ -94,41 +93,52 @@ We use a simplified monorepo solution with `native npm-workspace + ts reference
|
|||||||
- It's **recommended** to heavily rely on AI (aka "vibe coding") when maintaining **demo pages and tests**.
|
- It's **recommended** to heavily rely on AI (aka "vibe coding") when maintaining **demo pages and tests**.
|
||||||
- BUT **NOT the core lib!!!** Be very careful if AI ever touched the core lib!!!
|
- BUT **NOT the core lib!!!** Be very careful if AI ever touched the core lib!!!
|
||||||
- Review anything AI wrote before make a commit. You are the author of anything you commit. NOT AI.
|
- Review anything AI wrote before make a commit. You are the author of anything you commit. NOT AI.
|
||||||
- Update the AI instructions when structure changed.
|
- Update the [AI instructions](AGENTS.md) when structure changed.
|
||||||
- Cursor and Cline: `./.cursor/rules`
|
|
||||||
- Github Copilot: `./.github/copilot-instructions.md`
|
|
||||||
- Claude Code: `./CLAUDE.md`
|
|
||||||
|
|
||||||
## 🔧 Development Workflows
|
## 🔧 Development Workflows
|
||||||
|
|
||||||
|
### Test With Your Own LLM API
|
||||||
|
|
||||||
|
- Create a `.env` file in the repo root with your LLM API config
|
||||||
|
|
||||||
|
```env
|
||||||
|
LLM_MODEL_NAME=gpt-5.2
|
||||||
|
LLM_API_KEY=your-api-key
|
||||||
|
LLM_BASE_URL=https://api.your-llm-provider.com/v1
|
||||||
|
```
|
||||||
|
|
||||||
|
- Restart the dev server to load new env vars
|
||||||
|
- If not provided, the demo will the free testing proxy by default
|
||||||
|
|
||||||
### Website Development
|
### Website Development
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm start # React development server
|
npm start
|
||||||
```
|
```
|
||||||
|
|
||||||
### Core Lib Development and Testing
|
### Testing on Other Websites
|
||||||
|
|
||||||
> @TODO this part is outdated. Update this.
|
- Start and serve a local `iife` script
|
||||||
|
|
||||||
- Config your LLM API
|
```bash
|
||||||
- Start and serve a local umd script
|
npm run dev:iife # Serving IIFE with auto rebuild at http://localhost:5174/page-agent.js
|
||||||
|
```
|
||||||
|
|
||||||
```bash
|
- Add a new bookmark
|
||||||
npm run dev:umd # Serving UMD with auto rebuild at http://localhost:5173/page-agent.umd.cjs
|
|
||||||
```
|
|
||||||
|
|
||||||
- Add a new bookmark enable it on other website
|
```javascript
|
||||||
|
javascript:(function(){var s=document.createElement('script');s.src=`http://localhost:5174/page-agent.js?t=${Math.random()}`;s.onload=()=>console.log(%27PageAgent ready!%27);document.head.appendChild(s);})();
|
||||||
|
```
|
||||||
|
|
||||||
```
|
- Click the bookmark on any page to load Page-Agent
|
||||||
javascript:(function(){var s=document.createElement('script');s.src=`http://localhost:5173/page-agent.umd.cjs?t=${Math.random()}`;s.onload=()=>console.log(%27PageAgent ready!%27);document.head.appendChild(s);})();
|
|
||||||
```
|
> Warning: AK in your local `.env` will be inlined in the iife script.
|
||||||
|
|
||||||
### Adding Documentation
|
### Adding Documentation
|
||||||
|
|
||||||
1. Create `website/src/docs/section/page-name/page.tsx`
|
Ask an AI to help you add documentation to the `website/` package. Follow the existing style.
|
||||||
2. Add route to `website/src/router.tsx`
|
|
||||||
3. Add navigation link to `website/src/components/DocsLayout.tsx`
|
> Our AGENTS.md file and guardrails are designed for this purpose. But please be careful and review anything AI generated.
|
||||||
|
|
||||||
## 🎯 Contribution Areas
|
## 🎯 Contribution Areas
|
||||||
|
|
||||||
@@ -143,7 +153,7 @@ We especially welcome contributions in:
|
|||||||
|
|
||||||
## 🚫 What We Don't Accept
|
## 🚫 What We Don't Accept
|
||||||
|
|
||||||
- Changes that break existing API compatibility
|
- Changes that break existing API compatibility (Discuss first)
|
||||||
- Heavy dependencies to core library
|
- Heavy dependencies to core library
|
||||||
- Contributions without proper testing
|
- Contributions without proper testing
|
||||||
- Code that doesn't follow project conventions
|
- Code that doesn't follow project conventions
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2025 Alibaba
|
Copyright (c) 2026 Alibaba
|
||||||
|
Copyright (c) 2026 Simon
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
+43
-48
@@ -1,8 +1,11 @@
|
|||||||
# PageAgent 🤖🪄
|
# PageAgent 🤖🪄
|
||||||
|
|
||||||

|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: dark)" srcset="https://img.alicdn.com/imgextra/i4/O1CN01qKig1P1FnhpFKNdi6_!!6000000000532-2-tps-1280-256.png">
|
||||||
|
<img alt="Page Agent Banner" src="https://img.alicdn.com/imgextra/i1/O1CN01NCMKXj1Gn4tkFTsxf_!!6000000000666-2-tps-1280-256.png">
|
||||||
|
</picture>
|
||||||
|
|
||||||
[](https://badge.fury.io/js/page-agent) [](https://opensource.org/licenses/MIT) [](http://www.typescriptlang.org/) [](https://www.npmjs.com/package/page-agent) [](https://bundlephobia.com/package/page-agent) [](https://github.com/alibaba/page-agent)
|
[](https://opensource.org/licenses/MIT) [](http://www.typescriptlang.org/) [](https://www.npmjs.com/package/page-agent) [](https://bundlephobia.com/package/page-agent) [](https://github.com/alibaba/page-agent)
|
||||||
|
|
||||||
纯 JS 实现的 GUI agent。使用自然语言操作你的 Web 应用。无须后端、客户端、浏览器插件。
|
纯 JS 实现的 GUI agent。使用自然语言操作你的 Web 应用。无须后端、客户端、浏览器插件。
|
||||||
|
|
||||||
@@ -10,7 +13,7 @@
|
|||||||
|
|
||||||
👉 <a href="https://alibaba.github.io/page-agent/" target="_blank"><b>🚀 Demo</b></a> | <a href="https://alibaba.github.io/page-agent/#/docs/introduction/overview" target="_blank"><b>📖 Documentation</b></a>
|
👉 <a href="https://alibaba.github.io/page-agent/" target="_blank"><b>🚀 Demo</b></a> | <a href="https://alibaba.github.io/page-agent/#/docs/introduction/overview" target="_blank"><b>📖 Documentation</b></a>
|
||||||
|
|
||||||
<video id="demo-video" src="https://github.com/user-attachments/assets/141bbb01-8022-4d1f-919d-9efc9a1dc1cf" width="640" crossorigin muted autoplay loop></video>
|
<video id="demo-video" src="https://github.com/user-attachments/assets/34d8444d-cbfb-44a3-a24e-fd5c167bb0bf" controls crossorigin muted></video>
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -28,6 +31,24 @@
|
|||||||
|
|
||||||
## 🚀 快速开始
|
## 🚀 快速开始
|
||||||
|
|
||||||
|
### 快速体验 (With Testing LLM)
|
||||||
|
|
||||||
|
最快的体验方式:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script
|
||||||
|
src="https://registry.npmmirror.com/@page-agent/cdn/latest/files/dist/page-agent.demo.js"
|
||||||
|
crossorigin="true"
|
||||||
|
></script>
|
||||||
|
```
|
||||||
|
|
||||||
|
> ⚠️ **仅用于技术评估。** Demo 模型有速率限制和使用限制,生产环境请使用 NPM 方式。
|
||||||
|
|
||||||
|
| Mirrors | URL |
|
||||||
|
| ------- | ----------------------------------------------------------------------------------- |
|
||||||
|
| Global | https://cdn.jsdelivr.net/npm/@page-agent/cdn/dist/page-agent.demo.js |
|
||||||
|
| China | https://registry.npmmirror.com/@page-agent/cdn/latest/files/dist/page-agent.demo.js |
|
||||||
|
|
||||||
### NPM 安装
|
### NPM 安装
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -37,39 +58,17 @@ npm install page-agent
|
|||||||
```javascript
|
```javascript
|
||||||
import { PageAgent } from 'page-agent'
|
import { PageAgent } from 'page-agent'
|
||||||
|
|
||||||
// 测试接口
|
|
||||||
// @note: 限流,限制 prompt 内容,限制来源,随时变更,请替换成你自己的
|
|
||||||
// @note: 使用 DeepSeek-chat(3.2) 官方版本,使用协议和隐私策略见 DeepSeek 网站
|
|
||||||
const DEMO_MODEL = 'PAGE-AGENT-FREE-TESTING-RANDOM'
|
|
||||||
const DEMO_BASE_URL = 'https://hwcxiuzfylggtcktqgij.supabase.co/functions/v1/llm-testing-proxy'
|
|
||||||
const DEMO_API_KEY = 'PAGE-AGENT-FREE-TESTING-RANDOM'
|
|
||||||
|
|
||||||
const agent = new PageAgent({
|
const agent = new PageAgent({
|
||||||
model: DEMO_MODEL,
|
model: 'deepseek-chat',
|
||||||
baseURL: DEMO_BASE_URL,
|
baseURL: 'https://api.deepseek.com',
|
||||||
apiKey: DEMO_API_KEY,
|
apiKey: 'YOUR_API_KEY',
|
||||||
language: 'zh-CN',
|
language: 'zh-CN',
|
||||||
})
|
})
|
||||||
|
|
||||||
await agent.execute('点击登录按钮')
|
await agent.execute('点击登录按钮')
|
||||||
```
|
```
|
||||||
|
|
||||||
### CDN 集成
|
适用于无法使用 NPM 的环境,我们也提供了 IIFE 构建的 CDN 方式。[@see CDN Usage](https://alibaba.github.io/page-agent/#/docs/integration/cdn-setup)
|
||||||
|
|
||||||
Fastest way to try PageAgent is to include it via CDN. Demo model will be used by default.
|
|
||||||
|
|
||||||
| Location | URL |
|
|
||||||
| -------- | ----------------------------------------------------------------------------- |
|
|
||||||
| Global | https://cdn.jsdelivr.net/npm/page-agent@latest/dist/umd/page-agent.js |
|
|
||||||
| China | https://registry.npmmirror.com/page-agent/latest/files/dist/umd/page-agent.js |
|
|
||||||
|
|
||||||
```html
|
|
||||||
<script
|
|
||||||
src="https://registry.npmmirror.com/page-agent/latest/files/dist/umd/page-agent.js"
|
|
||||||
crossorigin="true"
|
|
||||||
type="text/javascript"
|
|
||||||
></script>
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🏗️ 架构设计
|
## 🏗️ 架构设计
|
||||||
|
|
||||||
@@ -79,8 +78,9 @@ PageAgent adopts a simplified monorepo structure:
|
|||||||
packages/
|
packages/
|
||||||
├── page-agent/ # AI agent (npm: page-agent)
|
├── page-agent/ # AI agent (npm: page-agent)
|
||||||
├── llms/ # LLM 客户端 (npm: @page-agent/llms)
|
├── llms/ # LLM 客户端 (npm: @page-agent/llms)
|
||||||
├── page-controller/ # DOM 操作 (npm: @page-agent/page-controller)
|
├── page-controller/ # DOM 操作 & 蒙层 & 模拟鼠标 (npm: @page-agent/page-controller)
|
||||||
├── ui/ # 面板 & 蒙层 & 模拟鼠标 (npm: @page-agent/ui)
|
├── ui/ # 面板 & i18n (npm: @page-agent/ui)
|
||||||
|
├── cdn/ # CDN IIFE builds (npm: @page-agent/cdn)
|
||||||
└── website/ # 文档站点
|
└── website/ # 文档站点
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -88,28 +88,19 @@ packages/
|
|||||||
|
|
||||||
欢迎社区贡献!以下是参与方式:
|
欢迎社区贡献!以下是参与方式:
|
||||||
|
|
||||||
### 开发环境
|
1. Fork 并 clone。`git clone https://github.com/alibaba/page-agent.git && cd page-agent`
|
||||||
|
2. 安装依赖: `npm install`
|
||||||
|
3. 启动开发: `npm start`
|
||||||
|
|
||||||
1. Fork 项目仓库
|
更多详情请参阅 [CONTRIBUTING.md](CONTRIBUTING.md)。
|
||||||
2. Clone or fork: `git clone https://github.com/alibaba/page-agent.git && cd page-agent`
|
|
||||||
3. 安装依赖: `npm install`
|
|
||||||
4. 启动开发: `npm start`
|
|
||||||
|
|
||||||
### 贡献指南
|
请在贡献前阅读我们的[行为准则](CODE_OF_CONDUCT.md)。
|
||||||
|
|
||||||
请在贡献前阅读我们的[行为准则](CODE_OF_CONDUCT.md)和[贡献指南](CONTRIBUTING.md)。
|
|
||||||
|
|
||||||
## 👏 致谢
|
## 👏 致谢
|
||||||
|
|
||||||
本项目基于以下优秀项目构建:
|
本项目基于 **[`browser-use`](https://github.com/browser-use/browser-use)** 的优秀工作构建。
|
||||||
|
|
||||||
- **[browser-use](https://github.com/browser-use/browser-use)**
|
`PageAgent` 专为**客户端网页增强**设计,不是服务端自动化工具。
|
||||||
|
|
||||||
PageAgent 专为**客户端网页增强**设计,不是服务端自动化工具。
|
|
||||||
|
|
||||||
## 📄 许可证
|
|
||||||
|
|
||||||
MIT 许可证 - 详见 [LICENSE](LICENSE) 文件。
|
|
||||||
|
|
||||||
```
|
```
|
||||||
DOM processing components and prompt are derived from browser-use:
|
DOM processing components and prompt are derived from browser-use:
|
||||||
@@ -128,6 +119,10 @@ Third-party dependencies and their licenses can be found in the package.json
|
|||||||
file and in the node_modules directory after installation.
|
file and in the node_modules directory after installation.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 📄 许可证
|
||||||
|
|
||||||
|
[MIT License](LICENSE)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**⭐ 如果觉得 PageAgent 有用或有趣,请给项目点个星!**
|
**⭐ 如果觉得 PageAgent 有用或有趣,请给项目点个星!**
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
# PageAgent 🤖🪄
|
# PageAgent 🤖🪄
|
||||||
|
|
||||||

|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: dark)" srcset="https://img.alicdn.com/imgextra/i4/O1CN01qKig1P1FnhpFKNdi6_!!6000000000532-2-tps-1280-256.png">
|
||||||
|
<img alt="Page Agent Banner" src="https://img.alicdn.com/imgextra/i1/O1CN01NCMKXj1Gn4tkFTsxf_!!6000000000666-2-tps-1280-256.png">
|
||||||
|
</picture>
|
||||||
|
|
||||||
[](https://badge.fury.io/js/page-agent) [](https://opensource.org/licenses/MIT) [](http://www.typescriptlang.org/) [](https://www.npmjs.com/package/page-agent) [](https://bundlephobia.com/package/page-agent) [](https://github.com/alibaba/page-agent)
|
[](https://opensource.org/licenses/MIT) [](http://www.typescriptlang.org/) [](https://www.npmjs.com/package/page-agent) [](https://bundlephobia.com/package/page-agent) [](https://github.com/alibaba/page-agent)
|
||||||
|
|
||||||
The GUI Agent Living in Your Webpage. Control web interfaces with natural language.
|
The GUI Agent Living in Your Webpage. Control web interfaces with natural language.
|
||||||
|
|
||||||
@@ -10,7 +13,7 @@ The GUI Agent Living in Your Webpage. Control web interfaces with natural langua
|
|||||||
|
|
||||||
👉 <a href="https://alibaba.github.io/page-agent/" target="_blank"><b>🚀 Demo</b></a> | <a href="https://alibaba.github.io/page-agent/#/docs/introduction/overview" target="_blank"><b>📖 Documentation</b></a>
|
👉 <a href="https://alibaba.github.io/page-agent/" target="_blank"><b>🚀 Demo</b></a> | <a href="https://alibaba.github.io/page-agent/#/docs/introduction/overview" target="_blank"><b>📖 Documentation</b></a>
|
||||||
|
|
||||||
<video id="demo-video" src="https://github.com/user-attachments/assets/de8d1964-8bde-494f-a52f-2975469557a5" width="640" crossorigin muted autoplay loop></video>
|
<video id="demo-video" src="https://github.com/user-attachments/assets/34d8444d-cbfb-44a3-a24e-fd5c167bb0bf" controls crossorigin muted></video>
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -28,6 +31,24 @@ The GUI Agent Living in Your Webpage. Control web interfaces with natural langua
|
|||||||
|
|
||||||
## 🚀 Quick Start
|
## 🚀 Quick Start
|
||||||
|
|
||||||
|
### Quick Try (With Testing LLM)
|
||||||
|
|
||||||
|
Fastest way to try PageAgent:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script
|
||||||
|
src="https://cdn.jsdelivr.net/npm/@page-agent/cdn/dist/page-agent.demo.js"
|
||||||
|
crossorigin="true"
|
||||||
|
></script>
|
||||||
|
```
|
||||||
|
|
||||||
|
> ⚠️ **For technical evaluation only.** Demo model has rate limits and usage restrictions. Use NPM for production.
|
||||||
|
|
||||||
|
| Mirrors | URL |
|
||||||
|
| ------- | ----------------------------------------------------------------------------------- |
|
||||||
|
| Global | https://cdn.jsdelivr.net/npm/@page-agent/cdn/dist/page-agent.demo.js |
|
||||||
|
| China | https://registry.npmmirror.com/@page-agent/cdn/latest/files/dist/page-agent.demo.js |
|
||||||
|
|
||||||
### NPM Installation
|
### NPM Installation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -37,39 +58,17 @@ npm install page-agent
|
|||||||
```javascript
|
```javascript
|
||||||
import { PageAgent } from 'page-agent'
|
import { PageAgent } from 'page-agent'
|
||||||
|
|
||||||
// test server
|
|
||||||
// @note: rate limit. prompt limit. Origin limit. May change anytime. Use your own llm!
|
|
||||||
// @note Using official DeepSeek-chat(3.2). Go to DeepSeek website for privacy policy.
|
|
||||||
const DEMO_MODEL = 'PAGE-AGENT-FREE-TESTING-RANDOM'
|
|
||||||
const DEMO_BASE_URL = 'https://hwcxiuzfylggtcktqgij.supabase.co/functions/v1/llm-testing-proxy'
|
|
||||||
const DEMO_API_KEY = 'PAGE-AGENT-FREE-TESTING-RANDOM'
|
|
||||||
|
|
||||||
const agent = new PageAgent({
|
const agent = new PageAgent({
|
||||||
model: DEMO_MODEL,
|
model: 'deepseek-chat',
|
||||||
baseURL: DEMO_BASE_URL,
|
baseURL: 'https://api.deepseek.com',
|
||||||
apiKey: DEMO_API_KEY,
|
apiKey: 'YOUR_API_KEY',
|
||||||
language: 'en-US',
|
language: 'en-US',
|
||||||
})
|
})
|
||||||
|
|
||||||
await agent.execute('Click the login button')
|
await agent.execute('Click the login button')
|
||||||
```
|
```
|
||||||
|
|
||||||
### CDN Integration
|
For environments where NPM is not available. We do offer a IIFE build via CDN. [@see CDN Usage](https://alibaba.github.io/page-agent/#/docs/integration/cdn-setup)
|
||||||
|
|
||||||
Fastest way to try PageAgent is to include it via CDN. Demo model will be used by default.
|
|
||||||
|
|
||||||
| Location | URL |
|
|
||||||
| -------- | ----------------------------------------------------------------------------- |
|
|
||||||
| Global | https://cdn.jsdelivr.net/npm/page-agent@latest/dist/umd/page-agent.js |
|
|
||||||
| China | https://registry.npmmirror.com/page-agent/latest/files/dist/umd/page-agent.js |
|
|
||||||
|
|
||||||
```html
|
|
||||||
<script
|
|
||||||
src="https://cdn.jsdelivr.net/npm/page-agent@latest/dist/umd/page-agent.js"
|
|
||||||
crossorigin="true"
|
|
||||||
type="text/javascript"
|
|
||||||
></script>
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🏗️ Structure
|
## 🏗️ Structure
|
||||||
|
|
||||||
@@ -79,8 +78,9 @@ PageAgent adopts a simplified monorepo structure:
|
|||||||
packages/
|
packages/
|
||||||
├── page-agent/ # AI agent (npm: page-agent)
|
├── page-agent/ # AI agent (npm: page-agent)
|
||||||
├── llms/ # LLM client (npm: @page-agent/llms)
|
├── llms/ # LLM client (npm: @page-agent/llms)
|
||||||
├── page-controller/ # DOM operations (npm: @page-agent/page-controller)
|
├── page-controller/ # DOM operations & Visual Mask (npm: @page-agent/page-controller)
|
||||||
├── ui/ # Panel & Mask & Mouse Animation (npm: @page-agent/ui)
|
├── ui/ # Panel & i18n (npm: @page-agent/ui)
|
||||||
|
├── cdn/ # CDN IIFE builds (npm: @page-agent/cdn)
|
||||||
└── website/ # Demo & Documentation site
|
└── website/ # Demo & Documentation site
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -88,28 +88,19 @@ packages/
|
|||||||
|
|
||||||
We welcome contributions from the community! Here's how to get started:
|
We welcome contributions from the community! Here's how to get started:
|
||||||
|
|
||||||
### Setup
|
1. Fork and clone. `git clone https://github.com/alibaba/page-agent.git && cd page-agent`
|
||||||
|
2. Install dependencies: `npm install`
|
||||||
|
3. Start development: `npm start`
|
||||||
|
|
||||||
1. Fork the repository
|
More details in [CONTRIBUTING.md](CONTRIBUTING.md).
|
||||||
2. Clone your fork: `git clone https://github.com/alibaba/page-agent.git && cd page-agent`
|
|
||||||
3. Install dependencies: `npm install`
|
|
||||||
4. Start development: `npm start`
|
|
||||||
|
|
||||||
### Contributing Guidelines
|
Please read our [Code of Conduct](CODE_OF_CONDUCT.md) before contributing.
|
||||||
|
|
||||||
Please read our [Code of Conduct](CODE_OF_CONDUCT.md) and [Contributing Guide](CONTRIBUTING.md) before contributing.
|
|
||||||
|
|
||||||
## 👏 Acknowledgments
|
## 👏 Acknowledgments
|
||||||
|
|
||||||
This project builds upon the excellent work of:
|
This project builds upon the excellent work of **[`browser-use`](https://github.com/browser-use/browser-use)**.
|
||||||
|
|
||||||
- **[browser-use](https://github.com/browser-use/browser-use)**
|
`PageAgent` is designed for **client-side web enhancement**, not server-side automation.
|
||||||
|
|
||||||
PageAgent is designed for **client-side web enhancement**, not server-side automation.
|
|
||||||
|
|
||||||
## 📄 License
|
|
||||||
|
|
||||||
MIT License - see the [LICENSE](LICENSE) file for details.
|
|
||||||
|
|
||||||
```
|
```
|
||||||
DOM processing components and prompt are derived from browser-use:
|
DOM processing components and prompt are derived from browser-use:
|
||||||
@@ -128,6 +119,10 @@ Third-party dependencies and their licenses can be found in the package.json
|
|||||||
file and in the node_modules directory after installation.
|
file and in the node_modules directory after installation.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 📄 License
|
||||||
|
|
||||||
|
[MIT License](LICENSE)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**⭐ Star this repo if you find PageAgent helpful!**
|
**⭐ Star this repo if you find PageAgent helpful!**
|
||||||
|
|||||||
Generated
+30
-31
@@ -1,18 +1,19 @@
|
|||||||
{
|
{
|
||||||
"name": "root",
|
"name": "root",
|
||||||
"version": "0.0.21",
|
"version": "0.2.5",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "root",
|
"name": "root",
|
||||||
"version": "0.0.21",
|
"version": "0.2.5",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/page-controller",
|
"packages/page-controller",
|
||||||
"packages/ui",
|
"packages/ui",
|
||||||
"packages/llms",
|
"packages/llms",
|
||||||
"packages/page-agent",
|
"packages/page-agent",
|
||||||
|
"packages/cdn",
|
||||||
"packages/website"
|
"packages/website"
|
||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -1539,20 +1540,6 @@
|
|||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@microsoft/api-extractor/node_modules/typescript": {
|
|
||||||
"version": "5.8.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz",
|
|
||||||
"integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"bin": {
|
|
||||||
"tsc": "bin/tsc",
|
|
||||||
"tsserver": "bin/tsserver"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=14.17"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@microsoft/api-extractor/node_modules/yallist": {
|
"node_modules/@microsoft/api-extractor/node_modules/yallist": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||||
@@ -1597,6 +1584,10 @@
|
|||||||
"url": "https://github.com/sponsors/epoberezkin"
|
"url": "https://github.com/sponsors/epoberezkin"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@page-agent/cdn": {
|
||||||
|
"resolved": "packages/cdn",
|
||||||
|
"link": true
|
||||||
|
},
|
||||||
"node_modules/@page-agent/llms": {
|
"node_modules/@page-agent/llms": {
|
||||||
"resolved": "packages/llms",
|
"resolved": "packages/llms",
|
||||||
"link": true
|
"link": true
|
||||||
@@ -4622,9 +4613,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/diff": {
|
"node_modules/diff": {
|
||||||
"version": "8.0.2",
|
"version": "8.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/diff/-/diff-8.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/diff/-/diff-8.0.3.tgz",
|
||||||
"integrity": "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==",
|
"integrity": "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -8140,9 +8131,17 @@
|
|||||||
"zod": "^3.25.0 || ^4.0.0"
|
"zod": "^3.25.0 || ^4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"packages/cdn": {
|
||||||
|
"name": "@page-agent/cdn",
|
||||||
|
"version": "0.2.5",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"page-agent": "0.2.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"packages/llms": {
|
"packages/llms": {
|
||||||
"name": "@page-agent/llms",
|
"name": "@page-agent/llms",
|
||||||
"version": "0.0.21",
|
"version": "0.2.5",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chalk": "^5.6.2",
|
"chalk": "^5.6.2",
|
||||||
@@ -8150,32 +8149,32 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/page-agent": {
|
"packages/page-agent": {
|
||||||
"version": "0.0.21",
|
"version": "0.2.5",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@page-agent/llms": "0.0.21",
|
"@page-agent/llms": "0.2.5",
|
||||||
"@page-agent/page-controller": "0.0.21",
|
"@page-agent/page-controller": "0.2.5",
|
||||||
"@page-agent/ui": "0.0.21",
|
"@page-agent/ui": "0.2.5",
|
||||||
"chalk": "^5.6.2",
|
"chalk": "^5.6.2",
|
||||||
"zod": "^4.3.5"
|
"zod": "^4.3.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/page-controller": {
|
"packages/page-controller": {
|
||||||
"name": "@page-agent/page-controller",
|
"name": "@page-agent/page-controller",
|
||||||
"version": "0.0.21",
|
"version": "0.2.5",
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"packages/ui": {
|
|
||||||
"name": "@page-agent/ui",
|
|
||||||
"version": "0.0.21",
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ai-motion": "^0.4.8"
|
"ai-motion": "^0.4.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"packages/ui": {
|
||||||
|
"name": "@page-agent/ui",
|
||||||
|
"version": "0.2.5",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"packages/website": {
|
"packages/website": {
|
||||||
"name": "@page-agent/website",
|
"name": "@page-agent/website",
|
||||||
"version": "0.0.21",
|
"version": "0.2.5",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@radix-ui/react-icons": "^1.3.2",
|
"@radix-ui/react-icons": "^1.3.2",
|
||||||
"@radix-ui/react-separator": "^1.1.8",
|
"@radix-ui/react-separator": "^1.1.8",
|
||||||
|
|||||||
+23
-3
@@ -1,13 +1,14 @@
|
|||||||
{
|
{
|
||||||
"name": "root",
|
"name": "root",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.21",
|
"version": "0.2.5",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/page-controller",
|
"packages/page-controller",
|
||||||
"packages/ui",
|
"packages/ui",
|
||||||
"packages/llms",
|
"packages/llms",
|
||||||
"packages/page-agent",
|
"packages/page-agent",
|
||||||
|
"packages/cdn",
|
||||||
"packages/website"
|
"packages/website"
|
||||||
],
|
],
|
||||||
"description": "AI-powered UI agent for web applications",
|
"description": "AI-powered UI agent for web applications",
|
||||||
@@ -28,7 +29,7 @@
|
|||||||
"build:website": "npm run build:website --workspace=@page-agent/website",
|
"build:website": "npm run build:website --workspace=@page-agent/website",
|
||||||
"build:libs": "npm run build --workspaces --if-present",
|
"build:libs": "npm run build --workspaces --if-present",
|
||||||
"build": "npm run build:libs && npm run build:website",
|
"build": "npm run build:libs && npm run build:website",
|
||||||
"dev:umd": "npm run dev:umd --workspace=page-agent",
|
"dev:iife": "npm run dev:iife --workspace=page-agent",
|
||||||
"version": "node scripts/sync-version.js",
|
"version": "node scripts/sync-version.js",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"prepare": "husky"
|
"prepare": "husky"
|
||||||
@@ -58,6 +59,9 @@
|
|||||||
"vite": "^7.3.1",
|
"vite": "^7.3.1",
|
||||||
"vite-plugin-css-injected-by-js": "^3.5.2"
|
"vite-plugin-css-injected-by-js": "^3.5.2"
|
||||||
},
|
},
|
||||||
|
"overrides": {
|
||||||
|
"typescript": "^5.9.3"
|
||||||
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"*.{js,ts,cjs,cts,mjs,mts}": [
|
"*.{js,ts,cjs,cts,mjs,mts}": [
|
||||||
"npx prettier --write --ignore-unknown",
|
"npx prettier --write --ignore-unknown",
|
||||||
@@ -98,6 +102,22 @@
|
|||||||
".css$"
|
".css$"
|
||||||
],
|
],
|
||||||
"importOrderSeparation": true,
|
"importOrderSeparation": true,
|
||||||
"importOrderSortSpecifiers": true
|
"importOrderSortSpecifiers": true,
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": "*.md",
|
||||||
|
"options": {
|
||||||
|
"useTabs": false,
|
||||||
|
"tabWidth": 4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": "*.json",
|
||||||
|
"options": {
|
||||||
|
"useTabs": false,
|
||||||
|
"tabWidth": 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"name": "@page-agent/cdn",
|
||||||
|
"private": false,
|
||||||
|
"version": "0.2.5",
|
||||||
|
"type": "module",
|
||||||
|
"files": [
|
||||||
|
"dist/"
|
||||||
|
],
|
||||||
|
"description": "CDN builds for page-agent - IIFE bundles for script tag usage",
|
||||||
|
"author": "Simon<gaomeng1900>",
|
||||||
|
"license": "MIT",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/alibaba/page-agent.git"
|
||||||
|
},
|
||||||
|
"homepage": "https://alibaba.github.io/page-agent/",
|
||||||
|
"scripts": {
|
||||||
|
"build": "vite build && vite build --mode demo"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"page-agent": "0.2.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
/**
|
||||||
|
* Demo CDN build for page-agent
|
||||||
|
* Auto-initializes with built-in demo API for testing
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* <script src="https://unpkg.com/@page-agent/cdn/dist/page-agent.demo.js"></script>
|
||||||
|
*/
|
||||||
|
import { PageAgent } from 'page-agent'
|
||||||
|
|
||||||
|
// Clean up existing instances to prevent multiple injections from bookmarklet
|
||||||
|
if (window.pageAgent) {
|
||||||
|
window.pageAgent.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mount to global window object
|
||||||
|
window.PageAgent = PageAgent
|
||||||
|
|
||||||
|
// Export for IIFE
|
||||||
|
export { 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'
|
||||||
|
|
||||||
|
// Demo warning
|
||||||
|
console.log(
|
||||||
|
'%c⚠️ DEMO MODE %c\n' +
|
||||||
|
'This build uses a shared testing LLM with rate limits.\n' +
|
||||||
|
'For production, use page-agent.js with your own API key:\n' +
|
||||||
|
'https://alibaba.github.io/page-agent/#/docs/features/models',
|
||||||
|
'background: #f59e0b; color: #000; font-size: 14px; font-weight: bold; padding: 4px 8px; border-radius: 4px;',
|
||||||
|
'color: #f59e0b; font-size: 12px;'
|
||||||
|
)
|
||||||
|
|
||||||
|
// in case document.x is not ready yet
|
||||||
|
setTimeout(() => {
|
||||||
|
const currentScript = document.currentScript
|
||||||
|
|
||||||
|
if (currentScript) {
|
||||||
|
console.log('🚀 page-agent.js detected current script:', currentScript.src)
|
||||||
|
const url = new URL(currentScript.src)
|
||||||
|
const model = url.searchParams.get('model') || DEMO_MODEL
|
||||||
|
const baseURL = url.searchParams.get('baseURL') || DEMO_BASE_URL
|
||||||
|
const apiKey = url.searchParams.get('apiKey') || DEMO_API_KEY
|
||||||
|
const language = url.searchParams.get('lang') || 'zh-CN'
|
||||||
|
const config = { model, baseURL, apiKey, language }
|
||||||
|
window.pageAgent = new PageAgent(config)
|
||||||
|
} else {
|
||||||
|
console.log('🚀 page-agent.js no current script detected, using default demo config')
|
||||||
|
const config = {
|
||||||
|
model: DEMO_MODEL,
|
||||||
|
baseURL: DEMO_BASE_URL,
|
||||||
|
apiKey: DEMO_API_KEY,
|
||||||
|
}
|
||||||
|
window.pageAgent = new PageAgent(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('🚀 page-agent.js initialized with config:', window.pageAgent.config)
|
||||||
|
|
||||||
|
window.pageAgent.panel.show()
|
||||||
|
})
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
/**
|
||||||
|
* Full CDN build for page-agent
|
||||||
|
* Exposes PageAgent class for manual instantiation and configuration
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* <script src="https://unpkg.com/@page-agent/cdn/dist/page-agent.js"></script>
|
||||||
|
* <script>
|
||||||
|
* const agent = new PageAgent({ model: 'gpt-4o', apiKey: 'your-key' })
|
||||||
|
* agent.panel.show()
|
||||||
|
* </script>
|
||||||
|
*/
|
||||||
|
import { PageAgent } from 'page-agent'
|
||||||
|
|
||||||
|
export { PageAgent }
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import { dirname, resolve } from 'path'
|
||||||
|
import { fileURLToPath } from 'url'
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js'
|
||||||
|
|
||||||
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CDN IIFE builds for page-agent
|
||||||
|
*
|
||||||
|
* Build targets (via --mode):
|
||||||
|
* - demo: page-agent.demo.js with auto-init and built-in API
|
||||||
|
* - full (default): page-agent.js exposes PageAgent class only
|
||||||
|
*/
|
||||||
|
export default defineConfig(({ mode }) => {
|
||||||
|
const isDemo = mode === 'demo'
|
||||||
|
const entry = isDemo ? resolve(__dirname, 'src/demo.js') : resolve(__dirname, 'src/full.js')
|
||||||
|
const fileName = isDemo ? 'page-agent.demo' : 'page-agent'
|
||||||
|
|
||||||
|
return {
|
||||||
|
plugins: [cssInjectedByJsPlugin({ relativeCSSInjection: true })],
|
||||||
|
publicDir: false,
|
||||||
|
esbuild: {
|
||||||
|
keepNames: true,
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
lib: {
|
||||||
|
entry,
|
||||||
|
name: 'PageAgent',
|
||||||
|
fileName: () => `${fileName}.js`,
|
||||||
|
formats: ['iife'],
|
||||||
|
},
|
||||||
|
outDir: resolve(__dirname, 'dist'),
|
||||||
|
emptyOutDir: !isDemo, // only empty on first build (full)
|
||||||
|
minify: false,
|
||||||
|
cssCodeSplit: true,
|
||||||
|
rollupOptions: {
|
||||||
|
onwarn: function (message, handler) {
|
||||||
|
if (message.code === 'EVAL') return
|
||||||
|
handler(message)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
define: {
|
||||||
|
'process.env.NODE_ENV': '"production"',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
# @page-agent/llms
|
|
||||||
|
|
||||||
LLM client with a **reflection-before-action** mental model for page-agent.
|
|
||||||
|
|
||||||
## Why This Package Exists
|
|
||||||
|
|
||||||
The LLM module and the agent logic are inherently coupled. This package exists not to decouple them, but to **define the interface contract** between the LLM and the agent.
|
|
||||||
|
|
||||||
The core abstraction is the `MacroToolInput` — a structured output format that **forces the model to reflect before acting**.
|
|
||||||
|
|
||||||
## The Reflection-Before-Action Model
|
|
||||||
|
|
||||||
Every tool call must first output its reasoning state before the actual action:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
interface MacroToolInput {
|
|
||||||
// Reflection (mandatory before any action)
|
|
||||||
evaluation_previous_goal?: string // How well did the previous action work?
|
|
||||||
memory?: string // Key information to remember
|
|
||||||
next_goal?: string // What to accomplish next
|
|
||||||
|
|
||||||
// Action (the actual operation)
|
|
||||||
action: Record<string, any>
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This design ensures that:
|
|
||||||
|
|
||||||
1. **The model evaluates its previous action** before deciding the next step
|
|
||||||
2. **Working memory is explicitly maintained** across conversation turns
|
|
||||||
3. **Goals are clearly stated**, making the agent's reasoning transparent and debuggable
|
|
||||||
|
|
||||||
## Key Components
|
|
||||||
|
|
||||||
| Export | Description |
|
|
||||||
|--------|-------------|
|
|
||||||
| `LLM` | Main LLM client class with retry logic |
|
|
||||||
| `MacroToolInput` | The reflection-before-action input schema |
|
|
||||||
| `AgentBrain` | Agent's thinking state (eval, memory, goal) |
|
|
||||||
| `LLMConfig` | Configuration for LLM connection |
|
|
||||||
| `parseLLMConfig` | Parse and apply defaults to config |
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@page-agent/llms",
|
"name": "@page-agent/llms",
|
||||||
"version": "0.0.21",
|
"version": "0.2.5",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/lib/page-agent-llms.js",
|
"main": "./dist/lib/page-agent-llms.js",
|
||||||
"module": "./dist/lib/page-agent-llms.js",
|
"module": "./dist/lib/page-agent-llms.js",
|
||||||
|
|||||||
@@ -1,20 +1,3 @@
|
|||||||
// Dev environment: use .env config if available, otherwise fallback to testing api
|
// Internal constants
|
||||||
export const DEFAULT_MODEL_NAME: string =
|
|
||||||
import.meta.env.DEV && import.meta.env.LLM_MODEL_NAME
|
|
||||||
? import.meta.env.LLM_MODEL_NAME
|
|
||||||
: 'PAGE-AGENT-FREE-TESTING-RANDOM'
|
|
||||||
|
|
||||||
export const DEFAULT_API_KEY: string =
|
|
||||||
import.meta.env.DEV && import.meta.env.LLM_API_KEY
|
|
||||||
? import.meta.env.LLM_API_KEY
|
|
||||||
: 'PAGE-AGENT-FREE-TESTING-RANDOM'
|
|
||||||
|
|
||||||
export const DEFAULT_BASE_URL: string =
|
|
||||||
import.meta.env.DEV && import.meta.env.LLM_BASE_URL
|
|
||||||
? import.meta.env.LLM_BASE_URL
|
|
||||||
: 'https://hwcxiuzfylggtcktqgij.supabase.co/functions/v1/llm-testing-proxy'
|
|
||||||
|
|
||||||
// internal
|
|
||||||
|
|
||||||
export const LLM_MAX_RETRIES = 2
|
export const LLM_MAX_RETRIES = 2
|
||||||
export const DEFAULT_TEMPERATURE = 0.7 // higher randomness helps auto-recovery
|
export const DEFAULT_TEMPERATURE = 0.7 // higher randomness helps auto-recovery
|
||||||
|
|||||||
+12
-10
@@ -1,21 +1,23 @@
|
|||||||
import { OpenAIClient } from './OpenAIClient'
|
import { OpenAIClient } from './OpenAIClient'
|
||||||
import {
|
import { DEFAULT_TEMPERATURE, LLM_MAX_RETRIES } from './constants'
|
||||||
DEFAULT_API_KEY,
|
|
||||||
DEFAULT_BASE_URL,
|
|
||||||
DEFAULT_MODEL_NAME,
|
|
||||||
DEFAULT_TEMPERATURE,
|
|
||||||
LLM_MAX_RETRIES,
|
|
||||||
} from './constants'
|
|
||||||
import { InvokeError } from './errors'
|
import { InvokeError } from './errors'
|
||||||
import type { InvokeOptions, InvokeResult, LLMClient, LLMConfig, Message, Tool } from './types'
|
import type { InvokeOptions, InvokeResult, LLMClient, LLMConfig, Message, Tool } from './types'
|
||||||
|
|
||||||
export type { InvokeOptions, InvokeResult, LLMClient, LLMConfig, Message, Tool }
|
export type { InvokeOptions, InvokeResult, LLMClient, LLMConfig, Message, Tool }
|
||||||
|
|
||||||
export function parseLLMConfig(config: LLMConfig): Required<LLMConfig> {
|
export function parseLLMConfig(config: LLMConfig): Required<LLMConfig> {
|
||||||
|
// Runtime validation as defensive programming (types already guarantee these)
|
||||||
|
if (!config.baseURL || !config.apiKey || !config.model) {
|
||||||
|
throw new Error(
|
||||||
|
'[PageAgent] LLM configuration required. Please provide: baseURL, apiKey, model. ' +
|
||||||
|
'See: https://alibaba.github.io/page-agent/#/docs/features/models'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
baseURL: config.baseURL ?? DEFAULT_BASE_URL,
|
baseURL: config.baseURL,
|
||||||
apiKey: config.apiKey ?? DEFAULT_API_KEY,
|
apiKey: config.apiKey,
|
||||||
model: config.model ?? DEFAULT_MODEL_NAME,
|
model: config.model,
|
||||||
temperature: config.temperature ?? DEFAULT_TEMPERATURE,
|
temperature: config.temperature ?? DEFAULT_TEMPERATURE,
|
||||||
maxRetries: config.maxRetries ?? LLM_MAX_RETRIES,
|
maxRetries: config.maxRetries ?? LLM_MAX_RETRIES,
|
||||||
customFetch: (config.customFetch ?? fetch).bind(globalThis), // fetch will be illegal unless bound
|
customFetch: (config.customFetch ?? fetch).bind(globalThis), // fetch will be illegal unless bound
|
||||||
|
|||||||
@@ -87,9 +87,9 @@ export interface InvokeResult<TResult = unknown> {
|
|||||||
* LLM configuration
|
* LLM configuration
|
||||||
*/
|
*/
|
||||||
export interface LLMConfig {
|
export interface LLMConfig {
|
||||||
baseURL?: string
|
baseURL: string
|
||||||
apiKey?: string
|
apiKey: string
|
||||||
model?: string
|
model: string
|
||||||
|
|
||||||
temperature?: number
|
temperature?: number
|
||||||
maxRetries?: number
|
maxRetries?: number
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
{
|
{
|
||||||
"extends": "./tsconfig.json",
|
"extends": "./tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
// @workaround DTS bug
|
// @workaround DTS bug
|
||||||
// dts do not work with monorepo path mapping
|
// dts do not work with monorepo path mapping
|
||||||
// disable path mapping for it
|
// disable path mapping for it
|
||||||
"paths": {}
|
"paths": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+10
-11
@@ -1,13 +1,12 @@
|
|||||||
{
|
{
|
||||||
"extends": "../../tsconfig.base.json",
|
"extends": "../../tsconfig.base.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo",
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo",
|
||||||
"noEmit": false,
|
"noEmit": false,
|
||||||
"allowImportingTsExtensions": false,
|
"allowImportingTsExtensions": false,
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"outDir": "dist"
|
"outDir": "dist"
|
||||||
},
|
},
|
||||||
"include": ["**/*.ts"],
|
"include": ["**/*.ts"],
|
||||||
"exclude": ["dist", "node_modules"]
|
"exclude": ["dist", "node_modules"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,4 +34,3 @@ export default defineConfig({
|
|||||||
'process.env.NODE_ENV': '"production"',
|
'process.env.NODE_ENV': '"production"',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "page-agent",
|
"name": "page-agent",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "0.0.21",
|
"version": "0.2.5",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/esm/page-agent.js",
|
"main": "./dist/esm/page-agent.js",
|
||||||
"module": "./dist/esm/page-agent.js",
|
"module": "./dist/esm/page-agent.js",
|
||||||
@@ -37,17 +37,16 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://alibaba.github.io/page-agent/",
|
"homepage": "https://alibaba.github.io/page-agent/",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "vite build && vite build --config vite.umd.config.js",
|
"build": "vite build",
|
||||||
"serve": "npx serve dist/umd -p 5173",
|
"dev:iife": "concurrently \"vite build --config vite.iife.config.js --watch\" \"npx serve dist/iife -p 5174\"",
|
||||||
"dev:umd": "concurrently \"vite build --config vite.umd.config.js --watch\" \"npm run serve\"",
|
|
||||||
"prepublishOnly": "node -e \"const fs=require('fs');['README.md','LICENSE'].forEach(f=>fs.copyFileSync('../../'+f,f))\"",
|
"prepublishOnly": "node -e \"const fs=require('fs');['README.md','LICENSE'].forEach(f=>fs.copyFileSync('../../'+f,f))\"",
|
||||||
"postpublish": "node -e \"['README.md','LICENSE'].forEach(f=>{try{require('fs').unlinkSync(f)}catch{}})\""
|
"postpublish": "node -e \"['README.md','LICENSE'].forEach(f=>{try{require('fs').unlinkSync(f)}catch{}})\""
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chalk": "^5.6.2",
|
"chalk": "^5.6.2",
|
||||||
"zod": "^4.3.5",
|
"zod": "^4.3.5",
|
||||||
"@page-agent/llms": "0.0.21",
|
"@page-agent/llms": "0.2.5",
|
||||||
"@page-agent/page-controller": "0.0.21",
|
"@page-agent/page-controller": "0.2.5",
|
||||||
"@page-agent/ui": "0.0.21"
|
"@page-agent/ui": "0.2.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
import { LLM, type Tool } from '@page-agent/llms'
|
import { LLM, type Tool } from '@page-agent/llms'
|
||||||
import { PageController } from '@page-agent/page-controller'
|
import { PageController } from '@page-agent/page-controller'
|
||||||
import { Panel, SimulatorMask } from '@page-agent/ui'
|
import { Panel } from '@page-agent/ui'
|
||||||
import chalk from 'chalk'
|
import chalk from 'chalk'
|
||||||
import zod from 'zod'
|
import zod from 'zod'
|
||||||
|
|
||||||
@@ -12,11 +12,11 @@ import type { PageAgentConfig } from './config'
|
|||||||
import { MAX_STEPS } from './config/constants'
|
import { MAX_STEPS } from './config/constants'
|
||||||
import SYSTEM_PROMPT from './prompts/system_prompt.md?raw'
|
import SYSTEM_PROMPT from './prompts/system_prompt.md?raw'
|
||||||
import { tools } from './tools'
|
import { tools } from './tools'
|
||||||
import { normalizeResponse, trimLines, uid, waitUntil } from './utils'
|
import { normalizeResponse, trimLines, uid } from './utils'
|
||||||
import { assert } from './utils/assert'
|
import { assert } from './utils/assert'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Agent brain state - the reflection-before-action model
|
* Agent reflection state - the reflection-before-action model
|
||||||
*
|
*
|
||||||
* Every tool call must first reflect on:
|
* Every tool call must first reflect on:
|
||||||
* - evaluation_previous_goal: How well did the previous action achieve its goal?
|
* - evaluation_previous_goal: How well did the previous action achieve its goal?
|
||||||
@@ -50,8 +50,12 @@ export interface MacroToolResult {
|
|||||||
export type { PageAgentConfig }
|
export type { PageAgentConfig }
|
||||||
export { tool, type PageAgentTool } from './tools'
|
export { tool, type PageAgentTool } from './tools'
|
||||||
|
|
||||||
export interface AgentHistory {
|
/**
|
||||||
brain: Partial<AgentReflection>
|
* A single agent step with reflection and action
|
||||||
|
*/
|
||||||
|
export interface AgentStep {
|
||||||
|
type: 'step'
|
||||||
|
reflection: Partial<AgentReflection>
|
||||||
action: {
|
action: {
|
||||||
name: string
|
name: string
|
||||||
input: any
|
input: any
|
||||||
@@ -66,24 +70,42 @@ export interface AgentHistory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Persistent observation event (stays in memory)
|
||||||
|
*/
|
||||||
|
export interface ObservationEvent {
|
||||||
|
type: 'observation'
|
||||||
|
content: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User takeover event
|
||||||
|
*/
|
||||||
|
export interface UserTakeoverEvent {
|
||||||
|
type: 'user_takeover'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Union type for all history events
|
||||||
|
*/
|
||||||
|
export type HistoryEvent = AgentStep | ObservationEvent | UserTakeoverEvent
|
||||||
|
|
||||||
export interface ExecutionResult {
|
export interface ExecutionResult {
|
||||||
success: boolean
|
success: boolean
|
||||||
data: string
|
data: string
|
||||||
history: AgentHistory[]
|
history: HistoryEvent[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PageAgent extends EventTarget {
|
export class PageAgent extends EventTarget {
|
||||||
config: PageAgentConfig
|
config: PageAgentConfig
|
||||||
id = uid()
|
id = uid()
|
||||||
panel: Panel
|
panel: Panel | null = null
|
||||||
tools: typeof tools
|
tools: typeof tools
|
||||||
paused = false
|
|
||||||
disposed = false
|
disposed = false
|
||||||
task = ''
|
task = ''
|
||||||
taskId = ''
|
taskId = ''
|
||||||
|
|
||||||
#llm: LLM
|
#llm: LLM
|
||||||
#totalWaitTime = 0
|
|
||||||
#abortController = new AbortController()
|
#abortController = new AbortController()
|
||||||
#llmRetryListener: ((e: Event) => void) | null = null
|
#llmRetryListener: ((e: Event) => void) | null = null
|
||||||
#llmErrorListener: ((e: Event) => void) | null = null
|
#llmErrorListener: ((e: Event) => void) | null = null
|
||||||
@@ -92,39 +114,49 @@ export class PageAgent extends EventTarget {
|
|||||||
/** PageController for DOM operations */
|
/** PageController for DOM operations */
|
||||||
pageController: PageController
|
pageController: PageController
|
||||||
|
|
||||||
/** Fullscreen mask */
|
/** Runtime states for tracking across steps */
|
||||||
mask = new SimulatorMask()
|
states = {
|
||||||
/** History records */
|
/** Accumulated wait time in seconds, used by wait tool */
|
||||||
history: AgentHistory[] = []
|
totalWaitTime: 0,
|
||||||
|
/** Last known URL for detecting navigation */
|
||||||
|
lastURL: '',
|
||||||
|
}
|
||||||
|
|
||||||
constructor(config: PageAgentConfig = {}) {
|
/** History events */
|
||||||
|
history: HistoryEvent[] = []
|
||||||
|
|
||||||
|
constructor(config: PageAgentConfig) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
this.config = config
|
this.config = config
|
||||||
this.#llm = new LLM(this.config)
|
this.#llm = new LLM(this.config)
|
||||||
this.panel = new Panel({
|
|
||||||
language: this.config.language,
|
// Conditionally initialize Panel
|
||||||
onExecuteTask: (task) => this.execute(task),
|
if (this.config.enablePanel !== false) {
|
||||||
onStop: () => this.dispose(),
|
this.panel = new Panel({
|
||||||
onPauseToggle: () => {
|
language: this.config.language,
|
||||||
this.paused = !this.paused
|
onExecuteTask: (task) => this.execute(task),
|
||||||
return this.paused
|
onStop: () => this.dispose(),
|
||||||
},
|
promptForNextTask: this.config.promptForNextTask,
|
||||||
getPaused: () => this.paused,
|
})
|
||||||
})
|
}
|
||||||
|
|
||||||
this.tools = new Map(tools)
|
this.tools = new Map(tools)
|
||||||
|
|
||||||
// Initialize PageController with config
|
// Initialize PageController with config (mask enabled by default)
|
||||||
this.pageController = new PageController(this.config)
|
this.pageController = new PageController({
|
||||||
|
...this.config,
|
||||||
|
enableMask: this.config.enableMask ?? true,
|
||||||
|
})
|
||||||
|
|
||||||
// Listen to LLM events
|
// Listen to LLM events
|
||||||
this.#llmRetryListener = (e) => {
|
this.#llmRetryListener = (e) => {
|
||||||
const { current, max } = (e as CustomEvent).detail
|
const { current, max } = (e as CustomEvent).detail
|
||||||
this.panel.update({ type: 'retry', current, max })
|
this.panel?.update({ type: 'retry', current, max })
|
||||||
}
|
}
|
||||||
this.#llmErrorListener = (e) => {
|
this.#llmErrorListener = (e) => {
|
||||||
const { error } = (e as CustomEvent).detail
|
const { error } = (e as CustomEvent).detail
|
||||||
this.panel.update({ type: 'error', message: `step failed: ${error.message}` })
|
this.panel?.update({ type: 'error', message: `step failed: ${error.message}` })
|
||||||
}
|
}
|
||||||
this.#llm.addEventListener('retry', this.#llmRetryListener)
|
this.#llm.addEventListener('retry', this.#llmRetryListener)
|
||||||
this.#llm.addEventListener('error', this.#llmErrorListener)
|
this.#llm.addEventListener('error', this.#llmErrorListener)
|
||||||
@@ -143,12 +175,26 @@ export class PageAgent extends EventTarget {
|
|||||||
this.tools.delete('execute_javascript')
|
this.tools.delete('execute_javascript')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Disable ask_user tool if enableAskUser is false or if panel is disabled
|
||||||
|
if (this.config.enableAskUser === false || this.config.enablePanel === false) {
|
||||||
|
this.tools.delete('ask_user')
|
||||||
|
}
|
||||||
|
|
||||||
this.#beforeUnloadListener = (e) => {
|
this.#beforeUnloadListener = (e) => {
|
||||||
if (!this.disposed) this.dispose('PAGE_UNLOADING')
|
if (!this.disposed) this.dispose('PAGE_UNLOADING')
|
||||||
}
|
}
|
||||||
window.addEventListener('beforeunload', this.#beforeUnloadListener)
|
window.addEventListener('beforeunload', this.#beforeUnloadListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Push a persistent observation to the history event stream.
|
||||||
|
* This will be visible in <agent_history> and remain in memory across steps.
|
||||||
|
*/
|
||||||
|
pushObservation(content: string): void {
|
||||||
|
this.history.push({ type: 'observation', content })
|
||||||
|
this.panel?.update({ type: 'observation', content })
|
||||||
|
}
|
||||||
|
|
||||||
async execute(task: string): Promise<ExecutionResult> {
|
async execute(task: string): Promise<ExecutionResult> {
|
||||||
if (!task) throw new Error('Task is required')
|
if (!task) throw new Error('Task is required')
|
||||||
this.task = task
|
this.task = task
|
||||||
@@ -162,12 +208,12 @@ export class PageAgent extends EventTarget {
|
|||||||
await onBeforeTask.call(this)
|
await onBeforeTask.call(this)
|
||||||
|
|
||||||
// Show mask and panel
|
// Show mask and panel
|
||||||
this.mask.show()
|
this.pageController.showMask()
|
||||||
|
|
||||||
this.panel.show()
|
this.panel?.show()
|
||||||
this.panel.reset()
|
this.panel?.reset()
|
||||||
|
|
||||||
this.panel.update({ type: 'input', task: this.task })
|
this.panel?.update({ type: 'input', task: this.task })
|
||||||
|
|
||||||
if (this.#abortController) {
|
if (this.#abortController) {
|
||||||
this.#abortController.abort()
|
this.#abortController.abort()
|
||||||
@@ -176,22 +222,28 @@ export class PageAgent extends EventTarget {
|
|||||||
|
|
||||||
this.history = []
|
this.history = []
|
||||||
|
|
||||||
|
// Reset states
|
||||||
|
this.states = {
|
||||||
|
totalWaitTime: 0,
|
||||||
|
lastURL: '',
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let step = 0
|
let step = 0
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
|
await this.#generateObservations(step)
|
||||||
|
|
||||||
await onBeforeStep.call(this, step)
|
await onBeforeStep.call(this, step)
|
||||||
|
|
||||||
console.group(`step: ${step}`)
|
console.group(`step: ${step}`)
|
||||||
|
|
||||||
// abort
|
// abort
|
||||||
if (this.#abortController.signal.aborted) throw new Error('AbortError')
|
if (this.#abortController.signal.aborted) throw new Error('AbortError')
|
||||||
// pause
|
|
||||||
await waitUntil(() => !this.paused)
|
|
||||||
|
|
||||||
// Update status to thinking
|
// Update status to thinking
|
||||||
console.log(chalk.blue('Thinking...'))
|
console.log(chalk.blue('Thinking...'))
|
||||||
this.panel.update({ type: 'thinking' })
|
this.panel?.update({ type: 'thinking' })
|
||||||
|
|
||||||
const result = await this.#llm.invoke(
|
const result = await this.#llm.invoke(
|
||||||
[
|
[
|
||||||
@@ -215,23 +267,24 @@ export class PageAgent extends EventTarget {
|
|||||||
const macroResult = result.toolResult as MacroToolResult
|
const macroResult = result.toolResult as MacroToolResult
|
||||||
const input = macroResult.input
|
const input = macroResult.input
|
||||||
const output = macroResult.output
|
const output = macroResult.output
|
||||||
const brain = {
|
const reflection: Partial<AgentReflection> = {
|
||||||
evaluation_previous_goal: input.evaluation_previous_goal || '',
|
evaluation_previous_goal: input.evaluation_previous_goal,
|
||||||
memory: input.memory || '',
|
memory: input.memory,
|
||||||
next_goal: input.next_goal || '',
|
next_goal: input.next_goal,
|
||||||
}
|
}
|
||||||
const actionName = Object.keys(input.action)[0]
|
const actionName = Object.keys(input.action)[0]
|
||||||
const action = {
|
const action: AgentStep['action'] = {
|
||||||
name: actionName,
|
name: actionName,
|
||||||
input: input.action[actionName],
|
input: input.action[actionName],
|
||||||
output: output,
|
output: output,
|
||||||
}
|
}
|
||||||
|
|
||||||
this.history.push({
|
this.history.push({
|
||||||
brain,
|
type: 'step',
|
||||||
|
reflection,
|
||||||
action,
|
action,
|
||||||
usage: result.usage,
|
usage: result.usage,
|
||||||
})
|
} as AgentStep)
|
||||||
|
|
||||||
console.log(chalk.green('Step finished:'), actionName)
|
console.log(chalk.green('Step finished:'), actionName)
|
||||||
console.groupEnd()
|
console.groupEnd()
|
||||||
@@ -310,50 +363,49 @@ export class PageAgent extends EventTarget {
|
|||||||
execute: async (input: MacroToolInput): Promise<MacroToolResult> => {
|
execute: async (input: MacroToolInput): Promise<MacroToolResult> => {
|
||||||
// abort
|
// abort
|
||||||
if (this.#abortController.signal.aborted) throw new Error('AbortError')
|
if (this.#abortController.signal.aborted) throw new Error('AbortError')
|
||||||
// pause
|
|
||||||
await waitUntil(() => !this.paused)
|
|
||||||
|
|
||||||
console.log(chalk.blue.bold('MacroTool execute'), input)
|
console.log(chalk.blue.bold('MacroTool execute'), input)
|
||||||
const action = input.action
|
const action = input.action
|
||||||
|
|
||||||
const toolName = Object.keys(action)[0]
|
const toolName = Object.keys(action)[0]
|
||||||
const toolInput = action[toolName]
|
const toolInput = action[toolName]
|
||||||
const brain = trimLines(`✅: ${input.evaluation_previous_goal}
|
|
||||||
💾: ${input.memory}
|
|
||||||
🎯: ${input.next_goal}
|
|
||||||
`)
|
|
||||||
|
|
||||||
console.log(brain)
|
// Build reflection text, only include non-empty fields
|
||||||
this.panel.update({ type: 'thinking', text: brain })
|
const reflectionLines: string[] = []
|
||||||
|
if (input.evaluation_previous_goal)
|
||||||
|
reflectionLines.push(`✅: ${input.evaluation_previous_goal}`)
|
||||||
|
if (input.memory) reflectionLines.push(`💾: ${input.memory}`)
|
||||||
|
if (input.next_goal) reflectionLines.push(`🎯: ${input.next_goal}`)
|
||||||
|
|
||||||
|
const reflectionText = reflectionLines.length > 0 ? reflectionLines.join('\n') : ''
|
||||||
|
|
||||||
|
if (reflectionText) {
|
||||||
|
console.log(reflectionText)
|
||||||
|
this.panel?.update({ type: 'thinking', text: reflectionText })
|
||||||
|
}
|
||||||
|
|
||||||
// Find the corresponding tool
|
// Find the corresponding tool
|
||||||
const tool = tools.get(toolName)
|
const tool = tools.get(toolName)
|
||||||
assert(tool, `Tool ${toolName} not found. (@note should have been caught before this!!!)`)
|
assert(tool, `Tool ${toolName} not found. (@note should have been caught before this!!!)`)
|
||||||
|
|
||||||
console.log(chalk.blue.bold(`Executing tool: ${toolName}`), toolInput)
|
console.log(chalk.blue.bold(`Executing tool: ${toolName}`), toolInput)
|
||||||
this.panel.update({ type: 'toolExecuting', toolName, args: toolInput })
|
this.panel?.update({ type: 'toolExecuting', toolName, args: toolInput })
|
||||||
|
|
||||||
const startTime = Date.now()
|
const startTime = Date.now()
|
||||||
|
|
||||||
// Execute tool, bind `this` to PageAgent
|
// Execute tool, bind `this` to PageAgent
|
||||||
let result = await tool.execute.bind(this)(toolInput)
|
const result = await tool.execute.bind(this)(toolInput)
|
||||||
|
|
||||||
const duration = Date.now() - startTime
|
const duration = Date.now() - startTime
|
||||||
console.log(chalk.green.bold(`Tool (${toolName}) executed for ${duration}ms`), result)
|
console.log(chalk.green.bold(`Tool (${toolName}) executed for ${duration}ms`), result)
|
||||||
|
|
||||||
if (toolName === 'wait') {
|
// Reset wait time for non-wait tools
|
||||||
this.#totalWaitTime += Math.round(toolInput.seconds + duration / 1000)
|
if (toolName !== 'wait') {
|
||||||
result += `\n<sys> You have waited ${this.#totalWaitTime} seconds accumulatively.`
|
this.states.totalWaitTime = 0
|
||||||
if (this.#totalWaitTime >= 3)
|
|
||||||
result += '\nDo NOT wait any longer unless you have a good reason.\n'
|
|
||||||
result += '</sys>'
|
|
||||||
} else {
|
|
||||||
// For other tools, reset wait time
|
|
||||||
this.#totalWaitTime = 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Briefly display execution result
|
// Briefly display execution result
|
||||||
this.panel.update({
|
this.panel?.update({
|
||||||
type: 'toolCompleted',
|
type: 'toolCompleted',
|
||||||
toolName,
|
toolName,
|
||||||
args: toolInput,
|
args: toolInput,
|
||||||
@@ -426,6 +478,34 @@ export class PageAgent extends EventTarget {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate observations before each step
|
||||||
|
* - URL change detection
|
||||||
|
* - Too many steps warning
|
||||||
|
* @todo loop detection
|
||||||
|
* @todo console error
|
||||||
|
*/
|
||||||
|
async #generateObservations(stepCount: number): Promise<void> {
|
||||||
|
// Detect URL change
|
||||||
|
const currentURL = await this.pageController.getCurrentUrl()
|
||||||
|
if (currentURL !== this.states.lastURL) {
|
||||||
|
this.pushObservation(`Page navigated to → ${currentURL}`)
|
||||||
|
this.states.lastURL = currentURL
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn about remaining steps
|
||||||
|
const remaining = MAX_STEPS - stepCount
|
||||||
|
if (remaining === 5) {
|
||||||
|
this.pushObservation(
|
||||||
|
`⚠️ Only ${remaining} steps remaining. Consider wrapping up or calling done with partial results.`
|
||||||
|
)
|
||||||
|
} else if (remaining === 2) {
|
||||||
|
this.pushObservation(
|
||||||
|
`⚠️ Critical: Only ${remaining} steps left! You must finish the task or call done immediately.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async #assembleUserPrompt(): Promise<string> {
|
async #assembleUserPrompt(): Promise<string> {
|
||||||
let prompt = ''
|
let prompt = ''
|
||||||
|
|
||||||
@@ -437,31 +517,42 @@ export class PageAgent extends EventTarget {
|
|||||||
// - <step_info>
|
// - <step_info>
|
||||||
// <agent_state>
|
// <agent_state>
|
||||||
|
|
||||||
|
const stepCount = this.history.filter((e) => e.type === 'step').length
|
||||||
|
|
||||||
prompt += `<agent_state>
|
prompt += `<agent_state>
|
||||||
<user_request>
|
<user_request>
|
||||||
${this.task}
|
${this.task}
|
||||||
</user_request>
|
</user_request>
|
||||||
<step_info>
|
<step_info>
|
||||||
Step ${this.history.length + 1} of ${MAX_STEPS} max possible steps
|
Step ${stepCount + 1} of ${MAX_STEPS} max possible steps
|
||||||
Current date and time: ${new Date().toISOString()}
|
Current date and time: ${new Date().toISOString()}
|
||||||
</step_info>
|
</step_info>
|
||||||
</agent_state>
|
</agent_state>
|
||||||
`
|
`
|
||||||
|
|
||||||
// <agent_history>
|
// <agent_history>
|
||||||
// - <step_>
|
// - <step_N> for steps
|
||||||
|
// - <sys> for observations and system messages
|
||||||
|
|
||||||
prompt += '\n<agent_history>\n'
|
prompt += '\n<agent_history>\n'
|
||||||
|
|
||||||
this.history.forEach((history, index) => {
|
let stepIndex = 0
|
||||||
prompt += `<step_${index + 1}>
|
for (const event of this.history) {
|
||||||
Evaluation of Previous Step: ${history.brain.evaluation_previous_goal}
|
if (event.type === 'step') {
|
||||||
Memory: ${history.brain.memory}
|
stepIndex++
|
||||||
Next Goal: ${history.brain.next_goal}
|
prompt += `<step_${stepIndex}>
|
||||||
Action Results: ${history.action.output}
|
Evaluation of Previous Step: ${event.reflection.evaluation_previous_goal}
|
||||||
</step_${index + 1}>
|
Memory: ${event.reflection.memory}
|
||||||
|
Next Goal: ${event.reflection.next_goal}
|
||||||
|
Action Results: ${event.action.output}
|
||||||
|
</step_${stepIndex}>
|
||||||
`
|
`
|
||||||
})
|
} else if (event.type === 'observation') {
|
||||||
|
prompt += `<sys>${event.content}</sys>\n`
|
||||||
|
} else if (event.type === 'user_takeover') {
|
||||||
|
prompt += `<sys>User took over control and made changes to the page.</sys>\n`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
prompt += '</agent_history>\n\n'
|
prompt += '</agent_history>\n\n'
|
||||||
|
|
||||||
@@ -477,75 +568,43 @@ export class PageAgent extends EventTarget {
|
|||||||
|
|
||||||
// Update panel status
|
// Update panel status
|
||||||
if (success) {
|
if (success) {
|
||||||
this.panel.update({ type: 'output', text })
|
this.panel?.update({ type: 'output', text })
|
||||||
} else {
|
} else {
|
||||||
this.panel.update({ type: 'error', message: text })
|
this.panel?.update({ type: 'error', message: text })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Task completed
|
// Task completed
|
||||||
this.panel.update({ type: 'completed' })
|
this.panel?.update({ type: 'completed' })
|
||||||
|
|
||||||
this.mask.hide()
|
this.pageController.hideMask()
|
||||||
|
|
||||||
this.#abortController.abort()
|
this.#abortController.abort()
|
||||||
}
|
}
|
||||||
|
|
||||||
async #getBrowserState(): Promise<string> {
|
async #getBrowserState(): Promise<string> {
|
||||||
const pageUrl = await this.pageController.getCurrentUrl()
|
const state = await this.pageController.getBrowserState()
|
||||||
const pageTitle = await this.pageController.getPageTitle()
|
|
||||||
const pi = await this.pageController.getPageInfo()
|
|
||||||
const viewportExpansion = await this.pageController.getViewportExpansion()
|
|
||||||
|
|
||||||
this.mask.wrapper.style.pointerEvents = 'none'
|
|
||||||
await this.pageController.updateTree()
|
|
||||||
this.mask.wrapper.style.pointerEvents = 'auto'
|
|
||||||
|
|
||||||
let simplifiedHTML = await this.pageController.getSimplifiedHTML()
|
|
||||||
|
|
||||||
|
let content = state.content
|
||||||
if (this.config.transformPageContent) {
|
if (this.config.transformPageContent) {
|
||||||
simplifiedHTML = await this.config.transformPageContent(simplifiedHTML)
|
content = await this.config.transformPageContent(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
let prompt = trimLines(`<browser_state>
|
return trimLines(`<browser_state>
|
||||||
Current Page: [${pageTitle}](${pageUrl})
|
Current Page: [${state.title}](${state.url})
|
||||||
|
|
||||||
Page info: ${pi.viewport_width}x${pi.viewport_height}px viewport, ${pi.page_width}x${pi.page_height}px total page size, ${pi.pages_above.toFixed(1)} pages above, ${pi.pages_below.toFixed(1)} pages below, ${pi.total_pages.toFixed(1)} total pages, at ${(pi.current_page_position * 100).toFixed(0)}% of page
|
${state.header}
|
||||||
|
${content}
|
||||||
${viewportExpansion === -1 ? 'Interactive elements from top layer of the current page (full page):' : 'Interactive elements from top layer of the current page inside the viewport:'}
|
${state.footer}
|
||||||
|
|
||||||
|
</browser_state>
|
||||||
`)
|
`)
|
||||||
|
|
||||||
// Page header info
|
|
||||||
const has_content_above = pi.pixels_above > 4
|
|
||||||
if (has_content_above && viewportExpansion !== -1) {
|
|
||||||
prompt += `... ${pi.pixels_above} pixels above (${pi.pages_above.toFixed(1)} pages) - scroll to see more ...\n`
|
|
||||||
} else {
|
|
||||||
prompt += `[Start of page]\n`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Current viewport info
|
|
||||||
prompt += simplifiedHTML
|
|
||||||
prompt += `\n`
|
|
||||||
|
|
||||||
// Page footer info
|
|
||||||
const has_content_below = pi.pixels_below > 4
|
|
||||||
if (has_content_below && viewportExpansion !== -1) {
|
|
||||||
prompt += `... ${pi.pixels_below} pixels below (${pi.pages_below.toFixed(1)} pages) - scroll to see more ...\n`
|
|
||||||
} else {
|
|
||||||
prompt += `[End of page]\n`
|
|
||||||
}
|
|
||||||
|
|
||||||
prompt += `</browser_state>\n`
|
|
||||||
|
|
||||||
return prompt
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose(reason?: string) {
|
dispose(reason?: string) {
|
||||||
console.log('Disposing PageAgent...')
|
console.log('Disposing PageAgent...')
|
||||||
this.disposed = true
|
this.disposed = true
|
||||||
this.pageController.dispose()
|
this.pageController.dispose()
|
||||||
this.panel.dispose()
|
this.panel?.dispose()
|
||||||
this.mask.dispose()
|
|
||||||
this.history = []
|
this.history = []
|
||||||
this.#abortController.abort(reason ?? 'PageAgent disposed')
|
this.#abortController.abort(reason ?? 'PageAgent disposed')
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { LLMConfig } from '@page-agent/llms'
|
|||||||
import type { PageControllerConfig } from '@page-agent/page-controller'
|
import type { PageControllerConfig } from '@page-agent/page-controller'
|
||||||
import type { SupportedLanguage } from '@page-agent/ui'
|
import type { SupportedLanguage } from '@page-agent/ui'
|
||||||
|
|
||||||
import type { AgentHistory, ExecutionResult, PageAgent } from '../PageAgent'
|
import type { ExecutionResult, HistoryEvent, PageAgent } from '../PageAgent'
|
||||||
import type { PageAgentTool } from '../tools'
|
import type { PageAgentTool } from '../tools'
|
||||||
|
|
||||||
export type { LLMConfig }
|
export type { LLMConfig }
|
||||||
@@ -11,6 +11,27 @@ export interface AgentConfig {
|
|||||||
// theme?: 'light' | 'dark'
|
// theme?: 'light' | 'dark'
|
||||||
language?: SupportedLanguage
|
language?: SupportedLanguage
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to prompt for next task after task completion
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
promptForNextTask?: boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable the UI panel for visual feedback and user interaction
|
||||||
|
* When disabled, the panel will not be created and all UI operations will be skipped.
|
||||||
|
* Useful for automated testing or when integrating PageAgent as a library.
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
enablePanel?: boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable the ask_user tool for agent to ask questions
|
||||||
|
* When disabled, the agent cannot ask user questions during execution.
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
enableAskUser?: boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom tools to extend PageAgent capabilities
|
* Custom tools to extend PageAgent capabilities
|
||||||
* @experimental
|
* @experimental
|
||||||
@@ -65,7 +86,7 @@ export interface AgentConfig {
|
|||||||
// @todo: remove `this` binding, pass agent as explicit parameter instead
|
// @todo: remove `this` binding, pass agent as explicit parameter instead
|
||||||
|
|
||||||
onBeforeStep?: (this: PageAgent, stepCnt: number) => Promise<void> | void
|
onBeforeStep?: (this: PageAgent, stepCnt: number) => Promise<void> | void
|
||||||
onAfterStep?: (this: PageAgent, stepCnt: number, history: AgentHistory[]) => Promise<void> | void
|
onAfterStep?: (this: PageAgent, stepCnt: number, history: HistoryEvent[]) => Promise<void> | void
|
||||||
onBeforeTask?: (this: PageAgent) => Promise<void> | void
|
onBeforeTask?: (this: PageAgent) => Promise<void> | void
|
||||||
onAfterTask?: (this: PageAgent, result: ExecutionResult) => Promise<void> | void
|
onAfterTask?: (this: PageAgent, result: ExecutionResult) => Promise<void> | void
|
||||||
|
|
||||||
@@ -108,14 +129,14 @@ export interface AgentConfig {
|
|||||||
* @note PageAgent will try to detect new pages and decide if it's caused by an action. But not very reliable.
|
* @note PageAgent will try to detect new pages and decide if it's caused by an action. But not very reliable.
|
||||||
* @todo remove `this` binding, pass agent as explicit parameter instead
|
* @todo remove `this` binding, pass agent as explicit parameter instead
|
||||||
*/
|
*/
|
||||||
onNewPageOpen?: (this: PageAgent, url: string) => Promise<void> | void
|
// onNewPageOpen?: (this: PageAgent, url: string) => Promise<void> | void
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO: @unimplemented
|
* TODO: @unimplemented
|
||||||
* try to navigate to a new page instead of opening a new tab/window.
|
* try to navigate to a new page instead of opening a new tab/window.
|
||||||
* @note will unload the current page when a action tries to open a new page. so that things keep in the same tab/window.
|
* @note will unload the current page when a action tries to open a new page. so that things keep in the same tab/window.
|
||||||
*/
|
*/
|
||||||
experimentalPreventNewPage?: boolean
|
// experimentalPreventNewPage?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PageAgentConfig = LLMConfig & AgentConfig & PageControllerConfig
|
export type PageAgentConfig = LLMConfig & AgentConfig & PageControllerConfig
|
||||||
|
|||||||
@@ -35,7 +35,16 @@ setTimeout(() => {
|
|||||||
window.pageAgent = new PageAgent(config)
|
window.pageAgent = new PageAgent(config)
|
||||||
} else {
|
} else {
|
||||||
console.log('🚀 page-agent.js no current script detected, using default demo config')
|
console.log('🚀 page-agent.js no current script detected, using default demo config')
|
||||||
window.pageAgent = new PageAgent()
|
const config: PageAgentConfig = {
|
||||||
|
// model: DEMO_MODEL,
|
||||||
|
// baseURL: DEMO_BASE_URL,
|
||||||
|
// apiKey: DEMO_API_KEY,
|
||||||
|
|
||||||
|
model: import.meta.env.LLM_MODEL_NAME ? import.meta.env.LLM_MODEL_NAME : DEMO_MODEL,
|
||||||
|
baseURL: import.meta.env.LLM_BASE_URL ? import.meta.env.LLM_BASE_URL : DEMO_BASE_URL,
|
||||||
|
apiKey: import.meta.env.LLM_API_KEY ? import.meta.env.LLM_API_KEY : DEMO_API_KEY,
|
||||||
|
}
|
||||||
|
window.pageAgent = new PageAgent(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('🚀 page-agent.js initialized with config:', window.pageAgent.config)
|
console.log('🚀 page-agent.js initialized with config:', window.pageAgent.config)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
system_prompt.md
|
||||||
@@ -92,7 +92,6 @@ Strictly follow these rules while using the browser and navigating the web:
|
|||||||
- User can be wrong. If the request of user is not achievable, inappropriate or you do not have enough information or tools to achieve it. Tell user to make a better request.
|
- User can be wrong. If the request of user is not achievable, inappropriate or you do not have enough information or tools to achieve it. Tell user to make a better request.
|
||||||
- Webpage can be broken. All webpages or apps have bugs. Some bug will make it hard for your job. It's encouraged to tell user the problem of current page. Your feedbacks (including failing) are valuable for user.
|
- Webpage can be broken. All webpages or apps have bugs. Some bug will make it hard for your job. It's encouraged to tell user the problem of current page. Your feedbacks (including failing) are valuable for user.
|
||||||
- Trying to hard can be harmful. Repeating some action back and forth or pushing for a complex procedure with little knowledge can cause unwanted result and harmful side-effects. User would rather you to complete the task with a fail.
|
- Trying to hard can be harmful. Repeating some action back and forth or pushing for a complex procedure with little knowledge can cause unwanted result and harmful side-effects. User would rather you to complete the task with a fail.
|
||||||
- If you are not clear about the request or steps. `ask_user` to clarify it.
|
|
||||||
- If you do not have knowledge for the current webpage or task. You must require user to give specific instructions and detailed steps.
|
- If you do not have knowledge for the current webpage or task. You must require user to give specific instructions and detailed steps.
|
||||||
</capability>
|
</capability>
|
||||||
|
|
||||||
@@ -120,7 +119,7 @@ Exhibit the following reasoning patterns to successfully achieve the <user_reque
|
|||||||
- Analyze all relevant items in <agent_history> and <browser_state> to understand your state.
|
- Analyze all relevant items in <agent_history> and <browser_state> to understand your state.
|
||||||
- Explicitly judge success/failure/uncertainty of the last action. Never assume an action succeeded just because it appears to be executed in your last step in <agent_history>. If the expected change is missing, mark the last action as failed (or uncertain) and plan a recovery.
|
- Explicitly judge success/failure/uncertainty of the last action. Never assume an action succeeded just because it appears to be executed in your last step in <agent_history>. If the expected change is missing, mark the last action as failed (or uncertain) and plan a recovery.
|
||||||
- Analyze whether you are stuck, e.g. when you repeat the same actions multiple times without any progress. Then consider alternative approaches e.g. scrolling for more context or ask user for help.
|
- Analyze whether you are stuck, e.g. when you repeat the same actions multiple times without any progress. Then consider alternative approaches e.g. scrolling for more context or ask user for help.
|
||||||
- `ask_user` for help if you have any difficulty. Users want to be kept in the loop.
|
- Ask user for help if you have any difficulty. Keep user in the loop.
|
||||||
- If you see information relevant to <user_request>, plan saving the information to memory.
|
- If you see information relevant to <user_request>, plan saving the information to memory.
|
||||||
- Always reason about the <user_request>. Make sure to carefully analyze the specific steps and information required. E.g. specific filters, specific form fields, specific information to search. Make sure to always compare the current trajectory with the user request and think carefully if thats how the user requested it.
|
- Always reason about the <user_request>. Make sure to carefully analyze the specific steps and information required. E.g. specific filters, specific form fields, specific information to search. Make sure to always compare the current trajectory with the user request and think carefully if thats how the user requested it.
|
||||||
</reasoning_rules>
|
</reasoning_rules>
|
||||||
|
|||||||
@@ -57,6 +57,15 @@ tools.set(
|
|||||||
const actualWaitTime = Math.max(0, input.seconds - (Date.now() - lastTimeUpdate) / 1000)
|
const actualWaitTime = Math.max(0, input.seconds - (Date.now() - lastTimeUpdate) / 1000)
|
||||||
console.log(`actualWaitTime: ${actualWaitTime} seconds`)
|
console.log(`actualWaitTime: ${actualWaitTime} seconds`)
|
||||||
await waitFor(actualWaitTime)
|
await waitFor(actualWaitTime)
|
||||||
|
|
||||||
|
this.states.totalWaitTime += input.seconds
|
||||||
|
|
||||||
|
if (this.states.totalWaitTime >= 3) {
|
||||||
|
this.pushObservation(
|
||||||
|
`You have waited ${this.states.totalWaitTime} seconds accumulatively. Do NOT wait any longer unless you have a good reason.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return `✅ Waited for ${input.seconds} seconds.`
|
return `✅ Waited for ${input.seconds} seconds.`
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -71,6 +80,9 @@ tools.set(
|
|||||||
question: zod.string(),
|
question: zod.string(),
|
||||||
}),
|
}),
|
||||||
execute: async function (this: PageAgent, input) {
|
execute: async function (this: PageAgent, input) {
|
||||||
|
if (!this.panel) {
|
||||||
|
throw new Error('ask_user tool requires panel to be enabled')
|
||||||
|
}
|
||||||
const answer = await this.panel.askUser(input.question)
|
const answer = await this.panel.askUser(input.question)
|
||||||
return `✅ Received user answer: ${answer}`
|
return `✅ Received user answer: ${answer}`
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ export interface PageAgentEventMap {
|
|||||||
// PageAgent status events
|
// PageAgent status events
|
||||||
// 'agent:execute': { params: { task: string } }
|
// 'agent:execute': { params: { task: string } }
|
||||||
// 'agent:done': { params: { text: string; success: boolean } }
|
// 'agent:done': { params: { text: string; success: boolean } }
|
||||||
// 'agent:paused': { params: undefined }
|
|
||||||
// 'agent:resumed': { params: undefined }
|
|
||||||
// 'agent:disposed': { params: undefined }
|
// 'agent:disposed': { params: undefined }
|
||||||
// 'agent:error': { params: { error: string | Error } }
|
// 'agent:error': { params: { error: string | Error } }
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"extends": "./tsconfig.json",
|
"extends": "./tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
// @workaround DTS bug
|
// @workaround DTS bug
|
||||||
// dts do not work with monorepo path mapping
|
// dts do not work with monorepo path mapping
|
||||||
// disable path mapping for it
|
// disable path mapping for it
|
||||||
"paths": {}
|
"paths": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,24 @@
|
|||||||
{
|
{
|
||||||
"extends": "../../tsconfig.base.json",
|
"extends": "../../tsconfig.base.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo",
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo",
|
||||||
"noEmit": false,
|
"noEmit": false,
|
||||||
"allowImportingTsExtensions": false,
|
"allowImportingTsExtensions": false,
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"outDir": "dist",
|
"outDir": "dist",
|
||||||
"paths": {
|
"paths": {
|
||||||
//
|
//
|
||||||
"@page-agent/llms": ["../llms/src/index.ts"],
|
"@page-agent/llms": ["../llms/src/index.ts"],
|
||||||
"@page-agent/page-controller": ["../page-controller/src/PageController.ts"],
|
"@page-agent/page-controller": ["../page-controller/src/PageController.ts"],
|
||||||
"@page-agent/ui": ["../ui/src/index.ts"]
|
"@page-agent/ui": ["../ui/src/index.ts"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["**/*.ts"],
|
"include": ["**/*.ts"],
|
||||||
"exclude": ["dist", "node_modules"],
|
"exclude": ["dist", "node_modules"],
|
||||||
"references": [
|
"references": [
|
||||||
//
|
//
|
||||||
{ "path": "../llms" },
|
{ "path": "../llms" },
|
||||||
{ "path": "../page-controller" },
|
{ "path": "../page-controller" },
|
||||||
{ "path": "../ui" }
|
{ "path": "../ui" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
// @ts-check
|
// @ts-check
|
||||||
|
import { config as dotenvConfig } from 'dotenv'
|
||||||
import { dirname, resolve } from 'path'
|
import { dirname, resolve } from 'path'
|
||||||
import { fileURLToPath } from 'url'
|
import { fileURLToPath } from 'url'
|
||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
@@ -6,11 +7,14 @@ import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js'
|
|||||||
|
|
||||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||||
|
|
||||||
|
// Load .env from repo root
|
||||||
|
dotenvConfig({ path: resolve(__dirname, '../../.env') })
|
||||||
|
|
||||||
// UMD Bundle for CDN
|
// UMD Bundle for CDN
|
||||||
// - alias all local packages so that they can be build in
|
// - alias all local packages so that they can be build in
|
||||||
// - no external
|
// - no external
|
||||||
// - no d.ts. dts does not work with monorepo aliasing
|
// - no d.ts. dts does not work with monorepo aliasing
|
||||||
export default defineConfig({
|
export default defineConfig(({ mode }) => ({
|
||||||
plugins: [cssInjectedByJsPlugin({ relativeCSSInjection: true })],
|
plugins: [cssInjectedByJsPlugin({ relativeCSSInjection: true })],
|
||||||
publicDir: false,
|
publicDir: false,
|
||||||
esbuild: {
|
esbuild: {
|
||||||
@@ -25,21 +29,28 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
lib: {
|
lib: {
|
||||||
entry: resolve(__dirname, 'src/umd.ts'),
|
entry: resolve(__dirname, 'src/iife.ts'),
|
||||||
name: 'PageAgent',
|
name: 'PageAgent',
|
||||||
fileName: 'page-agent',
|
fileName: 'page-agent',
|
||||||
formats: ['umd'],
|
formats: ['iife'],
|
||||||
},
|
},
|
||||||
outDir: resolve(__dirname, 'dist', 'umd'),
|
outDir: resolve(__dirname, 'dist', 'iife'),
|
||||||
|
cssCodeSplit: true,
|
||||||
|
minify: false,
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
output: {
|
output: {
|
||||||
// force use .js as extension
|
// force use .js as extension
|
||||||
entryFileNames: 'page-agent.js',
|
entryFileNames: 'page-agent.js',
|
||||||
},
|
},
|
||||||
|
onwarn: function (message, handler) {
|
||||||
|
if (message.code === 'EVAL') return
|
||||||
|
handler(message)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
cssCodeSplit: true,
|
|
||||||
},
|
},
|
||||||
define: {
|
define: {
|
||||||
'process.env.NODE_ENV': '"production"',
|
'import.meta.env.LLM_MODEL_NAME': JSON.stringify(process.env.LLM_MODEL_NAME),
|
||||||
|
'import.meta.env.LLM_API_KEY': JSON.stringify(process.env.LLM_API_KEY),
|
||||||
|
'import.meta.env.LLM_BASE_URL': JSON.stringify(process.env.LLM_BASE_URL),
|
||||||
},
|
},
|
||||||
})
|
}))
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@page-agent/page-controller",
|
"name": "@page-agent/page-controller",
|
||||||
"version": "0.0.21",
|
"version": "0.2.5",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/lib/page-controller.js",
|
"main": "./dist/lib/page-controller.js",
|
||||||
"module": "./dist/lib/page-controller.js",
|
"module": "./dist/lib/page-controller.js",
|
||||||
@@ -34,5 +34,8 @@
|
|||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"prepublishOnly": "node -e \"const fs=require('fs');['LICENSE'].forEach(f=>fs.copyFileSync('../../'+f,f))\"",
|
"prepublishOnly": "node -e \"const fs=require('fs');['LICENSE'].forEach(f=>fs.copyFileSync('../../'+f,f))\"",
|
||||||
"postpublish": "node -e \"['LICENSE'].forEach(f=>{try{require('fs').unlinkSync(f)}catch{}})\""
|
"postpublish": "node -e \"['LICENSE'].forEach(f=>{try{require('fs').unlinkSync(f)}catch{}})\""
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"ai-motion": "^0.4.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,13 +18,32 @@ import { VIEWPORT_EXPANSION } from './constants'
|
|||||||
import * as dom from './dom'
|
import * as dom from './dom'
|
||||||
import type { FlatDomTree, InteractiveElementDomNode } from './dom/dom_tree/type'
|
import type { FlatDomTree, InteractiveElementDomNode } from './dom/dom_tree/type'
|
||||||
import { getPageInfo } from './dom/getPageInfo'
|
import { getPageInfo } from './dom/getPageInfo'
|
||||||
|
import { SimulatorMask } from './mask/SimulatorMask'
|
||||||
import { patchReact } from './patches/react'
|
import { patchReact } from './patches/react'
|
||||||
|
|
||||||
|
export { SimulatorMask }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configuration for PageController
|
* Configuration for PageController
|
||||||
*/
|
*/
|
||||||
export interface PageControllerConfig extends dom.DomConfig {
|
export interface PageControllerConfig extends dom.DomConfig {
|
||||||
viewportExpansion?: number
|
viewportExpansion?: number
|
||||||
|
/** Enable visual mask overlay during operations (default: false) */
|
||||||
|
enableMask?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Structured browser state for LLM consumption
|
||||||
|
*/
|
||||||
|
export interface BrowserState {
|
||||||
|
url: string
|
||||||
|
title: string
|
||||||
|
/** Page info + scroll position hint (e.g. "Page info: 1920x1080px...\n[Start of page]") */
|
||||||
|
header: string
|
||||||
|
/** Simplified HTML of interactive elements */
|
||||||
|
content: string
|
||||||
|
/** Page footer hint (e.g. "... 300 pixels below ..." or "[End of page]") */
|
||||||
|
footer: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ActionResult {
|
interface ActionResult {
|
||||||
@@ -64,12 +83,19 @@ export class PageController extends EventTarget {
|
|||||||
/** last time the tree was updated */
|
/** last time the tree was updated */
|
||||||
private lastTimeUpdate = 0
|
private lastTimeUpdate = 0
|
||||||
|
|
||||||
|
/** Visual mask overlay for blocking user interaction during automation */
|
||||||
|
private mask: SimulatorMask | null = null
|
||||||
|
|
||||||
constructor(config: PageControllerConfig = {}) {
|
constructor(config: PageControllerConfig = {}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
this.config = config
|
this.config = config
|
||||||
|
|
||||||
patchReact(this)
|
patchReact(this)
|
||||||
|
|
||||||
|
if (config.enableMask) {
|
||||||
|
this.mask = new SimulatorMask()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ======= State Queries =======
|
// ======= State Queries =======
|
||||||
@@ -81,42 +107,6 @@ export class PageController extends EventTarget {
|
|||||||
return window.location.href
|
return window.location.href
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get current page title
|
|
||||||
*/
|
|
||||||
async getPageTitle(): Promise<string> {
|
|
||||||
return document.title
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get page scroll and size info
|
|
||||||
*/
|
|
||||||
async getPageInfo() {
|
|
||||||
return getPageInfo()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the simplified HTML representation of the page.
|
|
||||||
* This is used by LLM to understand the page structure.
|
|
||||||
*/
|
|
||||||
async getSimplifiedHTML(): Promise<string> {
|
|
||||||
return this.simplifiedHTML
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get text description for an element by index
|
|
||||||
*/
|
|
||||||
async getElementText(index: number): Promise<string | undefined> {
|
|
||||||
return this.elementTextMap.get(index)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get total number of indexed interactive elements
|
|
||||||
*/
|
|
||||||
async getElementCount(): Promise<number> {
|
|
||||||
return this.selectorMap.size
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get last tree update timestamp
|
* Get last tree update timestamp
|
||||||
*/
|
*/
|
||||||
@@ -125,10 +115,43 @@ export class PageController extends EventTarget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the viewport expansion setting
|
* Get structured browser state for LLM consumption.
|
||||||
|
* Automatically calls updateTree() to refresh the DOM state.
|
||||||
*/
|
*/
|
||||||
async getViewportExpansion(): Promise<number> {
|
async getBrowserState(): Promise<BrowserState> {
|
||||||
return this.config.viewportExpansion ?? VIEWPORT_EXPANSION
|
const url = window.location.href
|
||||||
|
const title = document.title
|
||||||
|
const pi = getPageInfo()
|
||||||
|
const viewportExpansion = this.config.viewportExpansion ?? VIEWPORT_EXPANSION
|
||||||
|
|
||||||
|
await this.updateTree()
|
||||||
|
|
||||||
|
const content = this.simplifiedHTML
|
||||||
|
|
||||||
|
// Build header: page info + scroll position hint
|
||||||
|
const pageInfoLine = `Page info: ${pi.viewport_width}x${pi.viewport_height}px viewport, ${pi.page_width}x${pi.page_height}px total page size, ${pi.pages_above.toFixed(1)} pages above, ${pi.pages_below.toFixed(1)} pages below, ${pi.total_pages.toFixed(1)} total pages, at ${(pi.current_page_position * 100).toFixed(0)}% of page`
|
||||||
|
|
||||||
|
const elementsLabel =
|
||||||
|
viewportExpansion === -1
|
||||||
|
? 'Interactive elements from top layer of the current page (full page):'
|
||||||
|
: 'Interactive elements from top layer of the current page inside the viewport:'
|
||||||
|
|
||||||
|
const hasContentAbove = pi.pixels_above > 4
|
||||||
|
const scrollHintAbove =
|
||||||
|
hasContentAbove && viewportExpansion !== -1
|
||||||
|
? `... ${pi.pixels_above} pixels above (${pi.pages_above.toFixed(1)} pages) - scroll to see more ...`
|
||||||
|
: '[Start of page]'
|
||||||
|
|
||||||
|
const header = `${pageInfoLine}\n\n${elementsLabel}\n\n${scrollHintAbove}`
|
||||||
|
|
||||||
|
// Build footer: scroll position hint
|
||||||
|
const hasContentBelow = pi.pixels_below > 4
|
||||||
|
const footer =
|
||||||
|
hasContentBelow && viewportExpansion !== -1
|
||||||
|
? `... ${pi.pixels_below} pixels below (${pi.pages_below.toFixed(1)} pages) - scroll to see more ...`
|
||||||
|
: '[End of page]'
|
||||||
|
|
||||||
|
return { url, title, header, content, footer }
|
||||||
}
|
}
|
||||||
|
|
||||||
// ======= DOM Tree Operations =======
|
// ======= DOM Tree Operations =======
|
||||||
@@ -136,12 +159,18 @@ export class PageController extends EventTarget {
|
|||||||
/**
|
/**
|
||||||
* Update DOM tree, returns simplified HTML for LLM.
|
* Update DOM tree, returns simplified HTML for LLM.
|
||||||
* This is the main method to refresh the page state.
|
* This is the main method to refresh the page state.
|
||||||
|
* Automatically bypasses mask during DOM extraction if enabled.
|
||||||
*/
|
*/
|
||||||
async updateTree(): Promise<string> {
|
async updateTree(): Promise<string> {
|
||||||
this.dispatchEvent(new Event('beforeUpdate'))
|
this.dispatchEvent(new Event('beforeUpdate'))
|
||||||
|
|
||||||
this.lastTimeUpdate = Date.now()
|
this.lastTimeUpdate = Date.now()
|
||||||
|
|
||||||
|
// Temporarily bypass mask to allow DOM extraction
|
||||||
|
if (this.mask) {
|
||||||
|
this.mask.wrapper.style.pointerEvents = 'none'
|
||||||
|
}
|
||||||
|
|
||||||
dom.cleanUpHighlights()
|
dom.cleanUpHighlights()
|
||||||
|
|
||||||
const blacklist = [
|
const blacklist = [
|
||||||
@@ -162,6 +191,11 @@ export class PageController extends EventTarget {
|
|||||||
this.elementTextMap.clear()
|
this.elementTextMap.clear()
|
||||||
this.elementTextMap = dom.getElementTextMap(this.simplifiedHTML)
|
this.elementTextMap = dom.getElementTextMap(this.simplifiedHTML)
|
||||||
|
|
||||||
|
// Restore mask blocking
|
||||||
|
if (this.mask) {
|
||||||
|
this.mask.wrapper.style.pointerEvents = 'auto'
|
||||||
|
}
|
||||||
|
|
||||||
this.dispatchEvent(new Event('afterUpdate'))
|
this.dispatchEvent(new Event('afterUpdate'))
|
||||||
|
|
||||||
return this.simplifiedHTML
|
return this.simplifiedHTML
|
||||||
@@ -326,6 +360,24 @@ export class PageController extends EventTarget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ======= Mask Operations =======
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the visual mask overlay.
|
||||||
|
* Only works if enableMask was set to true in config.
|
||||||
|
*/
|
||||||
|
showMask(): void {
|
||||||
|
this.mask?.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide the visual mask overlay.
|
||||||
|
* Only works if enableMask was set to true in config.
|
||||||
|
*/
|
||||||
|
hideMask(): void {
|
||||||
|
this.mask?.hide()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dispose and clean up resources
|
* Dispose and clean up resources
|
||||||
*/
|
*/
|
||||||
@@ -335,5 +387,7 @@ export class PageController extends EventTarget {
|
|||||||
this.selectorMap.clear()
|
this.selectorMap.clear()
|
||||||
this.elementTextMap.clear()
|
this.elementTextMap.clear()
|
||||||
this.simplifiedHTML = '<EMPTY>'
|
this.simplifiedHTML = '<EMPTY>'
|
||||||
|
this.mask?.dispose()
|
||||||
|
this.mask = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -287,12 +287,52 @@ export async function scrollVertically(
|
|||||||
(document.documentElement as HTMLElement)
|
(document.documentElement as HTMLElement)
|
||||||
|
|
||||||
if (el === document.scrollingElement || el === document.documentElement || el === document.body) {
|
if (el === document.scrollingElement || el === document.documentElement || el === document.body) {
|
||||||
|
// Page-level scroll
|
||||||
|
const scrollBefore = window.scrollY
|
||||||
|
const scrollMax = document.documentElement.scrollHeight - window.innerHeight
|
||||||
|
|
||||||
window.scrollBy(0, dy)
|
window.scrollBy(0, dy)
|
||||||
return `✅ Scrolled page by ${dy}px.`
|
|
||||||
|
const scrollAfter = window.scrollY
|
||||||
|
const scrolled = scrollAfter - scrollBefore
|
||||||
|
|
||||||
|
if (Math.abs(scrolled) < 1) {
|
||||||
|
return dy > 0
|
||||||
|
? `⚠️ Already at the bottom of the page, cannot scroll down further.`
|
||||||
|
: `⚠️ Already at the top of the page, cannot scroll up further.`
|
||||||
|
}
|
||||||
|
|
||||||
|
const reachedBottom = dy > 0 && scrollAfter >= scrollMax - 1
|
||||||
|
const reachedTop = dy < 0 && scrollAfter <= 1
|
||||||
|
|
||||||
|
if (reachedBottom) return `✅ Scrolled page by ${scrolled}px. Reached the bottom of the page.`
|
||||||
|
if (reachedTop) return `✅ Scrolled page by ${scrolled}px. Reached the top of the page.`
|
||||||
|
return `✅ Scrolled page by ${scrolled}px.`
|
||||||
} else {
|
} else {
|
||||||
|
// Container scroll
|
||||||
|
const scrollBefore = el!.scrollTop
|
||||||
|
const scrollMax = el!.scrollHeight - el!.clientHeight
|
||||||
|
|
||||||
el!.scrollBy({ top: dy, behavior: 'smooth' })
|
el!.scrollBy({ top: dy, behavior: 'smooth' })
|
||||||
await waitFor(0.1) // Animation playback
|
await waitFor(0.1)
|
||||||
return `✅ Scrolled container (${el!.tagName}) by ${dy}px.`
|
|
||||||
|
const scrollAfter = el!.scrollTop
|
||||||
|
const scrolled = scrollAfter - scrollBefore
|
||||||
|
|
||||||
|
if (Math.abs(scrolled) < 1) {
|
||||||
|
return dy > 0
|
||||||
|
? `⚠️ Already at the bottom of container (${el!.tagName}), cannot scroll down further.`
|
||||||
|
: `⚠️ Already at the top of container (${el!.tagName}), cannot scroll up further.`
|
||||||
|
}
|
||||||
|
|
||||||
|
const reachedBottom = dy > 0 && scrollAfter >= scrollMax - 1
|
||||||
|
const reachedTop = dy < 0 && scrollAfter <= 1
|
||||||
|
|
||||||
|
if (reachedBottom)
|
||||||
|
return `✅ Scrolled container (${el!.tagName}) by ${scrolled}px. Reached the bottom.`
|
||||||
|
if (reachedTop)
|
||||||
|
return `✅ Scrolled container (${el!.tagName}) by ${scrolled}px. Reached the top.`
|
||||||
|
return `✅ Scrolled container (${el!.tagName}) by ${scrolled}px.`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -410,11 +450,52 @@ export async function scrollHorizontally(
|
|||||||
(document.documentElement as HTMLElement)
|
(document.documentElement as HTMLElement)
|
||||||
|
|
||||||
if (el === document.scrollingElement || el === document.documentElement || el === document.body) {
|
if (el === document.scrollingElement || el === document.documentElement || el === document.body) {
|
||||||
|
// Page-level scroll
|
||||||
|
const scrollBefore = window.scrollX
|
||||||
|
const scrollMax = document.documentElement.scrollWidth - window.innerWidth
|
||||||
|
|
||||||
window.scrollBy(dx, 0)
|
window.scrollBy(dx, 0)
|
||||||
return `✅ Scrolled page horizontally by ${dx}px`
|
|
||||||
|
const scrollAfter = window.scrollX
|
||||||
|
const scrolled = scrollAfter - scrollBefore
|
||||||
|
|
||||||
|
if (Math.abs(scrolled) < 1) {
|
||||||
|
return dx > 0
|
||||||
|
? `⚠️ Already at the right edge of the page, cannot scroll right further.`
|
||||||
|
: `⚠️ Already at the left edge of the page, cannot scroll left further.`
|
||||||
|
}
|
||||||
|
|
||||||
|
const reachedRight = dx > 0 && scrollAfter >= scrollMax - 1
|
||||||
|
const reachedLeft = dx < 0 && scrollAfter <= 1
|
||||||
|
|
||||||
|
if (reachedRight)
|
||||||
|
return `✅ Scrolled page by ${scrolled}px. Reached the right edge of the page.`
|
||||||
|
if (reachedLeft) return `✅ Scrolled page by ${scrolled}px. Reached the left edge of the page.`
|
||||||
|
return `✅ Scrolled page horizontally by ${scrolled}px.`
|
||||||
} else {
|
} else {
|
||||||
|
// Container scroll
|
||||||
|
const scrollBefore = el!.scrollLeft
|
||||||
|
const scrollMax = el!.scrollWidth - el!.clientWidth
|
||||||
|
|
||||||
el!.scrollBy({ left: dx, behavior: 'smooth' })
|
el!.scrollBy({ left: dx, behavior: 'smooth' })
|
||||||
await waitFor(0.1) // Animation playback
|
await waitFor(0.1)
|
||||||
return `✅ Scrolled container (${el!.tagName}) horizontally by ${dx}px`
|
|
||||||
|
const scrollAfter = el!.scrollLeft
|
||||||
|
const scrolled = scrollAfter - scrollBefore
|
||||||
|
|
||||||
|
if (Math.abs(scrolled) < 1) {
|
||||||
|
return dx > 0
|
||||||
|
? `⚠️ Already at the right edge of container (${el!.tagName}), cannot scroll right further.`
|
||||||
|
: `⚠️ Already at the left edge of container (${el!.tagName}), cannot scroll left further.`
|
||||||
|
}
|
||||||
|
|
||||||
|
const reachedRight = dx > 0 && scrollAfter >= scrollMax - 1
|
||||||
|
const reachedLeft = dx < 0 && scrollAfter <= 1
|
||||||
|
|
||||||
|
if (reachedRight)
|
||||||
|
return `✅ Scrolled container (${el!.tagName}) by ${scrolled}px. Reached the right edge.`
|
||||||
|
if (reachedLeft)
|
||||||
|
return `✅ Scrolled container (${el!.tagName}) by ${scrolled}px. Reached the left edge.`
|
||||||
|
return `✅ Scrolled container (${el!.tagName}) horizontally by ${scrolled}px.`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
* @edit scrollable element detection
|
* @edit scrollable element detection
|
||||||
* @edit add `data-browser-use-ignore` attribute
|
* @edit add `data-browser-use-ignore` attribute
|
||||||
* @edit improve `sampleRect`, filter out rects with 0 area
|
* @edit improve `sampleRect`, filter out rects with 0 area
|
||||||
|
* @edit exclude aria-hidden elements
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export default (
|
export default (
|
||||||
@@ -1446,7 +1447,14 @@ export default (
|
|||||||
* @edit add `data-browser-use-ignore` attribute
|
* @edit add `data-browser-use-ignore` attribute
|
||||||
*/
|
*/
|
||||||
if (node.dataset?.browserUseIgnore === 'true') {
|
if (node.dataset?.browserUseIgnore === 'true') {
|
||||||
return true // Skip this node and its children
|
return null // Skip this node and its children
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @edit exclude aria-hidden elements
|
||||||
|
*/
|
||||||
|
if (node.getAttribute && node.getAttribute('aria-hidden') === 'true') {
|
||||||
|
return null // Skip this node and its children
|
||||||
}
|
}
|
||||||
|
|
||||||
// Special handling for root node (body)
|
// Special handling for root node (body)
|
||||||
|
|||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
|
declare module '*.module.css' {
|
||||||
|
const classes: Record<string, string>
|
||||||
|
export default classes
|
||||||
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"extends": "./tsconfig.json",
|
"extends": "./tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
// @workaround DTS bug
|
// @workaround DTS bug
|
||||||
// dts do not work with monorepo path mapping
|
// dts do not work with monorepo path mapping
|
||||||
// disable path mapping for it
|
// disable path mapping for it
|
||||||
"paths": {}
|
"paths": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"extends": "../../tsconfig.base.json",
|
"extends": "../../tsconfig.base.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo",
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo",
|
||||||
"noEmit": false,
|
"noEmit": false,
|
||||||
"allowImportingTsExtensions": false,
|
"allowImportingTsExtensions": false,
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"outDir": "dist"
|
"outDir": "dist"
|
||||||
},
|
},
|
||||||
"include": ["**/*.ts", "**/*.js"],
|
"include": ["**/*.ts", "**/*.js"],
|
||||||
"exclude": ["dist", "node_modules"]
|
"exclude": ["dist", "node_modules"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,11 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
outDir: resolve(__dirname, 'dist', 'lib'),
|
outDir: resolve(__dirname, 'dist', 'lib'),
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
external: ['@page-agent/*'],
|
external: ['@page-agent/*', 'ai-motion'],
|
||||||
|
onwarn: function (message, handler) {
|
||||||
|
if (message.code === 'EVAL') return
|
||||||
|
handler(message)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
minify: false,
|
minify: false,
|
||||||
sourcemap: true,
|
sourcemap: true,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@page-agent/ui",
|
"name": "@page-agent/ui",
|
||||||
"version": "0.0.21",
|
"version": "0.2.5",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/lib/page-agent-ui.js",
|
"main": "./dist/lib/page-agent-ui.js",
|
||||||
"module": "./dist/lib/page-agent-ui.js",
|
"module": "./dist/lib/page-agent-ui.js",
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
"files": [
|
"files": [
|
||||||
"dist/"
|
"dist/"
|
||||||
],
|
],
|
||||||
"description": "UI components for page-agent - Panel, SimulatorMask, and i18n",
|
"description": "UI components for page-agent - Panel and i18n",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"page-agent",
|
"page-agent",
|
||||||
"ui",
|
"ui",
|
||||||
@@ -34,8 +34,5 @@
|
|||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"prepublishOnly": "node -e \"const fs=require('fs');['LICENSE'].forEach(f=>fs.copyFileSync('../../'+f,f))\"",
|
"prepublishOnly": "node -e \"const fs=require('fs');['LICENSE'].forEach(f=>fs.copyFileSync('../../'+f,f))\"",
|
||||||
"postpublish": "node -e \"['LICENSE'].forEach(f=>{try{require('fs').unlinkSync(f)}catch{}})\""
|
"postpublish": "node -e \"['LICENSE'].forEach(f=>{try{require('fs').unlinkSync(f)}catch{}})\""
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"ai-motion": "^0.4.8"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -209,18 +209,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.pauseButton {
|
|
||||||
font-weight: 600;
|
|
||||||
&.paused {
|
|
||||||
background: rgba(34, 197, 94, 0.2); /* 绿色背景表示可以继续 */
|
|
||||||
color: rgb(34, 197, 94);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: rgba(34, 197, 94, 0.3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.stopButton {
|
.stopButton {
|
||||||
background: rgba(239, 68, 68, 0.2);
|
background: rgba(239, 68, 68, 0.2);
|
||||||
color: rgb(255, 41, 41);
|
color: rgb(255, 41, 41);
|
||||||
@@ -364,6 +352,11 @@
|
|||||||
background: linear-gradient(135deg, rgba(255, 214, 0, 0.1), rgba(255, 214, 0, 0.05));
|
background: linear-gradient(135deg, rgba(255, 214, 0, 0.1), rgba(255, 214, 0, 0.05));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.observation {
|
||||||
|
border-left-color: rgb(147, 51, 234);
|
||||||
|
background: linear-gradient(135deg, rgba(147, 51, 234, 0.1), rgba(147, 51, 234, 0.05));
|
||||||
|
}
|
||||||
|
|
||||||
/* 突出显示 done 成功结果 */
|
/* 突出显示 done 成功结果 */
|
||||||
&.doneSuccess {
|
&.doneSuccess {
|
||||||
background: linear-gradient(
|
background: linear-gradient(
|
||||||
|
|||||||
+25
-61
@@ -1,6 +1,6 @@
|
|||||||
import { type Step, UIState } from './UIState'
|
import { type Step, UIState } from './UIState'
|
||||||
import { I18n, type SupportedLanguage } from './i18n'
|
import { I18n, type SupportedLanguage } from './i18n'
|
||||||
import { truncate } from './utils'
|
import { escapeHtml, truncate } from './utils'
|
||||||
|
|
||||||
import styles from './Panel.module.css'
|
import styles from './Panel.module.css'
|
||||||
|
|
||||||
@@ -11,8 +11,11 @@ export interface PanelConfig {
|
|||||||
language?: SupportedLanguage
|
language?: SupportedLanguage
|
||||||
onExecuteTask: (task: string) => void
|
onExecuteTask: (task: string) => void
|
||||||
onStop: () => void
|
onStop: () => void
|
||||||
onPauseToggle: () => boolean // returns new paused state
|
/**
|
||||||
getPaused: () => boolean
|
* Whether to prompt for next task after task completion
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
promptForNextTask?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -29,6 +32,7 @@ export type PanelUpdate =
|
|||||||
| { type: 'completed' }
|
| { type: 'completed' }
|
||||||
| { type: 'toolExecuting'; toolName: string; args: any }
|
| { type: 'toolExecuting'; toolName: string; args: any }
|
||||||
| { type: 'toolCompleted'; toolName: string; args: any; result?: string; duration?: number }
|
| { type: 'toolCompleted'; toolName: string; args: any; result?: string; duration?: number }
|
||||||
|
| { type: 'observation'; content: string }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Agent control panel
|
* Agent control panel
|
||||||
@@ -39,7 +43,6 @@ export class Panel {
|
|||||||
#statusText: HTMLElement
|
#statusText: HTMLElement
|
||||||
#historySection: HTMLElement
|
#historySection: HTMLElement
|
||||||
#expandButton: HTMLElement
|
#expandButton: HTMLElement
|
||||||
#pauseButton: HTMLElement
|
|
||||||
#stopButton: HTMLElement
|
#stopButton: HTMLElement
|
||||||
#inputSection: HTMLElement
|
#inputSection: HTMLElement
|
||||||
#taskInput: HTMLInputElement
|
#taskInput: HTMLInputElement
|
||||||
@@ -66,7 +69,6 @@ export class Panel {
|
|||||||
this.#statusText = this.#wrapper.querySelector(`.${styles.statusText}`)!
|
this.#statusText = this.#wrapper.querySelector(`.${styles.statusText}`)!
|
||||||
this.#historySection = this.#wrapper.querySelector(`.${styles.historySection}`)!
|
this.#historySection = this.#wrapper.querySelector(`.${styles.historySection}`)!
|
||||||
this.#expandButton = this.#wrapper.querySelector(`.${styles.expandButton}`)!
|
this.#expandButton = this.#wrapper.querySelector(`.${styles.expandButton}`)!
|
||||||
this.#pauseButton = this.#wrapper.querySelector(`.${styles.pauseButton}`)!
|
|
||||||
this.#stopButton = this.#wrapper.querySelector(`.${styles.stopButton}`)!
|
this.#stopButton = this.#wrapper.querySelector(`.${styles.stopButton}`)!
|
||||||
this.#inputSection = this.#wrapper.querySelector(`.${styles.inputSectionWrapper}`)!
|
this.#inputSection = this.#wrapper.querySelector(`.${styles.inputSectionWrapper}`)!
|
||||||
this.#taskInput = this.#wrapper.querySelector(`.${styles.taskInput}`)!
|
this.#taskInput = this.#wrapper.querySelector(`.${styles.taskInput}`)!
|
||||||
@@ -75,6 +77,8 @@ export class Panel {
|
|||||||
this.#startHeaderUpdateLoop()
|
this.#startHeaderUpdateLoop()
|
||||||
|
|
||||||
this.#showInputArea()
|
this.#showInputArea()
|
||||||
|
|
||||||
|
this.hide() // Start hidden
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -120,11 +124,6 @@ export class Panel {
|
|||||||
this.#updateStatusIndicator('thinking')
|
this.#updateStatusIndicator('thinking')
|
||||||
this.#updateHistory()
|
this.#updateHistory()
|
||||||
this.#collapse()
|
this.#collapse()
|
||||||
// Reset pause state via callback
|
|
||||||
if (this.#config.getPaused()) {
|
|
||||||
this.#config.onPauseToggle()
|
|
||||||
}
|
|
||||||
this.#updatePauseButton()
|
|
||||||
// Reset user input state
|
// Reset user input state
|
||||||
this.#isWaitingForUserAnswer = false
|
this.#isWaitingForUserAnswer = false
|
||||||
this.#userAnswerResolver = null
|
this.#userAnswerResolver = null
|
||||||
@@ -205,6 +204,8 @@ export class Panel {
|
|||||||
duration: data.duration,
|
duration: data.duration,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case 'observation':
|
||||||
|
return { type: 'observation', displayText: data.content }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -277,39 +278,6 @@ export class Panel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Toggle pause state
|
|
||||||
*/
|
|
||||||
#togglePause(): void {
|
|
||||||
const paused = this.#config.onPauseToggle()
|
|
||||||
this.#updatePauseButton()
|
|
||||||
|
|
||||||
// Update status display
|
|
||||||
if (paused) {
|
|
||||||
this.#statusText.textContent = this.#i18n.t('ui.panel.paused')
|
|
||||||
this.#updateStatusIndicator('thinking')
|
|
||||||
} else {
|
|
||||||
this.#statusText.textContent = this.#i18n.t('ui.panel.continueExecution')
|
|
||||||
this.#updateStatusIndicator('tool_executing')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update pause button state
|
|
||||||
*/
|
|
||||||
#updatePauseButton(): void {
|
|
||||||
const paused = this.#config.getPaused()
|
|
||||||
if (paused) {
|
|
||||||
this.#pauseButton.textContent = '▶'
|
|
||||||
this.#pauseButton.title = this.#i18n.t('ui.panel.continue')
|
|
||||||
this.#pauseButton.classList.add(styles.paused)
|
|
||||||
} else {
|
|
||||||
this.#pauseButton.textContent = '⏸︎'
|
|
||||||
this.#pauseButton.title = this.#i18n.t('ui.panel.pause')
|
|
||||||
this.#pauseButton.classList.remove(styles.paused)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stop Agent
|
* Stop Agent
|
||||||
*/
|
*/
|
||||||
@@ -395,13 +363,20 @@ export class Panel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const lastStep = steps[steps.length - 1]
|
const lastStep = steps[steps.length - 1]
|
||||||
return lastStep.type === 'completed' || lastStep.type === 'error'
|
const isTaskEnded = lastStep.type === 'completed' || lastStep.type === 'error'
|
||||||
|
|
||||||
|
// Only show input area after task completion if configured to do so
|
||||||
|
if (isTaskEnded) {
|
||||||
|
return this.#config.promptForNextTask ?? true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
#createWrapper(): HTMLElement {
|
#createWrapper(): HTMLElement {
|
||||||
const wrapper = document.createElement('div')
|
const wrapper = document.createElement('div')
|
||||||
wrapper.id = 'page-agent-runtime_agent-panel'
|
wrapper.id = 'page-agent-runtime_agent-panel'
|
||||||
wrapper.className = `${styles.wrapper} ${styles.collapsed}`
|
wrapper.className = styles.wrapper
|
||||||
wrapper.setAttribute('data-browser-use-ignore', 'true')
|
wrapper.setAttribute('data-browser-use-ignore', 'true')
|
||||||
|
|
||||||
wrapper.innerHTML = `
|
wrapper.innerHTML = `
|
||||||
@@ -426,9 +401,6 @@ export class Panel {
|
|||||||
<button class="${styles.controlButton} ${styles.expandButton}" title="${this.#i18n.t('ui.panel.expand')}">
|
<button class="${styles.controlButton} ${styles.expandButton}" title="${this.#i18n.t('ui.panel.expand')}">
|
||||||
▼
|
▼
|
||||||
</button>
|
</button>
|
||||||
<button class="${styles.controlButton} ${styles.pauseButton}" title="${this.#i18n.t('ui.panel.pause')}">
|
|
||||||
⏸︎
|
|
||||||
</button>
|
|
||||||
<button class="${styles.controlButton} ${styles.stopButton}" title="${this.#i18n.t('ui.panel.stop')}">
|
<button class="${styles.controlButton} ${styles.stopButton}" title="${this.#i18n.t('ui.panel.stop')}">
|
||||||
X
|
X
|
||||||
</button>
|
</button>
|
||||||
@@ -466,12 +438,6 @@ export class Panel {
|
|||||||
this.#toggle()
|
this.#toggle()
|
||||||
})
|
})
|
||||||
|
|
||||||
// Pause/continue button
|
|
||||||
this.#pauseButton.addEventListener('click', (e) => {
|
|
||||||
e.stopPropagation()
|
|
||||||
this.#togglePause()
|
|
||||||
})
|
|
||||||
|
|
||||||
// Stop button
|
// Stop button
|
||||||
this.#stopButton.addEventListener('click', (e) => {
|
this.#stopButton.addEventListener('click', (e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
@@ -503,7 +469,6 @@ export class Panel {
|
|||||||
|
|
||||||
#expand(): void {
|
#expand(): void {
|
||||||
this.#isExpanded = true
|
this.#isExpanded = true
|
||||||
this.wrapper.classList.remove(styles.collapsed)
|
|
||||||
this.wrapper.classList.add(styles.expanded)
|
this.wrapper.classList.add(styles.expanded)
|
||||||
this.#expandButton.textContent = '▲'
|
this.#expandButton.textContent = '▲'
|
||||||
}
|
}
|
||||||
@@ -511,7 +476,6 @@ export class Panel {
|
|||||||
#collapse(): void {
|
#collapse(): void {
|
||||||
this.#isExpanded = false
|
this.#isExpanded = false
|
||||||
this.wrapper.classList.remove(styles.expanded)
|
this.wrapper.classList.remove(styles.expanded)
|
||||||
this.wrapper.classList.add(styles.collapsed)
|
|
||||||
this.#expandButton.textContent = '▼'
|
this.#expandButton.textContent = '▼'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -590,10 +554,7 @@ export class Panel {
|
|||||||
|
|
||||||
#updateHistory(): void {
|
#updateHistory(): void {
|
||||||
const steps = this.#state.getAllSteps()
|
const steps = this.#state.getAllSteps()
|
||||||
|
|
||||||
this.#historySection.innerHTML = steps.map((step) => this.#createHistoryItem(step)).join('')
|
this.#historySection.innerHTML = steps.map((step) => this.#createHistoryItem(step)).join('')
|
||||||
|
|
||||||
// Scroll to bottom to show latest records
|
|
||||||
this.#scrollToBottom()
|
this.#scrollToBottom()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -635,7 +596,7 @@ export class Panel {
|
|||||||
typeClass = styles.error
|
typeClass = styles.error
|
||||||
statusIcon = '❌'
|
statusIcon = '❌'
|
||||||
} else if (step.type === 'tool_executing') {
|
} else if (step.type === 'tool_executing') {
|
||||||
statusIcon = '⚙️'
|
statusIcon = '🔨'
|
||||||
} else if (step.type === 'output') {
|
} else if (step.type === 'output') {
|
||||||
typeClass = styles.output
|
typeClass = styles.output
|
||||||
statusIcon = '🤖'
|
statusIcon = '🤖'
|
||||||
@@ -645,6 +606,9 @@ export class Panel {
|
|||||||
} else if (step.type === 'retry') {
|
} else if (step.type === 'retry') {
|
||||||
typeClass = styles.retry
|
typeClass = styles.retry
|
||||||
statusIcon = '🔄'
|
statusIcon = '🔄'
|
||||||
|
} else if (step.type === 'observation') {
|
||||||
|
typeClass = styles.observation
|
||||||
|
statusIcon = '👁️'
|
||||||
} else {
|
} else {
|
||||||
statusIcon = '🧠'
|
statusIcon = '🧠'
|
||||||
}
|
}
|
||||||
@@ -660,7 +624,7 @@ export class Panel {
|
|||||||
<div class="${styles.historyItem} ${typeClass}">
|
<div class="${styles.historyItem} ${typeClass}">
|
||||||
<div class="${styles.historyContent}">
|
<div class="${styles.historyContent}">
|
||||||
<span class="${styles.statusIcon}">${statusIcon}</span>
|
<span class="${styles.statusIcon}">${statusIcon}</span>
|
||||||
<span>${step.displayText}</span>
|
<span>${escapeHtml(step.displayText)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="${styles.historyMeta}">
|
<div class="${styles.historyMeta}">
|
||||||
${stepLabel}
|
${stepLabel}
|
||||||
|
|||||||
@@ -6,7 +6,15 @@ export interface Step {
|
|||||||
id: string
|
id: string
|
||||||
stepNumber: number
|
stepNumber: number
|
||||||
timestamp: Date
|
timestamp: Date
|
||||||
type: 'thinking' | 'tool_executing' | 'completed' | 'error' | 'output' | 'input' | 'retry'
|
type:
|
||||||
|
| 'thinking'
|
||||||
|
| 'tool_executing'
|
||||||
|
| 'completed'
|
||||||
|
| 'error'
|
||||||
|
| 'output'
|
||||||
|
| 'input'
|
||||||
|
| 'retry'
|
||||||
|
| 'observation'
|
||||||
|
|
||||||
// Tool execution related
|
// Tool execution related
|
||||||
toolName?: string
|
toolName?: string
|
||||||
@@ -18,7 +26,7 @@ export interface Step {
|
|||||||
duration?: number
|
duration?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AgentStatus = 'idle' | 'running' | 'paused' | 'completed' | 'error'
|
export type AgentStatus = 'idle' | 'running' | 'completed' | 'error'
|
||||||
|
|
||||||
export class UIState {
|
export class UIState {
|
||||||
private steps: Step[] = []
|
private steps: Step[] = []
|
||||||
|
|||||||
@@ -4,17 +4,13 @@ const enUS = {
|
|||||||
panel: {
|
panel: {
|
||||||
ready: 'Ready',
|
ready: 'Ready',
|
||||||
thinking: 'Thinking...',
|
thinking: 'Thinking...',
|
||||||
paused: 'Paused',
|
|
||||||
taskInput: 'Enter new task, describe steps in detail, press Enter to submit',
|
taskInput: 'Enter new task, describe steps in detail, press Enter to submit',
|
||||||
userAnswerPrompt: 'Please answer the question above, press Enter to submit',
|
userAnswerPrompt: 'Please answer the question above, press Enter to submit',
|
||||||
taskTerminated: 'Task terminated',
|
taskTerminated: 'Task terminated',
|
||||||
taskCompleted: 'Task completed',
|
taskCompleted: 'Task completed',
|
||||||
continueExecution: 'Continue execution',
|
|
||||||
userAnswer: 'User answer: {{input}}',
|
userAnswer: 'User answer: {{input}}',
|
||||||
question: 'Question: {{question}}',
|
question: 'Question: {{question}}',
|
||||||
waitingPlaceholder: 'Waiting for task to start...',
|
waitingPlaceholder: 'Waiting for task to start...',
|
||||||
pause: 'Pause',
|
|
||||||
continue: 'Continue',
|
|
||||||
stop: 'Stop',
|
stop: 'Stop',
|
||||||
expand: 'Expand history',
|
expand: 'Expand history',
|
||||||
collapse: 'Collapse history',
|
collapse: 'Collapse history',
|
||||||
@@ -54,17 +50,13 @@ const zhCN = {
|
|||||||
panel: {
|
panel: {
|
||||||
ready: '准备就绪',
|
ready: '准备就绪',
|
||||||
thinking: '正在思考...',
|
thinking: '正在思考...',
|
||||||
paused: '暂停中,稍后',
|
|
||||||
taskInput: '输入新任务,详细描述步骤,回车提交',
|
taskInput: '输入新任务,详细描述步骤,回车提交',
|
||||||
userAnswerPrompt: '请回答上面问题,回车提交',
|
userAnswerPrompt: '请回答上面问题,回车提交',
|
||||||
taskTerminated: '任务已终止',
|
taskTerminated: '任务已终止',
|
||||||
taskCompleted: '任务结束',
|
taskCompleted: '任务结束',
|
||||||
continueExecution: '继续执行',
|
|
||||||
userAnswer: '用户回答: {{input}}',
|
userAnswer: '用户回答: {{input}}',
|
||||||
question: '询问: {{question}}',
|
question: '询问: {{question}}',
|
||||||
waitingPlaceholder: '等待任务开始...',
|
waitingPlaceholder: '等待任务开始...',
|
||||||
pause: '暂停',
|
|
||||||
continue: '继续',
|
|
||||||
stop: '终止',
|
stop: '终止',
|
||||||
expand: '展开历史',
|
expand: '展开历史',
|
||||||
collapse: '收起历史',
|
collapse: '收起历史',
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
export { Panel, type PanelConfig, type PanelUpdate } from './Panel'
|
export { Panel, type PanelConfig, type PanelUpdate } from './Panel'
|
||||||
export { SimulatorMask } from './SimulatorMask'
|
|
||||||
export { UIState, type Step, type AgentStatus } from './UIState'
|
export { UIState, type Step, type AgentStatus } from './UIState'
|
||||||
export { I18n, type SupportedLanguage, type TranslationKey } from './i18n'
|
export { I18n, type SupportedLanguage, type TranslationKey } from './i18n'
|
||||||
|
|||||||
@@ -4,3 +4,15 @@ export function truncate(text: string, maxLength: number): string {
|
|||||||
}
|
}
|
||||||
return text
|
return text
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escape HTML special characters to prevent XSS and rendering issues
|
||||||
|
*/
|
||||||
|
export function escapeHtml(text: string): string {
|
||||||
|
return text
|
||||||
|
.replace(/&/g, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/'/g, ''')
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
{
|
{
|
||||||
"extends": "./tsconfig.json",
|
"extends": "./tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
// @workaround DTS bug
|
// @workaround DTS bug
|
||||||
// dts do not work with monorepo path mapping
|
// dts do not work with monorepo path mapping
|
||||||
// disable path mapping for it
|
// disable path mapping for it
|
||||||
"paths": {}
|
"paths": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+10
-11
@@ -1,13 +1,12 @@
|
|||||||
{
|
{
|
||||||
"extends": "../../tsconfig.base.json",
|
"extends": "../../tsconfig.base.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo",
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo",
|
||||||
"noEmit": false,
|
"noEmit": false,
|
||||||
"allowImportingTsExtensions": false,
|
"allowImportingTsExtensions": false,
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"outDir": "dist"
|
"outDir": "dist"
|
||||||
},
|
},
|
||||||
"include": ["**/*.ts", "**/*.js"],
|
"include": ["**/*.ts", "**/*.js"],
|
||||||
"exclude": ["dist", "node_modules"]
|
"exclude": ["dist", "node_modules"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
outDir: resolve(__dirname, 'dist', 'lib'),
|
outDir: resolve(__dirname, 'dist', 'lib'),
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
external: ['ai-motion'],
|
external: [],
|
||||||
},
|
},
|
||||||
minify: false,
|
minify: false,
|
||||||
sourcemap: true,
|
sourcemap: true,
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
# Landing Page & Docs
|
|
||||||
@@ -1,24 +1,24 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://ui.shadcn.com/schema.json",
|
"$schema": "https://ui.shadcn.com/schema.json",
|
||||||
"style": "new-york",
|
"style": "new-york",
|
||||||
"rsc": false,
|
"rsc": false,
|
||||||
"tsx": true,
|
"tsx": true,
|
||||||
"tailwind": {
|
"tailwind": {
|
||||||
"config": "",
|
"config": "",
|
||||||
"css": "src/index.css",
|
"css": "src/index.css",
|
||||||
"baseColor": "neutral",
|
"baseColor": "neutral",
|
||||||
"cssVariables": true,
|
"cssVariables": true,
|
||||||
"prefix": ""
|
"prefix": ""
|
||||||
},
|
},
|
||||||
"iconLibrary": "lucide",
|
"iconLibrary": "lucide",
|
||||||
"aliases": {
|
"aliases": {
|
||||||
"components": "@/components",
|
"components": "@/components",
|
||||||
"utils": "@/lib/utils",
|
"utils": "@/lib/utils",
|
||||||
"ui": "@/components/ui",
|
"ui": "@/components/ui",
|
||||||
"lib": "@/lib",
|
"lib": "@/lib",
|
||||||
"hooks": "@/hooks"
|
"hooks": "@/hooks"
|
||||||
},
|
},
|
||||||
"registries": {
|
"registries": {
|
||||||
"@magicui": "https://magicui.design/r/{name}.json"
|
"@magicui": "https://magicui.design/r/{name}.json"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"name": "@page-agent/website",
|
"name": "@page-agent/website",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.21",
|
"version": "0.2.5",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite --host 0.0.0.0",
|
||||||
"build:website": "vite build",
|
"build:website": "vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit"
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export default function Header() {
|
|||||||
<div className="flex items-center justify-between gap-2">
|
<div className="flex items-center justify-between gap-2">
|
||||||
{/* Logo */}
|
{/* Logo */}
|
||||||
<Link
|
<Link
|
||||||
href="/"
|
href="~/"
|
||||||
className="flex items-center gap-2 sm:gap-3 group shrink-0"
|
className="flex items-center gap-2 sm:gap-3 group shrink-0"
|
||||||
aria-label={t('header.logo_alt')}
|
aria-label={t('header.logo_alt')}
|
||||||
onClick={() => setMobileMenuOpen(false)}
|
onClick={() => setMobileMenuOpen(false)}
|
||||||
@@ -41,6 +41,7 @@ export default function Header() {
|
|||||||
className="hidden sm:block text-xs text-gray-600 dark:text-gray-300 py-0 font-normal overflow-visible"
|
className="hidden sm:block text-xs text-gray-600 dark:text-gray-300 py-0 font-normal overflow-visible"
|
||||||
duration={600}
|
duration={600}
|
||||||
animateOnHover={true}
|
animateOnHover={true}
|
||||||
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
{t('header.slogan')}
|
{t('header.slogan')}
|
||||||
</HyperText>
|
</HyperText>
|
||||||
@@ -54,7 +55,7 @@ export default function Header() {
|
|||||||
aria-label="Mobile navigation"
|
aria-label="Mobile navigation"
|
||||||
>
|
>
|
||||||
<Link
|
<Link
|
||||||
href="/docs/introduction/overview"
|
href="~/docs/introduction/overview"
|
||||||
className="p-2 rounded-lg text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-blue-600 dark:hover:text-blue-400 transition-colors duration-200 shrink-0"
|
className="p-2 rounded-lg text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-blue-600 dark:hover:text-blue-400 transition-colors duration-200 shrink-0"
|
||||||
aria-label={t('header.nav_docs')}
|
aria-label={t('header.nav_docs')}
|
||||||
>
|
>
|
||||||
@@ -84,8 +85,11 @@ export default function Header() {
|
|||||||
role="navigation"
|
role="navigation"
|
||||||
aria-label={t('header.nav_docs')}
|
aria-label={t('header.nav_docs')}
|
||||||
>
|
>
|
||||||
|
<span className="text-xs font-mono text-gray-400 dark:text-gray-500 tabular-nums before:content-['v']">
|
||||||
|
{import.meta.env.VERSION}
|
||||||
|
</span>
|
||||||
<Link
|
<Link
|
||||||
href="/docs/introduction/overview"
|
href="~/docs/introduction/overview"
|
||||||
className="flex items-center gap-1.5 text-gray-600 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 transition-colors duration-200"
|
className="flex items-center gap-1.5 text-gray-600 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 transition-colors duration-200"
|
||||||
>
|
>
|
||||||
<BookOpen className="w-4 h-4" />
|
<BookOpen className="w-4 h-4" />
|
||||||
@@ -133,7 +137,7 @@ export default function Header() {
|
|||||||
role="navigation"
|
role="navigation"
|
||||||
>
|
>
|
||||||
<Link
|
<Link
|
||||||
href="/docs/introduction/overview"
|
href="~/docs/introduction/overview"
|
||||||
className="flex items-center gap-2 px-3 py-2 rounded-lg text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-blue-600 dark:hover:text-blue-400 transition-colors duration-200"
|
className="flex items-center gap-2 px-3 py-2 rounded-lg text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-blue-600 dark:hover:text-blue-400 transition-colors duration-200"
|
||||||
onClick={() => setMobileMenuOpen(false)}
|
onClick={() => setMobileMenuOpen(false)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,3 +1,15 @@
|
|||||||
export const CDN_URL = 'https://cdn.jsdelivr.net/npm/page-agent@latest/dist/umd/page-agent.js'
|
// Demo build (auto-init with demo LLM, for quick testing)
|
||||||
export const CDN_CN_URL =
|
export const CDN_DEMO_URL = 'https://cdn.jsdelivr.net/npm/@page-agent/cdn/dist/page-agent.demo.js'
|
||||||
'https://registry.npmmirror.com/page-agent/latest/files/dist/umd/page-agent.js'
|
export const CDN_DEMO_CN_URL =
|
||||||
|
'https://registry.npmmirror.com/@page-agent/cdn/latest/files/dist/page-agent.demo.js'
|
||||||
|
|
||||||
|
// Full build (exports PageAgent class, usage identical to npm)
|
||||||
|
export const CDN_FULL_URL = 'https://cdn.jsdelivr.net/npm/@page-agent/cdn/dist/page-agent.js'
|
||||||
|
export const CDN_FULL_CN_URL =
|
||||||
|
'https://registry.npmmirror.com/@page-agent/cdn/latest/files/dist/page-agent.js'
|
||||||
|
|
||||||
|
// Demo LLM for website testing
|
||||||
|
export const DEMO_MODEL = 'PAGE-AGENT-FREE-TESTING-RANDOM'
|
||||||
|
export const DEMO_BASE_URL =
|
||||||
|
'https://hwcxiuzfylggtcktqgij.supabase.co/functions/v1/llm-testing-proxy'
|
||||||
|
export const DEMO_API_KEY = 'PAGE-AGENT-FREE-TESTING-RANDOM'
|
||||||
|
|||||||
Vendored
+4
@@ -1,5 +1,9 @@
|
|||||||
/// <reference types="vite/client" />
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
|
interface ImportMetaEnv {
|
||||||
|
readonly VERSION: string
|
||||||
|
}
|
||||||
|
|
||||||
declare module '*.module.css' {
|
declare module '*.module.css' {
|
||||||
const classes: Record<string, string>
|
const classes: Record<string, string>
|
||||||
export default classes
|
export default classes
|
||||||
|
|||||||
@@ -28,9 +28,9 @@ pages/i18n/
|
|||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
function MyComponent() {
|
function MyComponent() {
|
||||||
const { t } = useTranslation('common') // 指定命名空间
|
const { t } = useTranslation('common') // 指定命名空间
|
||||||
|
|
||||||
return <h1>{t('header.nav_docs')}</h1>
|
return <h1>{t('header.nav_docs')}</h1>
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -71,4 +71,3 @@ t('common:header.nav_docs')
|
|||||||
- [ ] 文档页翻译(`docs.json`)
|
- [ ] 文档页翻译(`docs.json`)
|
||||||
- [ ] DocsLayout 导航结构国际化
|
- [ ] DocsLayout 导航结构国际化
|
||||||
- [ ] 404 页面国际化
|
- [ ] 404 页面国际化
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
/* eslint-disable react-dom/no-dangerously-set-innerhtml */
|
/* eslint-disable react-dom/no-dangerously-set-innerhtml */
|
||||||
import { Bot, Box, MessageSquare, PlayCircle, Shield, Sparkles, Users, Zap } from 'lucide-react'
|
import { Bot, Box, MessageSquare, PlayCircle, Shield, Sparkles, Users, Zap } from 'lucide-react'
|
||||||
import { PageAgent } from 'page-agent'
|
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Link, useSearchParams } from 'wouter'
|
import { Link, useSearchParams } from 'wouter'
|
||||||
@@ -12,10 +11,16 @@ import { Highlighter } from '../components/ui/highlighter'
|
|||||||
import { NeonGradientCard } from '../components/ui/neon-gradient-card'
|
import { NeonGradientCard } from '../components/ui/neon-gradient-card'
|
||||||
import { Particles } from '../components/ui/particles'
|
import { Particles } from '../components/ui/particles'
|
||||||
import { SparklesText } from '../components/ui/sparkles-text'
|
import { SparklesText } from '../components/ui/sparkles-text'
|
||||||
import { CDN_CN_URL, CDN_URL } from '../constants'
|
import {
|
||||||
|
CDN_DEMO_CN_URL,
|
||||||
|
CDN_DEMO_URL,
|
||||||
|
DEMO_API_KEY,
|
||||||
|
DEMO_BASE_URL,
|
||||||
|
DEMO_MODEL,
|
||||||
|
} from '../constants'
|
||||||
|
|
||||||
function getInjection(useCN?: boolean) {
|
function getInjection(useCN?: boolean) {
|
||||||
const cdn = useCN ? CDN_CN_URL : CDN_URL
|
const cdn = useCN ? CDN_DEMO_CN_URL : CDN_DEMO_URL
|
||||||
|
|
||||||
const injection = encodeURI(
|
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);})();`
|
`javascript:(function(){var s=document.createElement('script');s.src=\`${cdn}?t=\${Math.random()}\`;s.setAttribute('crossorigin', true);s.type="text/javascript";s.onload=()=>console.log('PageAgent script loaded!');document.body.appendChild(s);})();`
|
||||||
@@ -53,13 +58,12 @@ export default function HomePage() {
|
|||||||
const handleExecute = async () => {
|
const handleExecute = async () => {
|
||||||
if (!task.trim()) return
|
if (!task.trim()) return
|
||||||
|
|
||||||
let pageAgent: PageAgent
|
|
||||||
const win = window as any
|
const win = window as any
|
||||||
|
|
||||||
if (win.pageAgent && !win.pageAgent.disposed) {
|
// Lazy load PageAgent only when user clicks execute
|
||||||
pageAgent = win.pageAgent
|
if (!win.pageAgent || win.pageAgent.disposed) {
|
||||||
} else {
|
const { PageAgent } = await import('page-agent')
|
||||||
pageAgent = new PageAgent({
|
win.pageAgent = new PageAgent({
|
||||||
// 把 react 根元素排除掉,挂了很多冒泡时间导致假阳
|
// 把 react 根元素排除掉,挂了很多冒泡时间导致假阳
|
||||||
interactiveBlacklist: [document.getElementById('root')!],
|
interactiveBlacklist: [document.getElementById('root')!],
|
||||||
language: i18n.language as any,
|
language: i18n.language as any,
|
||||||
@@ -73,19 +77,26 @@ export default function HomePage() {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// experimentalScriptExecutionTool: true,
|
model:
|
||||||
|
import.meta.env.DEV && import.meta.env.LLM_MODEL_NAME
|
||||||
|
? import.meta.env.LLM_MODEL_NAME
|
||||||
|
: DEMO_MODEL,
|
||||||
|
baseURL:
|
||||||
|
import.meta.env.DEV && import.meta.env.LLM_BASE_URL
|
||||||
|
? import.meta.env.LLM_BASE_URL
|
||||||
|
: DEMO_BASE_URL,
|
||||||
|
apiKey:
|
||||||
|
import.meta.env.DEV && import.meta.env.LLM_API_KEY
|
||||||
|
? import.meta.env.LLM_API_KEY
|
||||||
|
: DEMO_API_KEY,
|
||||||
|
|
||||||
// testing server
|
// enableAskUser: false,
|
||||||
// @note: rate limit. prompt limit.
|
// promptForNextTask: false,
|
||||||
// model: DEMO_MODEL,
|
// enablePanel: false,
|
||||||
// baseURL: DEMO_BASE_URL,
|
|
||||||
// apiKey: DEMO_API_KEY,
|
|
||||||
})
|
})
|
||||||
win.pageAgent = pageAgent
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await pageAgent.execute(task)
|
const result = await win.pageAgent.execute(task)
|
||||||
|
|
||||||
console.log(result)
|
console.log(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,7 +106,10 @@ export default function HomePage() {
|
|||||||
|
|
||||||
{/* Hero Section */}
|
{/* Hero Section */}
|
||||||
<main id="main-content">
|
<main id="main-content">
|
||||||
<section className="relative px-6 py-22 lg:py-28" aria-labelledby="hero-heading">
|
<section
|
||||||
|
className="relative px-6 py-22 pb-18 lg:py-28 overflow-hidden"
|
||||||
|
aria-labelledby="hero-heading"
|
||||||
|
>
|
||||||
<div className="max-w-7xl mx-auto text-center">
|
<div className="max-w-7xl mx-auto text-center">
|
||||||
{/* Background Pattern + Particles */}
|
{/* Background Pattern + Particles */}
|
||||||
<div className="absolute inset-0 opacity-30" aria-hidden="true">
|
<div className="absolute inset-0 opacity-30" aria-hidden="true">
|
||||||
|
|||||||
@@ -24,38 +24,38 @@ export default function DocsLayout({ children }: DocsLayoutProps) {
|
|||||||
{
|
{
|
||||||
title: t('nav.introduction'),
|
title: t('nav.introduction'),
|
||||||
items: [
|
items: [
|
||||||
{ title: t('nav.overview'), path: '/docs/introduction/overview' },
|
{ title: t('nav.overview'), path: '/introduction/overview' },
|
||||||
{ title: t('nav.quick_start'), path: '/docs/introduction/quick-start' },
|
{ title: t('nav.quick_start'), path: '/introduction/quick-start' },
|
||||||
{ title: t('nav.limitations'), path: '/docs/introduction/limitations' },
|
{ title: t('nav.limitations'), path: '/introduction/limitations' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('nav.features'),
|
title: t('nav.features'),
|
||||||
items: [
|
items: [
|
||||||
{ title: t('nav.models'), path: '/docs/features/models' },
|
{ title: t('nav.models'), path: '/features/models' },
|
||||||
{ title: t('nav.custom_tools'), path: '/docs/features/custom-tools' },
|
{ title: t('nav.custom_tools'), path: '/features/custom-tools' },
|
||||||
{ title: t('nav.knowledge_injection'), path: '/docs/features/custom-instructions' },
|
{ title: t('nav.knowledge_injection'), path: '/features/custom-instructions' },
|
||||||
{ title: t('nav.data_masking'), path: '/docs/features/data-masking' },
|
{ title: t('nav.data_masking'), path: '/features/data-masking' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('nav.integration'),
|
title: t('nav.integration'),
|
||||||
items: [
|
items: [
|
||||||
{ title: t('nav.configuration'), path: '/docs/integration/configuration' },
|
{ title: t('nav.configuration'), path: '/integration/configuration' },
|
||||||
{ title: t('nav.third_party_agent'), path: '/docs/integration/third-party-agent' },
|
{ title: t('nav.third_party_agent'), path: '/integration/third-party-agent' },
|
||||||
{ title: '🚧 ' + t('nav.cdn_setup'), path: '/docs/integration/cdn-setup' },
|
{ title: t('nav.cdn_setup'), path: '/integration/cdn-setup' },
|
||||||
{
|
{
|
||||||
title: '🚧 ' + t('nav.security_permissions'),
|
title: '🚧 ' + t('nav.security_permissions'),
|
||||||
path: '/docs/integration/security-permissions',
|
path: '/integration/security-permissions',
|
||||||
},
|
},
|
||||||
{ title: '🚧 ' + t('nav.best_practices'), path: '/docs/integration/best-practices' },
|
{ title: '🚧 ' + t('nav.best_practices'), path: '/integration/best-practices' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-7xl mx-auto px-6 py-8 overflow-x-auto">
|
<div className="max-w-7xl mx-auto px-6 py-8 overflow-x-auto">
|
||||||
<div className="flex gap-8 min-w-[900px]">
|
<div className="flex gap-8 min-w-225">
|
||||||
{/* Sidebar */}
|
{/* Sidebar */}
|
||||||
<aside className="w-64 shrink-0" role="complementary" aria-label="文档导航">
|
<aside className="w-64 shrink-0" role="complementary" aria-label="文档导航">
|
||||||
<div className="sticky">
|
<div className="sticky">
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
import { Suspense } from 'react'
|
||||||
|
import { Route, Switch } from 'wouter'
|
||||||
|
|
||||||
|
import Header from '../../components/Header'
|
||||||
|
import DocsLayout from './Layout'
|
||||||
|
import Instructions from './features/custom-instructions/page'
|
||||||
|
// Features
|
||||||
|
import CustomTools from './features/custom-tools/page'
|
||||||
|
import DataMasking from './features/data-masking/page'
|
||||||
|
import Models from './features/models/page'
|
||||||
|
import BestPractices from './integration/best-practices/page'
|
||||||
|
// Integration
|
||||||
|
import CdnSetup from './integration/cdn-setup/page'
|
||||||
|
import Configuration from './integration/configuration/page'
|
||||||
|
import SecurityPermissions from './integration/security-permissions/page'
|
||||||
|
import ThirdPartyAgent from './integration/third-party-agent/page'
|
||||||
|
import Limitations from './introduction/limitations/page'
|
||||||
|
// Introduction
|
||||||
|
import Overview from './introduction/overview/page'
|
||||||
|
import QuickStart from './introduction/quick-start/page'
|
||||||
|
|
||||||
|
function DocsPage({ children }: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-white dark:bg-gray-900">
|
||||||
|
<Header />
|
||||||
|
<DocsLayout>
|
||||||
|
<Suspense>{children}</Suspense>
|
||||||
|
</DocsLayout>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DocsRouter() {
|
||||||
|
return (
|
||||||
|
<Switch>
|
||||||
|
{/* Introduction */}
|
||||||
|
<Route path="/introduction/overview">
|
||||||
|
<DocsPage>
|
||||||
|
<Overview />
|
||||||
|
</DocsPage>
|
||||||
|
</Route>
|
||||||
|
<Route path="/introduction/quick-start">
|
||||||
|
<DocsPage>
|
||||||
|
<QuickStart />
|
||||||
|
</DocsPage>
|
||||||
|
</Route>
|
||||||
|
<Route path="/introduction/limitations">
|
||||||
|
<DocsPage>
|
||||||
|
<Limitations />
|
||||||
|
</DocsPage>
|
||||||
|
</Route>
|
||||||
|
|
||||||
|
{/* Features */}
|
||||||
|
<Route path="/features/custom-tools">
|
||||||
|
<DocsPage>
|
||||||
|
<CustomTools />
|
||||||
|
</DocsPage>
|
||||||
|
</Route>
|
||||||
|
<Route path="/features/data-masking">
|
||||||
|
<DocsPage>
|
||||||
|
<DataMasking />
|
||||||
|
</DocsPage>
|
||||||
|
</Route>
|
||||||
|
<Route path="/features/custom-instructions">
|
||||||
|
<DocsPage>
|
||||||
|
<Instructions />
|
||||||
|
</DocsPage>
|
||||||
|
</Route>
|
||||||
|
<Route path="/features/models">
|
||||||
|
<DocsPage>
|
||||||
|
<Models />
|
||||||
|
</DocsPage>
|
||||||
|
</Route>
|
||||||
|
|
||||||
|
{/* Integration */}
|
||||||
|
<Route path="/integration/cdn-setup">
|
||||||
|
<DocsPage>
|
||||||
|
<CdnSetup />
|
||||||
|
</DocsPage>
|
||||||
|
</Route>
|
||||||
|
<Route path="/integration/security-permissions">
|
||||||
|
<DocsPage>
|
||||||
|
<SecurityPermissions />
|
||||||
|
</DocsPage>
|
||||||
|
</Route>
|
||||||
|
<Route path="/integration/configuration">
|
||||||
|
<DocsPage>
|
||||||
|
<Configuration />
|
||||||
|
</DocsPage>
|
||||||
|
</Route>
|
||||||
|
<Route path="/integration/best-practices">
|
||||||
|
<DocsPage>
|
||||||
|
<BestPractices />
|
||||||
|
</DocsPage>
|
||||||
|
</Route>
|
||||||
|
<Route path="/integration/third-party-agent">
|
||||||
|
<DocsPage>
|
||||||
|
<ThirdPartyAgent />
|
||||||
|
</DocsPage>
|
||||||
|
</Route>
|
||||||
|
|
||||||
|
{/* Default redirect or 404 */}
|
||||||
|
<Route path="/docs">
|
||||||
|
<DocsPage>
|
||||||
|
<Overview />
|
||||||
|
</DocsPage>
|
||||||
|
</Route>
|
||||||
|
</Switch>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,81 +1,124 @@
|
|||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
import BetaNotice from '@/components/BetaNotice'
|
|
||||||
import CodeEditor from '@/components/CodeEditor'
|
import CodeEditor from '@/components/CodeEditor'
|
||||||
import { CDN_CN_URL, CDN_URL } from '@/constants'
|
import { CDN_DEMO_CN_URL, CDN_DEMO_URL, CDN_FULL_CN_URL, CDN_FULL_URL } from '@/constants'
|
||||||
|
|
||||||
export default function CdnSetup() {
|
export default function CdnSetup() {
|
||||||
const { i18n } = useTranslation()
|
const { i18n } = useTranslation()
|
||||||
const isZh = i18n.language === 'zh-CN'
|
const isZh = i18n.language === 'zh-CN'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="space-y-10">
|
||||||
<BetaNotice />
|
<header>
|
||||||
|
<h1 className="text-4xl font-bold mb-4">{isZh ? 'CDN 引入' : 'CDN Setup'}</h1>
|
||||||
|
<p className="text-lg text-gray-600 dark:text-gray-300 leading-relaxed">
|
||||||
|
{isZh
|
||||||
|
? 'CDN 提供两种构建版本:Demo 版用于快速体验,标准版用法与 NPM 一致。'
|
||||||
|
: 'CDN provides two builds: Demo for quick testing, standard build with usage identical to NPM.'}
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
<h1 className="text-4xl font-bold mb-6">{isZh ? 'CDN 引入' : 'CDN Setup'}</h1>
|
{/* Demo Build Section */}
|
||||||
|
<section>
|
||||||
|
<h2 className="text-2xl font-bold mb-3">{isZh ? '🚀 快速体验' : '🚀 Quick Try'}</h2>
|
||||||
|
|
||||||
<p className="text-xl text-gray-600 dark:text-gray-300 mb-8 leading-relaxed">
|
<p className="text-gray-600 dark:text-gray-300 mb-3">
|
||||||
{isZh
|
{isZh
|
||||||
? '通过 CDN 快速集成 page-agent,无需复杂的构建配置。默认使用演示模型。'
|
? '自动初始化并使用内置 Demo LLM,无需任何配置:'
|
||||||
: 'Fastest way to integrate page-agent via CDN. No complex build setup needed. Demo model will be used by default.'}
|
: 'Auto-initializes with built-in demo LLM, no configuration needed:'}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<section className="mb-8">
|
|
||||||
<h2 className="text-2xl font-bold mb-4">{isZh ? 'CDN 地址' : 'CDN URLs'}</h2>
|
|
||||||
|
|
||||||
<div className="overflow-x-auto mb-6">
|
|
||||||
<table className="w-full border-collapse text-sm">
|
|
||||||
<thead>
|
|
||||||
<tr className="border-b border-gray-200 dark:border-gray-700">
|
|
||||||
<th className="text-left py-3 px-4 font-semibold">{isZh ? '位置' : 'Location'}</th>
|
|
||||||
<th className="text-left py-3 px-4 font-semibold">URL</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr className="border-b border-gray-100 dark:border-gray-800">
|
|
||||||
<td className="py-3 px-4">{isZh ? '全球' : 'Global'}</td>
|
|
||||||
<td className="py-3 px-4 font-mono text-xs break-all">{CDN_URL}</td>
|
|
||||||
</tr>
|
|
||||||
<tr className="border-b border-gray-100 dark:border-gray-800">
|
|
||||||
<td className="py-3 px-4">{isZh ? '中国镜像' : 'China Mirror'}</td>
|
|
||||||
<td className="py-3 px-4 font-mono text-xs break-all">{CDN_CN_URL}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section className="mb-8">
|
|
||||||
<h2 className="text-2xl font-bold mb-4">{isZh ? '快速开始' : 'Quick Start'}</h2>
|
|
||||||
|
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
className="mb-6"
|
className="mb-3"
|
||||||
code={`<script
|
code={`<script src="${CDN_DEMO_URL}" crossorigin="true"></script>`}
|
||||||
src="${CDN_URL}"
|
|
||||||
crossorigin="true"
|
|
||||||
type="text/javascript"
|
|
||||||
></script>`}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<div className="bg-yellow-50 dark:bg-yellow-900/20 px-4 py-3 rounded-lg mb-4 text-sm">
|
||||||
|
<span className="text-yellow-800 dark:text-yellow-200">
|
||||||
|
⚠️{' '}
|
||||||
|
{isZh
|
||||||
|
? '仅用于技术评估,Demo 模型有速率限制。'
|
||||||
|
: 'For evaluation only. Demo model has rate limits.'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table className="w-full border-collapse text-sm">
|
||||||
|
<thead>
|
||||||
|
<tr className="border-b border-gray-200 dark:border-gray-700">
|
||||||
|
<th className="text-left py-2 px-3 font-semibold w-28">
|
||||||
|
{isZh ? '位置' : 'Location'}
|
||||||
|
</th>
|
||||||
|
<th className="text-left py-2 px-3 font-semibold">URL</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr className="border-b border-gray-100 dark:border-gray-800">
|
||||||
|
<td className="py-2 px-3">{isZh ? '全球' : 'Global'}</td>
|
||||||
|
<td className="py-2 px-3 font-mono text-xs break-all">{CDN_DEMO_URL}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className="py-2 px-3">{isZh ? '中国' : 'China'}</td>
|
||||||
|
<td className="py-2 px-3 font-mono text-xs break-all">{CDN_DEMO_CN_URL}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="mb-8">
|
{/* CDN Build Section */}
|
||||||
<div className="bg-yellow-50 dark:bg-yellow-900/20 p-4 rounded-lg">
|
<section>
|
||||||
<h3 className="text-lg font-semibold mb-2 text-yellow-900 dark:text-yellow-300">
|
<h2 className="text-2xl font-bold mb-3">{isZh ? '📦 标准版' : '📦 Standard Build'}</h2>
|
||||||
⚠️ {isZh ? '注意事项' : 'Notes'}
|
|
||||||
</h3>
|
<p className="text-gray-600 dark:text-gray-300 mb-3">
|
||||||
<ul className="text-gray-600 dark:text-gray-300 space-y-1">
|
{isZh
|
||||||
<li>
|
? '将 PageAgent 类暴露到 globalThis,用法与 NPM 一致:'
|
||||||
• {isZh ? '生产环境建议使用固定版本号' : 'Use fixed version number in production'}
|
: 'Exposes PageAgent class to globalThis, usage identical to NPM:'}
|
||||||
</li>
|
</p>
|
||||||
<li>• {isZh ? '确保 HTTPS 环境下使用' : 'Ensure HTTPS environment'}</li>
|
|
||||||
<li>
|
<CodeEditor
|
||||||
•{' '}
|
className="mb-4"
|
||||||
{isZh
|
code={`<script src="${CDN_FULL_URL}" crossorigin="true"></script>
|
||||||
? '配置 CSP 策略允许脚本执行'
|
<script>
|
||||||
: 'Configure CSP policy to allow script execution'}
|
const agent = new PageAgent({
|
||||||
</li>
|
model: 'deepseek-chat',
|
||||||
</ul>
|
baseURL: 'https://api.deepseek.com',
|
||||||
</div>
|
apiKey: 'YOUR_API_KEY',
|
||||||
|
})
|
||||||
|
await agent.execute('Click the submit button')
|
||||||
|
</script>`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<table className="w-full border-collapse text-sm">
|
||||||
|
<thead>
|
||||||
|
<tr className="border-b border-gray-200 dark:border-gray-700">
|
||||||
|
<th className="text-left py-2 px-3 font-semibold w-28">
|
||||||
|
{isZh ? '位置' : 'Location'}
|
||||||
|
</th>
|
||||||
|
<th className="text-left py-2 px-3 font-semibold">URL</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr className="border-b border-gray-100 dark:border-gray-800">
|
||||||
|
<td className="py-2 px-3">{isZh ? '全球' : 'Global'}</td>
|
||||||
|
<td className="py-2 px-3 font-mono text-xs break-all">{CDN_FULL_URL}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className="py-2 px-3">{isZh ? '中国' : 'China'}</td>
|
||||||
|
<td className="py-2 px-3 font-mono text-xs break-all">{CDN_FULL_CN_URL}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Tips */}
|
||||||
|
<section className="bg-blue-50 dark:bg-blue-900/20 p-4 rounded-lg">
|
||||||
|
<h3 className="text-base font-semibold mb-2 text-blue-900 dark:text-blue-300">
|
||||||
|
💡 {isZh ? '提示' : 'Tips'}
|
||||||
|
</h3>
|
||||||
|
<ul className="text-gray-600 dark:text-gray-300 text-sm space-y-1">
|
||||||
|
<li>• {isZh ? '生产环境推荐使用 NPM' : 'NPM is recommended for production'}</li>
|
||||||
|
<li>• {isZh ? '生产环境建议锁定版本号' : 'Lock version number in production'}</li>
|
||||||
|
<li>• {isZh ? '确保 HTTPS 环境' : 'Ensure HTTPS environment'}</li>
|
||||||
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -28,15 +28,17 @@ export default function Configuration() {
|
|||||||
className="mb-4"
|
className="mb-4"
|
||||||
language="typescript"
|
language="typescript"
|
||||||
code={`interface LLMConfig {
|
code={`interface LLMConfig {
|
||||||
baseURL?: string
|
baseURL: string
|
||||||
apiKey?: string
|
apiKey: string
|
||||||
model?: string
|
model: string
|
||||||
|
|
||||||
temperature?: number
|
temperature?: number
|
||||||
maxRetries?: number
|
maxRetries?: number
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom fetch function for LLM API requests.
|
* Custom fetch function for LLM API requests.
|
||||||
* Use this to customize headers, credentials, proxy, etc.
|
* Use this to customize headers, credentials, proxy, etc.
|
||||||
|
* The response should follow OpenAI API format.
|
||||||
*/
|
*/
|
||||||
customFetch?: typeof globalThis.fetch
|
customFetch?: typeof globalThis.fetch
|
||||||
}`}
|
}`}
|
||||||
@@ -59,6 +61,27 @@ export default function Configuration() {
|
|||||||
code={`interface AgentConfig {
|
code={`interface AgentConfig {
|
||||||
language?: 'en-US' | 'zh-CN'
|
language?: 'en-US' | 'zh-CN'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to prompt for next task after task completion
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
promptForNextTask?: boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable the UI panel for visual feedback and user interaction
|
||||||
|
* When disabled, the panel will not be created and all UI operations will be skipped.
|
||||||
|
* Useful for automated testing or when integrating PageAgent as a library.
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
enablePanel?: boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable the ask_user tool for agent to ask questions
|
||||||
|
* When disabled, the agent cannot ask user questions during execution.
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
enableAskUser?: boolean
|
||||||
|
|
||||||
/** Custom tools to extend or override built-in tools */
|
/** Custom tools to extend or override built-in tools */
|
||||||
customTools?: Record<string, PageAgentTool | null>
|
customTools?: Record<string, PageAgentTool | null>
|
||||||
|
|
||||||
@@ -71,12 +94,12 @@ export default function Configuration() {
|
|||||||
getPageInstructions?: (url: string) => string | undefined | null
|
getPageInstructions?: (url: string) => string | undefined | null
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lifecycle hooks
|
// Lifecycle hooks (with \`this\` bound to PageAgent instance)
|
||||||
onBeforeStep?: (stepCnt: number) => Promise<void> | void
|
onBeforeStep?: (this: PageAgent, stepCnt: number) => Promise<void> | void
|
||||||
onAfterStep?: (stepCnt: number, history: AgentHistory[]) => Promise<void> | void
|
onAfterStep?: (this: PageAgent, stepCnt: number, history: HistoryEvent[]) => Promise<void> | void
|
||||||
onBeforeTask?: () => Promise<void> | void
|
onBeforeTask?: (this: PageAgent) => Promise<void> | void
|
||||||
onAfterTask?: (result: ExecutionResult) => Promise<void> | void
|
onAfterTask?: (this: PageAgent, result: ExecutionResult) => Promise<void> | void
|
||||||
onDispose?: (reason?: string) => void
|
onDispose?: (this: PageAgent, reason?: string) => void
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transform page content before sending to LLM.
|
* Transform page content before sending to LLM.
|
||||||
@@ -103,7 +126,7 @@ export default function Configuration() {
|
|||||||
<CodeEditor
|
<CodeEditor
|
||||||
className="mb-4"
|
className="mb-4"
|
||||||
language="typescript"
|
language="typescript"
|
||||||
code={`interface PageControllerConfig {
|
code={`interface DomConfig {
|
||||||
/** Elements to exclude from interaction */
|
/** Elements to exclude from interaction */
|
||||||
interactiveBlacklist?: (Element | (() => Element))[]
|
interactiveBlacklist?: (Element | (() => Element))[]
|
||||||
|
|
||||||
@@ -118,9 +141,14 @@ export default function Configuration() {
|
|||||||
|
|
||||||
/** Highlight label opacity (0-1) */
|
/** Highlight label opacity (0-1) */
|
||||||
highlightLabelOpacity?: number
|
highlightLabelOpacity?: number
|
||||||
|
}
|
||||||
|
|
||||||
/** Viewport expansion in pixels (-1 for full page) */
|
interface PageControllerConfig extends DomConfig {
|
||||||
|
/** Viewport expansion in pixels */
|
||||||
viewportExpansion?: number
|
viewportExpansion?: number
|
||||||
|
|
||||||
|
/** Enable visual mask overlay during operations (default: false) */
|
||||||
|
enableMask?: boolean
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
@@ -133,6 +161,39 @@ export default function Configuration() {
|
|||||||
code={`type PageAgentConfig = LLMConfig & AgentConfig & PageControllerConfig`}
|
code={`type PageAgentConfig = LLMConfig & AgentConfig & PageControllerConfig`}
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
{/* Programmatic Usage Example */}
|
||||||
|
<section className="mb-10">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">
|
||||||
|
{isZh ? '程序化使用配置' : 'Programmatic Usage'}
|
||||||
|
</h2>
|
||||||
|
<p className="text-gray-600 dark:text-gray-400 mb-4">
|
||||||
|
{isZh
|
||||||
|
? '对于程序化集成场景,可以禁用 UI。'
|
||||||
|
: 'For programmatic integration, you can disable UI.'}
|
||||||
|
</p>
|
||||||
|
<CodeEditor
|
||||||
|
language="typescript"
|
||||||
|
code={`const agent = new PageAgent({
|
||||||
|
baseURL: 'https://api.openai.com/v1',
|
||||||
|
apiKey: 'your-api-key',
|
||||||
|
model: 'your-model-name',
|
||||||
|
|
||||||
|
// Disable all UI features for pure programmatic usage
|
||||||
|
enablePanel: false, // Don't create Panel UI
|
||||||
|
enableMask: false, // Don't show visual overlay (mask and pointer)
|
||||||
|
// enableAskUser is automatically disabled when enablePanel is false
|
||||||
|
|
||||||
|
// Or keep Panel but disable post-task prompts
|
||||||
|
// enablePanel: true,
|
||||||
|
// promptForNextTask: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Pure programmatic execution
|
||||||
|
const result = await agent.execute('search for TypeScript documentation')
|
||||||
|
console.log(result.success, result.data, result.history)`}
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ const pageAgentTool = {
|
|||||||
},
|
},
|
||||||
execute: async (params) => {
|
execute: async (params) => {
|
||||||
const result = await pageAgent.execute(params.instruction)
|
const result = await pageAgent.execute(params.instruction)
|
||||||
return { success: result.success, message: result.message }
|
return { success: result.success, message: result.data }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,13 +22,6 @@ export default function Overview() {
|
|||||||
|
|
||||||
{/* Status Badges */}
|
{/* Status Badges */}
|
||||||
<div className="flex flex-wrap gap-2 items-center">
|
<div className="flex flex-wrap gap-2 items-center">
|
||||||
<a
|
|
||||||
href="https://www.npmjs.com/package/page-agent"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
<img src="https://badge.fury.io/js/page-agent.svg" alt="npm version" />
|
|
||||||
</a>
|
|
||||||
<a href="https://opensource.org/licenses/MIT" target="_blank" rel="noopener noreferrer">
|
<a href="https://opensource.org/licenses/MIT" target="_blank" rel="noopener noreferrer">
|
||||||
<img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="MIT License" />
|
<img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="MIT License" />
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -2,10 +2,11 @@ import { useTranslation } from 'react-i18next'
|
|||||||
|
|
||||||
import BetaNotice from '@/components/BetaNotice'
|
import BetaNotice from '@/components/BetaNotice'
|
||||||
import CodeEditor from '@/components/CodeEditor'
|
import CodeEditor from '@/components/CodeEditor'
|
||||||
import { CDN_CN_URL, CDN_URL } from '@/constants'
|
import { CDN_DEMO_CN_URL, CDN_DEMO_URL } from '@/constants'
|
||||||
|
|
||||||
export default function QuickStart() {
|
export default function QuickStart() {
|
||||||
const { t } = useTranslation('docs')
|
const { t, i18n } = useTranslation('docs')
|
||||||
|
const isZh = i18n.language === 'zh-CN'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -16,64 +17,62 @@ export default function QuickStart() {
|
|||||||
<h2 className="text-2xl font-bold mb-3">{t('quick_start.installation')}</h2>
|
<h2 className="text-2xl font-bold mb-3">{t('quick_start.installation')}</h2>
|
||||||
|
|
||||||
<div className="space-y-4 mb-6">
|
<div className="space-y-4 mb-6">
|
||||||
|
{/* Demo CDN - One Line */}
|
||||||
<div className="p-4 bg-blue-50 dark:bg-blue-900/20 rounded-lg">
|
<div className="p-4 bg-blue-50 dark:bg-blue-900/20 rounded-lg">
|
||||||
<h3 className="text-lg font-semibold mb-2 text-blue-900 dark:text-blue-300">
|
<h3 className="text-lg font-semibold mb-2 text-blue-900 dark:text-blue-300">
|
||||||
{t('quick_start.step1_title')}
|
{isZh ? '🚀 快速体验(Demo CDN)' : '🚀 Quick Try (Demo CDN)'}
|
||||||
</h3>
|
</h3>
|
||||||
<div className="space-y-3">
|
<div className="bg-yellow-50 dark:bg-yellow-900/20 p-2 rounded mb-3 text-sm">
|
||||||
<div>
|
<span className="text-yellow-800 dark:text-yellow-200">
|
||||||
<p className="text-sm font-medium mb-2">{t('quick_start.step1_cdn')}</p>
|
⚠️ {isZh ? '仅用于技术评估' : 'For evaluation only'}
|
||||||
<CodeEditor
|
</span>
|
||||||
code={`// CDN: \t${CDN_URL}
|
|
||||||
// Mirror: \t${CDN_CN_URL}
|
|
||||||
<script src="${CDN_URL}" crossorigin="true" type="text/javascript"></script>`}
|
|
||||||
language="html"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p className="text-sm font-medium mb-2">{t('quick_start.step1_npm')}</p>
|
|
||||||
<CodeEditor
|
|
||||||
code={`// npm install page-agent
|
|
||||||
import PageAgent from 'page-agent'`}
|
|
||||||
language="bash"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<CodeEditor
|
||||||
|
code={`// Global: ${CDN_DEMO_URL}
|
||||||
|
// China: ${CDN_DEMO_CN_URL}
|
||||||
|
<script src="${isZh ? CDN_DEMO_CN_URL : CDN_DEMO_URL}" crossorigin="true"></script>`}
|
||||||
|
language="html"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* NPM - Recommended */}
|
||||||
<div className="p-4 bg-green-50 dark:bg-green-900/20 rounded-lg">
|
<div className="p-4 bg-green-50 dark:bg-green-900/20 rounded-lg">
|
||||||
<h3 className="text-lg font-semibold mb-2 text-green-900 dark:text-green-300">
|
<h3 className="text-lg font-semibold mb-2 text-green-900 dark:text-green-300">
|
||||||
{t('quick_start.step2_title')}
|
{isZh ? '📦 NPM 安装(推荐)' : '📦 NPM Install (Recommended)'}
|
||||||
</h3>
|
</h3>
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
code={`// 仅供测试使用,生产环境需要配置 LLM 接入点,本工具不提供 LLM 服务
|
code={`npm install page-agent
|
||||||
// 测试接口
|
|
||||||
// @note: 限流,限制 prompt 内容,限制来源,随时变更,请替换成你自己的
|
|
||||||
// @note: 使用 DeepSeek-chat(3.2) 官方版本,使用协议和隐私策略见 DeepSeek 网站
|
|
||||||
const DEMO_MODEL = 'PAGE-AGENT-FREE-TESTING-RANDOM'
|
|
||||||
const DEMO_BASE_URL = 'https://hwcxiuzfylggtcktqgij.supabase.co/functions/v1/llm-testing-proxy'
|
|
||||||
const DEMO_API_KEY = 'PAGE-AGENT-FREE-TESTING-RANDOM'
|
|
||||||
|
|
||||||
const agent = new PageAgent({
|
import { PageAgent } from 'page-agent'`}
|
||||||
model: DEMO_MODEL,
|
language="bash"
|
||||||
baseURL: DEMO_BASE_URL,
|
|
||||||
apiKey: DEMO_API_KEY,
|
|
||||||
language: 'zh-CN'
|
|
||||||
})`}
|
|
||||||
language="javascript"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="p-4 bg-purple-50 dark:bg-purple-900/20 rounded-lg">
|
<div className="p-4 bg-purple-50 dark:bg-purple-900/20 rounded-lg">
|
||||||
<h3 className="text-lg font-semibold mb-2 text-purple-900 dark:text-purple-300">
|
<h3 className="text-lg font-semibold mb-2 text-purple-900 dark:text-purple-300">
|
||||||
|
{t('quick_start.step2_title')}
|
||||||
|
</h3>
|
||||||
|
<CodeEditor
|
||||||
|
code={`const agent = new PageAgent({
|
||||||
|
model: 'deepseek-chat',
|
||||||
|
baseURL: 'https://api.deepseek.com',
|
||||||
|
apiKey: 'YOUR_API_KEY',
|
||||||
|
language: '${isZh ? 'zh-CN' : 'en-US'}'
|
||||||
|
})`}
|
||||||
|
language="javascript"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="p-4 bg-orange-50 dark:bg-orange-900/20 rounded-lg">
|
||||||
|
<h3 className="text-lg font-semibold mb-2 text-orange-900 dark:text-orange-300">
|
||||||
{t('quick_start.step3_title')}
|
{t('quick_start.step3_title')}
|
||||||
</h3>
|
</h3>
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
code={`// 程序化执行自然语言指令
|
code={`// ${isZh ? '程序化执行自然语言指令' : 'Execute natural language instructions programmatically'}
|
||||||
await pageAgent.execute('点击提交按钮,然后填写用户名为张三');
|
await agent.execute('${isZh ? '点击提交按钮,然后填写用户名为张三' : 'Click submit button, then fill username as John'}');
|
||||||
|
|
||||||
// 或者显示对话框让用户输入指令
|
// ${isZh ? '或者显示对话框让用户输入指令' : 'Or show panel for user to input instructions'}
|
||||||
pageAgent.panel.show()
|
agent.panel.show()
|
||||||
`}
|
`}
|
||||||
language="javascript"
|
language="javascript"
|
||||||
/>
|
/>
|
||||||
|
|||||||
+24
-138
@@ -1,148 +1,34 @@
|
|||||||
|
import { Suspense, lazy } from 'react'
|
||||||
import { Route, Switch } from 'wouter'
|
import { Route, Switch } from 'wouter'
|
||||||
|
|
||||||
import Header from './components/Header'
|
// Lazy load pages
|
||||||
import HomePage from './pages/Home'
|
const HomePage = lazy(() => import('./pages/Home'))
|
||||||
import DocsLayout from './pages/docs/Layout'
|
const DocsPages = lazy(() => import('./pages/docs/index'))
|
||||||
// Features pages
|
|
||||||
import Instructions from './pages/docs/features/custom-instructions/page'
|
|
||||||
import CustomTools from './pages/docs/features/custom-tools/page'
|
|
||||||
import DataMasking from './pages/docs/features/data-masking/page'
|
|
||||||
import Models from './pages/docs/features/models/page'
|
|
||||||
// Integration pages
|
|
||||||
import BestPractices from './pages/docs/integration/best-practices/page'
|
|
||||||
import CdnSetup from './pages/docs/integration/cdn-setup/page'
|
|
||||||
import Configuration from './pages/docs/integration/configuration/page'
|
|
||||||
import SecurityPermissions from './pages/docs/integration/security-permissions/page'
|
|
||||||
import ThirdPartyAgent from './pages/docs/integration/third-party-agent/page'
|
|
||||||
// Introduction pages
|
|
||||||
import Limitations from './pages/docs/introduction/limitations/page'
|
|
||||||
import Overview from './pages/docs/introduction/overview/page'
|
|
||||||
import QuickStart from './pages/docs/introduction/quick-start/page'
|
|
||||||
|
|
||||||
export default function Router() {
|
export default function Router() {
|
||||||
return (
|
return (
|
||||||
<Switch>
|
<Suspense>
|
||||||
{/* Home page */}
|
<Switch>
|
||||||
<Route path="/" component={HomePage} />
|
{/* Home page */}
|
||||||
|
<Route path="/">
|
||||||
|
<HomePage />
|
||||||
|
</Route>
|
||||||
|
|
||||||
{/* Documentation pages with layout */}
|
{/* All docs pages */}
|
||||||
<Route path="/docs/introduction/overview">
|
<Route path="/docs" nest>
|
||||||
<div className="min-h-screen bg-white dark:bg-gray-900">
|
<DocsPages />
|
||||||
<Header />
|
</Route>
|
||||||
<DocsLayout>
|
|
||||||
<Overview />
|
|
||||||
</DocsLayout>
|
|
||||||
</div>
|
|
||||||
</Route>
|
|
||||||
|
|
||||||
<Route path="/docs/introduction/quick-start">
|
{/* 404 */}
|
||||||
<div className="min-h-screen bg-white dark:bg-gray-900">
|
<Route>
|
||||||
<Header />
|
<div className="min-h-screen bg-white dark:bg-gray-900 flex items-center justify-center">
|
||||||
<DocsLayout>
|
<div className="text-center">
|
||||||
<QuickStart />
|
<h1 className="text-4xl font-bold mb-4 text-gray-900 dark:text-white">404</h1>
|
||||||
</DocsLayout>
|
<p className="text-xl text-gray-600 dark:text-gray-300">页面未找到</p>
|
||||||
</div>
|
</div>
|
||||||
</Route>
|
|
||||||
|
|
||||||
<Route path="/docs/introduction/limitations">
|
|
||||||
<div className="min-h-screen bg-white dark:bg-gray-900">
|
|
||||||
<Header />
|
|
||||||
<DocsLayout>
|
|
||||||
<Limitations />
|
|
||||||
</DocsLayout>
|
|
||||||
</div>
|
|
||||||
</Route>
|
|
||||||
|
|
||||||
<Route path="/docs/features/custom-tools">
|
|
||||||
<div className="min-h-screen bg-white dark:bg-gray-900">
|
|
||||||
<Header />
|
|
||||||
<DocsLayout>
|
|
||||||
<CustomTools />
|
|
||||||
</DocsLayout>
|
|
||||||
</div>
|
|
||||||
</Route>
|
|
||||||
|
|
||||||
<Route path="/docs/features/data-masking">
|
|
||||||
<div className="min-h-screen bg-white dark:bg-gray-900">
|
|
||||||
<Header />
|
|
||||||
<DocsLayout>
|
|
||||||
<DataMasking />
|
|
||||||
</DocsLayout>
|
|
||||||
</div>
|
|
||||||
</Route>
|
|
||||||
|
|
||||||
<Route path="/docs/features/custom-instructions">
|
|
||||||
<div className="min-h-screen bg-white dark:bg-gray-900">
|
|
||||||
<Header />
|
|
||||||
<DocsLayout>
|
|
||||||
<Instructions />
|
|
||||||
</DocsLayout>
|
|
||||||
</div>
|
|
||||||
</Route>
|
|
||||||
|
|
||||||
<Route path="/docs/features/models">
|
|
||||||
<div className="min-h-screen bg-white dark:bg-gray-900">
|
|
||||||
<Header />
|
|
||||||
<DocsLayout>
|
|
||||||
<Models />
|
|
||||||
</DocsLayout>
|
|
||||||
</div>
|
|
||||||
</Route>
|
|
||||||
|
|
||||||
<Route path="/docs/integration/cdn-setup">
|
|
||||||
<div className="min-h-screen bg-white dark:bg-gray-900">
|
|
||||||
<Header />
|
|
||||||
<DocsLayout>
|
|
||||||
<CdnSetup />
|
|
||||||
</DocsLayout>
|
|
||||||
</div>
|
|
||||||
</Route>
|
|
||||||
|
|
||||||
<Route path="/docs/integration/security-permissions">
|
|
||||||
<div className="min-h-screen bg-white dark:bg-gray-900">
|
|
||||||
<Header />
|
|
||||||
<DocsLayout>
|
|
||||||
<SecurityPermissions />
|
|
||||||
</DocsLayout>
|
|
||||||
</div>
|
|
||||||
</Route>
|
|
||||||
|
|
||||||
<Route path="/docs/integration/configuration">
|
|
||||||
<div className="min-h-screen bg-white dark:bg-gray-900">
|
|
||||||
<Header />
|
|
||||||
<DocsLayout>
|
|
||||||
<Configuration />
|
|
||||||
</DocsLayout>
|
|
||||||
</div>
|
|
||||||
</Route>
|
|
||||||
|
|
||||||
<Route path="/docs/integration/best-practices">
|
|
||||||
<div className="min-h-screen bg-white dark:bg-gray-900">
|
|
||||||
<Header />
|
|
||||||
<DocsLayout>
|
|
||||||
<BestPractices />
|
|
||||||
</DocsLayout>
|
|
||||||
</div>
|
|
||||||
</Route>
|
|
||||||
|
|
||||||
<Route path="/docs/integration/third-party-agent">
|
|
||||||
<div className="min-h-screen bg-white dark:bg-gray-900">
|
|
||||||
<Header />
|
|
||||||
<DocsLayout>
|
|
||||||
<ThirdPartyAgent />
|
|
||||||
</DocsLayout>
|
|
||||||
</div>
|
|
||||||
</Route>
|
|
||||||
|
|
||||||
{/* 404 page */}
|
|
||||||
<Route>
|
|
||||||
<div className="min-h-screen bg-white dark:bg-gray-900 flex items-center justify-center">
|
|
||||||
<div className="text-center">
|
|
||||||
<h1 className="text-4xl font-bold mb-4 text-gray-900 dark:text-white">404</h1>
|
|
||||||
<p className="text-xl text-gray-600 dark:text-gray-300">页面未找到</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Route>
|
||||||
</Route>
|
</Switch>
|
||||||
</Switch>
|
</Suspense>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,29 @@
|
|||||||
{
|
{
|
||||||
"extends": "../../tsconfig.base.json",
|
"extends": "../../tsconfig.base.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo",
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo",
|
||||||
"noEmit": false,
|
"noEmit": false,
|
||||||
"allowImportingTsExtensions": false,
|
"allowImportingTsExtensions": false,
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"outDir": "dist",
|
"outDir": "dist",
|
||||||
"paths": {
|
"paths": {
|
||||||
// Self root
|
// Self root
|
||||||
"@/*": ["src/*"],
|
"@/*": ["src/*"],
|
||||||
|
|
||||||
// Simplified monorepo solution (raw npm workspace with hoisting)
|
// Simplified monorepo solution (raw npm workspace with hoisting)
|
||||||
"page-agent": ["../page-agent/src/PageAgent.ts"],
|
"page-agent": ["../page-agent/src/PageAgent.ts"],
|
||||||
"@page-agent/llms": ["../llms/src/index.ts"],
|
"@page-agent/llms": ["../llms/src/index.ts"],
|
||||||
"@page-agent/page-controller": ["../page-controller/src/PageController.ts"],
|
"@page-agent/page-controller": ["../page-controller/src/PageController.ts"],
|
||||||
"@page-agent/ui": ["../ui/src/index.ts"]
|
"@page-agent/ui": ["../ui/src/index.ts"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["**/*.ts", "**/*.tsx"],
|
"include": ["**/*.ts", "**/*.tsx"],
|
||||||
"exclude": ["dist", "node_modules"],
|
"exclude": ["dist", "node_modules"],
|
||||||
"references": [
|
"references": [
|
||||||
//
|
//
|
||||||
{ "path": "../llms" },
|
{ "path": "../llms" },
|
||||||
{ "path": "../page-agent" },
|
{ "path": "../page-agent" },
|
||||||
{ "path": "../page-controller" },
|
{ "path": "../page-controller" },
|
||||||
{ "path": "../ui" }
|
{ "path": "../ui" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,35 @@
|
|||||||
import tailwindcss from '@tailwindcss/vite'
|
import tailwindcss from '@tailwindcss/vite'
|
||||||
import react from '@vitejs/plugin-react-swc'
|
import react from '@vitejs/plugin-react-swc'
|
||||||
import { config as dotenvConfig } from 'dotenv'
|
import { config as dotenvConfig } from 'dotenv'
|
||||||
|
import { readFileSync } from 'node:fs'
|
||||||
import process from 'node:process'
|
import process from 'node:process'
|
||||||
import { dirname, resolve } from 'path'
|
import { dirname, resolve } from 'path'
|
||||||
import { fileURLToPath } from 'url'
|
import { fileURLToPath } from 'url'
|
||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
|
|
||||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||||
|
const pageAgentPkg = JSON.parse(
|
||||||
|
readFileSync(resolve(__dirname, '../page-agent/package.json'), 'utf-8')
|
||||||
|
)
|
||||||
|
|
||||||
// Load .env from repo root
|
// Load .env from repo root
|
||||||
dotenvConfig({ path: resolve(__dirname, '../../.env') })
|
dotenvConfig({ path: resolve(__dirname, '../../.env') })
|
||||||
|
|
||||||
// Website Config (React Documentation Site)
|
// Website Config (React Documentation Site)
|
||||||
export default defineConfig({
|
export default defineConfig(({ mode }) => ({
|
||||||
base: './',
|
base: './',
|
||||||
clearScreen: false,
|
clearScreen: false,
|
||||||
plugins: [react(), tailwindcss()],
|
plugins: [react(), tailwindcss()],
|
||||||
|
build: {
|
||||||
|
chunkSizeWarningLimit: 2000,
|
||||||
|
cssCodeSplit: true,
|
||||||
|
rollupOptions: {
|
||||||
|
onwarn: function (message, handler) {
|
||||||
|
if (message.code === 'EVAL') return
|
||||||
|
handler(message)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
// Self root
|
// Self root
|
||||||
@@ -29,8 +43,11 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
define: {
|
define: {
|
||||||
'import.meta.env.LLM_MODEL_NAME': JSON.stringify(process.env.LLM_MODEL_NAME),
|
...(mode === 'development' && {
|
||||||
'import.meta.env.LLM_API_KEY': JSON.stringify(process.env.LLM_API_KEY),
|
'import.meta.env.LLM_MODEL_NAME': JSON.stringify(process.env.LLM_MODEL_NAME),
|
||||||
'import.meta.env.LLM_BASE_URL': JSON.stringify(process.env.LLM_BASE_URL),
|
'import.meta.env.LLM_API_KEY': JSON.stringify(process.env.LLM_API_KEY),
|
||||||
|
'import.meta.env.LLM_BASE_URL': JSON.stringify(process.env.LLM_BASE_URL),
|
||||||
|
}),
|
||||||
|
'import.meta.env.VERSION': JSON.stringify(pageAgentPkg.version),
|
||||||
},
|
},
|
||||||
})
|
}))
|
||||||
|
|||||||
+41
-41
@@ -1,47 +1,47 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"composite": true,
|
"composite": true,
|
||||||
"target": "ES2024",
|
"target": "ES2024",
|
||||||
"useDefineForClassFields": true,
|
"useDefineForClassFields": true,
|
||||||
"lib": ["ES2024", "DOM", "DOM.Iterable"],
|
"lib": ["ES2024", "DOM", "DOM.Iterable"],
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
|
|
||||||
// "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo",
|
// "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo",
|
||||||
// "baseUrl": "src",
|
// "baseUrl": "src",
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"outDir": "dist",
|
"outDir": "dist",
|
||||||
// "incremental": true,
|
// "incremental": true,
|
||||||
|
|
||||||
/* Bundler mode */
|
/* Bundler mode */
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"verbatimModuleSyntax": false,
|
"verbatimModuleSyntax": false,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"jsx": "react-jsx",
|
"jsx": "react-jsx",
|
||||||
"allowImportingTsExtensions": true,
|
"allowImportingTsExtensions": true,
|
||||||
|
|
||||||
/* Linting */
|
/* Linting */
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"noUnusedLocals": false,
|
"noUnusedLocals": false,
|
||||||
"noUnusedParameters": false,
|
"noUnusedParameters": false,
|
||||||
"erasableSyntaxOnly": true,
|
"erasableSyntaxOnly": true,
|
||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"noUncheckedSideEffectImports": true
|
"noUncheckedSideEffectImports": true
|
||||||
|
|
||||||
// "paths": {
|
// "paths": {
|
||||||
// // Simplified monorepo solution (raw npm workspace with hoisting)
|
// // Simplified monorepo solution (raw npm workspace with hoisting)
|
||||||
// "@page-agent/page-controller": ["./packages/page-controller/src/PageController.ts"],
|
// "@page-agent/page-controller": ["./packages/page-controller/src/PageController.ts"],
|
||||||
// "page-agent": ["./packages/page-agent/src/PageAgent.ts"]
|
// "page-agent": ["./packages/page-agent/src/PageAgent.ts"]
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
// "references": [
|
// "references": [
|
||||||
// { "path": "./packages/page-controller" },
|
// { "path": "./packages/page-controller" },
|
||||||
// { "path": "./packages/page-agent" },
|
// { "path": "./packages/page-agent" },
|
||||||
// { "path": "./packages/website" }
|
// { "path": "./packages/website" }
|
||||||
// ],
|
// ],
|
||||||
// "include": ["packages/*/src/**/*.ts", "packages/*/src/**/*.tsx"],
|
// "include": ["packages/*/src/**/*.ts", "packages/*/src/**/*.tsx"],
|
||||||
// "exclude": ["node_modules", "dist", "packages/*/dist"]
|
// "exclude": ["node_modules", "dist", "packages/*/dist"]
|
||||||
// "files": ["env.d.ts"]
|
// "files": ["env.d.ts"]
|
||||||
// "files": []
|
// "files": []
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-10
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"extends": "./tsconfig.base.json",
|
"extends": "./tsconfig.base.json",
|
||||||
"references": [
|
"references": [
|
||||||
{ "path": "./packages/page-controller" },
|
{ "path": "./packages/page-controller" },
|
||||||
{ "path": "./packages/ui" },
|
{ "path": "./packages/ui" },
|
||||||
{ "path": "./packages/llms" },
|
{ "path": "./packages/llms" },
|
||||||
{ "path": "./packages/page-agent" },
|
{ "path": "./packages/page-agent" },
|
||||||
{ "path": "./packages/website" }
|
{ "path": "./packages/website" }
|
||||||
],
|
],
|
||||||
"include": ["packages/*/src/**/*.ts", "packages/*/src/**/*.tsx"],
|
"include": ["packages/*/src/**/*.ts", "packages/*/src/**/*.tsx"],
|
||||||
"exclude": ["node_modules", "dist", "packages/*/dist"]
|
"exclude": ["node_modules", "dist", "packages/*/dist"]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user