Compare commits

...

10 Commits

Author SHA1 Message Date
Simon 78ee72eea4 chore(version): bump version to 0.0.23 2026-01-14 15:46:10 +08:00
Simon 3b71c535b1 fix: clean up debug code 2026-01-14 15:23:02 +08:00
Simon 6615f7b300 Merge pull request #110 from alibaba/fix/panel-text-uncompleted
feat: add HTML escaping to Panel
2026-01-14 15:17:43 +08:00
Simon 38caae5e23 feat: add HTML escaping to Panel 2026-01-14 15:15:51 +08:00
Simon 1751ba325d chore(version): bump version to 0.0.22 2026-01-13 21:01:16 +08:00
Simon 066b045822 Merge pull request #108 from alibaba/feat/dual-umd
feat: add `cdn` packages for iife builds
2026-01-13 20:58:37 +08:00
Simon 3b21c42cdb feat: add cdn packages for iife builds 2026-01-13 20:52:41 +08:00
Simon 8b1e91d665 Merge pull request #107 from alibaba/refactor/mv-simulator-mask-to-page-controller
Refactor: move simulator mask to page controller
2026-01-13 17:38:37 +08:00
Simon aa5909264a chore: mv mask code to folder 2026-01-13 17:35:24 +08:00
Simon b6232d4e21 refactor: mv SimulatorMask from ui to page-controller 2026-01-13 17:33:38 +08:00
27 changed files with 295 additions and 74 deletions
+29 -25
View File
@@ -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
+3 -2
View File
@@ -79,8 +79,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/ # 文档站点
``` ```
+3 -2
View File
@@ -79,8 +79,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
``` ```
+27 -14
View File
@@ -1,18 +1,19 @@
{ {
"name": "root", "name": "root",
"version": "0.0.21", "version": "0.0.23",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "root", "name": "root",
"version": "0.0.21", "version": "0.0.23",
"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": {
@@ -1597,6 +1598,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
@@ -8140,9 +8145,17 @@
"zod": "^3.25.0 || ^4.0.0" "zod": "^3.25.0 || ^4.0.0"
} }
}, },
"packages/cdn": {
"name": "@page-agent/cdn",
"version": "0.0.23",
"license": "MIT",
"dependencies": {
"page-agent": "0.0.23"
}
},
"packages/llms": { "packages/llms": {
"name": "@page-agent/llms", "name": "@page-agent/llms",
"version": "0.0.21", "version": "0.0.23",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"chalk": "^5.6.2", "chalk": "^5.6.2",
@@ -8150,32 +8163,32 @@
} }
}, },
"packages/page-agent": { "packages/page-agent": {
"version": "0.0.21", "version": "0.0.23",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@page-agent/llms": "0.0.21", "@page-agent/llms": "0.0.23",
"@page-agent/page-controller": "0.0.21", "@page-agent/page-controller": "0.0.23",
"@page-agent/ui": "0.0.21", "@page-agent/ui": "0.0.23",
"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.0.23",
"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.0.23",
"license": "MIT"
},
"packages/website": { "packages/website": {
"name": "@page-agent/website", "name": "@page-agent/website",
"version": "0.0.21", "version": "0.0.23",
"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",
+2 -1
View File
@@ -1,13 +1,14 @@
{ {
"name": "root", "name": "root",
"private": true, "private": true,
"version": "0.0.21", "version": "0.0.23",
"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",
+23
View File
@@ -0,0 +1,23 @@
{
"name": "@page-agent/cdn",
"private": false,
"version": "0.0.23",
"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.0.23"
}
}
+59
View File
@@ -0,0 +1,59 @@
// @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')
window.pageAgent = new PageAgent()
}
console.log('🚀 page-agent.js initialized with config:', window.pageAgent.config)
window.pageAgent.panel.show()
})
+15
View File
@@ -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 }
+42
View File
@@ -0,0 +1,42 @@
// @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,
},
define: {
'process.env.NODE_ENV': '"production"',
},
}
})
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@page-agent/llms", "name": "@page-agent/llms",
"version": "0.0.21", "version": "0.0.23",
"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",
+4 -4
View File
@@ -1,7 +1,7 @@
{ {
"name": "page-agent", "name": "page-agent",
"private": false, "private": false,
"version": "0.0.21", "version": "0.0.23",
"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.21", "@page-agent/llms": "0.0.23",
"@page-agent/page-controller": "0.0.21", "@page-agent/page-controller": "0.0.23",
"@page-agent/ui": "0.0.21" "@page-agent/ui": "0.0.23"
} }
} }
+8 -10
View File
@@ -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'
@@ -92,8 +92,6 @@ export class PageAgent extends EventTarget {
/** PageController for DOM operations */ /** PageController for DOM operations */
pageController: PageController pageController: PageController
/** Fullscreen mask */
mask = new SimulatorMask()
/** History records */ /** History records */
history: AgentHistory[] = [] history: AgentHistory[] = []
@@ -114,8 +112,11 @@ export class PageAgent extends EventTarget {
}) })
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) => {
@@ -162,7 +163,7 @@ 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()
@@ -485,7 +486,7 @@ export class PageAgent extends EventTarget {
// 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()
} }
@@ -496,9 +497,7 @@ export class PageAgent extends EventTarget {
const pi = await this.pageController.getPageInfo() const pi = await this.pageController.getPageInfo()
const viewportExpansion = await this.pageController.getViewportExpansion() const viewportExpansion = await this.pageController.getViewportExpansion()
this.mask.wrapper.style.pointerEvents = 'none'
await this.pageController.updateTree() await this.pageController.updateTree()
this.mask.wrapper.style.pointerEvents = 'auto'
let simplifiedHTML = await this.pageController.getSimplifiedHTML() let simplifiedHTML = await this.pageController.getSimplifiedHTML()
@@ -545,7 +544,6 @@ export class PageAgent extends EventTarget {
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')
+4 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@page-agent/page-controller", "name": "@page-agent/page-controller",
"version": "0.0.21", "version": "0.0.23",
"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,18 @@ 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
} }
interface ActionResult { interface ActionResult {
@@ -64,12 +69,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 =======
@@ -136,12 +148,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 +180,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 +349,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 +376,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
} }
} }
+6
View File
@@ -0,0 +1,6 @@
/// <reference types="vite/client" />
declare module '*.module.css' {
const classes: Record<string, string>
export default classes
}
+1 -1
View File
@@ -29,7 +29,7 @@ export default defineConfig({
}, },
outDir: resolve(__dirname, 'dist', 'lib'), outDir: resolve(__dirname, 'dist', 'lib'),
rollupOptions: { rollupOptions: {
external: ['@page-agent/*'], external: ['@page-agent/*', 'ai-motion'],
}, },
minify: false, minify: false,
sourcemap: true, sourcemap: true,
+2 -5
View File
@@ -1,6 +1,6 @@
{ {
"name": "@page-agent/ui", "name": "@page-agent/ui",
"version": "0.0.21", "version": "0.0.23",
"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"
} }
} }
+2 -5
View File
@@ -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}
-1
View File
@@ -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'
+12
View File
@@ -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, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;')
}
+1 -1
View File
@@ -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 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "@page-agent/website", "name": "@page-agent/website",
"private": true, "private": true,
"version": "0.0.21", "version": "0.0.23",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
@@ -121,6 +121,13 @@ export default function Configuration() {
/** Viewport expansion in pixels (-1 for full page) */ /** Viewport expansion in pixels (-1 for full page) */
viewportExpansion?: number viewportExpansion?: number
/**
* Enable visual mask overlay during automation.
* Blocks user interaction while agent is running.
* Default: false for PageController, true for PageAgent.
*/
enableMask?: boolean
}`} }`}
/> />
</section> </section>