Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c0ce6e7d14 | |||
| 8f5f98f889 | |||
| ee4719cdcf | |||
| b4c82b7833 | |||
| aefc3cfb89 | |||
| 7f2b8c2766 | |||
| 62e24a3050 | |||
| 4f6249a252 | |||
| 62519416bb | |||
| a1e70e663d | |||
| 338e317be1 | |||
| 9a66d5205f | |||
| 7d45bf6598 | |||
| 78ee72eea4 | |||
| 3b71c535b1 | |||
| 6615f7b300 | |||
| 38caae5e23 |
+35
-23
@@ -28,7 +28,22 @@
|
|||||||
|
|
||||||
## 🚀 快速开始
|
## 🚀 快速开始
|
||||||
|
|
||||||
### NPM 安装
|
### 快速体验 (Demo CDN)
|
||||||
|
|
||||||
|
最快的体验方式:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script src="https://registry.npmmirror.com/@page-agent/cdn/latest/files/dist/page-agent.demo.js" crossorigin="true"></script>
|
||||||
|
```
|
||||||
|
|
||||||
|
> ⚠️ **仅用于技术评估。** Demo 模型有速率限制和使用限制,生产环境请使用 NPM 方式。
|
||||||
|
|
||||||
|
| 位置 | URL |
|
||||||
|
| ------ | --------------------------------------------------------------------------------------- |
|
||||||
|
| 全球 | https://cdn.jsdelivr.net/npm/@page-agent/cdn/dist/page-agent.demo.js |
|
||||||
|
| 中国 | https://registry.npmmirror.com/@page-agent/cdn/latest/files/dist/page-agent.demo.js |
|
||||||
|
|
||||||
|
### NPM 安装(推荐)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install page-agent
|
npm install page-agent
|
||||||
@@ -37,40 +52,37 @@ 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 集成
|
### CDN 引入
|
||||||
|
|
||||||
Fastest way to try PageAgent is to include it via CDN. Demo model will be used by default.
|
适用于无法使用 NPM 的环境,用法与 NPM 一致:
|
||||||
|
|
||||||
| 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
|
```html
|
||||||
<script
|
<script src="https://registry.npmmirror.com/@page-agent/cdn/latest/files/dist/page-agent.js" crossorigin="true"></script>
|
||||||
src="https://registry.npmmirror.com/page-agent/latest/files/dist/umd/page-agent.js"
|
<script>
|
||||||
crossorigin="true"
|
const agent = new PageAgent({
|
||||||
type="text/javascript"
|
model: 'deepseek-chat',
|
||||||
></script>
|
baseURL: 'https://api.deepseek.com',
|
||||||
|
apiKey: 'YOUR_API_KEY',
|
||||||
|
})
|
||||||
|
await agent.execute('点击登录按钮')
|
||||||
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
| 位置 | URL |
|
||||||
|
| ------ | ----------------------------------------------------------------------------------- |
|
||||||
|
| 全球 | https://cdn.jsdelivr.net/npm/@page-agent/cdn/dist/page-agent.js |
|
||||||
|
| 中国 | https://registry.npmmirror.com/@page-agent/cdn/latest/files/dist/page-agent.js |
|
||||||
|
|
||||||
## 🏗️ 架构设计
|
## 🏗️ 架构设计
|
||||||
|
|
||||||
PageAgent adopts a simplified monorepo structure:
|
PageAgent adopts a simplified monorepo structure:
|
||||||
|
|||||||
@@ -28,7 +28,22 @@ The GUI Agent Living in Your Webpage. Control web interfaces with natural langua
|
|||||||
|
|
||||||
## 🚀 Quick Start
|
## 🚀 Quick Start
|
||||||
|
|
||||||
### NPM Installation
|
### Quick Try (Demo CDN)
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
| Location | 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 (Recommended)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install page-agent
|
npm install page-agent
|
||||||
@@ -37,40 +52,37 @@ 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
|
### CDN Build
|
||||||
|
|
||||||
Fastest way to try PageAgent is to include it via CDN. Demo model will be used by default.
|
For environments where NPM is not available. Usage is identical to NPM:
|
||||||
|
|
||||||
| 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
|
```html
|
||||||
<script
|
<script src="https://cdn.jsdelivr.net/npm/@page-agent/cdn/dist/page-agent.js" crossorigin="true"></script>
|
||||||
src="https://cdn.jsdelivr.net/npm/page-agent@latest/dist/umd/page-agent.js"
|
<script>
|
||||||
crossorigin="true"
|
const agent = new PageAgent({
|
||||||
type="text/javascript"
|
model: 'deepseek-chat',
|
||||||
></script>
|
baseURL: 'https://api.deepseek.com',
|
||||||
|
apiKey: 'YOUR_API_KEY',
|
||||||
|
})
|
||||||
|
await agent.execute('Click the login button')
|
||||||
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
| Location | URL |
|
||||||
|
| -------- | ----------------------------------------------------------------------------------- |
|
||||||
|
| Global | https://cdn.jsdelivr.net/npm/@page-agent/cdn/dist/page-agent.js |
|
||||||
|
| China | https://registry.npmmirror.com/@page-agent/cdn/latest/files/dist/page-agent.js |
|
||||||
|
|
||||||
## 🏗️ Structure
|
## 🏗️ Structure
|
||||||
|
|
||||||
PageAgent adopts a simplified monorepo structure:
|
PageAgent adopts a simplified monorepo structure:
|
||||||
|
|||||||
Generated
+12
-12
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "root",
|
"name": "root",
|
||||||
"version": "0.0.22",
|
"version": "0.2.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "root",
|
"name": "root",
|
||||||
"version": "0.0.22",
|
"version": "0.2.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/page-controller",
|
"packages/page-controller",
|
||||||
@@ -8147,15 +8147,15 @@
|
|||||||
},
|
},
|
||||||
"packages/cdn": {
|
"packages/cdn": {
|
||||||
"name": "@page-agent/cdn",
|
"name": "@page-agent/cdn",
|
||||||
"version": "0.0.22",
|
"version": "0.2.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"page-agent": "0.0.22"
|
"page-agent": "0.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/llms": {
|
"packages/llms": {
|
||||||
"name": "@page-agent/llms",
|
"name": "@page-agent/llms",
|
||||||
"version": "0.0.22",
|
"version": "0.2.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chalk": "^5.6.2",
|
"chalk": "^5.6.2",
|
||||||
@@ -8163,19 +8163,19 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/page-agent": {
|
"packages/page-agent": {
|
||||||
"version": "0.0.22",
|
"version": "0.2.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@page-agent/llms": "0.0.22",
|
"@page-agent/llms": "0.2.0",
|
||||||
"@page-agent/page-controller": "0.0.22",
|
"@page-agent/page-controller": "0.2.0",
|
||||||
"@page-agent/ui": "0.0.22",
|
"@page-agent/ui": "0.2.0",
|
||||||
"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.22",
|
"version": "0.2.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ai-motion": "^0.4.8"
|
"ai-motion": "^0.4.8"
|
||||||
@@ -8183,12 +8183,12 @@
|
|||||||
},
|
},
|
||||||
"packages/ui": {
|
"packages/ui": {
|
||||||
"name": "@page-agent/ui",
|
"name": "@page-agent/ui",
|
||||||
"version": "0.0.22",
|
"version": "0.2.0",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"packages/website": {
|
"packages/website": {
|
||||||
"name": "@page-agent/website",
|
"name": "@page-agent/website",
|
||||||
"version": "0.0.22",
|
"version": "0.2.0",
|
||||||
"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",
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "root",
|
"name": "root",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.22",
|
"version": "0.2.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/page-controller",
|
"packages/page-controller",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@page-agent/cdn",
|
"name": "@page-agent/cdn",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "0.0.22",
|
"version": "0.2.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"files": [
|
"files": [
|
||||||
"dist/"
|
"dist/"
|
||||||
@@ -18,6 +18,6 @@
|
|||||||
"build": "vite build && vite build --mode demo"
|
"build": "vite build && vite build --mode demo"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"page-agent": "0.0.22"
|
"page-agent": "0.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,12 @@ setTimeout(() => {
|
|||||||
window.pageAgent = new PageAgent(config)
|
window.pageAgent = new PageAgent(config)
|
||||||
} else {
|
} else {
|
||||||
console.log('🚀 page-agent.js no current script detected, using default demo config')
|
console.log('🚀 page-agent.js no current script detected, using default demo config')
|
||||||
window.pageAgent = new PageAgent()
|
const config = {
|
||||||
|
model: DEMO_MODEL,
|
||||||
|
baseURL: DEMO_BASE_URL,
|
||||||
|
apiKey: DEMO_API_KEY,
|
||||||
|
}
|
||||||
|
window.pageAgent = new PageAgent(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('🚀 page-agent.js initialized with config:', window.pageAgent.config)
|
console.log('🚀 page-agent.js initialized with config:', window.pageAgent.config)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@page-agent/llms",
|
"name": "@page-agent/llms",
|
||||||
"version": "0.0.22",
|
"version": "0.2.0",
|
||||||
"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,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "page-agent",
|
"name": "page-agent",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "0.0.22",
|
"version": "0.2.0",
|
||||||
"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",
|
||||||
@@ -46,8 +46,8 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chalk": "^5.6.2",
|
"chalk": "^5.6.2",
|
||||||
"zod": "^4.3.5",
|
"zod": "^4.3.5",
|
||||||
"@page-agent/llms": "0.0.22",
|
"@page-agent/llms": "0.2.0",
|
||||||
"@page-agent/page-controller": "0.0.22",
|
"@page-agent/page-controller": "0.2.0",
|
||||||
"@page-agent/ui": "0.0.22"
|
"@page-agent/ui": "0.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import { normalizeResponse, trimLines, uid, waitUntil } from './utils'
|
|||||||
import { assert } from './utils/assert'
|
import { assert } from './utils/assert'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Agent brain state - the reflection-before-action model
|
* Agent reflection state - the reflection-before-action model
|
||||||
*
|
*
|
||||||
* Every tool call must first reflect on:
|
* Every tool call must first reflect on:
|
||||||
* - evaluation_previous_goal: How well did the previous action achieve its goal?
|
* - evaluation_previous_goal: How well did the previous action achieve its goal?
|
||||||
@@ -50,8 +50,12 @@ export interface MacroToolResult {
|
|||||||
export type { PageAgentConfig }
|
export type { PageAgentConfig }
|
||||||
export { tool, type PageAgentTool } from './tools'
|
export { tool, type PageAgentTool } from './tools'
|
||||||
|
|
||||||
export interface AgentHistory {
|
/**
|
||||||
brain: Partial<AgentReflection>
|
* A single agent step with reflection and action
|
||||||
|
*/
|
||||||
|
export interface AgentStep {
|
||||||
|
type: 'step'
|
||||||
|
reflection: Partial<AgentReflection>
|
||||||
action: {
|
action: {
|
||||||
name: string
|
name: string
|
||||||
input: any
|
input: any
|
||||||
@@ -66,10 +70,33 @@ 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
|
||||||
|
|
||||||
|
/** @deprecated Use AgentStep instead */
|
||||||
|
export type AgentHistory = AgentStep
|
||||||
|
|
||||||
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 {
|
||||||
@@ -83,7 +110,6 @@ export class PageAgent extends EventTarget {
|
|||||||
taskId = ''
|
taskId = ''
|
||||||
|
|
||||||
#llm: LLM
|
#llm: LLM
|
||||||
#totalWaitTime = 0
|
|
||||||
#abortController = new AbortController()
|
#abortController = new AbortController()
|
||||||
#llmRetryListener: ((e: Event) => void) | null = null
|
#llmRetryListener: ((e: Event) => void) | null = null
|
||||||
#llmErrorListener: ((e: Event) => void) | null = null
|
#llmErrorListener: ((e: Event) => void) | null = null
|
||||||
@@ -92,10 +118,18 @@ export class PageAgent extends EventTarget {
|
|||||||
/** PageController for DOM operations */
|
/** PageController for DOM operations */
|
||||||
pageController: PageController
|
pageController: PageController
|
||||||
|
|
||||||
/** History records */
|
/** Runtime states for tracking across steps */
|
||||||
history: AgentHistory[] = []
|
states = {
|
||||||
|
/** Accumulated wait time in seconds, used by wait tool */
|
||||||
|
totalWaitTime: 0,
|
||||||
|
/** Last known URL for detecting navigation */
|
||||||
|
lastURL: '',
|
||||||
|
}
|
||||||
|
|
||||||
constructor(config: PageAgentConfig = {}) {
|
/** History event stream */
|
||||||
|
history: HistoryEvent[] = []
|
||||||
|
|
||||||
|
constructor(config: PageAgentConfig) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
this.config = config
|
this.config = config
|
||||||
@@ -150,6 +184,14 @@ export class PageAgent extends EventTarget {
|
|||||||
window.addEventListener('beforeunload', this.#beforeUnloadListener)
|
window.addEventListener('beforeunload', this.#beforeUnloadListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Push a persistent observation to the history event stream.
|
||||||
|
* This will be visible in <agent_history> and remain in memory across steps.
|
||||||
|
*/
|
||||||
|
pushObservation(content: string): void {
|
||||||
|
this.history.push({ type: 'observation', content })
|
||||||
|
}
|
||||||
|
|
||||||
async execute(task: string): Promise<ExecutionResult> {
|
async execute(task: string): Promise<ExecutionResult> {
|
||||||
if (!task) throw new Error('Task is required')
|
if (!task) throw new Error('Task is required')
|
||||||
this.task = task
|
this.task = task
|
||||||
@@ -177,10 +219,18 @@ 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}`)
|
||||||
@@ -216,23 +266,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()
|
||||||
@@ -319,13 +370,20 @@ export class PageAgent extends EventTarget {
|
|||||||
|
|
||||||
const toolName = Object.keys(action)[0]
|
const toolName = Object.keys(action)[0]
|
||||||
const toolInput = action[toolName]
|
const toolInput = action[toolName]
|
||||||
const brain = trimLines(`✅: ${input.evaluation_previous_goal}
|
|
||||||
💾: ${input.memory}
|
|
||||||
🎯: ${input.next_goal}
|
|
||||||
`)
|
|
||||||
|
|
||||||
console.log(brain)
|
// Build reflection text, only include non-empty fields
|
||||||
this.panel.update({ type: 'thinking', text: brain })
|
const reflectionLines: string[] = []
|
||||||
|
if (input.evaluation_previous_goal)
|
||||||
|
reflectionLines.push(`✅: ${input.evaluation_previous_goal}`)
|
||||||
|
if (input.memory) reflectionLines.push(`💾: ${input.memory}`)
|
||||||
|
if (input.next_goal) reflectionLines.push(`🎯: ${input.next_goal}`)
|
||||||
|
|
||||||
|
const reflectionText = reflectionLines.length > 0 ? reflectionLines.join('\n') : ''
|
||||||
|
|
||||||
|
if (reflectionText) {
|
||||||
|
console.log(reflectionText)
|
||||||
|
this.panel.update({ type: 'thinking', text: reflectionText })
|
||||||
|
}
|
||||||
|
|
||||||
// Find the corresponding tool
|
// Find the corresponding tool
|
||||||
const tool = tools.get(toolName)
|
const tool = tools.get(toolName)
|
||||||
@@ -337,20 +395,14 @@ export class PageAgent extends EventTarget {
|
|||||||
const startTime = Date.now()
|
const startTime = Date.now()
|
||||||
|
|
||||||
// Execute tool, bind `this` to PageAgent
|
// Execute tool, bind `this` to PageAgent
|
||||||
let result = await tool.execute.bind(this)(toolInput)
|
const result = await tool.execute.bind(this)(toolInput)
|
||||||
|
|
||||||
const duration = Date.now() - startTime
|
const duration = Date.now() - startTime
|
||||||
console.log(chalk.green.bold(`Tool (${toolName}) executed for ${duration}ms`), result)
|
console.log(chalk.green.bold(`Tool (${toolName}) executed for ${duration}ms`), result)
|
||||||
|
|
||||||
if (toolName === 'wait') {
|
// Reset wait time for non-wait tools
|
||||||
this.#totalWaitTime += Math.round(toolInput.seconds + duration / 1000)
|
if (toolName !== 'wait') {
|
||||||
result += `\n<sys> You have waited ${this.#totalWaitTime} seconds accumulatively.`
|
this.states.totalWaitTime = 0
|
||||||
if (this.#totalWaitTime >= 3)
|
|
||||||
result += '\nDo NOT wait any longer unless you have a good reason.\n'
|
|
||||||
result += '</sys>'
|
|
||||||
} else {
|
|
||||||
// For other tools, reset wait time
|
|
||||||
this.#totalWaitTime = 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Briefly display execution result
|
// Briefly display execution result
|
||||||
@@ -427,6 +479,34 @@ export class PageAgent extends EventTarget {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate observations before each step
|
||||||
|
* - URL change detection
|
||||||
|
* - Too many steps warning
|
||||||
|
* @todo loop detection
|
||||||
|
* @todo console error
|
||||||
|
*/
|
||||||
|
async #generateObservations(stepCount: number): Promise<void> {
|
||||||
|
// Detect URL change
|
||||||
|
const currentURL = await this.pageController.getCurrentUrl()
|
||||||
|
if (currentURL !== this.states.lastURL) {
|
||||||
|
this.pushObservation(`Page navigated to → ${currentURL}`)
|
||||||
|
this.states.lastURL = currentURL
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn about remaining steps
|
||||||
|
const remaining = MAX_STEPS - stepCount
|
||||||
|
if (remaining === 5) {
|
||||||
|
this.pushObservation(
|
||||||
|
`⚠️ Only ${remaining} steps remaining. Consider wrapping up or calling done with partial results.`
|
||||||
|
)
|
||||||
|
} else if (remaining === 2) {
|
||||||
|
this.pushObservation(
|
||||||
|
`⚠️ Critical: Only ${remaining} steps left! You must finish the task or call done immediately.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async #assembleUserPrompt(): Promise<string> {
|
async #assembleUserPrompt(): Promise<string> {
|
||||||
let prompt = ''
|
let prompt = ''
|
||||||
|
|
||||||
@@ -438,31 +518,42 @@ export class PageAgent extends EventTarget {
|
|||||||
// - <step_info>
|
// - <step_info>
|
||||||
// <agent_state>
|
// <agent_state>
|
||||||
|
|
||||||
|
const stepCount = this.history.filter((e) => e.type === 'step').length
|
||||||
|
|
||||||
prompt += `<agent_state>
|
prompt += `<agent_state>
|
||||||
<user_request>
|
<user_request>
|
||||||
${this.task}
|
${this.task}
|
||||||
</user_request>
|
</user_request>
|
||||||
<step_info>
|
<step_info>
|
||||||
Step ${this.history.length + 1} of ${MAX_STEPS} max possible steps
|
Step ${stepCount + 1} of ${MAX_STEPS} max possible steps
|
||||||
Current date and time: ${new Date().toISOString()}
|
Current date and time: ${new Date().toISOString()}
|
||||||
</step_info>
|
</step_info>
|
||||||
</agent_state>
|
</agent_state>
|
||||||
`
|
`
|
||||||
|
|
||||||
// <agent_history>
|
// <agent_history>
|
||||||
// - <step_>
|
// - <step_N> for steps
|
||||||
|
// - <sys> for observations and system messages
|
||||||
|
|
||||||
prompt += '\n<agent_history>\n'
|
prompt += '\n<agent_history>\n'
|
||||||
|
|
||||||
this.history.forEach((history, index) => {
|
let stepIndex = 0
|
||||||
prompt += `<step_${index + 1}>
|
for (const event of this.history) {
|
||||||
Evaluation of Previous Step: ${history.brain.evaluation_previous_goal}
|
if (event.type === 'step') {
|
||||||
Memory: ${history.brain.memory}
|
stepIndex++
|
||||||
Next Goal: ${history.brain.next_goal}
|
prompt += `<step_${stepIndex}>
|
||||||
Action Results: ${history.action.output}
|
Evaluation of Previous Step: ${event.reflection.evaluation_previous_goal}
|
||||||
</step_${index + 1}>
|
Memory: ${event.reflection.memory}
|
||||||
|
Next Goal: ${event.reflection.next_goal}
|
||||||
|
Action Results: ${event.action.output}
|
||||||
|
</step_${stepIndex}>
|
||||||
`
|
`
|
||||||
})
|
} else if (event.type === 'observation') {
|
||||||
|
prompt += `<sys>${event.content}</sys>\n`
|
||||||
|
} else if (event.type === 'user_takeover') {
|
||||||
|
prompt += `<sys>User took over control and made changes to the page.</sys>\n`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
prompt += '</agent_history>\n\n'
|
prompt += '</agent_history>\n\n'
|
||||||
|
|
||||||
@@ -492,51 +583,22 @@ export class PageAgent extends EventTarget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async #getBrowserState(): Promise<string> {
|
async #getBrowserState(): Promise<string> {
|
||||||
const pageUrl = await this.pageController.getCurrentUrl()
|
const state = await this.pageController.getBrowserState()
|
||||||
const pageTitle = await this.pageController.getPageTitle()
|
|
||||||
const pi = await this.pageController.getPageInfo()
|
|
||||||
const viewportExpansion = await this.pageController.getViewportExpansion()
|
|
||||||
|
|
||||||
await this.pageController.updateTree()
|
|
||||||
|
|
||||||
let simplifiedHTML = await this.pageController.getSimplifiedHTML()
|
|
||||||
|
|
||||||
|
let content = state.content
|
||||||
if (this.config.transformPageContent) {
|
if (this.config.transformPageContent) {
|
||||||
simplifiedHTML = await this.config.transformPageContent(simplifiedHTML)
|
content = await this.config.transformPageContent(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
let prompt = trimLines(`<browser_state>
|
return trimLines(`<browser_state>
|
||||||
Current Page: [${pageTitle}](${pageUrl})
|
Current Page: [${state.title}](${state.url})
|
||||||
|
|
||||||
Page info: ${pi.viewport_width}x${pi.viewport_height}px viewport, ${pi.page_width}x${pi.page_height}px total page size, ${pi.pages_above.toFixed(1)} pages above, ${pi.pages_below.toFixed(1)} pages below, ${pi.total_pages.toFixed(1)} total pages, at ${(pi.current_page_position * 100).toFixed(0)}% of page
|
${state.header}
|
||||||
|
${content}
|
||||||
${viewportExpansion === -1 ? 'Interactive elements from top layer of the current page (full page):' : 'Interactive elements from top layer of the current page inside the viewport:'}
|
${state.footer}
|
||||||
|
|
||||||
|
</browser_state>
|
||||||
`)
|
`)
|
||||||
|
|
||||||
// Page header info
|
|
||||||
const has_content_above = pi.pixels_above > 4
|
|
||||||
if (has_content_above && viewportExpansion !== -1) {
|
|
||||||
prompt += `... ${pi.pixels_above} pixels above (${pi.pages_above.toFixed(1)} pages) - scroll to see more ...\n`
|
|
||||||
} else {
|
|
||||||
prompt += `[Start of page]\n`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Current viewport info
|
|
||||||
prompt += simplifiedHTML
|
|
||||||
prompt += `\n`
|
|
||||||
|
|
||||||
// Page footer info
|
|
||||||
const has_content_below = pi.pixels_below > 4
|
|
||||||
if (has_content_below && viewportExpansion !== -1) {
|
|
||||||
prompt += `... ${pi.pixels_below} pixels below (${pi.pages_below.toFixed(1)} pages) - scroll to see more ...\n`
|
|
||||||
} else {
|
|
||||||
prompt += `[End of page]\n`
|
|
||||||
}
|
|
||||||
|
|
||||||
prompt += `</browser_state>\n`
|
|
||||||
|
|
||||||
return prompt
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose(reason?: string) {
|
dispose(reason?: string) {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { LLMConfig } from '@page-agent/llms'
|
|||||||
import type { PageControllerConfig } from '@page-agent/page-controller'
|
import type { PageControllerConfig } from '@page-agent/page-controller'
|
||||||
import type { SupportedLanguage } from '@page-agent/ui'
|
import type { SupportedLanguage } from '@page-agent/ui'
|
||||||
|
|
||||||
import type { AgentHistory, ExecutionResult, PageAgent } from '../PageAgent'
|
import type { ExecutionResult, HistoryEvent, PageAgent } from '../PageAgent'
|
||||||
import type { PageAgentTool } from '../tools'
|
import type { PageAgentTool } from '../tools'
|
||||||
|
|
||||||
export type { LLMConfig }
|
export type { LLMConfig }
|
||||||
@@ -65,7 +65,7 @@ export interface AgentConfig {
|
|||||||
// @todo: remove `this` binding, pass agent as explicit parameter instead
|
// @todo: remove `this` binding, pass agent as explicit parameter instead
|
||||||
|
|
||||||
onBeforeStep?: (this: PageAgent, stepCnt: number) => Promise<void> | void
|
onBeforeStep?: (this: PageAgent, stepCnt: number) => Promise<void> | void
|
||||||
onAfterStep?: (this: PageAgent, stepCnt: number, history: AgentHistory[]) => Promise<void> | void
|
onAfterStep?: (this: PageAgent, stepCnt: number, history: HistoryEvent[]) => Promise<void> | void
|
||||||
onBeforeTask?: (this: PageAgent) => Promise<void> | void
|
onBeforeTask?: (this: PageAgent) => Promise<void> | void
|
||||||
onAfterTask?: (this: PageAgent, result: ExecutionResult) => Promise<void> | void
|
onAfterTask?: (this: PageAgent, result: ExecutionResult) => Promise<void> | void
|
||||||
|
|
||||||
|
|||||||
@@ -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.`
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -35,7 +35,12 @@ setTimeout(() => {
|
|||||||
window.pageAgent = new PageAgent(config)
|
window.pageAgent = new PageAgent(config)
|
||||||
} else {
|
} else {
|
||||||
console.log('🚀 page-agent.js no current script detected, using default demo config')
|
console.log('🚀 page-agent.js no current script detected, using default demo config')
|
||||||
window.pageAgent = new PageAgent()
|
const config: PageAgentConfig = {
|
||||||
|
model: DEMO_MODEL,
|
||||||
|
baseURL: DEMO_BASE_URL,
|
||||||
|
apiKey: DEMO_API_KEY,
|
||||||
|
}
|
||||||
|
window.pageAgent = new PageAgent(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('🚀 page-agent.js initialized with config:', window.pageAgent.config)
|
console.log('🚀 page-agent.js initialized with config:', window.pageAgent.config)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@page-agent/page-controller",
|
"name": "@page-agent/page-controller",
|
||||||
"version": "0.0.22",
|
"version": "0.2.0",
|
||||||
"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",
|
||||||
|
|||||||
@@ -32,6 +32,20 @@ export interface PageControllerConfig extends dom.DomConfig {
|
|||||||
enableMask?: boolean
|
enableMask?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Structured browser state for LLM consumption
|
||||||
|
*/
|
||||||
|
export interface BrowserState {
|
||||||
|
url: string
|
||||||
|
title: string
|
||||||
|
/** Page info + scroll position hint (e.g. "Page info: 1920x1080px...\n[Start of page]") */
|
||||||
|
header: string
|
||||||
|
/** Simplified HTML of interactive elements */
|
||||||
|
content: string
|
||||||
|
/** Page footer hint (e.g. "... 300 pixels below ..." or "[End of page]") */
|
||||||
|
footer: string
|
||||||
|
}
|
||||||
|
|
||||||
interface ActionResult {
|
interface ActionResult {
|
||||||
success: boolean
|
success: boolean
|
||||||
message: string
|
message: string
|
||||||
@@ -93,42 +107,6 @@ export class PageController extends EventTarget {
|
|||||||
return window.location.href
|
return window.location.href
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get current page title
|
|
||||||
*/
|
|
||||||
async getPageTitle(): Promise<string> {
|
|
||||||
return document.title
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get page scroll and size info
|
|
||||||
*/
|
|
||||||
async getPageInfo() {
|
|
||||||
return getPageInfo()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the simplified HTML representation of the page.
|
|
||||||
* This is used by LLM to understand the page structure.
|
|
||||||
*/
|
|
||||||
async getSimplifiedHTML(): Promise<string> {
|
|
||||||
return this.simplifiedHTML
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get text description for an element by index
|
|
||||||
*/
|
|
||||||
async getElementText(index: number): Promise<string | undefined> {
|
|
||||||
return this.elementTextMap.get(index)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get total number of indexed interactive elements
|
|
||||||
*/
|
|
||||||
async getElementCount(): Promise<number> {
|
|
||||||
return this.selectorMap.size
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get last tree update timestamp
|
* Get last tree update timestamp
|
||||||
*/
|
*/
|
||||||
@@ -137,10 +115,43 @@ export class PageController extends EventTarget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the viewport expansion setting
|
* Get structured browser state for LLM consumption.
|
||||||
|
* Automatically calls updateTree() to refresh the DOM state.
|
||||||
*/
|
*/
|
||||||
async getViewportExpansion(): Promise<number> {
|
async getBrowserState(): Promise<BrowserState> {
|
||||||
return this.config.viewportExpansion ?? VIEWPORT_EXPANSION
|
const url = window.location.href
|
||||||
|
const title = document.title
|
||||||
|
const pi = getPageInfo()
|
||||||
|
const viewportExpansion = this.config.viewportExpansion ?? VIEWPORT_EXPANSION
|
||||||
|
|
||||||
|
await this.updateTree()
|
||||||
|
|
||||||
|
const content = this.simplifiedHTML
|
||||||
|
|
||||||
|
// Build header: page info + scroll position hint
|
||||||
|
const pageInfoLine = `Page info: ${pi.viewport_width}x${pi.viewport_height}px viewport, ${pi.page_width}x${pi.page_height}px total page size, ${pi.pages_above.toFixed(1)} pages above, ${pi.pages_below.toFixed(1)} pages below, ${pi.total_pages.toFixed(1)} total pages, at ${(pi.current_page_position * 100).toFixed(0)}% of page`
|
||||||
|
|
||||||
|
const elementsLabel =
|
||||||
|
viewportExpansion === -1
|
||||||
|
? 'Interactive elements from top layer of the current page (full page):'
|
||||||
|
: 'Interactive elements from top layer of the current page inside the viewport:'
|
||||||
|
|
||||||
|
const hasContentAbove = pi.pixels_above > 4
|
||||||
|
const scrollHintAbove =
|
||||||
|
hasContentAbove && viewportExpansion !== -1
|
||||||
|
? `... ${pi.pixels_above} pixels above (${pi.pages_above.toFixed(1)} pages) - scroll to see more ...`
|
||||||
|
: '[Start of page]'
|
||||||
|
|
||||||
|
const header = `${pageInfoLine}\n\n${elementsLabel}\n\n${scrollHintAbove}`
|
||||||
|
|
||||||
|
// Build footer: scroll position hint
|
||||||
|
const hasContentBelow = pi.pixels_below > 4
|
||||||
|
const footer =
|
||||||
|
hasContentBelow && viewportExpansion !== -1
|
||||||
|
? `... ${pi.pixels_below} pixels below (${pi.pages_below.toFixed(1)} pages) - scroll to see more ...`
|
||||||
|
: '[End of page]'
|
||||||
|
|
||||||
|
return { url, title, header, content, footer }
|
||||||
}
|
}
|
||||||
|
|
||||||
// ======= DOM Tree Operations =======
|
// ======= DOM Tree Operations =======
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@page-agent/ui",
|
"name": "@page-agent/ui",
|
||||||
"version": "0.0.22",
|
"version": "0.2.0",
|
||||||
"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",
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|
||||||
@@ -590,10 +590,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()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -660,7 +657,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}
|
||||||
|
|||||||
@@ -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,10 @@
|
|||||||
{
|
{
|
||||||
"name": "@page-agent/website",
|
"name": "@page-agent/website",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.22",
|
"version": "0.2.0",
|
||||||
"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"
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -84,6 +85,9 @@ 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"
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -12,10 +12,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);})();`
|
||||||
@@ -73,13 +79,18 @@ export default function HomePage() {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// experimentalScriptExecutionTool: true,
|
model:
|
||||||
|
import.meta.env.DEV && import.meta.env.LLM_MODEL_NAME
|
||||||
// testing server
|
? import.meta.env.LLM_MODEL_NAME
|
||||||
// @note: rate limit. prompt limit.
|
: DEMO_MODEL,
|
||||||
// model: DEMO_MODEL,
|
baseURL:
|
||||||
// baseURL: DEMO_BASE_URL,
|
import.meta.env.DEV && import.meta.env.LLM_BASE_URL
|
||||||
// apiKey: DEMO_API_KEY,
|
? import.meta.env.LLM_BASE_URL
|
||||||
|
: DEMO_BASE_URL,
|
||||||
|
apiKey:
|
||||||
|
import.meta.env.DEV && import.meta.env.LLM_API_KEY
|
||||||
|
? import.meta.env.LLM_API_KEY
|
||||||
|
: DEMO_API_KEY,
|
||||||
})
|
})
|
||||||
win.pageAgent = pageAgent
|
win.pageAgent = pageAgent
|
||||||
}
|
}
|
||||||
@@ -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 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">
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export default function DocsLayout({ children }: DocsLayoutProps) {
|
|||||||
items: [
|
items: [
|
||||||
{ title: t('nav.configuration'), path: '/docs/integration/configuration' },
|
{ title: t('nav.configuration'), path: '/docs/integration/configuration' },
|
||||||
{ title: t('nav.third_party_agent'), path: '/docs/integration/third-party-agent' },
|
{ title: t('nav.third_party_agent'), path: '/docs/integration/third-party-agent' },
|
||||||
{ title: '🚧 ' + t('nav.cdn_setup'), path: '/docs/integration/cdn-setup' },
|
{ title: t('nav.cdn_setup'), path: '/docs/integration/cdn-setup' },
|
||||||
{
|
{
|
||||||
title: '🚧 ' + t('nav.security_permissions'),
|
title: '🚧 ' + t('nav.security_permissions'),
|
||||||
path: '/docs/integration/security-permissions',
|
path: '/docs/integration/security-permissions',
|
||||||
|
|||||||
@@ -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>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
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') })
|
||||||
@@ -32,5 +36,6 @@ export default defineConfig({
|
|||||||
'import.meta.env.LLM_MODEL_NAME': JSON.stringify(process.env.LLM_MODEL_NAME),
|
'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_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.LLM_BASE_URL': JSON.stringify(process.env.LLM_BASE_URL),
|
||||||
|
'import.meta.env.VERSION': JSON.stringify(pageAgentPkg.version),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user