Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f21914284c | |||
| 035091045a | |||
| 1cd18b3ba9 | |||
| 8ba04bf243 | |||
| 67bf38c8d5 | |||
| 8885bde0ec | |||
| b33e361cee | |||
| d1c8ca8197 | |||
| 2b8b6ef86a | |||
| 0b46fd1d7a | |||
| e2819714f1 | |||
| 3e6c431a0b | |||
| d0836fd788 | |||
| 32c17627e9 | |||
| 13a6d2c164 | |||
| d756a2ef1c | |||
| b9b812cc25 | |||
| 70caf6856b | |||
| 34dad35698 | |||
| 34e3a1a75c | |||
| cac86f2fbf | |||
| d1290c8070 | |||
| 49d6163a70 | |||
| 20f33ac478 | |||
| 683602bb6b | |||
| ad19a26a57 | |||
| 51f81fc44c | |||
| 71905a6ca0 | |||
| 9ada66bb43 |
@@ -13,10 +13,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [20, 22]
|
||||
node-version: [20, 24, 25]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v6
|
||||
@@ -24,6 +24,13 @@ jobs:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'npm'
|
||||
|
||||
# test on default version of npm
|
||||
# - 9.6~10.8 on node@20
|
||||
# - 11.3~11.6 on node@24
|
||||
|
||||
- name: Node and NPM version
|
||||
run: node --version && npm --version
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
|
||||
|
||||
@@ -13,12 +13,12 @@ jobs:
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 24
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
|
||||
@@ -13,12 +13,12 @@ jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 24
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
# Ensure npm 11.5.1 or later is installed
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
run: npm ci
|
||||
|
||||
- name: Build
|
||||
run: npm run build:lib
|
||||
run: npm run build
|
||||
|
||||
- name: Publish to npm
|
||||
run: npm publish --workspace=page-agent
|
||||
- name: Publish all public packages
|
||||
run: npm publish --workspaces --access public
|
||||
@@ -23,6 +23,7 @@ dist-ssr
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
.qoder
|
||||
|
||||
|
||||
#
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
npx lint-staged
|
||||
npx lint-staged --allow-empty
|
||||
Vendored
+1
-1
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"]
|
||||
"recommendations": ["dbaeumer.vscode-eslint", "prettier.prettier-vscode"]
|
||||
}
|
||||
|
||||
@@ -7,13 +7,16 @@ This is a **monorepo** with npm workspaces containing **two main packages**:
|
||||
1. **Core Library** (`packages/page-agent/`) - Pure JavaScript/TypeScript AI agent library for browser DOM automation, published as `page-agent` on npm
|
||||
2. **Website** (`packages/website/`) - React documentation and landing page. Also as demo and test page for the core lib. private package `@page-agent/website`
|
||||
|
||||
And other internal packages. Such as:
|
||||
|
||||
- **Page Controller** (`packages/page-controller/`) - DOM operations and element interactions module. Independent of LLM, can be tested in unit tests.
|
||||
|
||||
## Development Commands
|
||||
|
||||
### Core Commands
|
||||
|
||||
```bash
|
||||
npm start # Start website dev server
|
||||
npm run dev # Same as start
|
||||
npm run build # Build all packages
|
||||
npm run build:lib # Build page-agent library only
|
||||
npm run lint # ESLint with TypeScript strict rules
|
||||
@@ -35,43 +38,74 @@ npm run build --workspace=@page-agent/website
|
||||
|
||||
### Monorepo Structure
|
||||
|
||||
```
|
||||
We adopt a very simple monorepo solution: ts reference + vite alias.
|
||||
|
||||
You must update tsconfig and vite config if you add/remove/rename a package.
|
||||
|
||||
```bash
|
||||
packages/
|
||||
├── page-agent/ # npm: "page-agent"
|
||||
│ ├── src/ # Core library source
|
||||
├── page-agent/ # npm: "page-agent" ⭐ MAIN
|
||||
│ ├── src/ # AI agent source
|
||||
│ │ ├── PageAgent.ts # Main AI agent class
|
||||
│ │ ├── tools/ # LLM tool definitions
|
||||
│ │ ├── llms/ # LLM integration
|
||||
│ │ └── ui/ # UI components
|
||||
│ ├── vite.config.js # Library build (ES + UMD)
|
||||
│ └── package.json
|
||||
└── website/ # npm: "@page-agent/website" (private)
|
||||
├── src/ # Website source (formerly pages/)
|
||||
├── index.html
|
||||
├── vite.config.js # Website build
|
||||
└── package.json
|
||||
├── website/ # npm: "@page-agent/website" (private) ⭐ MAIN
|
||||
│ ├── src/ # Website source
|
||||
│ └── index.html # Entry of vite webpage
|
||||
│
|
||||
│ # ...internal packages below...
|
||||
│
|
||||
└── page-controller/ # npm: "@page-agent/page-controller"
|
||||
└── src/ # DOM operations source
|
||||
├── PageController.ts # Main controller class
|
||||
├── actions.ts # Element interaction actions
|
||||
└── dom/ # DOM tree extraction
|
||||
```
|
||||
|
||||
`workspaces` must be written in topological order to guarantee build order.
|
||||
|
||||
```json
|
||||
"workspaces": [
|
||||
// internal deps ...
|
||||
"packages/page-controller",
|
||||
"packages/page-agent",
|
||||
"packages/website"
|
||||
],
|
||||
```
|
||||
|
||||
### Module Boundaries (Critical)
|
||||
|
||||
- **Core library** (`packages/page-agent/`): NEVER import from website - must remain pure JavaScript
|
||||
- **Website** (`packages/website/`): CAN import from `page-agent` for demos. Alias `@/` → `website/src/`
|
||||
- **Page Agent** (`packages/page-agent/`): The core lib. Imports from all internal packages. Never import from website.
|
||||
- **Page Controller** (`packages/page-controller/`): Internal lib. Pure DOM operations, NO LLM dependency. Never import from page-agent.
|
||||
|
||||
### PageController ↔ PageAgent Communication
|
||||
|
||||
All communication between PageAgent and PageController is async and isolated:
|
||||
|
||||
```typescript
|
||||
// PageAgent delegates DOM operations to PageController
|
||||
await this.pageController.updateTree() // Refresh DOM state
|
||||
await this.pageController.clickElement(index) // Click by index
|
||||
await this.pageController.inputText(index, text)
|
||||
await this.pageController.scroll({ down: true, numPages: 1 })
|
||||
|
||||
// PageController exposes state via async methods
|
||||
const simplifiedHTML = await this.pageController.getSimplifiedHTML()
|
||||
const pageInfo = await this.pageController.getPageInfo()
|
||||
```
|
||||
|
||||
DOM element references and internal state (selectorMap, elementTextMap) are encapsulated in PageController.
|
||||
|
||||
### DOM Pipeline
|
||||
|
||||
1. **DOM Extraction**: Convert live DOM to `FlatDomTree` via `src/dom/dom_tree/`
|
||||
1. **DOM Extraction**: Convert live DOM to `FlatDomTree` via `page-controller/src/dom/dom_tree/`
|
||||
2. **Dehydration**: DOM tree → simplified text for LLM processing
|
||||
3. **LLM Processing**: AI model returns action plans
|
||||
4. **Indexed Operations**: Map LLM responses back to specific DOM elements
|
||||
|
||||
### Event Bus Communication
|
||||
|
||||
Use `src/utils/bus.ts` for decoupled PageAgent ↔ UI communication:
|
||||
|
||||
```typescript
|
||||
// Emit from PageAgent
|
||||
getEventBus().emit('panel:show')
|
||||
getEventBus().emit('panel:update', { status: 'thinking' })
|
||||
|
||||
// Listen in UI components
|
||||
getEventBus().on('panel:show', () => panel.show())
|
||||
```
|
||||
3. **LLM Processing**: AI model returns action plans (in page-agent)
|
||||
4. **Indexed Operations**: PageAgent calls PageController methods by element index
|
||||
|
||||
### Hash Routing Requirement
|
||||
|
||||
@@ -91,27 +125,40 @@ Library auto-initializes when injected via script tag:
|
||||
|
||||
Query params configure `PageAgentConfig` automatically in `src/entry.ts`.
|
||||
|
||||
## File Organization
|
||||
## Key Files Reference
|
||||
|
||||
### Core Library (`packages/page-agent/src/`)
|
||||
### Page Agent (`packages/page-agent/`)
|
||||
|
||||
- `entry.ts` - CDN/UMD entry point with auto-initialization
|
||||
- `PageAgent.ts` - **Main AI agent class** orchestrating DOM operations
|
||||
- `tools/` - Agent tool implementations for web actions
|
||||
- `ui/` - UI components (Panel, SimulatorMask) with CSS modules
|
||||
- `utils/bus.ts` - **Type-safe event bus** for decoupled communication
|
||||
- `patches/` - Framework-specific optimizations (React, Antd compatibility)
|
||||
- `llms/` - LLM integration and communication layer
|
||||
- `dom/` - HTML serialization and page analysis utilities
|
||||
- `config/` - Configuration constants and settings
|
||||
| File | Description |
|
||||
|------|-------------|
|
||||
| `src/PageAgent.ts` | ⭐ Main AI agent class orchestrating tools and LLM |
|
||||
| `src/entry.ts` | CDN/UMD entry point with auto-initialization |
|
||||
| `src/tools/` | Tool definitions that call PageController methods |
|
||||
| `src/ui/` | UI components (Panel, SimulatorMask) with CSS modules |
|
||||
| `src/llms/` | LLM integration and communication layer |
|
||||
| `vite.config.js` | Library build configuration (ES + UMD) |
|
||||
|
||||
### Website (`packages/website/src/`)
|
||||
### Page Controller (`packages/page-controller/`)
|
||||
|
||||
- `main.tsx` - Site entry with hash routing setup
|
||||
- `router.tsx` - **Manual route definitions** (requires explicit registration)
|
||||
- `components/DocsLayout.tsx` - Navigation structure (hardcoded nav items)
|
||||
- `docs/[section]/[topic]/page.tsx` - Documentation pages
|
||||
- `test-pages/` - Library integration test pages
|
||||
| File | Description |
|
||||
|------|-------------|
|
||||
| `src/PageController.ts` | ⭐ Main controller class managing DOM state and actions |
|
||||
| `src/actions.ts` | Element interaction implementations (click, input, scroll) |
|
||||
| `src/dom/dom_tree/index.js` | Core DOM extraction engine (ported from browser-use) |
|
||||
| `src/dom/getPageInfo.ts` | Page scroll/size information |
|
||||
| `src/patches/` | Framework-specific optimizations (React, Antd) |
|
||||
| `src/types.ts` | TypeScript interfaces for controller |
|
||||
|
||||
### Website (`packages/website/`)
|
||||
|
||||
| File | Description |
|
||||
|------|-------------|
|
||||
| `src/router.tsx` | ⭐ Central routing (manual registration required) |
|
||||
| `src/components/DocsLayout.tsx` | Navigation structure (hardcoded nav items) |
|
||||
| `src/main.tsx` | Site entry with hash routing setup |
|
||||
| `src/docs/[section]/[topic]/page.tsx` | Documentation pages |
|
||||
| `src/test-pages/` | Library integration test pages |
|
||||
| `vite.config.js` | Website build configuration |
|
||||
|
||||
## Adding New Features
|
||||
|
||||
@@ -123,14 +170,15 @@ Query params configure `PageAgentConfig` automatically in `src/entry.ts`.
|
||||
|
||||
### New Agent Tool
|
||||
|
||||
1. Implement under `packages/page-agent/src/tools/`
|
||||
2. Export via `packages/page-agent/src/tools/index.ts`
|
||||
3. Wire into `PageAgent.ts` if needed
|
||||
1. Implement tool in `packages/page-agent/src/tools/index.ts`
|
||||
2. If tool needs DOM operations, add method to PageController first
|
||||
3. Tool calls `this.pageController.methodName()` for DOM interactions
|
||||
|
||||
### New UI Component
|
||||
### New PageController Action
|
||||
|
||||
1. Create in `packages/page-agent/src/ui/` with colocated CSS modules
|
||||
2. Use event bus for PageAgent communication
|
||||
1. Add action implementation in `packages/page-controller/src/actions.ts`
|
||||
2. Expose via async method in `PageController.ts`
|
||||
3. Export from `packages/page-controller/src/index.ts`
|
||||
|
||||
## Code Standards
|
||||
|
||||
@@ -153,18 +201,6 @@ Query params configure `PageAgentConfig` automatically in `src/entry.ts`.
|
||||
- Relative imports last
|
||||
- Blank lines between groups
|
||||
|
||||
## Critical Files to Understand
|
||||
|
||||
- `packages/page-agent/src/PageAgent.ts` - Core AI agent class with DOM manipulation
|
||||
- `packages/page-agent/src/dom/dom_tree/index.js` - DOM extraction engine
|
||||
- `packages/page-agent/src/utils/bus.ts` - Type-safe event bus system
|
||||
- `packages/page-agent/src/entry.ts` - Library entry point for CDN usage
|
||||
- `packages/page-agent/vite.config.js` - Library build configuration
|
||||
|
||||
- `packages/website/src/router.tsx` - Central routing definition (manual registration required)
|
||||
- `packages/website/src/components/DocsLayout.tsx` - Navigation structure
|
||||
- `packages/website/vite.config.js` - Website build configuration
|
||||
|
||||
## Debugging Common Issues
|
||||
|
||||
### Blank Documentation Pages
|
||||
@@ -178,5 +214,4 @@ Query params configure `PageAgentConfig` automatically in `src/entry.ts`.
|
||||
|
||||
1. Check `packages/page-agent/dist/lib/page-agent.umd.js` builds correctly
|
||||
2. Test CDN injection with query params
|
||||
3. Verify event bus communications are properly typed
|
||||
4. Use `packages/website/src/test-pages/` for isolated testing
|
||||
|
||||
+14
-23
@@ -7,11 +7,9 @@ Thank you for your interest in contributing to Page-Agent! We welcome contributi
|
||||
### Development Setup
|
||||
|
||||
1. **Prerequisites**
|
||||
- Node.js 20+
|
||||
- npm 10+
|
||||
- typescript as required in package.json
|
||||
- vscode or other editors that support ts/eslint/prettier
|
||||
- make sure `eslint`, `auto-format` and `commit-lint` all work
|
||||
- `node.js >= 20` with `npm >= 10`
|
||||
- An editor that supports `ts/eslint/prettier`
|
||||
- Make sure `eslint`, `prettier` and `commitlint` work well
|
||||
|
||||
2. **Setup**
|
||||
|
||||
@@ -27,7 +25,11 @@ This is a **monorepo** with npm workspaces containing **two main packages**:
|
||||
1. **Core Library** (`packages/page-agent/`) - Pure JavaScript/TypeScript AI agent library for browser DOM automation, published as `page-agent` on npm
|
||||
2. **Website** (`packages/website/`) - React documentation and landing page. Also as demo and test page for the core lib. private package `@page-agent/website`
|
||||
|
||||
We use a simplified monorepo solution with native npm-workspace. No fancy tooling. Hoisting is required.
|
||||
We use a simplified monorepo solution with `native npm-workspace + ts reference + vite alias`. No fancy tooling. Hoisting is required.
|
||||
|
||||
- When developing. Use alias so that we don't have to pre-build.
|
||||
- When bundling. Use external and disable ts `paths` alias to leave deps out.
|
||||
- When bundling `UMD` and `Website`. Bundle everything including local packages.
|
||||
|
||||
## 🤝 How to Contribute
|
||||
|
||||
@@ -97,19 +99,6 @@ We use a simplified monorepo solution with native npm-workspace. No fancy toolin
|
||||
- Github Copilot: `./.github/copilot-instructions.md`
|
||||
- Claude Code: `./CLAUDE.md`
|
||||
|
||||
### Core Library (`src/`)
|
||||
|
||||
- **No React dependencies** - Pure JavaScript/TypeScript only
|
||||
- Use event bus for component communication
|
||||
- Follow browser-use patterns for DOM operations
|
||||
|
||||
### Doc&demo Site (`pages/`)
|
||||
|
||||
- Use React 19 + TypeScript
|
||||
- Follow hash routing patterns (`useHashLocation`)
|
||||
- Use Tailwind CSS for styling
|
||||
- Add new docs pages to both router and sidebar
|
||||
|
||||
## 🔧 Development Workflows
|
||||
|
||||
### Website Development
|
||||
@@ -120,11 +109,13 @@ npm start # React development server
|
||||
|
||||
### Core Lib Development and Testing
|
||||
|
||||
> @TODO this part is outdated. Update this.
|
||||
|
||||
- Config your LLM API
|
||||
- Start a local script
|
||||
|
||||
```bash
|
||||
npm run build:lib:watch # Auto-rebuild on changes
|
||||
npm run build:watch --workspace=page-agent # UMD with auto rebuild
|
||||
```
|
||||
|
||||
- Add a new bookmark enable it on other website
|
||||
@@ -135,9 +126,9 @@ npm start # React development server
|
||||
|
||||
### Adding Documentation
|
||||
|
||||
1. Create `pages/docs/section/page-name/page.tsx`
|
||||
2. Add route to `pages/router.tsx`
|
||||
3. Add navigation link to `pages/components/DocsLayout.tsx`
|
||||
1. Create `website/src/docs/section/page-name/page.tsx`
|
||||
2. Add route to `website/src/router.tsx`
|
||||
3. Add navigation link to `website/src/components/DocsLayout.tsx`
|
||||
|
||||
## 🎯 Contribution Areas
|
||||
|
||||
|
||||
+9
-8
@@ -67,16 +67,17 @@ await agent.execute('点击登录按钮')
|
||||
|
||||
## 🏗️ 架构设计
|
||||
|
||||
PageAgent 采用清晰的模块化架构:
|
||||
PageAgent adopts a simplified monorepo structure:
|
||||
|
||||
```
|
||||
src/
|
||||
├── PageAgent.ts # Agent 主流程
|
||||
├── dom/ # DOM 理解
|
||||
├── tools/ # 代理交互工具
|
||||
├── ui/ # UI 组件和面板
|
||||
├── llms/ # LLM 集成层
|
||||
└── utils/ # 事件总线和工具
|
||||
packages/
|
||||
├── page-agent/ # AI agent (npm: page-agent)
|
||||
│ ├── PageAgent # Agent main loop
|
||||
│ ├── tools/ # LLM tool definitions
|
||||
│ ├── ui/ # UI components & panels
|
||||
│ └── llms/ # LLM integration layer
|
||||
├── page-controller/ # DOM operations (npm: @page-agent/page-controller)
|
||||
└── website/ # Documentation site
|
||||
```
|
||||
|
||||
## 🤝 贡献
|
||||
|
||||
@@ -67,16 +67,17 @@ await agent.execute('Click the login button')
|
||||
|
||||
## 🏗️ Structure
|
||||
|
||||
PageAgent follows a clean, modular architecture:
|
||||
PageAgent adopts a simplified monorepo structure:
|
||||
|
||||
```
|
||||
src/
|
||||
├── PageAgent.ts # Agent main loop
|
||||
├── dom/ # DOM processing
|
||||
├── tools/ # Agent tools
|
||||
├── ui/ # UI components & panels
|
||||
├── llms/ # LLM integration layer
|
||||
└── utils/ # Event bus & utilities
|
||||
packages/
|
||||
├── page-agent/ # AI agent (npm: page-agent)
|
||||
│ ├── PageAgent # Agent main loop
|
||||
│ ├── tools/ # LLM tool definitions
|
||||
│ ├── ui/ # UI components & panels
|
||||
│ └── llms/ # LLM integration layer
|
||||
├── page-controller/ # DOM operations (npm: @page-agent/page-controller)
|
||||
└── website/ # Demo & Documentation site
|
||||
```
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
+13
-13
@@ -24,33 +24,33 @@ The development progress and future plans for PageAgent.
|
||||
- [ ] **Hooks and Events**
|
||||
- [x] **lifecycle hooks**
|
||||
- [ ] **lifecycle events**
|
||||
- [ ] **❗Pause and intervene**
|
||||
- Current pause does not allow user intervention.
|
||||
- [ ] **User takeover**
|
||||
- [#64](https://github.com/alibaba/page-agent/issues/64)
|
||||
- [ ] **❗Hijack `page_open/page_change/page_unload` behavior**
|
||||
- [ ] **Custom knowledge base and instructions**
|
||||
- [#45](https://github.com/alibaba/page-agent/issues/45)
|
||||
- [ ] **Black/white-list safeguard**
|
||||
- [ ] **Data-masking**
|
||||
- [#44](https://github.com/alibaba/page-agent/issues/44)
|
||||
- [ ] **Improve Memory**
|
||||
- Current phrasing can cause logic-loop for some models.
|
||||
- Test adding `Action` to memory.
|
||||
- [ ] **Tools for more complex tasks**
|
||||
- todo list
|
||||
- file sys
|
||||
- [#66](https://github.com/alibaba/page-agent/issues/66)
|
||||
- [#65](https://github.com/alibaba/page-agent/issues/65)
|
||||
- [ ] **Optimize for popular UI frameworks**
|
||||
- [x] **i18n of the website**
|
||||
- [x] Chinese version
|
||||
- [x] English version
|
||||
- [ ] **Testing suits**
|
||||
- [ ] **Support custom llm fetch**
|
||||
- [ ] **Refactor: Separate `Agent` and `Page-Controller`**
|
||||
- Agent should be able to run w/o dom.
|
||||
- Actions should be able to be injected through iframe.
|
||||
- [x] **Refactor: Separate `Agent` and `PageController`**
|
||||
- [ ] **Chrome-ext wrapper for multi-page tasks**
|
||||
|
||||
♻️ Following browser-use's update and contribute back.
|
||||
|
||||
## 📋 Pending Features
|
||||
|
||||
- [ ] **Chrome-ext wrapper**
|
||||
- [ ] **Tools for more complex tasks**
|
||||
- todo list
|
||||
- file sys
|
||||
- [ ] **Support custom llm fetch**
|
||||
- [ ] **Testing suits**
|
||||
- [ ] **Same-origin multi-page-app rally**
|
||||
- [ ] **Local MCP proxy**
|
||||
|
||||
|
||||
+7
-2
@@ -37,8 +37,10 @@ export default defineConfig([
|
||||
],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
project: ['./packages/*/tsconfig.json'],
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
// project: ['./tsconfig.json'],
|
||||
// project: ['./packages/*/tsconfig.json'],
|
||||
// tsconfigRootDir: import.meta.dirname,
|
||||
projectService: true,
|
||||
},
|
||||
ecmaVersion: 2020,
|
||||
globals: globals.browser,
|
||||
@@ -67,6 +69,9 @@ export default defineConfig([
|
||||
'react-dom/no-missing-button-type': 'off',
|
||||
'react-x/no-nested-component-definitions': 'off',
|
||||
'@typescript-eslint/prefer-optional-chain': 'off',
|
||||
|
||||
// 'require-await': 'off',
|
||||
'@typescript-eslint/require-await': 'off',
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
Generated
+172
-97
@@ -1,23 +1,27 @@
|
||||
{
|
||||
"name": "root",
|
||||
"version": "0.0.4",
|
||||
"version": "0.0.8",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "root",
|
||||
"version": "0.0.4",
|
||||
"version": "0.0.8",
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
"packages/page-controller",
|
||||
"packages/ui",
|
||||
"packages/page-agent",
|
||||
"packages/website"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^20.1.0",
|
||||
"@commitlint/config-conventional": "^20.0.0",
|
||||
"@eslint/js": "^9.37.0",
|
||||
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
|
||||
"@eslint/js": "^9.39.1",
|
||||
"@microsoft/api-extractor": "^7.55.1",
|
||||
"@trivago/prettier-plugin-sort-imports": "^6.0.0",
|
||||
"dotenv": "^17.2.3",
|
||||
"eslint": "^9.37.0",
|
||||
"eslint": "^9.39.1",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-react-dom": "^2.3.9",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
@@ -28,8 +32,10 @@
|
||||
"lint-staged": "^16.2.4",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.46.0",
|
||||
"vite": "^7.1.9"
|
||||
"typescript-eslint": "^8.48.1",
|
||||
"unplugin-dts": "^1.0.0-beta.6",
|
||||
"vite": "^7.2.6",
|
||||
"vite-plugin-css-injected-by-js": "^3.5.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0",
|
||||
@@ -1551,6 +1557,14 @@
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/@page-agent/page-controller": {
|
||||
"resolved": "packages/page-controller",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@page-agent/ui": {
|
||||
"resolved": "packages/ui",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@page-agent/website": {
|
||||
"resolved": "packages/website",
|
||||
"link": true
|
||||
@@ -2568,25 +2582,28 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@trivago/prettier-plugin-sort-imports": {
|
||||
"version": "5.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@trivago/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-5.2.2.tgz",
|
||||
"integrity": "sha512-fYDQA9e6yTNmA13TLVSA+WMQRc5Bn/c0EUBditUHNfMMxN7M82c38b1kEggVE3pLpZ0FwkwJkUEKMiOi52JXFA==",
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@trivago/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-6.0.0.tgz",
|
||||
"integrity": "sha512-Xarx55ow0R8oC7ViL5fPmDsg1EBa1dVhyZFVbFXNtPPJyW2w9bJADIla8YFSaNG9N06XfcklA9O9vmw4noNxkQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@babel/generator": "^7.26.5",
|
||||
"@babel/parser": "^7.26.7",
|
||||
"@babel/traverse": "^7.26.7",
|
||||
"@babel/types": "^7.26.7",
|
||||
"@babel/generator": "^7.28.0",
|
||||
"@babel/parser": "^7.28.0",
|
||||
"@babel/traverse": "^7.28.0",
|
||||
"@babel/types": "^7.28.0",
|
||||
"javascript-natural-sort": "^0.7.1",
|
||||
"lodash": "^4.17.21"
|
||||
"lodash-es": "^4.17.21",
|
||||
"minimatch": "^9.0.0",
|
||||
"parse-imports-exports": "^0.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">18.12"
|
||||
"node": ">= 20"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@vue/compiler-sfc": "3.x",
|
||||
"prettier": "2.x - 3.x",
|
||||
"prettier-plugin-ember-template-tag": ">= 2.0.0",
|
||||
"prettier-plugin-svelte": "3.x",
|
||||
"svelte": "4.x || 5.x"
|
||||
},
|
||||
@@ -2594,6 +2611,9 @@
|
||||
"@vue/compiler-sfc": {
|
||||
"optional": true
|
||||
},
|
||||
"prettier-plugin-ember-template-tag": {
|
||||
"optional": true
|
||||
},
|
||||
"prettier-plugin-svelte": {
|
||||
"optional": true
|
||||
},
|
||||
@@ -2602,6 +2622,32 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@trivago/prettier-plugin-sort-imports/node_modules/brace-expansion": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@trivago/prettier-plugin-sort-imports/node_modules/minimatch": {
|
||||
"version": "9.0.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
||||
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/argparse": {
|
||||
"version": "1.0.38",
|
||||
"resolved": "https://registry.npmjs.org/@types/argparse/-/argparse-1.0.38.tgz",
|
||||
@@ -2666,17 +2712,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.48.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.48.0.tgz",
|
||||
"integrity": "sha512-XxXP5tL1txl13YFtrECECQYeZjBZad4fyd3cFV4a19LkAY/bIp9fev3US4S5fDVV2JaYFiKAZ/GRTOLer+mbyQ==",
|
||||
"version": "8.48.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.48.1.tgz",
|
||||
"integrity": "sha512-X63hI1bxl5ohelzr0LY5coufyl0LJNthld+abwxpCoo6Gq+hSqhKwci7MUWkXo67mzgUK6YFByhmaHmUcuBJmA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.10.0",
|
||||
"@typescript-eslint/scope-manager": "8.48.0",
|
||||
"@typescript-eslint/type-utils": "8.48.0",
|
||||
"@typescript-eslint/utils": "8.48.0",
|
||||
"@typescript-eslint/visitor-keys": "8.48.0",
|
||||
"@typescript-eslint/scope-manager": "8.48.1",
|
||||
"@typescript-eslint/type-utils": "8.48.1",
|
||||
"@typescript-eslint/utils": "8.48.1",
|
||||
"@typescript-eslint/visitor-keys": "8.48.1",
|
||||
"graphemer": "^1.4.0",
|
||||
"ignore": "^7.0.0",
|
||||
"natural-compare": "^1.4.0",
|
||||
@@ -2690,7 +2736,7 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@typescript-eslint/parser": "^8.48.0",
|
||||
"@typescript-eslint/parser": "^8.48.1",
|
||||
"eslint": "^8.57.0 || ^9.0.0",
|
||||
"typescript": ">=4.8.4 <6.0.0"
|
||||
}
|
||||
@@ -2706,17 +2752,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "8.48.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.48.0.tgz",
|
||||
"integrity": "sha512-jCzKdm/QK0Kg4V4IK/oMlRZlY+QOcdjv89U2NgKHZk1CYTj82/RVSx1mV/0gqCVMJ/DA+Zf/S4NBWNF8GQ+eqQ==",
|
||||
"version": "8.48.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.48.1.tgz",
|
||||
"integrity": "sha512-PC0PDZfJg8sP7cmKe6L3QIL8GZwU5aRvUFedqSIpw3B+QjRSUZeeITC2M5XKeMXEzL6wccN196iy3JLwKNvDVA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.48.0",
|
||||
"@typescript-eslint/types": "8.48.0",
|
||||
"@typescript-eslint/typescript-estree": "8.48.0",
|
||||
"@typescript-eslint/visitor-keys": "8.48.0",
|
||||
"@typescript-eslint/scope-manager": "8.48.1",
|
||||
"@typescript-eslint/types": "8.48.1",
|
||||
"@typescript-eslint/typescript-estree": "8.48.1",
|
||||
"@typescript-eslint/visitor-keys": "8.48.1",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
@@ -2732,14 +2778,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/project-service": {
|
||||
"version": "8.48.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.48.0.tgz",
|
||||
"integrity": "sha512-Ne4CTZyRh1BecBf84siv42wv5vQvVmgtk8AuiEffKTUo3DrBaGYZueJSxxBZ8fjk/N3DrgChH4TOdIOwOwiqqw==",
|
||||
"version": "8.48.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.48.1.tgz",
|
||||
"integrity": "sha512-HQWSicah4s9z2/HifRPQ6b6R7G+SBx64JlFQpgSSHWPKdvCZX57XCbszg/bapbRsOEv42q5tayTYcEFpACcX1w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/tsconfig-utils": "^8.48.0",
|
||||
"@typescript-eslint/types": "^8.48.0",
|
||||
"@typescript-eslint/tsconfig-utils": "^8.48.1",
|
||||
"@typescript-eslint/types": "^8.48.1",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
@@ -2754,14 +2800,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.48.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.48.0.tgz",
|
||||
"integrity": "sha512-uGSSsbrtJrLduti0Q1Q9+BF1/iFKaxGoQwjWOIVNJv0o6omrdyR8ct37m4xIl5Zzpkp69Kkmvom7QFTtue89YQ==",
|
||||
"version": "8.48.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.48.1.tgz",
|
||||
"integrity": "sha512-rj4vWQsytQbLxC5Bf4XwZ0/CKd362DkWMUkviT7DCS057SK64D5lH74sSGzhI6PDD2HCEq02xAP9cX68dYyg1w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.48.0",
|
||||
"@typescript-eslint/visitor-keys": "8.48.0"
|
||||
"@typescript-eslint/types": "8.48.1",
|
||||
"@typescript-eslint/visitor-keys": "8.48.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -2772,9 +2818,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/tsconfig-utils": {
|
||||
"version": "8.48.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.48.0.tgz",
|
||||
"integrity": "sha512-WNebjBdFdyu10sR1M4OXTt2OkMd5KWIL+LLfeH9KhgP+jzfDV/LI3eXzwJ1s9+Yc0Kzo2fQCdY/OpdusCMmh6w==",
|
||||
"version": "8.48.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.48.1.tgz",
|
||||
"integrity": "sha512-k0Jhs4CpEffIBm6wPaCXBAD7jxBtrHjrSgtfCjUvPp9AZ78lXKdTR8fxyZO5y4vWNlOvYXRtngSZNSn+H53Jkw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -2789,15 +2835,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "8.48.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.48.0.tgz",
|
||||
"integrity": "sha512-zbeVaVqeXhhab6QNEKfK96Xyc7UQuoFWERhEnj3mLVnUWrQnv15cJNseUni7f3g557gm0e46LZ6IJ4NJVOgOpw==",
|
||||
"version": "8.48.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.48.1.tgz",
|
||||
"integrity": "sha512-1jEop81a3LrJQLTf/1VfPQdhIY4PlGDBc/i67EVWObrtvcziysbLN3oReexHOM6N3jyXgCrkBsZpqwH0hiDOQg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.48.0",
|
||||
"@typescript-eslint/typescript-estree": "8.48.0",
|
||||
"@typescript-eslint/utils": "8.48.0",
|
||||
"@typescript-eslint/types": "8.48.1",
|
||||
"@typescript-eslint/typescript-estree": "8.48.1",
|
||||
"@typescript-eslint/utils": "8.48.1",
|
||||
"debug": "^4.3.4",
|
||||
"ts-api-utils": "^2.1.0"
|
||||
},
|
||||
@@ -2814,9 +2860,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "8.48.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.48.0.tgz",
|
||||
"integrity": "sha512-cQMcGQQH7kwKoVswD1xdOytxQR60MWKM1di26xSUtxehaDs/32Zpqsu5WJlXTtTTqyAVK8R7hvsUnIXRS+bjvA==",
|
||||
"version": "8.48.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.48.1.tgz",
|
||||
"integrity": "sha512-+fZ3LZNeiELGmimrujsDCT4CRIbq5oXdHe7chLiW8qzqyPMnn1puNstCrMNVAqwcl2FdIxkuJ4tOs/RFDBVc/Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -2828,16 +2874,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.48.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.48.0.tgz",
|
||||
"integrity": "sha512-ljHab1CSO4rGrQIAyizUS6UGHHCiAYhbfcIZ1zVJr5nMryxlXMVWS3duFPSKvSUbFPwkXMFk1k0EMIjub4sRRQ==",
|
||||
"version": "8.48.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.48.1.tgz",
|
||||
"integrity": "sha512-/9wQ4PqaefTK6POVTjJaYS0bynCgzh6ClJHGSBj06XEHjkfylzB+A3qvyaXnErEZSaxhIo4YdyBgq6j4RysxDg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/project-service": "8.48.0",
|
||||
"@typescript-eslint/tsconfig-utils": "8.48.0",
|
||||
"@typescript-eslint/types": "8.48.0",
|
||||
"@typescript-eslint/visitor-keys": "8.48.0",
|
||||
"@typescript-eslint/project-service": "8.48.1",
|
||||
"@typescript-eslint/tsconfig-utils": "8.48.1",
|
||||
"@typescript-eslint/types": "8.48.1",
|
||||
"@typescript-eslint/visitor-keys": "8.48.1",
|
||||
"debug": "^4.3.4",
|
||||
"minimatch": "^9.0.4",
|
||||
"semver": "^7.6.0",
|
||||
@@ -2882,16 +2928,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.48.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.48.0.tgz",
|
||||
"integrity": "sha512-yTJO1XuGxCsSfIVt1+1UrLHtue8xz16V8apzPYI06W0HbEbEWHxHXgZaAgavIkoh+GeV6hKKd5jm0sS6OYxWXQ==",
|
||||
"version": "8.48.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.48.1.tgz",
|
||||
"integrity": "sha512-fAnhLrDjiVfey5wwFRwrweyRlCmdz5ZxXz2G/4cLn0YDLjTapmN4gcCsTBR1N2rWnZSDeWpYtgLDsJt+FpmcwA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.7.0",
|
||||
"@typescript-eslint/scope-manager": "8.48.0",
|
||||
"@typescript-eslint/types": "8.48.0",
|
||||
"@typescript-eslint/typescript-estree": "8.48.0"
|
||||
"@typescript-eslint/scope-manager": "8.48.1",
|
||||
"@typescript-eslint/types": "8.48.1",
|
||||
"@typescript-eslint/typescript-estree": "8.48.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -2906,13 +2952,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.48.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.48.0.tgz",
|
||||
"integrity": "sha512-T0XJMaRPOH3+LBbAfzR2jalckP1MSG/L9eUtY0DEzUyVaXJ/t6zN0nR7co5kz0Jko/nkSYCBRkz1djvjajVTTg==",
|
||||
"version": "8.48.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.48.1.tgz",
|
||||
"integrity": "sha512-BmxxndzEWhE4TIEEMBs8lP3MBWN3jFPs/p6gPm/wkv02o41hI6cq9AuSmGAaTTHPtA1FTi2jBre4A9rm5ZmX+Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.48.0",
|
||||
"@typescript-eslint/types": "8.48.1",
|
||||
"eslint-visitor-keys": "^4.2.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -3005,7 +3051,6 @@
|
||||
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fast-uri": "^3.0.1",
|
||||
@@ -4503,9 +4548,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/i18next": {
|
||||
"version": "25.6.3",
|
||||
"resolved": "https://registry.npmjs.org/i18next/-/i18next-25.6.3.tgz",
|
||||
"integrity": "sha512-AEQvoPDljhp67a1+NsnG/Wb1Nh6YoSvtrmeEd24sfGn3uujCtXCF3cXpr7ulhMywKNFF7p3TX1u2j7y+caLOJg==",
|
||||
"version": "25.7.1",
|
||||
"resolved": "https://registry.npmjs.org/i18next/-/i18next-25.7.1.tgz",
|
||||
"integrity": "sha512-XbTnkh1yCZWSAZGnA9xcQfHcYNgZs2cNxm+c6v1Ma9UAUGCeJPplRe1ILia6xnDvXBjk0uXU+Z8FYWhA19SKFw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -5262,6 +5307,13 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash-es": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
|
||||
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.camelcase": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
|
||||
@@ -5603,6 +5655,16 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/parse-imports-exports": {
|
||||
"version": "0.2.4",
|
||||
"resolved": "https://registry.npmjs.org/parse-imports-exports/-/parse-imports-exports-0.2.4.tgz",
|
||||
"integrity": "sha512-4s6vd6dx1AotCx/RCI2m7t7GCh5bDRUtGNvRfHSP2wbBQdMi67pPe7mtzmgwcaQ8VKK/6IB7Glfyu3qdZJPybQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"parse-statements": "1.0.11"
|
||||
}
|
||||
},
|
||||
"node_modules/parse-json": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
|
||||
@@ -5622,6 +5684,13 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/parse-statements": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/parse-statements/-/parse-statements-1.0.11.tgz",
|
||||
"integrity": "sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/path-browserify": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz",
|
||||
@@ -6368,16 +6437,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript-eslint": {
|
||||
"version": "8.48.0",
|
||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.48.0.tgz",
|
||||
"integrity": "sha512-fcKOvQD9GUn3Xw63EgiDqhvWJ5jsyZUaekl3KVpGsDJnN46WJTe3jWxtQP9lMZm1LJNkFLlTaWAxK2vUQR+cqw==",
|
||||
"version": "8.48.1",
|
||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.48.1.tgz",
|
||||
"integrity": "sha512-FbOKN1fqNoXp1hIl5KYpObVrp0mCn+CLgn479nmu2IsRMrx2vyv74MmsBLVlhg8qVwNFGbXSp8fh1zp8pEoC2A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "8.48.0",
|
||||
"@typescript-eslint/parser": "8.48.0",
|
||||
"@typescript-eslint/typescript-estree": "8.48.0",
|
||||
"@typescript-eslint/utils": "8.48.0"
|
||||
"@typescript-eslint/eslint-plugin": "8.48.1",
|
||||
"@typescript-eslint/parser": "8.48.1",
|
||||
"@typescript-eslint/typescript-estree": "8.48.1",
|
||||
"@typescript-eslint/utils": "8.48.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -6563,9 +6632,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "7.2.4",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.2.4.tgz",
|
||||
"integrity": "sha512-NL8jTlbo0Tn4dUEXEsUg8KeyG/Lkmc4Fnzb8JXN/Ykm9G4HNImjtABMJgkQoVjOBN/j2WAwDTRytdqJbZsah7w==",
|
||||
"version": "7.2.6",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.2.6.tgz",
|
||||
"integrity": "sha512-tI2l/nFHC5rLh7+5+o7QjKjSR04ivXDF4jcgV0f/bTQ+OJiITy5S6gaynVsEM+7RqzufMnVbIon6Sr5x1SDYaQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
@@ -6935,31 +7004,37 @@
|
||||
}
|
||||
},
|
||||
"packages/page-agent": {
|
||||
"version": "0.0.4",
|
||||
"version": "0.0.8",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ai-motion": "^0.4.7",
|
||||
"@page-agent/page-controller": "0.0.8",
|
||||
"@page-agent/ui": "0.0.8",
|
||||
"chalk": "^5.6.2",
|
||||
"zod": "^4.1.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@microsoft/api-extractor": "^7.55.1",
|
||||
"unplugin-dts": "^1.0.0-beta.6",
|
||||
"vite-plugin-css-injected-by-js": "^3.5.2"
|
||||
}
|
||||
},
|
||||
"packages/page-controller": {
|
||||
"name": "@page-agent/page-controller",
|
||||
"version": "0.0.8",
|
||||
"license": "MIT"
|
||||
},
|
||||
"packages/ui": {
|
||||
"name": "@page-agent/ui",
|
||||
"version": "0.0.8",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ai-motion": "^0.4.7"
|
||||
}
|
||||
},
|
||||
"packages/website": {
|
||||
"name": "@page-agent/website",
|
||||
"version": "0.0.4",
|
||||
"dependencies": {
|
||||
"page-agent": "*"
|
||||
},
|
||||
"version": "0.0.8",
|
||||
"devDependencies": {
|
||||
"@tailwindcss/vite": "^4.1.14",
|
||||
"@types/react": "^19.2.2",
|
||||
"@types/react-dom": "^19.2.1",
|
||||
"@vitejs/plugin-react-swc": "^4.1.0",
|
||||
"i18next": "^25.6.0",
|
||||
"i18next": "^25.7.1",
|
||||
"i18next-browser-languagedetector": "^8.2.0",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
|
||||
+14
-9
@@ -1,10 +1,13 @@
|
||||
{
|
||||
"name": "root",
|
||||
"private": true,
|
||||
"version": "0.0.6",
|
||||
"version": "0.0.8",
|
||||
"type": "module",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
"packages/page-controller",
|
||||
"packages/ui",
|
||||
"packages/page-agent",
|
||||
"packages/website"
|
||||
],
|
||||
"description": "AI-powered UI agent for web applications",
|
||||
"author": "Simon<gaomeng1900>",
|
||||
@@ -19,10 +22,9 @@
|
||||
"npm": ">=10.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "npm run dev --workspace=@page-agent/website",
|
||||
"start": "npm run dev --workspace=@page-agent/website",
|
||||
"build": "npm run build --workspaces --if-present",
|
||||
"build:lib": "npm run build --workspace=page-agent",
|
||||
"build:lib": "npm run build:lib --workspace=page-agent",
|
||||
"version": "node scripts/sync-version.js",
|
||||
"lint": "eslint .",
|
||||
"prepare": "husky"
|
||||
@@ -30,10 +32,10 @@
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^20.1.0",
|
||||
"@commitlint/config-conventional": "^20.0.0",
|
||||
"@eslint/js": "^9.37.0",
|
||||
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
|
||||
"@eslint/js": "^9.39.1",
|
||||
"@trivago/prettier-plugin-sort-imports": "^6.0.0",
|
||||
"dotenv": "^17.2.3",
|
||||
"eslint": "^9.37.0",
|
||||
"eslint": "^9.39.1",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-react-dom": "^2.3.9",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
@@ -44,8 +46,11 @@
|
||||
"lint-staged": "^16.2.4",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.46.0",
|
||||
"vite": "^7.1.9"
|
||||
"typescript-eslint": "^8.48.1",
|
||||
"vite": "^7.2.6",
|
||||
"@microsoft/api-extractor": "^7.55.1",
|
||||
"unplugin-dts": "^1.0.0-beta.6",
|
||||
"vite-plugin-css-injected-by-js": "^3.5.2"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,ts,cjs,cts,mjs,mts}": [
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "page-agent",
|
||||
"private": false,
|
||||
"version": "0.0.6",
|
||||
"version": "0.0.8",
|
||||
"type": "module",
|
||||
"main": "./dist/lib/page-agent.js",
|
||||
"module": "./dist/lib/page-agent.js",
|
||||
@@ -14,9 +14,7 @@
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist/",
|
||||
"README.md",
|
||||
"LICENSE"
|
||||
"dist/"
|
||||
],
|
||||
"description": "GUI agent for web applications - add intelligent automation to any webpage with a single script",
|
||||
"keywords": [
|
||||
@@ -39,21 +37,17 @@
|
||||
},
|
||||
"homepage": "https://alibaba.github.io/page-agent/",
|
||||
"scripts": {
|
||||
"build": "MODE=lib vite build && MODE=umd vite build",
|
||||
"build": "npm run build:lib && npm run build:umd",
|
||||
"build:lib": "MODE=lib vite build",
|
||||
"build:umd": "MODE=umd vite build",
|
||||
"build:watch": "MODE=lib vite build --watch",
|
||||
"build:watch": "MODE=umd vite build --watch",
|
||||
"prepublishOnly": "node -e \"const fs=require('fs');['README.md','LICENSE'].forEach(f=>fs.copyFileSync('../../'+f,f))\"",
|
||||
"postpublish": "node -e \"['README.md','LICENSE'].forEach(f=>{try{require('fs').unlinkSync(f)}catch{}})\""
|
||||
},
|
||||
"dependencies": {
|
||||
"ai-motion": "^0.4.7",
|
||||
"chalk": "^5.6.2",
|
||||
"zod": "^4.1.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@microsoft/api-extractor": "^7.55.1",
|
||||
"unplugin-dts": "^1.0.0-beta.6",
|
||||
"vite-plugin-css-injected-by-js": "^3.5.2"
|
||||
"zod": "^4.1.12",
|
||||
"@page-agent/page-controller": "0.0.8",
|
||||
"@page-agent/ui": "0.0.8"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,24 +2,18 @@
|
||||
* Copyright (C) 2025 Alibaba Group Holding Limited
|
||||
* All rights reserved.
|
||||
*/
|
||||
import { PageController } from '@page-agent/page-controller'
|
||||
import { Panel, SimulatorMask } from '@page-agent/ui'
|
||||
import chalk from 'chalk'
|
||||
import zod from 'zod'
|
||||
|
||||
import type { PageAgentConfig } from './config'
|
||||
import { MAX_STEPS, VIEWPORT_EXPANSION } from './config/constants'
|
||||
import * as dom from './dom'
|
||||
import { FlatDomTree, InteractiveElementDomNode } from './dom/dom_tree/type'
|
||||
import { getPageInfo } from './dom/getPageInfo'
|
||||
import { I18n } from './i18n'
|
||||
import { MAX_STEPS } from './config/constants'
|
||||
import { LLM, type Tool } from './llms'
|
||||
import { patchReact } from './patches/react'
|
||||
import SYSTEM_PROMPT from './prompts/system_prompt.md?raw'
|
||||
import { tools } from './tools'
|
||||
import { Panel, getToolCompletedText, getToolExecutingText } from './ui/Panel'
|
||||
import { SimulatorMask } from './ui/SimulatorMask'
|
||||
import { trimLines, uid, waitUntil } from './utils'
|
||||
import { assert } from './utils/assert'
|
||||
import { getEventBus } from './utils/bus'
|
||||
|
||||
export type { PageAgentConfig }
|
||||
export { tool, type PageAgentTool } from './tools'
|
||||
@@ -74,8 +68,6 @@ export interface ExecutionResult {
|
||||
export class PageAgent extends EventTarget {
|
||||
config: PageAgentConfig
|
||||
id = uid()
|
||||
bus = getEventBus(this.id)
|
||||
i18n: I18n
|
||||
panel: Panel
|
||||
tools: typeof tools
|
||||
paused = false
|
||||
@@ -86,20 +78,12 @@ export class PageAgent extends EventTarget {
|
||||
#llm: LLM
|
||||
#totalWaitTime = 0
|
||||
#abortController = new AbortController()
|
||||
#llmRetryListener: ((e: Event) => void) | null = null
|
||||
#llmErrorListener: ((e: Event) => void) | null = null
|
||||
#beforeUnloadListener: ((e: Event) => void) | null = null
|
||||
|
||||
/** Corresponds to eval_page in browser-use */
|
||||
flatTree: FlatDomTree | null = null
|
||||
/**
|
||||
* All highlighted index-mapped interactive elements
|
||||
* Corresponds to DOMState.selector_map in browser-use
|
||||
*/
|
||||
selectorMap = new Map<number, InteractiveElementDomNode>()
|
||||
/** highlight index -> element text */
|
||||
elementTextMap = new Map<number, string>()
|
||||
/** Corresponds to clickable_elements_to_string in browser-use */
|
||||
simplifiedHTML = '<EMPTY>'
|
||||
/** last time the tree was updated */
|
||||
lastTimeUpdate = 0
|
||||
/** PageController for DOM operations */
|
||||
pageController: PageController
|
||||
|
||||
/** Fullscreen mask */
|
||||
mask = new SimulatorMask()
|
||||
@@ -110,11 +94,34 @@ export class PageAgent extends EventTarget {
|
||||
super()
|
||||
|
||||
this.config = config
|
||||
this.#llm = new LLM(this.config, this.id)
|
||||
this.i18n = new I18n(this.config.language)
|
||||
this.panel = new Panel(this)
|
||||
this.#llm = new LLM(this.config)
|
||||
this.panel = new Panel({
|
||||
language: this.config.language,
|
||||
onExecuteTask: (task) => this.execute(task),
|
||||
onStop: () => this.dispose(),
|
||||
onPauseToggle: () => {
|
||||
this.paused = !this.paused
|
||||
return this.paused
|
||||
},
|
||||
getPaused: () => this.paused,
|
||||
})
|
||||
this.tools = new Map(tools)
|
||||
|
||||
// Initialize PageController with config
|
||||
this.pageController = new PageController(this.config)
|
||||
|
||||
// Listen to LLM events
|
||||
this.#llmRetryListener = (e) => {
|
||||
const { current, max } = (e as CustomEvent).detail
|
||||
this.panel.update({ type: 'retry', current, max })
|
||||
}
|
||||
this.#llmErrorListener = (e) => {
|
||||
const { error } = (e as CustomEvent).detail
|
||||
this.panel.update({ type: 'error', message: `step failed: ${error.message}` })
|
||||
}
|
||||
this.#llm.addEventListener('retry', this.#llmRetryListener)
|
||||
this.#llm.addEventListener('error', this.#llmErrorListener)
|
||||
|
||||
if (this.config.customTools) {
|
||||
for (const [name, tool] of Object.entries(this.config.customTools)) {
|
||||
if (tool === null) {
|
||||
@@ -129,11 +136,10 @@ export class PageAgent extends EventTarget {
|
||||
this.tools.delete('execute_javascript')
|
||||
}
|
||||
|
||||
patchReact(this)
|
||||
|
||||
window.addEventListener('beforeunload', (e) => {
|
||||
this.#beforeUnloadListener = (e) => {
|
||||
if (!this.disposed) this.dispose('PAGE_UNLOADING')
|
||||
})
|
||||
}
|
||||
window.addEventListener('beforeunload', this.#beforeUnloadListener)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -154,13 +160,10 @@ export class PageAgent extends EventTarget {
|
||||
// Show mask and panel
|
||||
this.mask.show()
|
||||
|
||||
this.bus.emit('panel:show')
|
||||
this.bus.emit('panel:reset')
|
||||
this.panel.show()
|
||||
this.panel.reset()
|
||||
|
||||
this.bus.emit('panel:update', {
|
||||
type: 'input',
|
||||
displayText: this.task,
|
||||
})
|
||||
this.panel.update({ type: 'input', task: this.task })
|
||||
|
||||
if (this.#abortController) {
|
||||
this.#abortController.abort()
|
||||
@@ -175,7 +178,7 @@ export class PageAgent extends EventTarget {
|
||||
while (true) {
|
||||
await onBeforeStep.call(this, step)
|
||||
|
||||
console.group(`step: ${step + 1}`)
|
||||
console.group(`step: ${step}`)
|
||||
|
||||
// abort
|
||||
if (this.#abortController.signal.aborted) throw new Error('AbortError')
|
||||
@@ -184,10 +187,7 @@ export class PageAgent extends EventTarget {
|
||||
|
||||
// Update status to thinking
|
||||
console.log(chalk.blue('Thinking...'))
|
||||
this.bus.emit('panel:update', {
|
||||
type: 'thinking',
|
||||
displayText: this.i18n.t('ui.panel.thinking'),
|
||||
})
|
||||
this.panel.update({ type: 'thinking' })
|
||||
|
||||
const result = await this.#llm.invoke(
|
||||
[
|
||||
@@ -197,7 +197,7 @@ export class PageAgent extends EventTarget {
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: this.#assembleUserPrompt(),
|
||||
content: await this.#assembleUserPrompt(),
|
||||
},
|
||||
],
|
||||
{ AgentOutput: this.#packMacroTool() },
|
||||
@@ -317,22 +317,14 @@ export class PageAgent extends EventTarget {
|
||||
`)
|
||||
|
||||
console.log(brain)
|
||||
this.bus.emit('panel:update', {
|
||||
type: 'thinking',
|
||||
displayText: brain,
|
||||
})
|
||||
this.panel.update({ type: 'thinking', text: brain })
|
||||
|
||||
// Find the corresponding tool
|
||||
const tool = tools.get(toolName)
|
||||
assert(tool, `Tool ${toolName} not found. (@note should have been caught before this!!!)`)
|
||||
|
||||
console.log(chalk.blue.bold(`Executing tool: ${toolName}`), toolInput)
|
||||
this.bus.emit('panel:update', {
|
||||
type: 'tool_executing',
|
||||
toolName,
|
||||
toolArgs: toolInput,
|
||||
displayText: getToolExecutingText(toolName, toolInput, this.i18n),
|
||||
})
|
||||
this.panel.update({ type: 'toolExecuting', toolName, args: toolInput })
|
||||
|
||||
const startTime = Date.now()
|
||||
|
||||
@@ -354,16 +346,13 @@ export class PageAgent extends EventTarget {
|
||||
}
|
||||
|
||||
// Briefly display execution result
|
||||
const displayResult = getToolCompletedText(toolName, toolInput, this.i18n)
|
||||
if (displayResult)
|
||||
this.bus.emit('panel:update', {
|
||||
type: 'tool_executing',
|
||||
toolName,
|
||||
toolArgs: toolInput,
|
||||
toolResult: result,
|
||||
displayText: displayResult,
|
||||
duration,
|
||||
})
|
||||
this.panel.update({
|
||||
type: 'toolCompleted',
|
||||
toolName,
|
||||
args: toolInput,
|
||||
result,
|
||||
duration,
|
||||
})
|
||||
|
||||
// Wait a moment to let user see the result
|
||||
await new Promise((resolve) => setTimeout(resolve, 100))
|
||||
@@ -392,7 +381,7 @@ export class PageAgent extends EventTarget {
|
||||
return systemPrompt
|
||||
}
|
||||
|
||||
#assembleUserPrompt(): string {
|
||||
async #assembleUserPrompt(): Promise<string> {
|
||||
let prompt = ''
|
||||
|
||||
// <agent_history>
|
||||
@@ -430,62 +419,65 @@ export class PageAgent extends EventTarget {
|
||||
|
||||
// <browser_state>
|
||||
|
||||
prompt += this.#getBrowserState()
|
||||
prompt += await this.#getBrowserState()
|
||||
|
||||
return trimLines(prompt)
|
||||
}
|
||||
|
||||
#onDone(text: string, success = true) {
|
||||
dom.cleanUpHighlights()
|
||||
this.pageController.cleanUpHighlights()
|
||||
|
||||
// Update panel status
|
||||
this.bus.emit('panel:update', {
|
||||
type: success ? 'output' : 'error',
|
||||
displayText: text,
|
||||
})
|
||||
if (success) {
|
||||
this.panel.update({ type: 'output', text })
|
||||
} else {
|
||||
this.panel.update({ type: 'error', message: text })
|
||||
}
|
||||
|
||||
// Task completed
|
||||
this.bus.emit('panel:update', {
|
||||
type: 'completed',
|
||||
displayText: this.i18n.t('ui.panel.taskCompleted'),
|
||||
})
|
||||
this.panel.update({ type: 'completed' })
|
||||
|
||||
this.mask.hide()
|
||||
|
||||
this.#abortController.abort()
|
||||
}
|
||||
|
||||
#getBrowserState(): string {
|
||||
const pageUrl = window.location.href
|
||||
const pageTitle = document.title
|
||||
const pi = getPageInfo()
|
||||
async #getBrowserState(): Promise<string> {
|
||||
const pageUrl = await this.pageController.getCurrentUrl()
|
||||
const pageTitle = await this.pageController.getPageTitle()
|
||||
const pi = await this.pageController.getPageInfo()
|
||||
const viewportExpansion = await this.pageController.getViewportExpansion()
|
||||
|
||||
this.#updateTree()
|
||||
this.mask.wrapper.style.pointerEvents = 'none'
|
||||
await this.pageController.updateTree()
|
||||
this.mask.wrapper.style.pointerEvents = 'auto'
|
||||
|
||||
const simplifiedHTML = await this.pageController.getSimplifiedHTML()
|
||||
|
||||
let prompt = trimLines(`<browser_state>
|
||||
Current Page: [${pageTitle}](${pageUrl})
|
||||
|
||||
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
|
||||
|
||||
${VIEWPORT_EXPANSION === -1 ? 'Interactive elements from top layer of the current page (full page):' : 'Interactive elements from top layer of the current page inside the viewport:'}
|
||||
${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:'}
|
||||
|
||||
`)
|
||||
|
||||
// Page header info
|
||||
const has_content_above = pi.pixels_above > 4
|
||||
if (has_content_above && VIEWPORT_EXPANSION !== -1) {
|
||||
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 += this.simplifiedHTML
|
||||
prompt += simplifiedHTML
|
||||
prompt += `\n`
|
||||
|
||||
// Page footer info
|
||||
const has_content_below = pi.pixels_below > 4
|
||||
if (has_content_below && VIEWPORT_EXPANSION !== -1) {
|
||||
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`
|
||||
@@ -496,42 +488,31 @@ export class PageAgent extends EventTarget {
|
||||
return prompt
|
||||
}
|
||||
|
||||
/**
|
||||
* Update document tree
|
||||
*/
|
||||
#updateTree() {
|
||||
this.dispatchEvent(new Event('beforeUpdate'))
|
||||
this.lastTimeUpdate = Date.now()
|
||||
dom.cleanUpHighlights()
|
||||
this.mask.wrapper.style.pointerEvents = 'none'
|
||||
this.flatTree = dom.getFlatTree({
|
||||
...this.config,
|
||||
interactiveBlacklist: [
|
||||
...(this.config.interactiveBlacklist || []),
|
||||
...document.querySelectorAll('[data-page-agent-not-interactive]').values(),
|
||||
],
|
||||
})
|
||||
this.mask.wrapper.style.pointerEvents = 'auto'
|
||||
this.simplifiedHTML = dom.flatTreeToString(this.flatTree, this.config.include_attributes)
|
||||
this.selectorMap.clear()
|
||||
this.selectorMap = dom.getSelectorMap(this.flatTree)
|
||||
this.elementTextMap.clear()
|
||||
this.elementTextMap = dom.getElementTextMap(this.simplifiedHTML)
|
||||
this.dispatchEvent(new Event('afterUpdate'))
|
||||
}
|
||||
|
||||
dispose(reason?: string) {
|
||||
console.log('Disposing PageAgent...')
|
||||
this.disposed = true
|
||||
dom.cleanUpHighlights()
|
||||
this.flatTree = null
|
||||
this.selectorMap.clear()
|
||||
this.elementTextMap.clear()
|
||||
this.pageController.dispose()
|
||||
this.panel.dispose()
|
||||
this.mask.dispose()
|
||||
this.history = []
|
||||
this.#abortController.abort(reason ?? 'PageAgent disposed')
|
||||
|
||||
// Clean up LLM event listeners
|
||||
if (this.#llmRetryListener) {
|
||||
this.#llm.removeEventListener('retry', this.#llmRetryListener)
|
||||
this.#llmRetryListener = null
|
||||
}
|
||||
if (this.#llmErrorListener) {
|
||||
this.#llm.removeEventListener('error', this.#llmErrorListener)
|
||||
this.#llmErrorListener = null
|
||||
}
|
||||
|
||||
// Clean up window event listeners
|
||||
if (this.#beforeUnloadListener) {
|
||||
window.removeEventListener('beforeunload', this.#beforeUnloadListener)
|
||||
this.#beforeUnloadListener = null
|
||||
}
|
||||
|
||||
this.config.onDispose?.call(this, reason)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
/**
|
||||
* @note Since isTopElement depends on elementFromPoint,
|
||||
* it returns null when out of viewport, this feature has no practical use, only differ between -1 and 0
|
||||
*/
|
||||
// export const VIEWPORT_EXPANSION = 100
|
||||
export const VIEWPORT_EXPANSION = -1
|
||||
|
||||
// Dev environment: use .env config if available, otherwise fallback to testing api
|
||||
export const DEFAULT_MODEL_NAME: string =
|
||||
import.meta.env.DEV && import.meta.env.LLM_MODEL_NAME
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { PageControllerConfig } from '@page-agent/page-controller'
|
||||
import type { SupportedLanguage } from '@page-agent/ui'
|
||||
|
||||
import type { AgentHistory, ExecutionResult, PageAgent } from '../PageAgent'
|
||||
import type { DomConfig } from '../dom'
|
||||
import type { SupportedLanguage } from '../i18n'
|
||||
import type { PageAgentTool } from '../tools'
|
||||
import {
|
||||
DEFAULT_API_KEY,
|
||||
@@ -94,7 +95,7 @@ export interface AgentConfig {
|
||||
experimentalPreventNewPage?: boolean
|
||||
}
|
||||
|
||||
export type PageAgentConfig = LLMConfig & AgentConfig & DomConfig
|
||||
export type PageAgentConfig = LLMConfig & AgentConfig & PageControllerConfig
|
||||
|
||||
export function parseLLMConfig(config: LLMConfig): Required<LLMConfig> {
|
||||
return {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/// <reference types="vite/client" />
|
||||
import type { PageAgent } from './src/PageAgent'
|
||||
import type { PageAgent } from './PageAgent'
|
||||
|
||||
declare module '*.module.css' {
|
||||
const classes: Record<string, string>
|
||||
@@ -33,24 +33,19 @@
|
||||
*/
|
||||
import type { LLMConfig } from '../config'
|
||||
import { parseLLMConfig } from '../config'
|
||||
import { EventBus, getEventBus } from '../utils/bus'
|
||||
import { OpenAIClient } from './OpenAILenientClient'
|
||||
import { InvokeError } from './errors'
|
||||
import type { InvokeResult, LLMClient, Message, Tool } from './types'
|
||||
|
||||
export type { Message, Tool, InvokeResult, LLMClient }
|
||||
|
||||
export class LLM {
|
||||
export class LLM extends EventTarget {
|
||||
config: Required<LLMConfig>
|
||||
id: string
|
||||
client: LLMClient
|
||||
#bus: EventBus
|
||||
|
||||
constructor(config: LLMConfig, id: string) {
|
||||
constructor(config: LLMConfig) {
|
||||
super()
|
||||
this.config = parseLLMConfig(config)
|
||||
this.id = id
|
||||
|
||||
this.#bus = getEventBus(id)
|
||||
|
||||
// Default to OpenAI client
|
||||
this.client = new OpenAIClient({
|
||||
@@ -81,17 +76,13 @@ export class LLM {
|
||||
// retry settings
|
||||
{
|
||||
maxRetries: this.config.maxRetries,
|
||||
onRetry: (retries: number) => {
|
||||
this.#bus.emit('panel:update', {
|
||||
type: 'retry',
|
||||
displayText: `retry-ing (${retries} / ${this.config.maxRetries})`,
|
||||
})
|
||||
onRetry: (current: number) => {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('retry', { detail: { current, max: this.config.maxRetries } })
|
||||
)
|
||||
},
|
||||
onError: (error: Error, withRetry: boolean) => {
|
||||
this.#bus.emit('panel:update', {
|
||||
type: 'error',
|
||||
displayText: `step failed: ${(error as Error).message}`,
|
||||
})
|
||||
onError: (error: Error) => {
|
||||
this.dispatchEvent(new CustomEvent('error', { detail: { error } }))
|
||||
},
|
||||
}
|
||||
)
|
||||
@@ -103,7 +94,7 @@ async function withRetry<T>(
|
||||
settings: {
|
||||
maxRetries: number
|
||||
onRetry: (retries: number) => void
|
||||
onError: (error: Error, withRetry: boolean) => void
|
||||
onError: (error: Error) => void
|
||||
}
|
||||
): Promise<T> {
|
||||
let retries = 0
|
||||
@@ -118,7 +109,7 @@ async function withRetry<T>(
|
||||
return await fn()
|
||||
} catch (error: unknown) {
|
||||
console.error(error)
|
||||
settings.onError(error as Error, retries < settings.maxRetries)
|
||||
settings.onError(error as Error)
|
||||
|
||||
// do not retry if aborted by user
|
||||
if ((error as { name?: string })?.name === 'AbortError') throw error
|
||||
|
||||
@@ -5,21 +5,7 @@
|
||||
import zod, { type z } from 'zod'
|
||||
|
||||
import type { PageAgent } from '../PageAgent'
|
||||
import {
|
||||
clickElement,
|
||||
getElementByIndex,
|
||||
getSystemInfo,
|
||||
inputTextElement,
|
||||
scrollHorizontally,
|
||||
scrollVertically,
|
||||
selectOptionElement,
|
||||
waitFor,
|
||||
} from './actions'
|
||||
// debug
|
||||
import * as utils from './actions'
|
||||
|
||||
// @ts-expect-error debug only
|
||||
window.utils = utils
|
||||
import { waitFor } from '../utils'
|
||||
|
||||
/**
|
||||
* Internal tool definition that has access to PageAgent `this` context
|
||||
@@ -41,18 +27,6 @@ export function tool<TParams>(options: PageAgentTool<TParams>): PageAgentTool<TP
|
||||
*/
|
||||
export const tools = new Map<string, PageAgentTool>()
|
||||
|
||||
// tools.set(
|
||||
// 'get_current_html',
|
||||
// tool({
|
||||
// description: 'Get the current (updated) simplified HTML of the page',
|
||||
// inputSchema: zod.object({}),
|
||||
// execute: function (this: PageAgent) {
|
||||
// this.updateTree()
|
||||
// return this.simplifiedHTML
|
||||
// },
|
||||
// })
|
||||
// )
|
||||
|
||||
tools.set(
|
||||
'done',
|
||||
tool({
|
||||
@@ -79,11 +53,11 @@ tools.set(
|
||||
seconds: zod.number().min(1).max(10).default(1),
|
||||
}),
|
||||
execute: async function (this: PageAgent, input) {
|
||||
const lastTimeUpdate = this.lastTimeUpdate
|
||||
const lastTimeUpdate = await this.pageController.getLastUpdateTime()
|
||||
const actualWaitTime = Math.max(0, input.seconds - (Date.now() - lastTimeUpdate) / 1000)
|
||||
console.log(`actualWaitTime: ${actualWaitTime} seconds`)
|
||||
await waitFor(actualWaitTime)
|
||||
return `✅ Waited for ${input.seconds} seconds.` + (await getSystemInfo())
|
||||
return `✅ Waited for ${input.seconds} seconds.`
|
||||
},
|
||||
})
|
||||
)
|
||||
@@ -98,7 +72,7 @@ tools.set(
|
||||
}),
|
||||
execute: async function (this: PageAgent, input) {
|
||||
const answer = await this.panel.askUser(input.question)
|
||||
return `✅ Received user answer: ${answer}` + (await getSystemInfo())
|
||||
return `✅ Received user answer: ${answer}`
|
||||
},
|
||||
})
|
||||
)
|
||||
@@ -111,16 +85,8 @@ tools.set(
|
||||
index: zod.int().min(0),
|
||||
}),
|
||||
execute: async function (this: PageAgent, input) {
|
||||
const element = getElementByIndex(this, input.index)
|
||||
const elemText = this.elementTextMap.get(input.index)
|
||||
await clickElement(element)
|
||||
|
||||
// @workaround: Handle links that open in new tabs
|
||||
if (element instanceof HTMLAnchorElement && element.target === '_blank') {
|
||||
return `⚠️ Clicked link that opens in a new tab (${elemText ? elemText : input.index}). You are not capable of reading new tabs.`
|
||||
}
|
||||
|
||||
return `✅ Clicked element (${elemText ? elemText : input.index}).` + (await getSystemInfo())
|
||||
const result = await this.pageController.clickElement(input.index)
|
||||
return result.message
|
||||
},
|
||||
})
|
||||
)
|
||||
@@ -134,13 +100,8 @@ tools.set(
|
||||
text: zod.string(),
|
||||
}),
|
||||
execute: async function (this: PageAgent, input) {
|
||||
const element = getElementByIndex(this, input.index)
|
||||
const elemText = this.elementTextMap.get(input.index)
|
||||
await inputTextElement(element, input.text)
|
||||
return (
|
||||
`✅ Input text (${input.text}) into element (${elemText ? elemText : input.index}).` +
|
||||
(await getSystemInfo())
|
||||
)
|
||||
const result = await this.pageController.inputText(input.index, input.text)
|
||||
return result.message
|
||||
},
|
||||
})
|
||||
)
|
||||
@@ -155,13 +116,8 @@ tools.set(
|
||||
text: zod.string(),
|
||||
}),
|
||||
execute: async function (this: PageAgent, input) {
|
||||
const element = getElementByIndex(this, input.index)
|
||||
const elemText = this.elementTextMap.get(input.index)
|
||||
await selectOptionElement(element as HTMLSelectElement, input.text)
|
||||
return (
|
||||
`✅ Selected option (${input.text}) in element (${elemText ? elemText : input.index}).` +
|
||||
(await getSystemInfo())
|
||||
)
|
||||
const result = await this.pageController.selectOption(input.index, input.text)
|
||||
return result.message
|
||||
},
|
||||
})
|
||||
)
|
||||
@@ -181,13 +137,11 @@ tools.set(
|
||||
index: zod.number().int().min(0).optional(),
|
||||
}),
|
||||
execute: async function (this: PageAgent, input) {
|
||||
const { down, num_pages, index, pixels } = input
|
||||
|
||||
const scroll_amount = pixels ? pixels : num_pages * (down ? 1 : -1) * window.innerHeight
|
||||
|
||||
const element = index !== undefined ? getElementByIndex(this, index) : null
|
||||
|
||||
return (await scrollVertically(down, scroll_amount, element)) + (await getSystemInfo())
|
||||
const result = await this.pageController.scroll({
|
||||
...input,
|
||||
numPages: input.num_pages,
|
||||
})
|
||||
return result.message
|
||||
},
|
||||
})
|
||||
)
|
||||
@@ -203,13 +157,8 @@ tools.set(
|
||||
index: zod.number().int().min(0).optional(),
|
||||
}),
|
||||
execute: async function (this: PageAgent, input) {
|
||||
const { right, pixels, index } = input
|
||||
|
||||
const scroll_amount = pixels * (right ? 1 : -1)
|
||||
|
||||
const element = index !== undefined ? getElementByIndex(this, index) : null
|
||||
|
||||
return (await scrollHorizontally(right, scroll_amount, element)) + (await getSystemInfo())
|
||||
const result = await this.pageController.scrollHorizontally(input)
|
||||
return result.message
|
||||
},
|
||||
})
|
||||
)
|
||||
@@ -223,14 +172,8 @@ tools.set(
|
||||
script: zod.string(),
|
||||
}),
|
||||
execute: async function (this: PageAgent, input) {
|
||||
try {
|
||||
// Wrap script in async function to support await
|
||||
const asyncFunction = eval(`(async () => { ${input.script} })`)
|
||||
const result = await asyncFunction()
|
||||
return `✅ Executed JavaScript. Result: ${result}` + (await getSystemInfo())
|
||||
} catch (error) {
|
||||
return `❌ Error executing JavaScript: ${error}` + (await getSystemInfo())
|
||||
}
|
||||
const result = await this.pageController.executeJavascript(input.script)
|
||||
return result.message
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
@@ -37,4 +37,4 @@ if (currentScript) {
|
||||
|
||||
console.log('🚀 page-agent.js initialized with config:', window.pageAgent.config)
|
||||
|
||||
window.pageAgent.bus.emit('panel:show') // Show panel
|
||||
window.pageAgent.panel.show() // Show panel
|
||||
@@ -1,30 +1,9 @@
|
||||
/**
|
||||
* Type-safe event bus for decoupling PageAgent and Panel
|
||||
*/
|
||||
import type { Step } from '../ui/UIState'
|
||||
|
||||
/**
|
||||
* Event mapping definitions
|
||||
* @note Event bus callbacks must be repeatable without errors
|
||||
*/
|
||||
export interface PageAgentEventMap {
|
||||
// Panel control events
|
||||
// call panel.show()
|
||||
'panel:show': { params: undefined }
|
||||
// call panel.hide()
|
||||
'panel:hide': { params: undefined }
|
||||
// call panel.reset()
|
||||
'panel:reset': { params: undefined }
|
||||
// call panel.update()
|
||||
'panel:update': { params: Omit<Step, 'id' | 'stepNumber' | 'timestamp'> }
|
||||
// call panel.expand()
|
||||
'panel:expand': { params: undefined }
|
||||
// call panel.collapse()
|
||||
'panel:collapse': { params: undefined }
|
||||
|
||||
// PageAgent status events
|
||||
// 'agent:beforeUpdate': { params: undefined }
|
||||
// 'agent:afterUpdate': { params: undefined }
|
||||
// 'agent:execute': { params: { task: string } }
|
||||
// 'agent:done': { params: { text: string; success: boolean } }
|
||||
// 'agent:paused': { params: undefined }
|
||||
@@ -33,8 +12,7 @@ export interface PageAgentEventMap {
|
||||
// 'agent:error': { params: { error: string | Error } }
|
||||
|
||||
// Task status change events
|
||||
// 'task:start': { params: { task: string } }
|
||||
// 'task:step': { params: Omit<AgentStep, 'id' | 'stepNumber' | 'timestamp'> }
|
||||
'task:start': { params: { task: string } }
|
||||
// 'task:complete': { params: { text: string; success: boolean } }
|
||||
// 'task:error': { params: { error: string | Error } }
|
||||
|
||||
|
||||
@@ -20,6 +20,10 @@ export async function waitUntil(check: () => boolean, timeout = 60 * 60_1000): P
|
||||
})
|
||||
}
|
||||
|
||||
export async function waitFor(seconds: number): Promise<void> {
|
||||
await new Promise((resolve) => setTimeout(resolve, seconds * 1000))
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
export function truncate(text: string, maxLength: number): string {
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
// @workaround DTS bug
|
||||
// dts do not work with monorepo path mapping
|
||||
// disable path mapping for it
|
||||
"paths": {}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,16 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo",
|
||||
"noEmit": false,
|
||||
"outDir": "./dist",
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo"
|
||||
"allowImportingTsExtensions": false,
|
||||
"baseUrl": ".",
|
||||
"outDir": "dist",
|
||||
"paths": {
|
||||
"@page-agent/page-controller": ["../page-controller/src/PageController.ts"]
|
||||
}
|
||||
},
|
||||
"include": ["src", "env.d.ts"]
|
||||
"include": ["**/*.ts"],
|
||||
"exclude": ["dist", "node_modules"],
|
||||
"references": [{ "path": "../page-controller" }]
|
||||
}
|
||||
|
||||
@@ -17,7 +17,8 @@ const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||
const libConfig = {
|
||||
clearScreen: false,
|
||||
plugins: [
|
||||
dts({ tsconfigPath: './tsconfig.json', bundleTypes: true }),
|
||||
dts({ tsconfigPath: './tsconfig.dts.json', bundleTypes: true }),
|
||||
// dts({ tsconfigPath: './tsconfig.json', bundleTypes: true, compilerOptions: { paths: {} } }),
|
||||
cssInjectedByJsPlugin({ relativeCSSInjection: true }),
|
||||
],
|
||||
publicDir: false,
|
||||
@@ -33,7 +34,7 @@ const libConfig = {
|
||||
},
|
||||
outDir: resolve(__dirname, 'dist', 'lib'),
|
||||
rollupOptions: {
|
||||
external: ['ai', 'ai-motion', 'chalk', 'zod'],
|
||||
external: ['ai', 'ai-motion', 'chalk', 'zod', '@page-agent/*'],
|
||||
},
|
||||
minify: false,
|
||||
sourcemap: true,
|
||||
@@ -46,6 +47,9 @@ const libConfig = {
|
||||
|
||||
// ============================================================================
|
||||
// UMD Config (Browser Bundle for CDN)
|
||||
// - alias all local packages so that they can be build in
|
||||
// - no external
|
||||
// - no d.ts. dts does not work with monorepo aliasing
|
||||
// ============================================================================
|
||||
/** @type {import('vite').UserConfig} */
|
||||
const umdConfig = {
|
||||
@@ -54,9 +58,15 @@ const umdConfig = {
|
||||
esbuild: {
|
||||
keepNames: true,
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@page-agent/page-controller': resolve(__dirname, '../page-controller/src/PageController.ts'),
|
||||
'@page-agent/ui': resolve(__dirname, '../ui/src/index.ts'),
|
||||
},
|
||||
},
|
||||
build: {
|
||||
lib: {
|
||||
entry: resolve(__dirname, 'src/entry.ts'),
|
||||
entry: resolve(__dirname, 'src/umd.ts'),
|
||||
name: 'PageAgent',
|
||||
fileName: 'page-agent',
|
||||
formats: ['umd'],
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "@page-agent/page-controller",
|
||||
"version": "0.0.8",
|
||||
"type": "module",
|
||||
"main": "./dist/lib/page-controller.js",
|
||||
"module": "./dist/lib/page-controller.js",
|
||||
"types": "./dist/lib/PageController.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/lib/PageController.d.ts",
|
||||
"import": "./dist/lib/page-controller.js",
|
||||
"default": "./dist/lib/page-controller.js"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist/"
|
||||
],
|
||||
"description": "Page controller for page-agent - DOM operations and element interactions",
|
||||
"keywords": [
|
||||
"page-agent",
|
||||
"dom",
|
||||
"browser-automation",
|
||||
"web-automation"
|
||||
],
|
||||
"author": "Simon<gaomeng1900>",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/alibaba/page-agent.git",
|
||||
"directory": "packages/page-controller"
|
||||
},
|
||||
"homepage": "https://alibaba.github.io/page-agent/",
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
"build:watch": "vite build --watch",
|
||||
"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{}})\""
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,339 @@
|
||||
/**
|
||||
* Copyright (C) 2025 Alibaba Group Holding Limited
|
||||
* All rights reserved.
|
||||
*
|
||||
* PageController - Manages DOM operations and element interactions.
|
||||
* Designed to be independent of LLM and can be tested in unit tests.
|
||||
* All public methods are async for potential remote calling support.
|
||||
*/
|
||||
import {
|
||||
clickElement,
|
||||
getElementByIndex,
|
||||
inputTextElement,
|
||||
scrollHorizontally,
|
||||
scrollVertically,
|
||||
selectOptionElement,
|
||||
} from './actions'
|
||||
import { VIEWPORT_EXPANSION } from './constants'
|
||||
import * as dom from './dom'
|
||||
import type { FlatDomTree, InteractiveElementDomNode } from './dom/dom_tree/type'
|
||||
import { getPageInfo } from './dom/getPageInfo'
|
||||
import { patchReact } from './patches/react'
|
||||
|
||||
/**
|
||||
* Configuration for PageController
|
||||
*/
|
||||
export interface PageControllerConfig extends dom.DomConfig {
|
||||
viewportExpansion?: number
|
||||
}
|
||||
|
||||
interface ActionResult {
|
||||
success: boolean
|
||||
message: string
|
||||
}
|
||||
|
||||
/**
|
||||
* PageController manages DOM state and element interactions.
|
||||
* It provides async methods for all DOM operations, keeping state isolated.
|
||||
*
|
||||
* @lifecycle
|
||||
* - beforeUpdate: Emitted before the DOM tree is updated.
|
||||
* - afterUpdate: Emitted after the DOM tree is updated.
|
||||
*/
|
||||
export class PageController extends EventTarget {
|
||||
private config: PageControllerConfig
|
||||
|
||||
/** Corresponds to eval_page in browser-use */
|
||||
private flatTree: FlatDomTree | null = null
|
||||
|
||||
/**
|
||||
* All highlighted index-mapped interactive elements
|
||||
* Corresponds to DOMState.selector_map in browser-use
|
||||
*/
|
||||
private selectorMap = new Map<number, InteractiveElementDomNode>()
|
||||
|
||||
/** Index -> element text description mapping */
|
||||
private elementTextMap = new Map<number, string>()
|
||||
|
||||
/**
|
||||
* Simplified HTML for LLM consumption.
|
||||
* Corresponds to clickable_elements_to_string in browser-use
|
||||
*/
|
||||
private simplifiedHTML = '<EMPTY>'
|
||||
|
||||
/** last time the tree was updated */
|
||||
private lastTimeUpdate = 0
|
||||
|
||||
constructor(config: PageControllerConfig = {}) {
|
||||
super()
|
||||
|
||||
this.config = config
|
||||
|
||||
patchReact(this)
|
||||
}
|
||||
|
||||
// ======= State Queries =======
|
||||
|
||||
/**
|
||||
* Get current page URL
|
||||
*/
|
||||
async getCurrentUrl(): Promise<string> {
|
||||
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
|
||||
*/
|
||||
async getLastUpdateTime(): Promise<number> {
|
||||
return this.lastTimeUpdate
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the viewport expansion setting
|
||||
*/
|
||||
async getViewportExpansion(): Promise<number> {
|
||||
return this.config.viewportExpansion ?? VIEWPORT_EXPANSION
|
||||
}
|
||||
|
||||
// ======= DOM Tree Operations =======
|
||||
|
||||
/**
|
||||
* Update DOM tree, returns simplified HTML for LLM.
|
||||
* This is the main method to refresh the page state.
|
||||
*/
|
||||
async updateTree(): Promise<string> {
|
||||
this.dispatchEvent(new Event('beforeUpdate'))
|
||||
|
||||
this.lastTimeUpdate = Date.now()
|
||||
|
||||
dom.cleanUpHighlights()
|
||||
|
||||
const blacklist = [
|
||||
...(this.config.interactiveBlacklist || []),
|
||||
...document.querySelectorAll('[data-page-agent-not-interactive]').values(),
|
||||
]
|
||||
|
||||
this.flatTree = dom.getFlatTree({
|
||||
...this.config,
|
||||
interactiveBlacklist: blacklist,
|
||||
})
|
||||
|
||||
this.simplifiedHTML = dom.flatTreeToString(this.flatTree, this.config.include_attributes)
|
||||
|
||||
this.selectorMap.clear()
|
||||
this.selectorMap = dom.getSelectorMap(this.flatTree)
|
||||
|
||||
this.elementTextMap.clear()
|
||||
this.elementTextMap = dom.getElementTextMap(this.simplifiedHTML)
|
||||
|
||||
this.dispatchEvent(new Event('afterUpdate'))
|
||||
|
||||
return this.simplifiedHTML
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up all element highlights
|
||||
*/
|
||||
async cleanUpHighlights(): Promise<void> {
|
||||
dom.cleanUpHighlights()
|
||||
}
|
||||
|
||||
// ======= Element Actions =======
|
||||
|
||||
/**
|
||||
* Click element by index
|
||||
*/
|
||||
async clickElement(index: number): Promise<ActionResult> {
|
||||
try {
|
||||
const element = getElementByIndex(this.selectorMap, index)
|
||||
const elemText = this.elementTextMap.get(index)
|
||||
await clickElement(element)
|
||||
|
||||
// Handle links that open in new tabs
|
||||
if (element instanceof HTMLAnchorElement && element.target === '_blank') {
|
||||
return {
|
||||
success: true,
|
||||
message: `✅ Clicked element (${elemText ?? index}). ⚠️ Link opens in a new tab. You are not capable of reading new tabs.`,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `✅ Clicked element (${elemText ?? index}).`,
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
message: `❌ Failed to click element: ${error}`,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Input text into element by index
|
||||
*/
|
||||
async inputText(index: number, text: string): Promise<ActionResult> {
|
||||
try {
|
||||
const element = getElementByIndex(this.selectorMap, index)
|
||||
const elemText = this.elementTextMap.get(index)
|
||||
await inputTextElement(element, text)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `✅ Input text (${text}) into element (${elemText ?? index}).`,
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
message: `❌ Failed to input text: ${error}`,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Select dropdown option by index and option text
|
||||
*/
|
||||
async selectOption(index: number, optionText: string): Promise<ActionResult> {
|
||||
try {
|
||||
const element = getElementByIndex(this.selectorMap, index)
|
||||
const elemText = this.elementTextMap.get(index)
|
||||
await selectOptionElement(element as HTMLSelectElement, optionText)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `✅ Selected option (${optionText}) in element (${elemText ?? index}).`,
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
message: `❌ Failed to select option: ${error}`,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll vertically
|
||||
*/
|
||||
async scroll(options: {
|
||||
down: boolean
|
||||
numPages: number
|
||||
pixels?: number
|
||||
index?: number
|
||||
}): Promise<ActionResult> {
|
||||
try {
|
||||
const { down, numPages, pixels, index } = options
|
||||
|
||||
const scrollAmount = pixels ?? numPages * (down ? 1 : -1) * window.innerHeight
|
||||
|
||||
const element = index !== undefined ? getElementByIndex(this.selectorMap, index) : null
|
||||
|
||||
const message = await scrollVertically(down, scrollAmount, element)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message,
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
message: `❌ Failed to scroll: ${error}`,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll horizontally
|
||||
*/
|
||||
async scrollHorizontally(options: {
|
||||
right: boolean
|
||||
pixels: number
|
||||
index?: number
|
||||
}): Promise<ActionResult> {
|
||||
try {
|
||||
const { right, pixels, index } = options
|
||||
|
||||
const scrollAmount = pixels * (right ? 1 : -1)
|
||||
|
||||
const element = index !== undefined ? getElementByIndex(this.selectorMap, index) : null
|
||||
|
||||
const message = await scrollHorizontally(right, scrollAmount, element)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message,
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
message: `❌ Failed to scroll horizontally: ${error}`,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute arbitrary JavaScript on the page
|
||||
*/
|
||||
async executeJavascript(script: string): Promise<ActionResult> {
|
||||
try {
|
||||
// Wrap script in async function to support await
|
||||
const asyncFunction = eval(`(async () => { ${script} })`)
|
||||
const result = await asyncFunction()
|
||||
return {
|
||||
success: true,
|
||||
message: `✅ Executed JavaScript. Result: ${result}`,
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
message: `❌ Error executing JavaScript: ${error}`,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispose and clean up resources
|
||||
*/
|
||||
dispose(): void {
|
||||
dom.cleanUpHighlights()
|
||||
this.flatTree = null
|
||||
this.selectorMap.clear()
|
||||
this.elementTextMap.clear()
|
||||
this.simplifiedHTML = '<EMPTY>'
|
||||
}
|
||||
}
|
||||
@@ -2,26 +2,14 @@
|
||||
* Copyright (C) 2025 Alibaba Group Holding Limited
|
||||
* All rights reserved.
|
||||
*/
|
||||
import type { PageAgent } from '../PageAgent'
|
||||
import type { InteractiveElementDomNode } from './dom/dom_tree/type'
|
||||
|
||||
// ======= general utils =======
|
||||
|
||||
export async function waitFor(seconds: number): Promise<void> {
|
||||
async function waitFor(seconds: number): Promise<void> {
|
||||
await new Promise((resolve) => setTimeout(resolve, seconds * 1000))
|
||||
}
|
||||
|
||||
let currentUrl = window.location.href
|
||||
export async function getSystemInfo() {
|
||||
// If current URL is already up to date, no need to add message
|
||||
if (currentUrl === window.location.href) return ''
|
||||
|
||||
await waitFor(0.3) // Wait a bit longer for page to load
|
||||
|
||||
currentUrl = window.location.href
|
||||
|
||||
return `\n<sys> Current URL changed to: ${currentUrl} </sys>`
|
||||
}
|
||||
|
||||
// ======= dom utils =======
|
||||
|
||||
export async function movePointerToElement(element: HTMLElement) {
|
||||
@@ -35,10 +23,13 @@ export async function movePointerToElement(element: HTMLElement) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the HTMLElement by index from the selectorMap in PageAgent.
|
||||
* Get the HTMLElement by index from a selectorMap.
|
||||
*/
|
||||
export function getElementByIndex(pageAgent: PageAgent, index: number): HTMLElement {
|
||||
const interactiveNode = pageAgent.selectorMap.get(index)
|
||||
export function getElementByIndex(
|
||||
selectorMap: Map<number, InteractiveElementDomNode>,
|
||||
index: number
|
||||
): HTMLElement {
|
||||
const interactiveNode = selectorMap.get(index)
|
||||
if (!interactiveNode) {
|
||||
throw new Error(`No interactive element found at index ${index}`)
|
||||
}
|
||||
@@ -170,7 +161,6 @@ export async function selectOptionElement(selectElement: HTMLSelectElement, opti
|
||||
await waitFor(0.1) // Wait to ensure change event processing completes
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/require-await
|
||||
export async function scrollIntoViewIfNeeded(element: HTMLElement) {
|
||||
const el = element as any
|
||||
if (el.scrollIntoViewIfNeeded) {
|
||||
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Copyright (C) 2025 Alibaba Group Holding Limited
|
||||
* All rights reserved.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Viewport expansion for DOM tree extraction.
|
||||
* -1 means full page (no viewport restriction)
|
||||
* 0 means viewport only
|
||||
* positive values expand the viewport by that many pixels
|
||||
*
|
||||
* @note Since isTopElement depends on elementFromPoint,
|
||||
* it returns null when out of viewport, this feature has no practical use, only differ between -1 and 0
|
||||
*/
|
||||
// export const VIEWPORT_EXPANSION = 100
|
||||
export const VIEWPORT_EXPANSION = -1
|
||||
@@ -1,5 +1,5 @@
|
||||
import { VIEWPORT_EXPANSION } from '../config/constants'
|
||||
import domTree from './dom_tree/index'
|
||||
import { VIEWPORT_EXPANSION } from '../constants'
|
||||
import domTree from './dom_tree/index.js'
|
||||
import {
|
||||
ElementDomNode,
|
||||
FlatDomTree,
|
||||
+4
-4
@@ -1,4 +1,4 @@
|
||||
import type { PageAgent } from '../PageAgent'
|
||||
import type { PageController } from '../PageController'
|
||||
|
||||
const clearFunctions = [] as (() => void)[]
|
||||
|
||||
@@ -11,9 +11,9 @@ function fixAntdSelect() {
|
||||
// for (const select of selects) {}
|
||||
}
|
||||
|
||||
export function patchAntd(pageAgent: PageAgent) {
|
||||
pageAgent.addEventListener('beforeUpdate', fixAntdSelect)
|
||||
pageAgent.addEventListener('afterUpdate', () => {
|
||||
export function patchAntd(pageController: PageController) {
|
||||
pageController.addEventListener('beforeUpdate', fixAntdSelect)
|
||||
pageController.addEventListener('afterUpdate', () => {
|
||||
for (const fn of clearFunctions) fn()
|
||||
clearFunctions.length = 0
|
||||
})
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import type { PageAgent } from '../PageAgent'
|
||||
import type { PageController } from '../PageController'
|
||||
|
||||
// Find common React root elements and add data-page-agent-not-interactive attribute
|
||||
export function patchReact(pageAgent: PageAgent) {
|
||||
export function patchReact(pageController: PageController) {
|
||||
const reactRootElements = document.querySelectorAll(
|
||||
'[data-reactroot], [data-reactid], [data-react-checksum], #root, #app, [id^="root-"], [id^="app-"], #adex-wrapper, #adex-root'
|
||||
)
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
// @workaround DTS bug
|
||||
// dts do not work with monorepo path mapping
|
||||
// disable path mapping for it
|
||||
"paths": {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo",
|
||||
"noEmit": false,
|
||||
"allowImportingTsExtensions": false,
|
||||
"baseUrl": ".",
|
||||
"outDir": "dist"
|
||||
},
|
||||
"include": ["**/*.ts", "**/*.js"],
|
||||
"exclude": ["dist", "node_modules"]
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// @ts-check
|
||||
import chalk from 'chalk'
|
||||
import { dirname, resolve } from 'path'
|
||||
import dts from 'unplugin-dts/vite'
|
||||
import { fileURLToPath } from 'url'
|
||||
import { defineConfig } from 'vite'
|
||||
import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js'
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||
|
||||
console.log(chalk.cyan(`📦 Building @page-agent/page-controller`))
|
||||
|
||||
export default defineConfig({
|
||||
clearScreen: false,
|
||||
plugins: [
|
||||
dts({ tsconfigPath: './tsconfig.dts.json', bundleTypes: true }),
|
||||
cssInjectedByJsPlugin({ relativeCSSInjection: true }),
|
||||
],
|
||||
publicDir: false,
|
||||
esbuild: {
|
||||
keepNames: true,
|
||||
},
|
||||
build: {
|
||||
lib: {
|
||||
entry: resolve(__dirname, 'src/PageController.ts'),
|
||||
name: 'PageController',
|
||||
fileName: 'page-controller',
|
||||
formats: ['es'],
|
||||
},
|
||||
outDir: resolve(__dirname, 'dist', 'lib'),
|
||||
rollupOptions: {
|
||||
external: ['@page-agent/*'],
|
||||
},
|
||||
minify: false,
|
||||
sourcemap: true,
|
||||
cssCodeSplit: true,
|
||||
},
|
||||
define: {
|
||||
'process.env.NODE_ENV': '"production"',
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"name": "@page-agent/ui",
|
||||
"version": "0.0.8",
|
||||
"type": "module",
|
||||
"main": "./dist/lib/page-agent-ui.js",
|
||||
"module": "./dist/lib/page-agent-ui.js",
|
||||
"types": "./dist/lib/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/lib/index.d.ts",
|
||||
"import": "./dist/lib/page-agent-ui.js",
|
||||
"default": "./dist/lib/page-agent-ui.js"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist/"
|
||||
],
|
||||
"description": "UI components for page-agent - Panel, SimulatorMask, and i18n",
|
||||
"keywords": [
|
||||
"page-agent",
|
||||
"ui",
|
||||
"panel",
|
||||
"i18n"
|
||||
],
|
||||
"author": "Simon<gaomeng1900>",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/alibaba/page-agent.git",
|
||||
"directory": "packages/ui"
|
||||
},
|
||||
"homepage": "https://alibaba.github.io/page-agent/",
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
"build:watch": "vite build --watch",
|
||||
"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{}})\""
|
||||
},
|
||||
"dependencies": {
|
||||
"ai-motion": "^0.4.7"
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,35 @@
|
||||
import type { PageAgent } from '../PageAgent'
|
||||
import type { I18n } from '../i18n'
|
||||
import { truncate } from '../utils'
|
||||
import type { EventBus } from '../utils/bus'
|
||||
import { type Step, UIState } from './UIState'
|
||||
import { I18n, type SupportedLanguage } from './i18n'
|
||||
import { truncate } from './utils'
|
||||
|
||||
import styles from './Panel.module.css'
|
||||
|
||||
/**
|
||||
* Panel configuration
|
||||
*/
|
||||
export interface PanelConfig {
|
||||
language?: SupportedLanguage
|
||||
onExecuteTask: (task: string) => void
|
||||
onStop: () => void
|
||||
onPauseToggle: () => boolean // returns new paused state
|
||||
getPaused: () => boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Semantic update types - Panel handles i18n internally
|
||||
*/
|
||||
export type PanelUpdate =
|
||||
| { type: 'thinking'; text?: string } // text is optional, defaults to i18n thinking text
|
||||
| { type: 'input'; task: string }
|
||||
| { type: 'question'; question: string }
|
||||
| { type: 'userAnswer'; input: string }
|
||||
| { type: 'retry'; current: number; max: number }
|
||||
| { type: 'error'; message: string }
|
||||
| { type: 'output'; text: string }
|
||||
| { type: 'completed' }
|
||||
| { type: 'toolExecuting'; toolName: string; args: any }
|
||||
| { type: 'toolCompleted'; toolName: string; args: any; result?: string; duration?: number }
|
||||
|
||||
/**
|
||||
* Agent control panel
|
||||
*/
|
||||
@@ -19,11 +43,11 @@ export class Panel {
|
||||
#stopButton: HTMLElement
|
||||
#inputSection: HTMLElement
|
||||
#taskInput: HTMLInputElement
|
||||
#bus: EventBus
|
||||
|
||||
#state = new UIState()
|
||||
#isExpanded = false
|
||||
#pageAgent: PageAgent
|
||||
#config: PanelConfig
|
||||
#i18n: I18n
|
||||
#userAnswerResolver: ((input: string) => void) | null = null
|
||||
#isWaitingForUserAnswer: boolean = false
|
||||
#headerUpdateTimer: ReturnType<typeof setInterval> | null = null
|
||||
@@ -34,9 +58,9 @@ export class Panel {
|
||||
return this.#wrapper
|
||||
}
|
||||
|
||||
constructor(pageAgent: PageAgent) {
|
||||
this.#pageAgent = pageAgent
|
||||
this.#bus = pageAgent.bus
|
||||
constructor(config: PanelConfig) {
|
||||
this.#config = config
|
||||
this.#i18n = new I18n(config.language ?? 'en-US')
|
||||
this.#wrapper = this.#createWrapper()
|
||||
this.#indicator = this.#wrapper.querySelector(`.${styles.indicator}`)!
|
||||
this.#statusText = this.#wrapper.querySelector(`.${styles.statusText}`)!
|
||||
@@ -49,16 +73,8 @@ export class Panel {
|
||||
|
||||
this.#setupEventListeners()
|
||||
this.#startHeaderUpdateLoop()
|
||||
// this.#expand() // debug
|
||||
|
||||
this.#showInputArea()
|
||||
|
||||
this.#bus.on('panel:show', () => this.#show())
|
||||
this.#bus.on('panel:hide', () => this.#hide())
|
||||
this.#bus.on('panel:reset', () => this.#reset())
|
||||
this.#bus.on('panel:update', (stepData) => this.#update(stepData))
|
||||
this.#bus.on('panel:expand', () => this.#expand())
|
||||
this.#bus.on('panel:collapse', () => this.#collapse())
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,18 +87,67 @@ export class Panel {
|
||||
this.#userAnswerResolver = resolve
|
||||
|
||||
// Update state to `running`
|
||||
this.#update({
|
||||
this.#updateInternal({
|
||||
type: 'output',
|
||||
displayText: this.#pageAgent.i18n.t('ui.panel.question', { question }),
|
||||
displayText: this.#i18n.t('ui.panel.question', { question }),
|
||||
}) // Expand history panel
|
||||
if (!this.#isExpanded) {
|
||||
this.#expand()
|
||||
}
|
||||
|
||||
this.#showInputArea(this.#pageAgent.i18n.t('ui.panel.userAnswerPrompt'))
|
||||
this.#showInputArea(this.#i18n.t('ui.panel.userAnswerPrompt'))
|
||||
})
|
||||
}
|
||||
|
||||
// ========== Public control methods ==========
|
||||
|
||||
show(): void {
|
||||
this.wrapper.style.display = 'block'
|
||||
void this.wrapper.offsetHeight
|
||||
this.wrapper.style.opacity = '1'
|
||||
this.wrapper.style.transform = 'translateX(-50%) translateY(0)'
|
||||
}
|
||||
|
||||
hide(): void {
|
||||
this.wrapper.style.opacity = '0'
|
||||
this.wrapper.style.transform = 'translateX(-50%) translateY(20px)'
|
||||
this.wrapper.style.display = 'none'
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
this.#state.reset()
|
||||
this.#statusText.textContent = this.#i18n.t('ui.panel.ready')
|
||||
this.#updateStatusIndicator('thinking')
|
||||
this.#updateHistory()
|
||||
this.#collapse()
|
||||
// Reset pause state via callback
|
||||
if (this.#config.getPaused()) {
|
||||
this.#config.onPauseToggle()
|
||||
}
|
||||
this.#updatePauseButton()
|
||||
// Reset user input state
|
||||
this.#isWaitingForUserAnswer = false
|
||||
this.#userAnswerResolver = null
|
||||
// Show input area
|
||||
this.#showInputArea()
|
||||
}
|
||||
|
||||
expand(): void {
|
||||
this.#expand()
|
||||
}
|
||||
|
||||
collapse(): void {
|
||||
this.#collapse()
|
||||
}
|
||||
|
||||
/**
|
||||
* Update panel with semantic data - i18n handled internally
|
||||
*/
|
||||
update(data: PanelUpdate): void {
|
||||
const stepData = this.#toStepData(data)
|
||||
this.#updateInternal(stepData)
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispose panel
|
||||
*/
|
||||
@@ -92,10 +157,102 @@ export class Panel {
|
||||
this.wrapper.remove()
|
||||
}
|
||||
|
||||
// ========== Private methods ==========
|
||||
|
||||
/**
|
||||
* Update status
|
||||
* Convert semantic update to step data with i18n
|
||||
*/
|
||||
#update(stepData: Omit<Step, 'id' | 'stepNumber' | 'timestamp'>): void {
|
||||
#toStepData(data: PanelUpdate): Omit<Step, 'id' | 'stepNumber' | 'timestamp'> {
|
||||
switch (data.type) {
|
||||
case 'thinking':
|
||||
return { type: 'thinking', displayText: data.text ?? this.#i18n.t('ui.panel.thinking') }
|
||||
case 'input':
|
||||
return { type: 'input', displayText: data.task }
|
||||
case 'question':
|
||||
return {
|
||||
type: 'output',
|
||||
displayText: this.#i18n.t('ui.panel.question', { question: data.question }),
|
||||
}
|
||||
case 'userAnswer':
|
||||
return {
|
||||
type: 'input',
|
||||
displayText: this.#i18n.t('ui.panel.userAnswer', { input: data.input }),
|
||||
}
|
||||
case 'retry':
|
||||
return { type: 'retry', displayText: `retry-ing (${data.current} / ${data.max})` }
|
||||
case 'error':
|
||||
return { type: 'error', displayText: data.message }
|
||||
case 'output':
|
||||
return { type: 'output', displayText: data.text }
|
||||
case 'completed':
|
||||
return { type: 'completed', displayText: this.#i18n.t('ui.panel.taskCompleted') }
|
||||
case 'toolExecuting':
|
||||
return {
|
||||
type: 'tool_executing',
|
||||
toolName: data.toolName,
|
||||
toolArgs: data.args,
|
||||
displayText: this.#getToolExecutingText(data.toolName, data.args),
|
||||
}
|
||||
case 'toolCompleted': {
|
||||
const displayText = this.#getToolCompletedText(data.toolName, data.args)
|
||||
if (!displayText) return { type: 'tool_executing', displayText: '' } // will be filtered
|
||||
return {
|
||||
type: 'tool_executing',
|
||||
toolName: data.toolName,
|
||||
toolArgs: data.args,
|
||||
toolResult: data.result,
|
||||
displayText,
|
||||
duration: data.duration,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#getToolExecutingText(toolName: string, args: any): string {
|
||||
switch (toolName) {
|
||||
case 'click_element_by_index':
|
||||
return this.#i18n.t('ui.tools.clicking', { index: args.index })
|
||||
case 'input_text':
|
||||
return this.#i18n.t('ui.tools.inputting', { index: args.index })
|
||||
case 'select_dropdown_option':
|
||||
return this.#i18n.t('ui.tools.selecting', { text: args.text })
|
||||
case 'scroll':
|
||||
return this.#i18n.t('ui.tools.scrolling')
|
||||
case 'wait':
|
||||
return this.#i18n.t('ui.tools.waiting', { seconds: args.seconds })
|
||||
case 'done':
|
||||
return this.#i18n.t('ui.tools.done')
|
||||
default:
|
||||
return this.#i18n.t('ui.tools.executing', { toolName })
|
||||
}
|
||||
}
|
||||
|
||||
#getToolCompletedText(toolName: string, args: any): string | null {
|
||||
switch (toolName) {
|
||||
case 'click_element_by_index':
|
||||
return this.#i18n.t('ui.tools.clicked', { index: args.index })
|
||||
case 'input_text':
|
||||
return this.#i18n.t('ui.tools.inputted', { text: args.text })
|
||||
case 'select_dropdown_option':
|
||||
return this.#i18n.t('ui.tools.selected', { text: args.text })
|
||||
case 'scroll':
|
||||
return this.#i18n.t('ui.tools.scrolled')
|
||||
case 'wait':
|
||||
return this.#i18n.t('ui.tools.waited')
|
||||
case 'done':
|
||||
return null
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update status (internal)
|
||||
*/
|
||||
#updateInternal(stepData: Omit<Step, 'id' | 'stepNumber' | 'timestamp'>): void {
|
||||
// Skip empty displayText (filtered toolCompleted for 'done')
|
||||
if (!stepData.displayText) return
|
||||
|
||||
const step = this.#state.addStep(stepData)
|
||||
|
||||
// Queue header text update (will be processed by periodic check)
|
||||
@@ -120,59 +277,20 @@ export class Panel {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show panel
|
||||
*/
|
||||
#show(): void {
|
||||
this.wrapper.style.display = 'block'
|
||||
// Force reflow to trigger animation
|
||||
void this.wrapper.offsetHeight
|
||||
this.wrapper.style.opacity = '1'
|
||||
this.wrapper.style.transform = 'translateX(-50%) translateY(0)'
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide panel
|
||||
*/
|
||||
#hide(): void {
|
||||
this.wrapper.style.opacity = '0'
|
||||
this.wrapper.style.transform = 'translateX(-50%) translateY(20px)'
|
||||
this.wrapper.style.display = 'none'
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset state
|
||||
*/
|
||||
#reset(): void {
|
||||
this.#state.reset()
|
||||
this.#statusText.textContent = this.#pageAgent.i18n.t('ui.panel.ready')
|
||||
this.#updateStatusIndicator('thinking')
|
||||
this.#updateHistory()
|
||||
this.#collapse()
|
||||
// Reset pause state
|
||||
this.#pageAgent.paused = false
|
||||
this.#updatePauseButton()
|
||||
// Reset user input state
|
||||
this.#isWaitingForUserAnswer = false
|
||||
this.#userAnswerResolver = null
|
||||
// Show input area
|
||||
this.#showInputArea()
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle pause state
|
||||
*/
|
||||
#togglePause(): void {
|
||||
this.#pageAgent.paused = !this.#pageAgent.paused
|
||||
const paused = this.#config.onPauseToggle()
|
||||
this.#updatePauseButton()
|
||||
|
||||
// Update status display
|
||||
if (this.#pageAgent.paused) {
|
||||
this.#statusText.textContent = this.#pageAgent.i18n.t('ui.panel.paused')
|
||||
this.#updateStatusIndicator('thinking') // Use existing thinking state
|
||||
if (paused) {
|
||||
this.#statusText.textContent = this.#i18n.t('ui.panel.paused')
|
||||
this.#updateStatusIndicator('thinking')
|
||||
} else {
|
||||
this.#statusText.textContent = this.#pageAgent.i18n.t('ui.panel.continueExecution')
|
||||
this.#updateStatusIndicator('tool_executing') // Restore to execution state
|
||||
this.#statusText.textContent = this.#i18n.t('ui.panel.continueExecution')
|
||||
this.#updateStatusIndicator('tool_executing')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,13 +298,14 @@ export class Panel {
|
||||
* Update pause button state
|
||||
*/
|
||||
#updatePauseButton(): void {
|
||||
if (this.#pageAgent.paused) {
|
||||
const paused = this.#config.getPaused()
|
||||
if (paused) {
|
||||
this.#pauseButton.textContent = '▶'
|
||||
this.#pauseButton.title = this.#pageAgent.i18n.t('ui.panel.continue')
|
||||
this.#pauseButton.title = this.#i18n.t('ui.panel.continue')
|
||||
this.#pauseButton.classList.add(styles.paused)
|
||||
} else {
|
||||
this.#pauseButton.textContent = '⏸︎'
|
||||
this.#pauseButton.title = this.#pageAgent.i18n.t('ui.panel.pause')
|
||||
this.#pauseButton.title = this.#i18n.t('ui.panel.pause')
|
||||
this.#pauseButton.classList.remove(styles.paused)
|
||||
}
|
||||
}
|
||||
@@ -196,12 +315,12 @@ export class Panel {
|
||||
*/
|
||||
#stopAgent(): void {
|
||||
// Update status display
|
||||
this.#update({
|
||||
this.#updateInternal({
|
||||
type: 'error',
|
||||
displayText: this.#pageAgent.i18n.t('ui.panel.taskTerminated'),
|
||||
displayText: this.#i18n.t('ui.panel.taskTerminated'),
|
||||
})
|
||||
|
||||
this.#pageAgent.dispose()
|
||||
this.#config.onStop()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -218,7 +337,7 @@ export class Panel {
|
||||
// Handle user input mode
|
||||
this.#handleUserAnswer(input)
|
||||
} else {
|
||||
this.#pageAgent.execute(input)
|
||||
this.#config.onExecuteTask(input)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,9 +346,9 @@ export class Panel {
|
||||
*/
|
||||
#handleUserAnswer(input: string): void {
|
||||
// Add user input to history
|
||||
this.#update({
|
||||
this.#updateInternal({
|
||||
type: 'input',
|
||||
displayText: this.#pageAgent.i18n.t('ui.panel.userAnswer', { input }),
|
||||
displayText: this.#i18n.t('ui.panel.userAnswer', { input }),
|
||||
})
|
||||
|
||||
// Reset state
|
||||
@@ -248,7 +367,7 @@ export class Panel {
|
||||
#showInputArea(placeholder?: string): void {
|
||||
// Clear input field
|
||||
this.#taskInput.value = ''
|
||||
this.#taskInput.placeholder = placeholder || this.#pageAgent.i18n.t('ui.panel.taskInput')
|
||||
this.#taskInput.placeholder = placeholder || this.#i18n.t('ui.panel.taskInput')
|
||||
this.#inputSection.classList.remove(styles.hidden)
|
||||
// Focus on input field
|
||||
setTimeout(() => {
|
||||
@@ -294,23 +413,23 @@ export class Panel {
|
||||
stepNumber: 0,
|
||||
timestamp: new Date(),
|
||||
type: 'thinking',
|
||||
displayText: this.#pageAgent.i18n.t('ui.panel.waitingPlaceholder'),
|
||||
displayText: this.#i18n.t('ui.panel.waitingPlaceholder'),
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div class="${styles.header}">
|
||||
<div class="${styles.statusSection}">
|
||||
<div class="${styles.indicator} ${styles.thinking}"></div>
|
||||
<div class="${styles.statusText}">${this.#pageAgent.i18n.t('ui.panel.ready')}</div>
|
||||
<div class="${styles.statusText}">${this.#i18n.t('ui.panel.ready')}</div>
|
||||
</div>
|
||||
<div class="${styles.controls}">
|
||||
<button class="${styles.controlButton} ${styles.expandButton}" title="${this.#pageAgent.i18n.t('ui.panel.expand')}">
|
||||
<button class="${styles.controlButton} ${styles.expandButton}" title="${this.#i18n.t('ui.panel.expand')}">
|
||||
▼
|
||||
</button>
|
||||
<button class="${styles.controlButton} ${styles.pauseButton}" title="${this.#pageAgent.i18n.t('ui.panel.pause')}">
|
||||
<button class="${styles.controlButton} ${styles.pauseButton}" title="${this.#i18n.t('ui.panel.pause')}">
|
||||
⏸︎
|
||||
</button>
|
||||
<button class="${styles.controlButton} ${styles.stopButton}" title="${this.#pageAgent.i18n.t('ui.panel.stop')}">
|
||||
<button class="${styles.controlButton} ${styles.stopButton}" title="${this.#i18n.t('ui.panel.stop')}">
|
||||
X
|
||||
</button>
|
||||
</div>
|
||||
@@ -501,8 +620,8 @@ export class Panel {
|
||||
// Check if this is a result from done tool
|
||||
if (step.toolName === 'done') {
|
||||
// Judge success or failure based on result
|
||||
const failureKeyword = this.#pageAgent.i18n.t('ui.tools.resultFailure')
|
||||
const errorKeyword = this.#pageAgent.i18n.t('ui.tools.resultError')
|
||||
const failureKeyword = this.#i18n.t('ui.tools.resultFailure')
|
||||
const errorKeyword = this.#i18n.t('ui.tools.resultError')
|
||||
const isSuccess =
|
||||
!step.toolResult ||
|
||||
(!step.toolResult.includes(failureKeyword) && !step.toolResult.includes(errorKeyword))
|
||||
@@ -531,7 +650,7 @@ export class Panel {
|
||||
}
|
||||
|
||||
const durationText = step.duration ? ` · ${step.duration}ms` : ''
|
||||
const stepLabel = this.#pageAgent.i18n.t('ui.panel.step', {
|
||||
const stepLabel = this.#i18n.t('ui.panel.step', {
|
||||
number: step.stepNumber.toString(),
|
||||
time,
|
||||
duration: durationText || '', // Explicitly pass empty string to replace template
|
||||
@@ -550,47 +669,3 @@ export class Panel {
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get display text for tool execution
|
||||
*/
|
||||
export function getToolExecutingText(toolName: string, args: any, i18n: I18n): string {
|
||||
switch (toolName) {
|
||||
case 'click_element_by_index':
|
||||
return i18n.t('ui.tools.clicking', { index: args.index })
|
||||
case 'input_text':
|
||||
return i18n.t('ui.tools.inputting', { index: args.index })
|
||||
case 'select_dropdown_option':
|
||||
return i18n.t('ui.tools.selecting', { text: args.text })
|
||||
case 'scroll':
|
||||
return i18n.t('ui.tools.scrolling')
|
||||
case 'wait':
|
||||
return i18n.t('ui.tools.waiting', { seconds: args.seconds })
|
||||
case 'done':
|
||||
return i18n.t('ui.tools.done')
|
||||
default:
|
||||
return i18n.t('ui.tools.executing', { toolName })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get display text for tool completion
|
||||
*/
|
||||
export function getToolCompletedText(toolName: string, args: any, i18n: I18n): string | null {
|
||||
switch (toolName) {
|
||||
case 'click_element_by_index':
|
||||
return i18n.t('ui.tools.clicked', { index: args.index })
|
||||
case 'input_text':
|
||||
return i18n.t('ui.tools.inputted', { text: args.text })
|
||||
case 'select_dropdown_option':
|
||||
return i18n.t('ui.tools.selected', { text: args.text })
|
||||
case 'scroll':
|
||||
return i18n.t('ui.tools.scrolled')
|
||||
case 'wait':
|
||||
return i18n.t('ui.tools.waited')
|
||||
case 'done':
|
||||
return null
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Motion } from 'ai-motion'
|
||||
|
||||
import { isPageDark } from '../utils/checkDarkMode'
|
||||
import { isPageDark } from './checkDarkMode'
|
||||
|
||||
import styles from './SimulatorMask.module.css'
|
||||
import cursorStyles from './cursor.module.css'
|
||||
@@ -0,0 +1,4 @@
|
||||
export { Panel, type PanelConfig, type PanelUpdate } from './Panel'
|
||||
export { SimulatorMask } from './SimulatorMask'
|
||||
export { UIState, type Step, type AgentStatus } from './UIState'
|
||||
export { I18n, type SupportedLanguage, type TranslationKey } from './i18n'
|
||||
@@ -0,0 +1,6 @@
|
||||
export function truncate(text: string, maxLength: number): string {
|
||||
if (text.length > maxLength) {
|
||||
return text.substring(0, maxLength) + '...'
|
||||
}
|
||||
return text
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
// @workaround DTS bug
|
||||
// dts do not work with monorepo path mapping
|
||||
// disable path mapping for it
|
||||
"paths": {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo",
|
||||
"noEmit": false,
|
||||
"allowImportingTsExtensions": false,
|
||||
"baseUrl": ".",
|
||||
"outDir": "dist"
|
||||
},
|
||||
"include": ["**/*.ts", "**/*.js"],
|
||||
"exclude": ["dist", "node_modules"]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
// @ts-check
|
||||
import chalk from 'chalk'
|
||||
import { dirname, resolve } from 'path'
|
||||
import dts from 'unplugin-dts/vite'
|
||||
import { fileURLToPath } from 'url'
|
||||
import { defineConfig } from 'vite'
|
||||
import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js'
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||
|
||||
console.log(chalk.cyan(`📦 Building @page-agent/ui`))
|
||||
|
||||
export default defineConfig({
|
||||
clearScreen: false,
|
||||
plugins: [
|
||||
dts({ tsconfigPath: './tsconfig.dts.json', bundleTypes: true }),
|
||||
cssInjectedByJsPlugin({ relativeCSSInjection: true }),
|
||||
],
|
||||
publicDir: false,
|
||||
esbuild: {
|
||||
keepNames: true,
|
||||
},
|
||||
build: {
|
||||
lib: {
|
||||
entry: resolve(__dirname, 'src/index.ts'),
|
||||
name: 'PageAgentUI',
|
||||
fileName: 'page-agent-ui',
|
||||
formats: ['es'],
|
||||
},
|
||||
outDir: resolve(__dirname, 'dist', 'lib'),
|
||||
rollupOptions: {
|
||||
external: ['ai-motion'],
|
||||
},
|
||||
minify: false,
|
||||
sourcemap: true,
|
||||
cssCodeSplit: true,
|
||||
},
|
||||
define: {
|
||||
'process.env.NODE_ENV': '"production"',
|
||||
},
|
||||
})
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@page-agent/website",
|
||||
"private": true,
|
||||
"version": "0.0.6",
|
||||
"version": "0.0.8",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -9,15 +9,12 @@
|
||||
"preview": "vite preview",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"page-agent": "*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/vite": "^4.1.14",
|
||||
"@types/react": "^19.2.2",
|
||||
"@types/react-dom": "^19.2.1",
|
||||
"@vitejs/plugin-react-swc": "^4.1.0",
|
||||
"i18next": "^25.6.0",
|
||||
"i18next": "^25.7.1",
|
||||
"i18next-browser-languagedetector": "^8.2.0",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
|
||||
@@ -9,7 +9,7 @@ export default function Configuration() {
|
||||
className="mb-8"
|
||||
language="typescript"
|
||||
code={`// config
|
||||
type PageAgentConfig = LLMConfig & AgentConfig & DomConfig
|
||||
type PageAgentConfig = LLMConfig & AgentConfig & PageControllerConfig
|
||||
|
||||
interface LLMConfig {
|
||||
baseURL?: string
|
||||
@@ -93,12 +93,13 @@ interface AgentConfig {
|
||||
experimentalPreventNewPage?: boolean
|
||||
}
|
||||
|
||||
interface DomConfig {
|
||||
interface PageControllerConfig {
|
||||
interactiveBlacklist?: (Element | (() => Element))[]
|
||||
interactiveWhitelist?: (Element | (() => Element))[]
|
||||
include_attributes?: string[]
|
||||
highlightOpacity?: number
|
||||
highlightLabelOpacity?: number
|
||||
viewportExpansion?: number
|
||||
}
|
||||
|
||||
`}
|
||||
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
declare module '*.module.css' {
|
||||
const classes: Record<string, string>
|
||||
export default classes
|
||||
}
|
||||
@@ -1,17 +1,21 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo",
|
||||
"baseUrl": "./",
|
||||
"noEmit": false,
|
||||
"allowImportingTsExtensions": false,
|
||||
"baseUrl": ".",
|
||||
"outDir": "dist",
|
||||
"paths": {
|
||||
// Self root
|
||||
"@/*": ["src/*"],
|
||||
|
||||
// Simplified monorepo solution (raw npm workspace with hoisting)
|
||||
"@page-agent/page-controller": ["../page-controller/src/PageController.ts"],
|
||||
"page-agent": ["../page-agent/src/PageAgent.ts"]
|
||||
}
|
||||
},
|
||||
"include": ["src", "env.d.ts"],
|
||||
"references": [{ "path": "../page-agent" }]
|
||||
"include": ["**/*.ts", "**/*.tsx"],
|
||||
"exclude": ["dist", "node_modules"],
|
||||
"references": [{ "path": "../page-controller" }, { "path": "../page-agent" }]
|
||||
}
|
||||
|
||||
@@ -18,7 +18,9 @@ export default defineConfig({
|
||||
// Self root
|
||||
'@': resolve(__dirname, 'src'),
|
||||
|
||||
// Simplified monorepo solution (raw npm workspace with hoisting)
|
||||
// Monorepo packages (always bundle local code instead of npm versions)
|
||||
'@page-agent/page-controller': resolve(__dirname, '../page-controller/src/PageController.ts'),
|
||||
'@page-agent/ui': resolve(__dirname, '../ui/src/index.ts'),
|
||||
'page-agent': resolve(__dirname, '../page-agent/src/PageAgent.ts'),
|
||||
},
|
||||
},
|
||||
|
||||
+37
-2
@@ -41,19 +41,53 @@ const packages = readdirSync(packagesDir, { withFileTypes: true })
|
||||
|
||||
let hasChanges = !!versionArg
|
||||
|
||||
/**
|
||||
* Check if a dependency name is a page-agent internal package
|
||||
*/
|
||||
function isInternalPackage(name) {
|
||||
return name === 'page-agent' || name.startsWith('@page-agent/')
|
||||
}
|
||||
|
||||
/**
|
||||
* Update internal package versions in dependencies object
|
||||
* @returns {boolean} Whether any changes were made
|
||||
*/
|
||||
function updateInternalDeps(deps, newVersion) {
|
||||
if (!deps) return false
|
||||
let changed = false
|
||||
for (const [name, version] of Object.entries(deps)) {
|
||||
if (isInternalPackage(name) && version !== newVersion) {
|
||||
deps[name] = newVersion
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
return changed
|
||||
}
|
||||
|
||||
for (const pkg of packages) {
|
||||
const pkgPath = join(packagesDir, pkg, 'package.json')
|
||||
if (!existsSync(pkgPath)) continue
|
||||
|
||||
const pkgJson = JSON.parse(readFileSync(pkgPath, 'utf-8'))
|
||||
const oldVersion = pkgJson.version
|
||||
let pkgChanged = false
|
||||
|
||||
if (oldVersion === newVersion) {
|
||||
// Update package version
|
||||
if (oldVersion !== newVersion) {
|
||||
pkgJson.version = newVersion
|
||||
pkgChanged = true
|
||||
}
|
||||
|
||||
// Update internal dependencies (dependencies only, devDeps keep "*")
|
||||
if (updateInternalDeps(pkgJson.dependencies, newVersion)) {
|
||||
pkgChanged = true
|
||||
}
|
||||
|
||||
if (!pkgChanged) {
|
||||
console.log(chalk.dim(` ${pkgJson.name}: ${newVersion} (unchanged)`))
|
||||
continue
|
||||
}
|
||||
|
||||
pkgJson.version = newVersion
|
||||
writeFileSync(pkgPath, JSON.stringify(pkgJson, null, '\t') + '\n')
|
||||
console.log(
|
||||
chalk.green('✓') +
|
||||
@@ -68,6 +102,7 @@ console.log(chalk.green.bold(`\n✓ Version synced: ${newVersion}\n`))
|
||||
if (hasChanges) {
|
||||
const tagName = `v${newVersion}`
|
||||
console.log(chalk.cyan.bold('📋 Next steps:\n'))
|
||||
console.log(chalk.blueBright(`npm i`))
|
||||
console.log(
|
||||
chalk.blueBright(`git add . && git commit -m "chore(version): bump version to ${newVersion}"`)
|
||||
)
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"target": "ES2024",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2024", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
"allowJs": true,
|
||||
|
||||
// "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo",
|
||||
// "baseUrl": "src",
|
||||
"baseUrl": ".",
|
||||
"outDir": "dist",
|
||||
// "incremental": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"verbatimModuleSyntax": false,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"allowImportingTsExtensions": true,
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
|
||||
// "paths": {
|
||||
// // Simplified monorepo solution (raw npm workspace with hoisting)
|
||||
// "@page-agent/page-controller": ["./packages/page-controller/src/PageController.ts"],
|
||||
// "page-agent": ["./packages/page-agent/src/PageAgent.ts"]
|
||||
// }
|
||||
}
|
||||
// "references": [
|
||||
// { "path": "./packages/page-controller" },
|
||||
// { "path": "./packages/page-agent" },
|
||||
// { "path": "./packages/website" }
|
||||
// ],
|
||||
// "include": ["packages/*/src/**/*.ts", "packages/*/src/**/*.tsx"],
|
||||
// "exclude": ["node_modules", "dist", "packages/*/dist"]
|
||||
// "files": ["env.d.ts"]
|
||||
// "files": []
|
||||
}
|
||||
+9
-26
@@ -1,28 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2024",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2024", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
"allowJs": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
// "allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": false,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"references": [{ "path": "./packages/page-agent" }, { "path": "./packages/website" }],
|
||||
"files": []
|
||||
"extends": "./tsconfig.base.json",
|
||||
"references": [
|
||||
{ "path": "./packages/page-controller" },
|
||||
{ "path": "./packages/ui" },
|
||||
{ "path": "./packages/page-agent" },
|
||||
{ "path": "./packages/website" }
|
||||
],
|
||||
"include": ["packages/*/src/**/*.ts", "packages/*/src/**/*.tsx"],
|
||||
"exclude": ["node_modules", "dist", "packages/*/dist"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user