Compare commits
50 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 64f1a8c443 | |||
| 635416f964 | |||
| 7c2d000e29 | |||
| b36a0c0261 | |||
| 339b066d2d | |||
| c8efffa80a | |||
| 59235263ad | |||
| bc1e8fea3c | |||
| 85a4096b2d | |||
| 0ef84bac13 | |||
| a7834c77e1 | |||
| 030e5cfc54 | |||
| aa694e3b85 | |||
| 2fee2d5ea3 | |||
| 3082843810 | |||
| 34098e2f66 | |||
| 8461006c91 | |||
| 2e76a35188 | |||
| abc96b415f | |||
| 77ce3b0b49 | |||
| 3742f75d14 | |||
| c2e9e6a571 | |||
| 54eb50f7be | |||
| 9351948794 | |||
| 68d1a10dd0 | |||
| b9fe8146b6 | |||
| 2a727226bc | |||
| 0167377b2f | |||
| f21914284c | |||
| 035091045a | |||
| 1cd18b3ba9 | |||
| 8ba04bf243 | |||
| 67bf38c8d5 | |||
| 8885bde0ec | |||
| b33e361cee | |||
| d1c8ca8197 | |||
| 2b8b6ef86a | |||
| 0b46fd1d7a | |||
| e2819714f1 | |||
| 3e6c431a0b | |||
| d0836fd788 | |||
| 32c17627e9 | |||
| 13a6d2c164 | |||
| d756a2ef1c | |||
| b9b812cc25 | |||
| 70caf6856b | |||
| 34dad35698 | |||
| 34e3a1a75c | |||
| cac86f2fbf | |||
| d1290c8070 |
@@ -13,7 +13,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [20, 22]
|
node-version: [20, 24, 25]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
@@ -24,6 +24,13 @@ jobs:
|
|||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
cache: 'npm'
|
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
|
- name: Install dependencies
|
||||||
run: npm install
|
run: npm install
|
||||||
|
|
||||||
|
|||||||
@@ -18,14 +18,14 @@ jobs:
|
|||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 24
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm ci
|
run: npm ci
|
||||||
|
|
||||||
- name: Build demo
|
- name: Build demo
|
||||||
run: npm run build
|
run: npm run build:website
|
||||||
|
|
||||||
- name: Setup Pages
|
- name: Setup Pages
|
||||||
uses: actions/configure-pages@v5
|
uses: actions/configure-pages@v5
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ jobs:
|
|||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 24
|
||||||
registry-url: 'https://registry.npmjs.org'
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
|
||||||
# Ensure npm 11.5.1 or later is installed
|
# Ensure npm 11.5.1 or later is installed
|
||||||
@@ -29,7 +29,7 @@ jobs:
|
|||||||
run: npm ci
|
run: npm ci
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: npm run build
|
run: npm run build:libs
|
||||||
|
|
||||||
- name: Publish all public packages
|
- name: Publish all public packages
|
||||||
run: npm publish --workspaces --access public
|
run: npm publish --workspaces --access public
|
||||||
@@ -23,6 +23,7 @@ dist-ssr
|
|||||||
*.njsproj
|
*.njsproj
|
||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.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"]
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+8
-1
@@ -1,6 +1,13 @@
|
|||||||
{
|
{
|
||||||
"editor.fontLigatures": true,
|
"editor.fontLigatures": true,
|
||||||
"cSpell.words": ["HITL", "innerhtml", "opensource", "retryable", "wouter"],
|
"cSpell.words": [
|
||||||
|
"HITL",
|
||||||
|
"innerhtml",
|
||||||
|
"llms",
|
||||||
|
"opensource",
|
||||||
|
"retryable",
|
||||||
|
"wouter"
|
||||||
|
],
|
||||||
"markdownlint.config": {
|
"markdownlint.config": {
|
||||||
// "comment": "Relaxed rules",
|
// "comment": "Relaxed rules",
|
||||||
"default": true,
|
"default": true,
|
||||||
|
|||||||
@@ -7,9 +7,10 @@ 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
|
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`
|
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:
|
And other internal packages:
|
||||||
|
|
||||||
- **Page Controller** (`packages/page-controller/`) - DOM operations and element interactions module. Independent of LLM, can be tested in unit tests.
|
- **Page Controller** (`packages/page-controller/`) - DOM operations and element interactions. Independent of LLM.
|
||||||
|
- **UI** (`packages/ui/`) - Panel, SimulatorMask, and i18n. Decoupled from PageAgent.
|
||||||
|
|
||||||
## Development Commands
|
## Development Commands
|
||||||
|
|
||||||
@@ -17,24 +18,12 @@ And other internal packages. Such as:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm start # Start website dev server
|
npm start # Start website dev server
|
||||||
npm run dev # Same as start
|
|
||||||
npm run build # Build all packages
|
npm run build # Build all packages
|
||||||
npm run build:lib # Build page-agent library only
|
npm run build:libs # Build all libraries
|
||||||
|
npm run build:website # Build the website
|
||||||
npm run lint # ESLint with TypeScript strict rules
|
npm run lint # ESLint with TypeScript strict rules
|
||||||
```
|
```
|
||||||
|
|
||||||
### Package-specific Commands
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Core library
|
|
||||||
npm run build --workspace=page-agent
|
|
||||||
npm run build:watch --workspace=page-agent
|
|
||||||
|
|
||||||
# Website
|
|
||||||
npm run dev --workspace=@page-agent/website
|
|
||||||
npm run build --workspace=@page-agent/website
|
|
||||||
```
|
|
||||||
|
|
||||||
## Architecture & Critical Patterns
|
## Architecture & Critical Patterns
|
||||||
|
|
||||||
### Monorepo Structure
|
### Monorepo Structure
|
||||||
@@ -46,11 +35,10 @@ You must update tsconfig and vite config if you add/remove/rename a package.
|
|||||||
```bash
|
```bash
|
||||||
packages/
|
packages/
|
||||||
├── page-agent/ # npm: "page-agent" ⭐ MAIN
|
├── page-agent/ # npm: "page-agent" ⭐ MAIN
|
||||||
│ ├── src/ # AI agent source
|
│ ├── src/
|
||||||
│ │ ├── PageAgent.ts # Main AI agent class
|
│ │ ├── PageAgent.ts # Main AI agent class
|
||||||
│ │ ├── tools/ # LLM tool definitions
|
│ │ ├── tools/ # LLM tool definitions
|
||||||
│ │ ├── llms/ # LLM integration
|
│ │ └── llms/ # LLM integration
|
||||||
│ │ └── ui/ # UI components
|
|
||||||
│ ├── vite.config.js # Library build (ES + UMD)
|
│ ├── vite.config.js # Library build (ES + UMD)
|
||||||
│ └── package.json
|
│ └── package.json
|
||||||
├── website/ # npm: "@page-agent/website" (private) ⭐ MAIN
|
├── website/ # npm: "@page-agent/website" (private) ⭐ MAIN
|
||||||
@@ -59,19 +47,25 @@ packages/
|
|||||||
│
|
│
|
||||||
│ # ...internal packages below...
|
│ # ...internal packages below...
|
||||||
│
|
│
|
||||||
└── page-controller/ # npm: "@page-agent/page-controller"
|
├── page-controller/ # npm: "@page-agent/page-controller"
|
||||||
└── src/ # DOM operations source
|
│ └── src/ # DOM operations
|
||||||
├── PageController.ts # Main controller class
|
│ ├── PageController.ts
|
||||||
├── actions.ts # Element interaction actions
|
│ ├── actions.ts
|
||||||
└── dom/ # DOM tree extraction
|
│ └── dom/
|
||||||
|
└── ui/ # npm: "@page-agent/ui"
|
||||||
|
└── src/ # Panel and Mask Effects
|
||||||
|
├── Panel.ts
|
||||||
|
├── SimulatorMask.ts
|
||||||
|
└── i18n/
|
||||||
```
|
```
|
||||||
|
|
||||||
`workspaces` must be written in topological order to guarantee build order.
|
`workspaces` must be written in topological order to guarantee build order.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
// internal deps ...
|
// internal deps (topological order)
|
||||||
"packages/page-controller",
|
"packages/page-controller",
|
||||||
|
"packages/ui",
|
||||||
"packages/page-agent",
|
"packages/page-agent",
|
||||||
"packages/website"
|
"packages/website"
|
||||||
],
|
],
|
||||||
@@ -80,8 +74,9 @@ packages/
|
|||||||
### Module Boundaries (Critical)
|
### Module Boundaries (Critical)
|
||||||
|
|
||||||
- **Website** (`packages/website/`): CAN import from `page-agent` for demos. Alias `@/` → `website/src/`
|
- **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 Agent** (`packages/page-agent/`): The core lib. Imports from `@page-agent/page-controller` and `@page-agent/ui`.
|
||||||
- **Page Controller** (`packages/page-controller/`): Internal lib. Pure DOM operations, NO LLM dependency. Never import from page-agent.
|
- **UI** (`packages/ui/`): Panel, Mask, i18n. No dependency on page-agent.
|
||||||
|
- **Page Controller** (`packages/page-controller/`): Pure DOM operations. No LLM or UI dependency.
|
||||||
|
|
||||||
### PageController ↔ PageAgent Communication
|
### PageController ↔ PageAgent Communication
|
||||||
|
|
||||||
@@ -108,19 +103,6 @@ DOM element references and internal state (selectorMap, elementTextMap) are enca
|
|||||||
3. **LLM Processing**: AI model returns action plans (in page-agent)
|
3. **LLM Processing**: AI model returns action plans (in page-agent)
|
||||||
4. **Indexed Operations**: PageAgent calls PageController methods by element index
|
4. **Indexed Operations**: PageAgent calls PageController methods by element index
|
||||||
|
|
||||||
### 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())
|
|
||||||
```
|
|
||||||
|
|
||||||
### Hash Routing Requirement
|
### Hash Routing Requirement
|
||||||
|
|
||||||
Uses wouter with `useHashLocation` for static hosting:
|
Uses wouter with `useHashLocation` for static hosting:
|
||||||
@@ -146,10 +128,8 @@ Query params configure `PageAgentConfig` automatically in `src/entry.ts`.
|
|||||||
| File | Description |
|
| File | Description |
|
||||||
|------|-------------|
|
|------|-------------|
|
||||||
| `src/PageAgent.ts` | ⭐ Main AI agent class orchestrating tools and LLM |
|
| `src/PageAgent.ts` | ⭐ Main AI agent class orchestrating tools and LLM |
|
||||||
| `src/entry.ts` | CDN/UMD entry point with auto-initialization |
|
| `src/umd.ts` | CDN/UMD entry point with auto-initialization |
|
||||||
| `src/tools/` | Tool definitions that call PageController methods |
|
| `src/tools/` | Tool definitions that call PageController methods |
|
||||||
| `src/utils/bus.ts` | Type-safe event bus for decoupled communication |
|
|
||||||
| `src/ui/` | UI components (Panel, SimulatorMask) with CSS modules |
|
|
||||||
| `src/llms/` | LLM integration and communication layer |
|
| `src/llms/` | LLM integration and communication layer |
|
||||||
| `vite.config.js` | Library build configuration (ES + UMD) |
|
| `vite.config.js` | Library build configuration (ES + UMD) |
|
||||||
|
|
||||||
@@ -195,11 +175,6 @@ Query params configure `PageAgentConfig` automatically in `src/entry.ts`.
|
|||||||
2. Expose via async method in `PageController.ts`
|
2. Expose via async method in `PageController.ts`
|
||||||
3. Export from `packages/page-controller/src/index.ts`
|
3. Export from `packages/page-controller/src/index.ts`
|
||||||
|
|
||||||
### New UI Component
|
|
||||||
|
|
||||||
1. Create in `packages/page-agent/src/ui/` with colocated CSS modules
|
|
||||||
2. Use event bus for PageAgent communication
|
|
||||||
|
|
||||||
## Code Standards
|
## Code Standards
|
||||||
|
|
||||||
### TypeScript
|
### TypeScript
|
||||||
@@ -234,5 +209,4 @@ Query params configure `PageAgentConfig` automatically in `src/entry.ts`.
|
|||||||
|
|
||||||
1. Check `packages/page-agent/dist/lib/page-agent.umd.js` builds correctly
|
1. Check `packages/page-agent/dist/lib/page-agent.umd.js` builds correctly
|
||||||
2. Test CDN injection with query params
|
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
|
4. Use `packages/website/src/test-pages/` for isolated testing
|
||||||
|
|||||||
+16
-25
@@ -7,11 +7,9 @@ Thank you for your interest in contributing to Page-Agent! We welcome contributi
|
|||||||
### Development Setup
|
### Development Setup
|
||||||
|
|
||||||
1. **Prerequisites**
|
1. **Prerequisites**
|
||||||
- Node.js 20+
|
- `node.js >= 20` with `npm >= 10`
|
||||||
- npm 10+
|
- An editor that supports `ts/eslint/prettier`
|
||||||
- typescript as required in package.json
|
- Make sure `eslint`, `prettier` and `commitlint` work well
|
||||||
- vscode or other editors that support ts/eslint/prettier
|
|
||||||
- make sure `eslint`, `auto-format` and `commit-lint` all work
|
|
||||||
|
|
||||||
2. **Setup**
|
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
|
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`
|
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
|
## 🤝 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`
|
- Github Copilot: `./.github/copilot-instructions.md`
|
||||||
- Claude Code: `./CLAUDE.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
|
## 🔧 Development Workflows
|
||||||
|
|
||||||
### Website Development
|
### Website Development
|
||||||
@@ -120,24 +109,26 @@ npm start # React development server
|
|||||||
|
|
||||||
### Core Lib Development and Testing
|
### Core Lib Development and Testing
|
||||||
|
|
||||||
|
> @TODO this part is outdated. Update this.
|
||||||
|
|
||||||
- Config your LLM API
|
- Config your LLM API
|
||||||
- Start a local script
|
- Start and serve a local umd script
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run build:lib:watch # Auto-rebuild on changes
|
npm run dev:umd # Serving UMD with auto rebuild at http://localhost:5173/page-agent.umd.cjs
|
||||||
```
|
```
|
||||||
|
|
||||||
- Add a new bookmark enable it on other website
|
- Add a new bookmark enable it on other website
|
||||||
|
|
||||||
```
|
```
|
||||||
javascript:(function(){var s=document.createElement('script');s.src=`http://localhost:5173/dist/lib/page-agent.umd.cjs?t=${Math.random()}`;s.onload=()=>console.log(%27PageAgent ready!%27);document.head.appendChild(s);})();
|
javascript:(function(){var s=document.createElement('script');s.src=`http://localhost:5173/page-agent.umd.cjs?t=${Math.random()}`;s.onload=()=>console.log(%27PageAgent ready!%27);document.head.appendChild(s);})();
|
||||||
```
|
```
|
||||||
|
|
||||||
### Adding Documentation
|
### Adding Documentation
|
||||||
|
|
||||||
1. Create `pages/docs/section/page-name/page.tsx`
|
1. Create `website/src/docs/section/page-name/page.tsx`
|
||||||
2. Add route to `pages/router.tsx`
|
2. Add route to `website/src/router.tsx`
|
||||||
3. Add navigation link to `pages/components/DocsLayout.tsx`
|
3. Add navigation link to `website/src/components/DocsLayout.tsx`
|
||||||
|
|
||||||
## 🎯 Contribution Areas
|
## 🎯 Contribution Areas
|
||||||
|
|
||||||
|
|||||||
+8
-10
@@ -31,10 +31,11 @@
|
|||||||
### CDN 集成
|
### CDN 集成
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<!-- 临时 CDN URL. 未来会变更 -->
|
// CDN - https://cdn.jsdelivr.net/npm/page-agent@latest/dist/umd/page-agent.js
|
||||||
<script
|
// Mirror(CN) - https://registry.npmmirror.com/page-agent/latest/files/dist/umd/page-agent.js
|
||||||
src="https://hwcxiuzfylggtcktqgij.supabase.co/storage/v1/object/public/demo-public/v0.0.4/page-agent.js"
|
<script
|
||||||
crossorigin="true"
|
src="https://registry.npmmirror.com/page-agent/latest/files/dist/umd/page-agent.js"
|
||||||
|
crossorigin="true"
|
||||||
type="text/javascript"
|
type="text/javascript"
|
||||||
></script>
|
></script>
|
||||||
```
|
```
|
||||||
@@ -72,12 +73,9 @@ PageAgent adopts a simplified monorepo structure:
|
|||||||
```
|
```
|
||||||
packages/
|
packages/
|
||||||
├── page-agent/ # AI agent (npm: page-agent)
|
├── page-agent/ # AI agent (npm: page-agent)
|
||||||
│ ├── PageAgent # Agent main loop
|
├── page-controller/ # DOM 操作 (npm: @page-agent/page-controller)
|
||||||
│ ├── tools/ # LLM tool definitions
|
├── ui/ # 面板 & 蒙层 & 模拟鼠标 (npm: @page-agent/ui)
|
||||||
│ ├── ui/ # UI components & panels
|
└── website/ # 文档站点
|
||||||
│ └── llms/ # LLM integration layer
|
|
||||||
├── page-controller/ # DOM operations (npm: @page-agent/page-controller)
|
|
||||||
└── website/ # Documentation site
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🤝 贡献
|
## 🤝 贡献
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# PageAgent 🤖🪄
|
# PageAgent 🤖🪄
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
[](https://badge.fury.io/js/page-agent) [](https://opensource.org/licenses/MIT) [](http://www.typescriptlang.org/) [](https://www.npmjs.com/package/page-agent) [](https://bundlephobia.com/package/page-agent) [](https://github.com/alibaba/page-agent)
|
[](https://badge.fury.io/js/page-agent) [](https://opensource.org/licenses/MIT) [](http://www.typescriptlang.org/) [](https://www.npmjs.com/package/page-agent) [](https://bundlephobia.com/package/page-agent) [](https://github.com/alibaba/page-agent)
|
||||||
|
|
||||||
@@ -31,10 +31,11 @@ The GUI Agent Living in Your Webpage. Control web interfaces with natural langua
|
|||||||
### CDN Integration
|
### CDN Integration
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<!-- temporary CDN URL. May change in the future -->
|
// CDN - https://cdn.jsdelivr.net/npm/page-agent@latest/dist/umd/page-agent.js
|
||||||
<script
|
// Mirror(CN) - https://registry.npmmirror.com/page-agent/latest/files/dist/umd/page-agent.js
|
||||||
src="https://hwcxiuzfylggtcktqgij.supabase.co/storage/v1/object/public/demo-public/v0.0.4/page-agent.js"
|
<script
|
||||||
crossorigin="true"
|
src="https://cdn.jsdelivr.net/npm/page-agent@latest/dist/umd/page-agent.js"
|
||||||
|
crossorigin="true"
|
||||||
type="text/javascript"
|
type="text/javascript"
|
||||||
></script>
|
></script>
|
||||||
```
|
```
|
||||||
@@ -72,11 +73,8 @@ PageAgent adopts a simplified monorepo structure:
|
|||||||
```
|
```
|
||||||
packages/
|
packages/
|
||||||
├── page-agent/ # AI agent (npm: page-agent)
|
├── 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)
|
├── page-controller/ # DOM operations (npm: @page-agent/page-controller)
|
||||||
|
├── ui/ # Panel & Mask & Mouse Animation (npm: @page-agent/ui)
|
||||||
└── website/ # Demo & Documentation site
|
└── website/ # Demo & Documentation site
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
+13
-13
@@ -24,33 +24,33 @@ The development progress and future plans for PageAgent.
|
|||||||
- [ ] **Hooks and Events**
|
- [ ] **Hooks and Events**
|
||||||
- [x] **lifecycle hooks**
|
- [x] **lifecycle hooks**
|
||||||
- [ ] **lifecycle events**
|
- [ ] **lifecycle events**
|
||||||
- [ ] **❗Pause and intervene**
|
- [ ] **User takeover**
|
||||||
- Current pause does not allow user intervention.
|
- [#64](https://github.com/alibaba/page-agent/issues/64)
|
||||||
- [ ] **❗Hijack `page_open/page_change/page_unload` behavior**
|
- [ ] **❗Hijack `page_open/page_change/page_unload` behavior**
|
||||||
- [ ] **Custom knowledge base and instructions**
|
- [ ] **Custom knowledge base and instructions**
|
||||||
|
- [#45](https://github.com/alibaba/page-agent/issues/45)
|
||||||
- [ ] **Black/white-list safeguard**
|
- [ ] **Black/white-list safeguard**
|
||||||
- [ ] **Data-masking**
|
- [ ] **Data-masking**
|
||||||
|
- [#44](https://github.com/alibaba/page-agent/issues/44)
|
||||||
- [ ] **Improve Memory**
|
- [ ] **Improve Memory**
|
||||||
- Current phrasing can cause logic-loop for some models.
|
- [#66](https://github.com/alibaba/page-agent/issues/66)
|
||||||
- Test adding `Action` to memory.
|
- [#65](https://github.com/alibaba/page-agent/issues/65)
|
||||||
- [ ] **Tools for more complex tasks**
|
|
||||||
- todo list
|
|
||||||
- file sys
|
|
||||||
- [ ] **Optimize for popular UI frameworks**
|
- [ ] **Optimize for popular UI frameworks**
|
||||||
- [x] **i18n of the website**
|
- [x] **i18n of the website**
|
||||||
- [x] Chinese version
|
- [x] Chinese version
|
||||||
- [x] English version
|
- [x] English version
|
||||||
- [ ] **Testing suits**
|
- [x] **Refactor: Separate `Agent` and `PageController`**
|
||||||
- [ ] **Support custom llm fetch**
|
- [ ] **Chrome-ext wrapper for multi-page tasks**
|
||||||
- [ ] **Refactor: Separate `Agent` and `Page-Controller`**
|
|
||||||
- Agent should be able to run w/o dom.
|
|
||||||
- Actions should be able to be injected through iframe.
|
|
||||||
|
|
||||||
♻️ Following browser-use's update and contribute back.
|
♻️ Following browser-use's update and contribute back.
|
||||||
|
|
||||||
## 📋 Pending Features
|
## 📋 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**
|
- [ ] **Same-origin multi-page-app rally**
|
||||||
- [ ] **Local MCP proxy**
|
- [ ] **Local MCP proxy**
|
||||||
|
|
||||||
|
|||||||
Generated
+659
-452
File diff suppressed because it is too large
Load Diff
+19
-15
@@ -1,10 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "root",
|
"name": "root",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.7",
|
"version": "0.0.14",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/page-controller",
|
"packages/page-controller",
|
||||||
|
"packages/ui",
|
||||||
|
"packages/llms",
|
||||||
"packages/page-agent",
|
"packages/page-agent",
|
||||||
"packages/website"
|
"packages/website"
|
||||||
],
|
],
|
||||||
@@ -21,35 +23,37 @@
|
|||||||
"npm": ">=10.0.0"
|
"npm": ">=10.0.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "npm run dev --workspace=@page-agent/website",
|
|
||||||
"start": "npm run dev --workspace=@page-agent/website",
|
"start": "npm run dev --workspace=@page-agent/website",
|
||||||
"build": "npm run build --workspaces --if-present",
|
"build:website": "npm run build:website --workspace=@page-agent/website",
|
||||||
"build:lib": "npm run build --workspace=page-agent",
|
"build:libs": "npm run build --workspaces --if-present",
|
||||||
|
"build": "npm run build:libs && npm run build:website",
|
||||||
|
"dev:umd": "npm run dev:umd --workspace=page-agent",
|
||||||
"version": "node scripts/sync-version.js",
|
"version": "node scripts/sync-version.js",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"prepare": "husky"
|
"prepare": "husky"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commitlint/cli": "^20.1.0",
|
"@commitlint/cli": "^20.2.0",
|
||||||
"@commitlint/config-conventional": "^20.0.0",
|
"@commitlint/config-conventional": "^20.2.0",
|
||||||
"@eslint/js": "^9.39.1",
|
"@eslint/js": "^9.39.2",
|
||||||
|
"@microsoft/api-extractor": "^7.55.2",
|
||||||
"@trivago/prettier-plugin-sort-imports": "^6.0.0",
|
"@trivago/prettier-plugin-sort-imports": "^6.0.0",
|
||||||
|
"concurrently": "^9.2.1",
|
||||||
"dotenv": "^17.2.3",
|
"dotenv": "^17.2.3",
|
||||||
"eslint": "^9.39.1",
|
"eslint": "^9.39.2",
|
||||||
"eslint-config-prettier": "^10.1.8",
|
"eslint-config-prettier": "^10.1.8",
|
||||||
"eslint-plugin-react-dom": "^2.3.9",
|
"eslint-plugin-react-dom": "^2.3.13",
|
||||||
"eslint-plugin-react-hooks": "^7.0.1",
|
"eslint-plugin-react-hooks": "^7.0.1",
|
||||||
"eslint-plugin-react-refresh": "^0.4.24",
|
"eslint-plugin-react-refresh": "^0.4.25",
|
||||||
"eslint-plugin-react-x": "^2.3.9",
|
"eslint-plugin-react-x": "^2.3.13",
|
||||||
"globals": "^16.4.0",
|
"globals": "^16.4.0",
|
||||||
"husky": "^9.1.7",
|
"husky": "^9.1.7",
|
||||||
"lint-staged": "^16.2.4",
|
"lint-staged": "^16.2.4",
|
||||||
"prettier": "^3.6.2",
|
"prettier": "^3.7.4",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
"typescript-eslint": "^8.48.1",
|
"typescript-eslint": "^8.50.0",
|
||||||
"vite": "^7.2.6",
|
|
||||||
"@microsoft/api-extractor": "^7.55.1",
|
|
||||||
"unplugin-dts": "^1.0.0-beta.6",
|
"unplugin-dts": "^1.0.0-beta.6",
|
||||||
|
"vite": "^7.3.0",
|
||||||
"vite-plugin-css-injected-by-js": "^3.5.2"
|
"vite-plugin-css-injected-by-js": "^3.5.2"
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
# @page-agent/llms
|
||||||
|
|
||||||
|
LLM client with a **reflection-before-action** mental model for page-agent.
|
||||||
|
|
||||||
|
## Why This Package Exists
|
||||||
|
|
||||||
|
The LLM module and the agent logic are inherently coupled. This package exists not to decouple them, but to **define the interface contract** between the LLM and the agent.
|
||||||
|
|
||||||
|
The core abstraction is the `MacroToolInput` — a structured output format that **forces the model to reflect before acting**.
|
||||||
|
|
||||||
|
## The Reflection-Before-Action Model
|
||||||
|
|
||||||
|
Every tool call must first output its reasoning state before the actual action:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface MacroToolInput {
|
||||||
|
// Reflection (mandatory before any action)
|
||||||
|
evaluation_previous_goal?: string // How well did the previous action work?
|
||||||
|
memory?: string // Key information to remember
|
||||||
|
next_goal?: string // What to accomplish next
|
||||||
|
|
||||||
|
// Action (the actual operation)
|
||||||
|
action: Record<string, any>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This design ensures that:
|
||||||
|
|
||||||
|
1. **The model evaluates its previous action** before deciding the next step
|
||||||
|
2. **Working memory is explicitly maintained** across conversation turns
|
||||||
|
3. **Goals are clearly stated**, making the agent's reasoning transparent and debuggable
|
||||||
|
|
||||||
|
## Key Components
|
||||||
|
|
||||||
|
| Export | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `LLM` | Main LLM client class with retry logic |
|
||||||
|
| `MacroToolInput` | The reflection-before-action input schema |
|
||||||
|
| `AgentBrain` | Agent's thinking state (eval, memory, goal) |
|
||||||
|
| `LLMConfig` | Configuration for LLM connection |
|
||||||
|
| `parseLLMConfig` | Parse and apply defaults to config |
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"name": "@page-agent/llms",
|
||||||
|
"version": "0.0.14",
|
||||||
|
"type": "module",
|
||||||
|
"main": "./dist/lib/page-agent-llms.js",
|
||||||
|
"module": "./dist/lib/page-agent-llms.js",
|
||||||
|
"types": "./dist/lib/index.d.ts",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./dist/lib/index.d.ts",
|
||||||
|
"import": "./dist/lib/page-agent-llms.js",
|
||||||
|
"default": "./dist/lib/page-agent-llms.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist/"
|
||||||
|
],
|
||||||
|
"description": "LLM client with reflection-before-action mental model for page-agent",
|
||||||
|
"keywords": [
|
||||||
|
"page-agent",
|
||||||
|
"llm",
|
||||||
|
"openai",
|
||||||
|
"tool-calling",
|
||||||
|
"agent"
|
||||||
|
],
|
||||||
|
"author": "Simon<gaomeng1900>",
|
||||||
|
"license": "MIT",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/alibaba/page-agent.git",
|
||||||
|
"directory": "packages/llms"
|
||||||
|
},
|
||||||
|
"homepage": "https://alibaba.github.io/page-agent/",
|
||||||
|
"scripts": {
|
||||||
|
"build": "vite build",
|
||||||
|
"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": {
|
||||||
|
"chalk": "^5.6.2",
|
||||||
|
"zod": "^4.2.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
+8
-2
@@ -1,9 +1,15 @@
|
|||||||
/**
|
/**
|
||||||
* OpenAI Client implementation
|
* OpenAI Client implementation
|
||||||
*/
|
*/
|
||||||
import type { MacroToolInput } from '../PageAgent'
|
|
||||||
import { InvokeError, InvokeErrorType } from './errors'
|
import { InvokeError, InvokeErrorType } from './errors'
|
||||||
import type { InvokeResult, LLMClient, Message, OpenAIClientConfig, Tool } from './types'
|
import type {
|
||||||
|
InvokeResult,
|
||||||
|
LLMClient,
|
||||||
|
MacroToolInput,
|
||||||
|
Message,
|
||||||
|
OpenAIClientConfig,
|
||||||
|
Tool,
|
||||||
|
} from './types'
|
||||||
import { lenientParseMacroToolCall, modelPatch, zodToOpenAITool } from './utils'
|
import { lenientParseMacroToolCall, modelPatch, zodToOpenAITool } from './utils'
|
||||||
|
|
||||||
export class OpenAIClient implements LLMClient {
|
export class OpenAIClient implements LLMClient {
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
// 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
|
||||||
|
? import.meta.env.LLM_MODEL_NAME
|
||||||
|
: 'PAGE-AGENT-FREE-TESTING-RANDOM'
|
||||||
|
|
||||||
|
export const DEFAULT_API_KEY: string =
|
||||||
|
import.meta.env.DEV && import.meta.env.LLM_API_KEY
|
||||||
|
? import.meta.env.LLM_API_KEY
|
||||||
|
: 'PAGE-AGENT-FREE-TESTING-RANDOM'
|
||||||
|
|
||||||
|
export const DEFAULT_BASE_URL: string =
|
||||||
|
import.meta.env.DEV && import.meta.env.LLM_BASE_URL
|
||||||
|
? import.meta.env.LLM_BASE_URL
|
||||||
|
: 'https://hwcxiuzfylggtcktqgij.supabase.co/functions/v1/llm-testing-proxy'
|
||||||
|
|
||||||
|
// internal
|
||||||
|
|
||||||
|
export const LLM_MAX_RETRIES = 2
|
||||||
|
export const DEFAULT_TEMPERATURE = 0.7 // higher randomness helps auto-recovery
|
||||||
|
export const DEFAULT_MAX_TOKENS = 4096
|
||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
@@ -31,26 +31,56 @@
|
|||||||
* - 永远使用 tool call 来返回结构化数据,禁止模型直接返回(视为出错)
|
* - 永远使用 tool call 来返回结构化数据,禁止模型直接返回(视为出错)
|
||||||
* - 不能假设 tool 参数合法,必须有修复机制,而且修复也应该使用 tool call 返回
|
* - 不能假设 tool 参数合法,必须有修复机制,而且修复也应该使用 tool call 返回
|
||||||
*/
|
*/
|
||||||
import type { LLMConfig } from '../config'
|
|
||||||
import { parseLLMConfig } from '../config'
|
|
||||||
import { EventBus, getEventBus } from '../utils/bus'
|
|
||||||
import { OpenAIClient } from './OpenAILenientClient'
|
import { OpenAIClient } from './OpenAILenientClient'
|
||||||
|
import {
|
||||||
|
DEFAULT_API_KEY,
|
||||||
|
DEFAULT_BASE_URL,
|
||||||
|
DEFAULT_MAX_TOKENS,
|
||||||
|
DEFAULT_MODEL_NAME,
|
||||||
|
DEFAULT_TEMPERATURE,
|
||||||
|
LLM_MAX_RETRIES,
|
||||||
|
} from './constants'
|
||||||
import { InvokeError } from './errors'
|
import { InvokeError } from './errors'
|
||||||
import type { InvokeResult, LLMClient, Message, Tool } from './types'
|
import type {
|
||||||
|
AgentBrain,
|
||||||
|
InvokeResult,
|
||||||
|
LLMClient,
|
||||||
|
LLMConfig,
|
||||||
|
MacroToolInput,
|
||||||
|
MacroToolResult,
|
||||||
|
Message,
|
||||||
|
Tool,
|
||||||
|
} from './types'
|
||||||
|
|
||||||
export type { Message, Tool, InvokeResult, LLMClient }
|
export type {
|
||||||
|
AgentBrain,
|
||||||
|
InvokeResult,
|
||||||
|
LLMClient,
|
||||||
|
LLMConfig,
|
||||||
|
MacroToolInput,
|
||||||
|
MacroToolResult,
|
||||||
|
Message,
|
||||||
|
Tool,
|
||||||
|
}
|
||||||
|
|
||||||
export class LLM {
|
export function parseLLMConfig(config: LLMConfig): Required<LLMConfig> {
|
||||||
|
return {
|
||||||
|
baseURL: config.baseURL ?? DEFAULT_BASE_URL,
|
||||||
|
apiKey: config.apiKey ?? DEFAULT_API_KEY,
|
||||||
|
model: config.model ?? DEFAULT_MODEL_NAME,
|
||||||
|
temperature: config.temperature ?? DEFAULT_TEMPERATURE,
|
||||||
|
maxTokens: config.maxTokens ?? DEFAULT_MAX_TOKENS,
|
||||||
|
maxRetries: config.maxRetries ?? LLM_MAX_RETRIES,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class LLM extends EventTarget {
|
||||||
config: Required<LLMConfig>
|
config: Required<LLMConfig>
|
||||||
id: string
|
|
||||||
client: LLMClient
|
client: LLMClient
|
||||||
#bus: EventBus
|
|
||||||
|
|
||||||
constructor(config: LLMConfig, id: string) {
|
constructor(config: LLMConfig) {
|
||||||
|
super()
|
||||||
this.config = parseLLMConfig(config)
|
this.config = parseLLMConfig(config)
|
||||||
this.id = id
|
|
||||||
|
|
||||||
this.#bus = getEventBus(id)
|
|
||||||
|
|
||||||
// Default to OpenAI client
|
// Default to OpenAI client
|
||||||
this.client = new OpenAIClient({
|
this.client = new OpenAIClient({
|
||||||
@@ -81,17 +111,13 @@ export class LLM {
|
|||||||
// retry settings
|
// retry settings
|
||||||
{
|
{
|
||||||
maxRetries: this.config.maxRetries,
|
maxRetries: this.config.maxRetries,
|
||||||
onRetry: (retries: number) => {
|
onRetry: (current: number) => {
|
||||||
this.#bus.emit('panel:update', {
|
this.dispatchEvent(
|
||||||
type: 'retry',
|
new CustomEvent('retry', { detail: { current, max: this.config.maxRetries } })
|
||||||
displayText: `retry-ing (${retries} / ${this.config.maxRetries})`,
|
)
|
||||||
})
|
|
||||||
},
|
},
|
||||||
onError: (error: Error, withRetry: boolean) => {
|
onError: (error: Error) => {
|
||||||
this.#bus.emit('panel:update', {
|
this.dispatchEvent(new CustomEvent('error', { detail: { error } }))
|
||||||
type: 'error',
|
|
||||||
displayText: `step failed: ${(error as Error).message}`,
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -103,7 +129,7 @@ async function withRetry<T>(
|
|||||||
settings: {
|
settings: {
|
||||||
maxRetries: number
|
maxRetries: number
|
||||||
onRetry: (retries: number) => void
|
onRetry: (retries: number) => void
|
||||||
onError: (error: Error, withRetry: boolean) => void
|
onError: (error: Error) => void
|
||||||
}
|
}
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
let retries = 0
|
let retries = 0
|
||||||
@@ -118,7 +144,7 @@ async function withRetry<T>(
|
|||||||
return await fn()
|
return await fn()
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
settings.onError(error as Error, retries < settings.maxRetries)
|
settings.onError(error as Error)
|
||||||
|
|
||||||
// do not retry if aborted by user
|
// do not retry if aborted by user
|
||||||
if ((error as { name?: string })?.name === 'AbortError') throw error
|
if ((error as { name?: string })?.name === 'AbortError') throw error
|
||||||
@@ -75,3 +75,48 @@ export interface OpenAIClientConfig {
|
|||||||
maxTokens?: number
|
maxTokens?: number
|
||||||
maxRetries?: number
|
maxRetries?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LLM configuration for PageAgent
|
||||||
|
*/
|
||||||
|
export interface LLMConfig {
|
||||||
|
baseURL?: string
|
||||||
|
apiKey?: string
|
||||||
|
model?: string
|
||||||
|
temperature?: number
|
||||||
|
maxTokens?: number
|
||||||
|
maxRetries?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Agent brain state - the reflection-before-action model
|
||||||
|
*
|
||||||
|
* Every tool call must first reflect on:
|
||||||
|
* - evaluation_previous_goal: How well did the previous action achieve its goal?
|
||||||
|
* - memory: Key information to remember for future steps
|
||||||
|
* - next_goal: What should be accomplished in the next action?
|
||||||
|
*/
|
||||||
|
export interface AgentBrain {
|
||||||
|
// thinking?: string
|
||||||
|
evaluation_previous_goal: string
|
||||||
|
memory: string
|
||||||
|
next_goal: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MacroTool input structure
|
||||||
|
*
|
||||||
|
* This is the core abstraction that enforces the "reflection-before-action" mental model.
|
||||||
|
* Before executing any action, the LLM must output its reasoning state.
|
||||||
|
*/
|
||||||
|
export interface MacroToolInput extends AgentBrain {
|
||||||
|
action: Record<string, any>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MacroTool output structure
|
||||||
|
*/
|
||||||
|
export interface MacroToolResult {
|
||||||
|
input: MacroToolInput
|
||||||
|
output: string
|
||||||
|
}
|
||||||
@@ -4,9 +4,8 @@
|
|||||||
import chalk from 'chalk'
|
import chalk from 'chalk'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
|
|
||||||
import type { MacroToolInput } from '../PageAgent'
|
|
||||||
import { InvokeError, InvokeErrorType } from './errors'
|
import { InvokeError, InvokeErrorType } from './errors'
|
||||||
import type { Tool } from './types'
|
import type { MacroToolInput, Tool } from './types'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert Zod schema to OpenAI tool format
|
* Convert Zod schema to OpenAI tool format
|
||||||
@@ -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"],
|
||||||
|
"exclude": ["dist", "node_modules"]
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
// @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'
|
||||||
|
|
||||||
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||||
|
|
||||||
|
console.log(chalk.cyan(`📦 Building @page-agent/llms`))
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
clearScreen: false,
|
||||||
|
plugins: [dts({ tsconfigPath: './tsconfig.dts.json', bundleTypes: true })],
|
||||||
|
publicDir: false,
|
||||||
|
esbuild: {
|
||||||
|
keepNames: true,
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
lib: {
|
||||||
|
entry: resolve(__dirname, 'src/index.ts'),
|
||||||
|
name: 'PageAgentLLMs',
|
||||||
|
fileName: 'page-agent-llms',
|
||||||
|
formats: ['es'],
|
||||||
|
},
|
||||||
|
outDir: resolve(__dirname, 'dist', 'lib'),
|
||||||
|
rollupOptions: {
|
||||||
|
external: ['chalk', 'zod'],
|
||||||
|
},
|
||||||
|
minify: false,
|
||||||
|
sourcemap: true,
|
||||||
|
},
|
||||||
|
define: {
|
||||||
|
'process.env.NODE_ENV': '"production"',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
@@ -1,22 +1,20 @@
|
|||||||
{
|
{
|
||||||
"name": "page-agent",
|
"name": "page-agent",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "0.0.7",
|
"version": "0.0.14",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/lib/page-agent.js",
|
"main": "./dist/esm/page-agent.js",
|
||||||
"module": "./dist/lib/page-agent.js",
|
"module": "./dist/esm/page-agent.js",
|
||||||
"types": "./dist/lib/PageAgent.d.ts",
|
"types": "./dist/esm/PageAgent.d.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": {
|
".": {
|
||||||
"types": "./dist/lib/PageAgent.d.ts",
|
"types": "./dist/esm/PageAgent.d.ts",
|
||||||
"import": "./dist/lib/page-agent.js",
|
"import": "./dist/esm/page-agent.js",
|
||||||
"default": "./dist/lib/page-agent.js"
|
"default": "./dist/esm/page-agent.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist/",
|
"dist/"
|
||||||
"README.md",
|
|
||||||
"LICENSE"
|
|
||||||
],
|
],
|
||||||
"description": "GUI agent for web applications - add intelligent automation to any webpage with a single script",
|
"description": "GUI agent for web applications - add intelligent automation to any webpage with a single script",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
@@ -39,17 +37,17 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://alibaba.github.io/page-agent/",
|
"homepage": "https://alibaba.github.io/page-agent/",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "npm run build:lib && npm run build:umd",
|
"build": "vite build && vite build --config vite.umd.config.js",
|
||||||
"build:lib": "MODE=lib vite build",
|
"serve": "npx serve dist/umd -p 5173",
|
||||||
"build:umd": "MODE=umd vite build",
|
"dev:umd": "concurrently \"vite build --config vite.umd.config.js --watch\" \"npm run serve\"",
|
||||||
"build:watch": "MODE=umd vite build --watch",
|
|
||||||
"prepublishOnly": "node -e \"const fs=require('fs');['README.md','LICENSE'].forEach(f=>fs.copyFileSync('../../'+f,f))\"",
|
"prepublishOnly": "node -e \"const fs=require('fs');['README.md','LICENSE'].forEach(f=>fs.copyFileSync('../../'+f,f))\"",
|
||||||
"postpublish": "node -e \"['README.md','LICENSE'].forEach(f=>{try{require('fs').unlinkSync(f)}catch{}})\""
|
"postpublish": "node -e \"['README.md','LICENSE'].forEach(f=>{try{require('fs').unlinkSync(f)}catch{}})\""
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ai-motion": "^0.4.7",
|
|
||||||
"chalk": "^5.6.2",
|
"chalk": "^5.6.2",
|
||||||
"zod": "^4.1.12",
|
"zod": "^4.2.0",
|
||||||
"@page-agent/page-controller": "0.0.7"
|
"@page-agent/llms": "0.0.14",
|
||||||
|
"@page-agent/page-controller": "0.0.14",
|
||||||
|
"@page-agent/ui": "0.0.14"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,49 +2,28 @@
|
|||||||
* Copyright (C) 2025 Alibaba Group Holding Limited
|
* Copyright (C) 2025 Alibaba Group Holding Limited
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*/
|
*/
|
||||||
|
import {
|
||||||
|
type AgentBrain,
|
||||||
|
LLM,
|
||||||
|
type MacroToolInput,
|
||||||
|
type MacroToolResult,
|
||||||
|
type Tool,
|
||||||
|
} from '@page-agent/llms'
|
||||||
import { PageController } from '@page-agent/page-controller'
|
import { PageController } from '@page-agent/page-controller'
|
||||||
|
import { Panel, SimulatorMask } from '@page-agent/ui'
|
||||||
import chalk from 'chalk'
|
import chalk from 'chalk'
|
||||||
import zod from 'zod'
|
import zod from 'zod'
|
||||||
|
|
||||||
import type { PageAgentConfig } from './config'
|
import type { PageAgentConfig } from './config'
|
||||||
import { MAX_STEPS } from './config/constants'
|
import { MAX_STEPS } from './config/constants'
|
||||||
import { I18n } from './i18n'
|
|
||||||
import { LLM, type Tool } from './llms'
|
|
||||||
import SYSTEM_PROMPT from './prompts/system_prompt.md?raw'
|
import SYSTEM_PROMPT from './prompts/system_prompt.md?raw'
|
||||||
import { tools } from './tools'
|
import { tools } from './tools'
|
||||||
import { Panel, getToolCompletedText, getToolExecutingText } from './ui/Panel'
|
|
||||||
import { SimulatorMask } from './ui/SimulatorMask'
|
|
||||||
import { trimLines, uid, waitUntil } from './utils'
|
import { trimLines, uid, waitUntil } from './utils'
|
||||||
import { assert } from './utils/assert'
|
import { assert } from './utils/assert'
|
||||||
import { getEventBus } from './utils/bus'
|
|
||||||
|
|
||||||
export type { PageAgentConfig }
|
export type { PageAgentConfig }
|
||||||
export { tool, type PageAgentTool } from './tools'
|
export { tool, type PageAgentTool } from './tools'
|
||||||
|
export type { AgentBrain, MacroToolInput, MacroToolResult }
|
||||||
export interface AgentBrain {
|
|
||||||
// thinking?: string
|
|
||||||
evaluation_previous_goal: string
|
|
||||||
memory: string
|
|
||||||
next_goal: string
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* MacroTool input structure
|
|
||||||
*/
|
|
||||||
export interface MacroToolInput {
|
|
||||||
evaluation_previous_goal?: string
|
|
||||||
memory?: string
|
|
||||||
next_goal?: string
|
|
||||||
action: Record<string, any>
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* MacroTool output structure
|
|
||||||
*/
|
|
||||||
export interface MacroToolResult {
|
|
||||||
input: MacroToolInput
|
|
||||||
output: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AgentHistory {
|
export interface AgentHistory {
|
||||||
brain: AgentBrain
|
brain: AgentBrain
|
||||||
@@ -71,8 +50,6 @@ export interface ExecutionResult {
|
|||||||
export class PageAgent extends EventTarget {
|
export class PageAgent extends EventTarget {
|
||||||
config: PageAgentConfig
|
config: PageAgentConfig
|
||||||
id = uid()
|
id = uid()
|
||||||
bus = getEventBus(this.id)
|
|
||||||
i18n: I18n
|
|
||||||
panel: Panel
|
panel: Panel
|
||||||
tools: typeof tools
|
tools: typeof tools
|
||||||
paused = false
|
paused = false
|
||||||
@@ -83,6 +60,9 @@ export class PageAgent extends EventTarget {
|
|||||||
#llm: LLM
|
#llm: LLM
|
||||||
#totalWaitTime = 0
|
#totalWaitTime = 0
|
||||||
#abortController = new AbortController()
|
#abortController = new AbortController()
|
||||||
|
#llmRetryListener: ((e: Event) => void) | null = null
|
||||||
|
#llmErrorListener: ((e: Event) => void) | null = null
|
||||||
|
#beforeUnloadListener: ((e: Event) => void) | null = null
|
||||||
|
|
||||||
/** PageController for DOM operations */
|
/** PageController for DOM operations */
|
||||||
pageController: PageController
|
pageController: PageController
|
||||||
@@ -96,14 +76,34 @@ export class PageAgent extends EventTarget {
|
|||||||
super()
|
super()
|
||||||
|
|
||||||
this.config = config
|
this.config = config
|
||||||
this.#llm = new LLM(this.config, this.id)
|
this.#llm = new LLM(this.config)
|
||||||
this.i18n = new I18n(this.config.language)
|
this.panel = new Panel({
|
||||||
this.panel = new Panel(this)
|
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)
|
this.tools = new Map(tools)
|
||||||
|
|
||||||
// Initialize PageController with config
|
// Initialize PageController with config
|
||||||
this.pageController = new PageController(this.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) {
|
if (this.config.customTools) {
|
||||||
for (const [name, tool] of Object.entries(this.config.customTools)) {
|
for (const [name, tool] of Object.entries(this.config.customTools)) {
|
||||||
if (tool === null) {
|
if (tool === null) {
|
||||||
@@ -118,9 +118,10 @@ export class PageAgent extends EventTarget {
|
|||||||
this.tools.delete('execute_javascript')
|
this.tools.delete('execute_javascript')
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('beforeunload', (e) => {
|
this.#beforeUnloadListener = (e) => {
|
||||||
if (!this.disposed) this.dispose('PAGE_UNLOADING')
|
if (!this.disposed) this.dispose('PAGE_UNLOADING')
|
||||||
})
|
}
|
||||||
|
window.addEventListener('beforeunload', this.#beforeUnloadListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -141,13 +142,10 @@ export class PageAgent extends EventTarget {
|
|||||||
// Show mask and panel
|
// Show mask and panel
|
||||||
this.mask.show()
|
this.mask.show()
|
||||||
|
|
||||||
this.bus.emit('panel:show')
|
this.panel.show()
|
||||||
this.bus.emit('panel:reset')
|
this.panel.reset()
|
||||||
|
|
||||||
this.bus.emit('panel:update', {
|
this.panel.update({ type: 'input', task: this.task })
|
||||||
type: 'input',
|
|
||||||
displayText: this.task,
|
|
||||||
})
|
|
||||||
|
|
||||||
if (this.#abortController) {
|
if (this.#abortController) {
|
||||||
this.#abortController.abort()
|
this.#abortController.abort()
|
||||||
@@ -171,10 +169,7 @@ export class PageAgent extends EventTarget {
|
|||||||
|
|
||||||
// Update status to thinking
|
// Update status to thinking
|
||||||
console.log(chalk.blue('Thinking...'))
|
console.log(chalk.blue('Thinking...'))
|
||||||
this.bus.emit('panel:update', {
|
this.panel.update({ type: 'thinking' })
|
||||||
type: 'thinking',
|
|
||||||
displayText: this.i18n.t('ui.panel.thinking'),
|
|
||||||
})
|
|
||||||
|
|
||||||
const result = await this.#llm.invoke(
|
const result = await this.#llm.invoke(
|
||||||
[
|
[
|
||||||
@@ -304,22 +299,14 @@ export class PageAgent extends EventTarget {
|
|||||||
`)
|
`)
|
||||||
|
|
||||||
console.log(brain)
|
console.log(brain)
|
||||||
this.bus.emit('panel:update', {
|
this.panel.update({ type: 'thinking', text: brain })
|
||||||
type: 'thinking',
|
|
||||||
displayText: brain,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Find the corresponding tool
|
// Find the corresponding tool
|
||||||
const tool = tools.get(toolName)
|
const tool = tools.get(toolName)
|
||||||
assert(tool, `Tool ${toolName} not found. (@note should have been caught before this!!!)`)
|
assert(tool, `Tool ${toolName} not found. (@note should have been caught before this!!!)`)
|
||||||
|
|
||||||
console.log(chalk.blue.bold(`Executing tool: ${toolName}`), toolInput)
|
console.log(chalk.blue.bold(`Executing tool: ${toolName}`), toolInput)
|
||||||
this.bus.emit('panel:update', {
|
this.panel.update({ type: 'toolExecuting', toolName, args: toolInput })
|
||||||
type: 'tool_executing',
|
|
||||||
toolName,
|
|
||||||
toolArgs: toolInput,
|
|
||||||
displayText: getToolExecutingText(toolName, toolInput, this.i18n),
|
|
||||||
})
|
|
||||||
|
|
||||||
const startTime = Date.now()
|
const startTime = Date.now()
|
||||||
|
|
||||||
@@ -341,16 +328,13 @@ export class PageAgent extends EventTarget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Briefly display execution result
|
// Briefly display execution result
|
||||||
const displayResult = getToolCompletedText(toolName, toolInput, this.i18n)
|
this.panel.update({
|
||||||
if (displayResult)
|
type: 'toolCompleted',
|
||||||
this.bus.emit('panel:update', {
|
toolName,
|
||||||
type: 'tool_executing',
|
args: toolInput,
|
||||||
toolName,
|
result,
|
||||||
toolArgs: toolInput,
|
duration,
|
||||||
toolResult: result,
|
})
|
||||||
displayText: displayResult,
|
|
||||||
duration,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Wait a moment to let user see the result
|
// Wait a moment to let user see the result
|
||||||
await new Promise((resolve) => setTimeout(resolve, 100))
|
await new Promise((resolve) => setTimeout(resolve, 100))
|
||||||
@@ -426,16 +410,14 @@ export class PageAgent extends EventTarget {
|
|||||||
this.pageController.cleanUpHighlights()
|
this.pageController.cleanUpHighlights()
|
||||||
|
|
||||||
// Update panel status
|
// Update panel status
|
||||||
this.bus.emit('panel:update', {
|
if (success) {
|
||||||
type: success ? 'output' : 'error',
|
this.panel.update({ type: 'output', text })
|
||||||
displayText: text,
|
} else {
|
||||||
})
|
this.panel.update({ type: 'error', message: text })
|
||||||
|
}
|
||||||
|
|
||||||
// Task completed
|
// Task completed
|
||||||
this.bus.emit('panel:update', {
|
this.panel.update({ type: 'completed' })
|
||||||
type: 'completed',
|
|
||||||
displayText: this.i18n.t('ui.panel.taskCompleted'),
|
|
||||||
})
|
|
||||||
|
|
||||||
this.mask.hide()
|
this.mask.hide()
|
||||||
|
|
||||||
@@ -497,6 +479,22 @@ export class PageAgent extends EventTarget {
|
|||||||
this.history = []
|
this.history = []
|
||||||
this.#abortController.abort(reason ?? 'PageAgent disposed')
|
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)
|
this.config.onDispose?.call(this, reason)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,2 @@
|
|||||||
// Dev environment: use .env config if available, otherwise fallback to testing api
|
// Agent-specific constants (LLM constants moved to @page-agent/llms)
|
||||||
export const DEFAULT_MODEL_NAME: string =
|
|
||||||
import.meta.env.DEV && import.meta.env.LLM_MODEL_NAME
|
|
||||||
? import.meta.env.LLM_MODEL_NAME
|
|
||||||
: 'PAGE-AGENT-FREE-TESTING-RANDOM'
|
|
||||||
|
|
||||||
export const DEFAULT_API_KEY: string =
|
|
||||||
import.meta.env.DEV && import.meta.env.LLM_API_KEY
|
|
||||||
? import.meta.env.LLM_API_KEY
|
|
||||||
: 'PAGE-AGENT-FREE-TESTING-RANDOM'
|
|
||||||
|
|
||||||
export const DEFAULT_BASE_URL: string =
|
|
||||||
import.meta.env.DEV && import.meta.env.LLM_BASE_URL
|
|
||||||
? import.meta.env.LLM_BASE_URL
|
|
||||||
: 'https://hwcxiuzfylggtcktqgij.supabase.co/functions/v1/llm-testing-proxy'
|
|
||||||
|
|
||||||
// internal
|
|
||||||
|
|
||||||
export const LLM_MAX_RETRIES = 2
|
|
||||||
export const MAX_STEPS = 20
|
export const MAX_STEPS = 20
|
||||||
export const DEFAULT_TEMPERATURE = 0.7 // higher randomness helps auto-recovery
|
|
||||||
export const DEFAULT_MAX_TOKENS = 4096
|
|
||||||
|
|||||||
@@ -1,25 +1,11 @@
|
|||||||
|
import type { LLMConfig } from '@page-agent/llms'
|
||||||
import type { PageControllerConfig } from '@page-agent/page-controller'
|
import type { PageControllerConfig } from '@page-agent/page-controller'
|
||||||
|
import type { SupportedLanguage } from '@page-agent/ui'
|
||||||
|
|
||||||
import type { AgentHistory, ExecutionResult, PageAgent } from '../PageAgent'
|
import type { AgentHistory, ExecutionResult, PageAgent } from '../PageAgent'
|
||||||
import type { SupportedLanguage } from '../i18n'
|
|
||||||
import type { PageAgentTool } from '../tools'
|
import type { PageAgentTool } from '../tools'
|
||||||
import {
|
|
||||||
DEFAULT_API_KEY,
|
|
||||||
DEFAULT_BASE_URL,
|
|
||||||
DEFAULT_MAX_TOKENS,
|
|
||||||
DEFAULT_MODEL_NAME,
|
|
||||||
DEFAULT_TEMPERATURE,
|
|
||||||
LLM_MAX_RETRIES,
|
|
||||||
} from './constants'
|
|
||||||
|
|
||||||
export interface LLMConfig {
|
export type { LLMConfig }
|
||||||
baseURL?: string
|
|
||||||
apiKey?: string
|
|
||||||
model?: string
|
|
||||||
temperature?: number
|
|
||||||
maxTokens?: number
|
|
||||||
maxRetries?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AgentConfig {
|
export interface AgentConfig {
|
||||||
// theme?: 'light' | 'dark'
|
// theme?: 'light' | 'dark'
|
||||||
@@ -96,14 +82,3 @@ export interface AgentConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type PageAgentConfig = LLMConfig & AgentConfig & PageControllerConfig
|
export type PageAgentConfig = LLMConfig & AgentConfig & PageControllerConfig
|
||||||
|
|
||||||
export function parseLLMConfig(config: LLMConfig): Required<LLMConfig> {
|
|
||||||
return {
|
|
||||||
baseURL: config.baseURL ?? DEFAULT_BASE_URL,
|
|
||||||
apiKey: config.apiKey ?? DEFAULT_API_KEY,
|
|
||||||
model: config.model ?? DEFAULT_MODEL_NAME,
|
|
||||||
temperature: config.temperature ?? DEFAULT_TEMPERATURE,
|
|
||||||
maxTokens: config.maxTokens ?? DEFAULT_MAX_TOKENS,
|
|
||||||
maxRetries: config.maxRetries ?? LLM_MAX_RETRIES,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
/**
|
|
||||||
* Auto-run entry for page-agent.js. Insert this script into your page to get page-agent functionality.
|
|
||||||
*/
|
|
||||||
import { PageAgent, type PageAgentConfig } from './PageAgent'
|
|
||||||
|
|
||||||
// Clean up existing instances to prevent multiple injections from bookmarklet
|
|
||||||
if (window.pageAgent) {
|
|
||||||
window.pageAgent.dispose()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mount to global window object
|
|
||||||
window.PageAgent = PageAgent
|
|
||||||
|
|
||||||
// Export for ES module usage
|
|
||||||
// export { PageAgent }
|
|
||||||
|
|
||||||
console.log('🚀 page-agent.js loaded!')
|
|
||||||
|
|
||||||
const DEMO_MODEL = 'PAGE-AGENT-FREE-TESTING-RANDOM'
|
|
||||||
const DEMO_BASE_URL = 'https://hwcxiuzfylggtcktqgij.supabase.co/functions/v1/llm-testing-proxy'
|
|
||||||
const DEMO_API_KEY = 'PAGE-AGENT-FREE-TESTING-RANDOM'
|
|
||||||
|
|
||||||
const currentScript = document.currentScript as HTMLScriptElement | null
|
|
||||||
if (currentScript) {
|
|
||||||
console.log('🚀 page-agent.js detected current script:', currentScript.src)
|
|
||||||
const url = new URL(currentScript.src)
|
|
||||||
const model = url.searchParams.get('model') || DEMO_MODEL
|
|
||||||
const baseURL = url.searchParams.get('baseURL') || DEMO_BASE_URL
|
|
||||||
const apiKey = url.searchParams.get('apiKey') || DEMO_API_KEY
|
|
||||||
const language = (url.searchParams.get('lang') as 'zh-CN' | 'en-US') || 'zh-CN'
|
|
||||||
const config: PageAgentConfig = { model, baseURL, apiKey, language }
|
|
||||||
window.pageAgent = new PageAgent(config)
|
|
||||||
} else {
|
|
||||||
console.log('🚀 page-agent.js no current script detected, using default demo config')
|
|
||||||
window.pageAgent = new PageAgent()
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('🚀 page-agent.js initialized with config:', window.pageAgent.config)
|
|
||||||
|
|
||||||
window.pageAgent.bus.emit('panel:show') // Show panel
|
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
/**
|
||||||
|
* Auto-run entry for page-agent.js. Insert this script into your page to get page-agent functionality.
|
||||||
|
*/
|
||||||
|
import { PageAgent, type PageAgentConfig } from './PageAgent'
|
||||||
|
|
||||||
|
// Clean up existing instances to prevent multiple injections from bookmarklet
|
||||||
|
if (window.pageAgent) {
|
||||||
|
window.pageAgent.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mount to global window object
|
||||||
|
window.PageAgent = PageAgent
|
||||||
|
|
||||||
|
// Export for ES module usage
|
||||||
|
// export { PageAgent }
|
||||||
|
|
||||||
|
console.log('🚀 page-agent.js loaded!')
|
||||||
|
|
||||||
|
const DEMO_MODEL = 'PAGE-AGENT-FREE-TESTING-RANDOM'
|
||||||
|
const DEMO_BASE_URL = 'https://hwcxiuzfylggtcktqgij.supabase.co/functions/v1/llm-testing-proxy'
|
||||||
|
const DEMO_API_KEY = 'PAGE-AGENT-FREE-TESTING-RANDOM'
|
||||||
|
|
||||||
|
// in case document.x is not ready yet
|
||||||
|
// @todo give a switch to disable auto-init
|
||||||
|
setTimeout(() => {
|
||||||
|
const currentScript = document.currentScript as HTMLScriptElement | null
|
||||||
|
if (currentScript) {
|
||||||
|
console.log('🚀 page-agent.js detected current script:', currentScript.src)
|
||||||
|
const url = new URL(currentScript.src)
|
||||||
|
const model = url.searchParams.get('model') || DEMO_MODEL
|
||||||
|
const baseURL = url.searchParams.get('baseURL') || DEMO_BASE_URL
|
||||||
|
const apiKey = url.searchParams.get('apiKey') || DEMO_API_KEY
|
||||||
|
const language = (url.searchParams.get('lang') as 'zh-CN' | 'en-US') || 'zh-CN'
|
||||||
|
const config: PageAgentConfig = { model, baseURL, apiKey, language }
|
||||||
|
window.pageAgent = new PageAgent(config)
|
||||||
|
} else {
|
||||||
|
console.log('🚀 page-agent.js no current script detected, using default demo config')
|
||||||
|
window.pageAgent = new PageAgent()
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('🚀 page-agent.js initialized with config:', window.pageAgent.config)
|
||||||
|
|
||||||
|
window.pageAgent.panel.show() // Show panel
|
||||||
|
})
|
||||||
@@ -1,27 +1,8 @@
|
|||||||
/**
|
|
||||||
* Type-safe event bus for decoupling PageAgent and Panel
|
|
||||||
*/
|
|
||||||
import type { Step } from '../ui/UIState'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Event mapping definitions
|
* Event mapping definitions
|
||||||
* @note Event bus callbacks must be repeatable without errors
|
* @note Event bus callbacks must be repeatable without errors
|
||||||
*/
|
*/
|
||||||
export interface PageAgentEventMap {
|
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
|
// PageAgent status events
|
||||||
// 'agent:execute': { params: { task: string } }
|
// 'agent:execute': { params: { task: string } }
|
||||||
// 'agent:done': { params: { text: string; success: boolean } }
|
// 'agent:done': { params: { text: string; success: boolean } }
|
||||||
@@ -31,8 +12,7 @@ export interface PageAgentEventMap {
|
|||||||
// 'agent:error': { params: { error: string | Error } }
|
// 'agent:error': { params: { error: string | Error } }
|
||||||
|
|
||||||
// Task status change events
|
// Task status change events
|
||||||
// 'task:start': { params: { task: string } }
|
'task:start': { params: { task: string } }
|
||||||
// 'task:step': { params: Omit<AgentStep, 'id' | 'stepNumber' | 'timestamp'> }
|
|
||||||
// 'task:complete': { params: { text: string; success: boolean } }
|
// 'task:complete': { params: { text: string; success: boolean } }
|
||||||
// 'task:error': { params: { error: string | Error } }
|
// 'task:error': { params: { error: string | Error } }
|
||||||
|
|
||||||
|
|||||||
@@ -7,10 +7,18 @@
|
|||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"outDir": "dist",
|
"outDir": "dist",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@page-agent/page-controller": ["../page-controller/src/PageController.ts"]
|
//
|
||||||
|
"@page-agent/llms": ["../llms/src/index.ts"],
|
||||||
|
"@page-agent/page-controller": ["../page-controller/src/PageController.ts"],
|
||||||
|
"@page-agent/ui": ["../ui/src/index.ts"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["**/*.ts"],
|
"include": ["**/*.ts"],
|
||||||
"exclude": ["dist", "node_modules"],
|
"exclude": ["dist", "node_modules"],
|
||||||
"references": [{ "path": "../page-controller" }]
|
"references": [
|
||||||
|
//
|
||||||
|
{ "path": "../llms" },
|
||||||
|
{ "path": "../page-controller" },
|
||||||
|
{ "path": "../ui" }
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
// @ts-check
|
// @ts-check
|
||||||
import chalk from 'chalk'
|
|
||||||
import 'dotenv/config'
|
|
||||||
import process from 'node:process'
|
|
||||||
import { dirname, resolve } from 'path'
|
import { dirname, resolve } from 'path'
|
||||||
import dts from 'unplugin-dts/vite'
|
import dts from 'unplugin-dts/vite'
|
||||||
import { fileURLToPath } from 'url'
|
import { fileURLToPath } from 'url'
|
||||||
@@ -10,15 +7,11 @@ import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js'
|
|||||||
|
|
||||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||||
|
|
||||||
// ============================================================================
|
// ES Module for NPM Package
|
||||||
// Library Config (ES Module for NPM Package)
|
export default defineConfig({
|
||||||
// ============================================================================
|
|
||||||
/** @type {import('vite').UserConfig} */
|
|
||||||
const libConfig = {
|
|
||||||
clearScreen: false,
|
clearScreen: false,
|
||||||
plugins: [
|
plugins: [
|
||||||
dts({ tsconfigPath: './tsconfig.dts.json', bundleTypes: true }),
|
dts({ tsconfigPath: './tsconfig.dts.json', bundleTypes: true }),
|
||||||
// dts({ tsconfigPath: './tsconfig.json', bundleTypes: true, compilerOptions: { paths: {} } }),
|
|
||||||
cssInjectedByJsPlugin({ relativeCSSInjection: true }),
|
cssInjectedByJsPlugin({ relativeCSSInjection: true }),
|
||||||
],
|
],
|
||||||
publicDir: false,
|
publicDir: false,
|
||||||
@@ -32,9 +25,14 @@ const libConfig = {
|
|||||||
fileName: 'page-agent',
|
fileName: 'page-agent',
|
||||||
formats: ['es'],
|
formats: ['es'],
|
||||||
},
|
},
|
||||||
outDir: resolve(__dirname, 'dist', 'lib'),
|
outDir: resolve(__dirname, 'dist', 'esm'),
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
external: ['ai', 'ai-motion', 'chalk', 'zod', '@page-agent/*'],
|
external: [
|
||||||
|
'chalk',
|
||||||
|
'zod',
|
||||||
|
// all the internal packages
|
||||||
|
/^@page-agent\//,
|
||||||
|
],
|
||||||
},
|
},
|
||||||
minify: false,
|
minify: false,
|
||||||
sourcemap: true,
|
sourcemap: true,
|
||||||
@@ -43,49 +41,4 @@ const libConfig = {
|
|||||||
define: {
|
define: {
|
||||||
'process.env.NODE_ENV': '"production"',
|
'process.env.NODE_ENV': '"production"',
|
||||||
},
|
},
|
||||||
}
|
})
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// UMD Config (Browser Bundle for CDN)
|
|
||||||
// ============================================================================
|
|
||||||
/** @type {import('vite').UserConfig} */
|
|
||||||
const umdConfig = {
|
|
||||||
plugins: [cssInjectedByJsPlugin({ relativeCSSInjection: true })],
|
|
||||||
publicDir: false,
|
|
||||||
esbuild: {
|
|
||||||
keepNames: true,
|
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
alias: {
|
|
||||||
'@page-agent/page-controller': resolve(__dirname, '../page-controller/src/PageController.ts'),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
build: {
|
|
||||||
lib: {
|
|
||||||
entry: resolve(__dirname, 'src/entry.ts'),
|
|
||||||
name: 'PageAgent',
|
|
||||||
fileName: 'page-agent',
|
|
||||||
formats: ['umd'],
|
|
||||||
},
|
|
||||||
outDir: resolve(__dirname, 'dist', 'umd'),
|
|
||||||
cssCodeSplit: true,
|
|
||||||
},
|
|
||||||
define: {
|
|
||||||
'process.env.NODE_ENV': '"production"',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
const MODE = process.env.MODE
|
|
||||||
|
|
||||||
console.log(chalk.cyan(`📦 Build mode: ${chalk.bold(MODE || 'lib')}`))
|
|
||||||
|
|
||||||
let config
|
|
||||||
if (MODE === 'umd') {
|
|
||||||
config = umdConfig
|
|
||||||
} else {
|
|
||||||
config = libConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defineConfig(config)
|
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
// @ts-check
|
||||||
|
import { dirname, resolve } from 'path'
|
||||||
|
import { fileURLToPath } from 'url'
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js'
|
||||||
|
|
||||||
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||||
|
|
||||||
|
// UMD 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
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [cssInjectedByJsPlugin({ relativeCSSInjection: true })],
|
||||||
|
publicDir: false,
|
||||||
|
esbuild: {
|
||||||
|
keepNames: true,
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@page-agent/page-controller': resolve(__dirname, '../page-controller/src/PageController.ts'),
|
||||||
|
'@page-agent/llms': resolve(__dirname, '../llms/src/index.ts'),
|
||||||
|
'@page-agent/ui': resolve(__dirname, '../ui/src/index.ts'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
lib: {
|
||||||
|
entry: resolve(__dirname, 'src/umd.ts'),
|
||||||
|
name: 'PageAgent',
|
||||||
|
fileName: 'page-agent',
|
||||||
|
formats: ['umd'],
|
||||||
|
},
|
||||||
|
outDir: resolve(__dirname, 'dist', 'umd'),
|
||||||
|
rollupOptions: {
|
||||||
|
output: {
|
||||||
|
// force use .js as extension
|
||||||
|
entryFileNames: 'page-agent.js',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cssCodeSplit: true,
|
||||||
|
},
|
||||||
|
define: {
|
||||||
|
'process.env.NODE_ENV': '"production"',
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@page-agent/page-controller",
|
"name": "@page-agent/page-controller",
|
||||||
"version": "0.0.7",
|
"version": "0.0.14",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/lib/page-controller.js",
|
"main": "./dist/lib/page-controller.js",
|
||||||
"module": "./dist/lib/page-controller.js",
|
"module": "./dist/lib/page-controller.js",
|
||||||
@@ -13,9 +13,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist/",
|
"dist/"
|
||||||
"README.md",
|
|
||||||
"LICENSE"
|
|
||||||
],
|
],
|
||||||
"description": "Page controller for page-agent - DOM operations and element interactions",
|
"description": "Page controller for page-agent - DOM operations and element interactions",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
@@ -28,12 +26,12 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/alibaba/page-agent.git"
|
"url": "https://github.com/alibaba/page-agent.git",
|
||||||
|
"directory": "packages/page-controller"
|
||||||
},
|
},
|
||||||
"homepage": "https://alibaba.github.io/page-agent/",
|
"homepage": "https://alibaba.github.io/page-agent/",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"build:watch": "vite build --watch",
|
|
||||||
"prepublishOnly": "node -e \"const fs=require('fs');['LICENSE'].forEach(f=>fs.copyFileSync('../../'+f,f))\"",
|
"prepublishOnly": "node -e \"const fs=require('fs');['LICENSE'].forEach(f=>fs.copyFileSync('../../'+f,f))\"",
|
||||||
"postpublish": "node -e \"['LICENSE'].forEach(f=>{try{require('fs').unlinkSync(f)}catch{}})\""
|
"postpublish": "node -e \"['LICENSE'].forEach(f=>{try{require('fs').unlinkSync(f)}catch{}})\""
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,7 @@ console.log(chalk.cyan(`📦 Building @page-agent/page-controller`))
|
|||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
clearScreen: false,
|
clearScreen: false,
|
||||||
plugins: [
|
plugins: [
|
||||||
dts({ tsconfigPath: './tsconfig.json', bundleTypes: true }),
|
dts({ tsconfigPath: './tsconfig.dts.json', bundleTypes: true }),
|
||||||
cssInjectedByJsPlugin({ relativeCSSInjection: true }),
|
cssInjectedByJsPlugin({ relativeCSSInjection: true }),
|
||||||
],
|
],
|
||||||
publicDir: false,
|
publicDir: false,
|
||||||
@@ -29,7 +29,7 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
outDir: resolve(__dirname, 'dist', 'lib'),
|
outDir: resolve(__dirname, 'dist', 'lib'),
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
external: [],
|
external: ['@page-agent/*'],
|
||||||
},
|
},
|
||||||
minify: false,
|
minify: false,
|
||||||
sourcemap: true,
|
sourcemap: true,
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"name": "@page-agent/ui",
|
||||||
|
"version": "0.0.14",
|
||||||
|
"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",
|
||||||
|
"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 { type Step, UIState } from './UIState'
|
||||||
|
import { I18n, type SupportedLanguage } from './i18n'
|
||||||
|
import { truncate } from './utils'
|
||||||
|
|
||||||
import styles from './Panel.module.css'
|
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
|
* Agent control panel
|
||||||
*/
|
*/
|
||||||
@@ -19,11 +43,11 @@ export class Panel {
|
|||||||
#stopButton: HTMLElement
|
#stopButton: HTMLElement
|
||||||
#inputSection: HTMLElement
|
#inputSection: HTMLElement
|
||||||
#taskInput: HTMLInputElement
|
#taskInput: HTMLInputElement
|
||||||
#bus: EventBus
|
|
||||||
|
|
||||||
#state = new UIState()
|
#state = new UIState()
|
||||||
#isExpanded = false
|
#isExpanded = false
|
||||||
#pageAgent: PageAgent
|
#config: PanelConfig
|
||||||
|
#i18n: I18n
|
||||||
#userAnswerResolver: ((input: string) => void) | null = null
|
#userAnswerResolver: ((input: string) => void) | null = null
|
||||||
#isWaitingForUserAnswer: boolean = false
|
#isWaitingForUserAnswer: boolean = false
|
||||||
#headerUpdateTimer: ReturnType<typeof setInterval> | null = null
|
#headerUpdateTimer: ReturnType<typeof setInterval> | null = null
|
||||||
@@ -34,9 +58,9 @@ export class Panel {
|
|||||||
return this.#wrapper
|
return this.#wrapper
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(pageAgent: PageAgent) {
|
constructor(config: PanelConfig) {
|
||||||
this.#pageAgent = pageAgent
|
this.#config = config
|
||||||
this.#bus = pageAgent.bus
|
this.#i18n = new I18n(config.language ?? 'en-US')
|
||||||
this.#wrapper = this.#createWrapper()
|
this.#wrapper = this.#createWrapper()
|
||||||
this.#indicator = this.#wrapper.querySelector(`.${styles.indicator}`)!
|
this.#indicator = this.#wrapper.querySelector(`.${styles.indicator}`)!
|
||||||
this.#statusText = this.#wrapper.querySelector(`.${styles.statusText}`)!
|
this.#statusText = this.#wrapper.querySelector(`.${styles.statusText}`)!
|
||||||
@@ -49,16 +73,8 @@ export class Panel {
|
|||||||
|
|
||||||
this.#setupEventListeners()
|
this.#setupEventListeners()
|
||||||
this.#startHeaderUpdateLoop()
|
this.#startHeaderUpdateLoop()
|
||||||
// this.#expand() // debug
|
|
||||||
|
|
||||||
this.#showInputArea()
|
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
|
this.#userAnswerResolver = resolve
|
||||||
|
|
||||||
// Update state to `running`
|
// Update state to `running`
|
||||||
this.#update({
|
this.#updateInternal({
|
||||||
type: 'output',
|
type: 'output',
|
||||||
displayText: this.#pageAgent.i18n.t('ui.panel.question', { question }),
|
displayText: this.#i18n.t('ui.panel.question', { question }),
|
||||||
}) // Expand history panel
|
}) // Expand history panel
|
||||||
if (!this.#isExpanded) {
|
if (!this.#isExpanded) {
|
||||||
this.#expand()
|
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
|
* Dispose panel
|
||||||
*/
|
*/
|
||||||
@@ -92,10 +157,102 @@ export class Panel {
|
|||||||
this.wrapper.remove()
|
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)
|
const step = this.#state.addStep(stepData)
|
||||||
|
|
||||||
// Queue header text update (will be processed by periodic check)
|
// 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
|
* Toggle pause state
|
||||||
*/
|
*/
|
||||||
#togglePause(): void {
|
#togglePause(): void {
|
||||||
this.#pageAgent.paused = !this.#pageAgent.paused
|
const paused = this.#config.onPauseToggle()
|
||||||
this.#updatePauseButton()
|
this.#updatePauseButton()
|
||||||
|
|
||||||
// Update status display
|
// Update status display
|
||||||
if (this.#pageAgent.paused) {
|
if (paused) {
|
||||||
this.#statusText.textContent = this.#pageAgent.i18n.t('ui.panel.paused')
|
this.#statusText.textContent = this.#i18n.t('ui.panel.paused')
|
||||||
this.#updateStatusIndicator('thinking') // Use existing thinking state
|
this.#updateStatusIndicator('thinking')
|
||||||
} else {
|
} else {
|
||||||
this.#statusText.textContent = this.#pageAgent.i18n.t('ui.panel.continueExecution')
|
this.#statusText.textContent = this.#i18n.t('ui.panel.continueExecution')
|
||||||
this.#updateStatusIndicator('tool_executing') // Restore to execution state
|
this.#updateStatusIndicator('tool_executing')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,13 +298,14 @@ export class Panel {
|
|||||||
* Update pause button state
|
* Update pause button state
|
||||||
*/
|
*/
|
||||||
#updatePauseButton(): void {
|
#updatePauseButton(): void {
|
||||||
if (this.#pageAgent.paused) {
|
const paused = this.#config.getPaused()
|
||||||
|
if (paused) {
|
||||||
this.#pauseButton.textContent = '▶'
|
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)
|
this.#pauseButton.classList.add(styles.paused)
|
||||||
} else {
|
} else {
|
||||||
this.#pauseButton.textContent = '⏸︎'
|
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)
|
this.#pauseButton.classList.remove(styles.paused)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -196,12 +315,12 @@ export class Panel {
|
|||||||
*/
|
*/
|
||||||
#stopAgent(): void {
|
#stopAgent(): void {
|
||||||
// Update status display
|
// Update status display
|
||||||
this.#update({
|
this.#updateInternal({
|
||||||
type: 'error',
|
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
|
// Handle user input mode
|
||||||
this.#handleUserAnswer(input)
|
this.#handleUserAnswer(input)
|
||||||
} else {
|
} else {
|
||||||
this.#pageAgent.execute(input)
|
this.#config.onExecuteTask(input)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,9 +346,9 @@ export class Panel {
|
|||||||
*/
|
*/
|
||||||
#handleUserAnswer(input: string): void {
|
#handleUserAnswer(input: string): void {
|
||||||
// Add user input to history
|
// Add user input to history
|
||||||
this.#update({
|
this.#updateInternal({
|
||||||
type: 'input',
|
type: 'input',
|
||||||
displayText: this.#pageAgent.i18n.t('ui.panel.userAnswer', { input }),
|
displayText: this.#i18n.t('ui.panel.userAnswer', { input }),
|
||||||
})
|
})
|
||||||
|
|
||||||
// Reset state
|
// Reset state
|
||||||
@@ -248,7 +367,7 @@ export class Panel {
|
|||||||
#showInputArea(placeholder?: string): void {
|
#showInputArea(placeholder?: string): void {
|
||||||
// Clear input field
|
// Clear input field
|
||||||
this.#taskInput.value = ''
|
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)
|
this.#inputSection.classList.remove(styles.hidden)
|
||||||
// Focus on input field
|
// Focus on input field
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -294,23 +413,23 @@ export class Panel {
|
|||||||
stepNumber: 0,
|
stepNumber: 0,
|
||||||
timestamp: new Date(),
|
timestamp: new Date(),
|
||||||
type: 'thinking',
|
type: 'thinking',
|
||||||
displayText: this.#pageAgent.i18n.t('ui.panel.waitingPlaceholder'),
|
displayText: this.#i18n.t('ui.panel.waitingPlaceholder'),
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="${styles.header}">
|
<div class="${styles.header}">
|
||||||
<div class="${styles.statusSection}">
|
<div class="${styles.statusSection}">
|
||||||
<div class="${styles.indicator} ${styles.thinking}"></div>
|
<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>
|
||||||
<div class="${styles.controls}">
|
<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>
|
||||||
<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>
|
||||||
<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
|
X
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -501,8 +620,8 @@ export class Panel {
|
|||||||
// Check if this is a result from done tool
|
// Check if this is a result from done tool
|
||||||
if (step.toolName === 'done') {
|
if (step.toolName === 'done') {
|
||||||
// Judge success or failure based on result
|
// Judge success or failure based on result
|
||||||
const failureKeyword = this.#pageAgent.i18n.t('ui.tools.resultFailure')
|
const failureKeyword = this.#i18n.t('ui.tools.resultFailure')
|
||||||
const errorKeyword = this.#pageAgent.i18n.t('ui.tools.resultError')
|
const errorKeyword = this.#i18n.t('ui.tools.resultError')
|
||||||
const isSuccess =
|
const isSuccess =
|
||||||
!step.toolResult ||
|
!step.toolResult ||
|
||||||
(!step.toolResult.includes(failureKeyword) && !step.toolResult.includes(errorKeyword))
|
(!step.toolResult.includes(failureKeyword) && !step.toolResult.includes(errorKeyword))
|
||||||
@@ -531,7 +650,7 @@ export class Panel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const durationText = step.duration ? ` · ${step.duration}ms` : ''
|
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(),
|
number: step.stepNumber.toString(),
|
||||||
time,
|
time,
|
||||||
duration: durationText || '', // Explicitly pass empty string to replace template
|
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 { Motion } from 'ai-motion'
|
||||||
|
|
||||||
import { isPageDark } from '../utils/checkDarkMode'
|
import { isPageDark } from './checkDarkMode'
|
||||||
|
|
||||||
import styles from './SimulatorMask.module.css'
|
import styles from './SimulatorMask.module.css'
|
||||||
import cursorStyles from './cursor.module.css'
|
import cursorStyles from './cursor.module.css'
|
||||||
@@ -3,14 +3,14 @@
|
|||||||
* @returns {boolean} - True if a common dark mode class is found.
|
* @returns {boolean} - True if a common dark mode class is found.
|
||||||
*/
|
*/
|
||||||
function hasDarkModeClass() {
|
function hasDarkModeClass() {
|
||||||
const DFEAULT_DARK_MODE_CLASSES = ['dark', 'dark-mode', 'theme-dark', 'night', 'night-mode']
|
const DEFAULT_DARK_MODE_CLASSES = ['dark', 'dark-mode', 'theme-dark', 'night', 'night-mode']
|
||||||
|
|
||||||
const htmlElement = document.documentElement
|
const htmlElement = document.documentElement
|
||||||
const bodyElement = document.body
|
const bodyElement = document.body || document.documentElement // can be null in some cases
|
||||||
|
|
||||||
// Check class names on <html> and <body>
|
// Check class names on <html> and <body>
|
||||||
for (const className of DFEAULT_DARK_MODE_CLASSES) {
|
for (const className of DEFAULT_DARK_MODE_CLASSES) {
|
||||||
if (htmlElement.classList.contains(className) || bodyElement.classList.contains(className)) {
|
if (htmlElement.classList.contains(className) || bodyElement?.classList.contains(className)) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -70,7 +70,7 @@ function isColorDark(colorString: string, threshold = 128) {
|
|||||||
function isBackgroundDark() {
|
function isBackgroundDark() {
|
||||||
// We check both <html> and <body> because some pages set the color on <html>
|
// We check both <html> and <body> because some pages set the color on <html>
|
||||||
const htmlStyle = window.getComputedStyle(document.documentElement)
|
const htmlStyle = window.getComputedStyle(document.documentElement)
|
||||||
const bodyStyle = window.getComputedStyle(document.body)
|
const bodyStyle = window.getComputedStyle(document.body || document.documentElement)
|
||||||
|
|
||||||
// Get background colors
|
// Get background colors
|
||||||
const htmlBgColor = htmlStyle.backgroundColor
|
const htmlBgColor = htmlStyle.backgroundColor
|
||||||
@@ -93,18 +93,23 @@ function isBackgroundDark() {
|
|||||||
* @returns {boolean} - True if the page is likely dark.
|
* @returns {boolean} - True if the page is likely dark.
|
||||||
*/
|
*/
|
||||||
export function isPageDark() {
|
export function isPageDark() {
|
||||||
// Strategy 1: Check for common dark mode classes
|
try {
|
||||||
if (hasDarkModeClass()) {
|
// Strategy 1: Check for common dark mode classes
|
||||||
return true
|
if (hasDarkModeClass()) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strategy 2: Analyze the computed background color
|
||||||
|
if (isBackgroundDark()) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// @TODO add more checks here, e.g., analyzing text color,
|
||||||
|
// or checking the background of major layout elements like <main> or #app.
|
||||||
|
|
||||||
|
return false
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Error determining if page is dark:', error)
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Strategy 2: Analyze the computed background color
|
|
||||||
if (isBackgroundDark()) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// @TODO add more checks here, e.g., analyzing text color,
|
|
||||||
// or checking the background of major layout elements like <main> or #app.
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
Vendored
+6
@@ -0,0 +1,6 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
|
declare module '*.module.css' {
|
||||||
|
const classes: Record<string, string>
|
||||||
|
export default classes
|
||||||
|
}
|
||||||
@@ -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,26 +1,25 @@
|
|||||||
{
|
{
|
||||||
"name": "@page-agent/website",
|
"name": "@page-agent/website",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.7",
|
"version": "0.0.14",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build",
|
"build:website": "vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"page-agent": "*",
|
"@tailwindcss/vite": "^4.1.18",
|
||||||
"@tailwindcss/vite": "^4.1.14",
|
|
||||||
"@types/react": "^19.2.2",
|
"@types/react": "^19.2.2",
|
||||||
"@types/react-dom": "^19.2.1",
|
"@types/react-dom": "^19.2.1",
|
||||||
"@vitejs/plugin-react-swc": "^4.1.0",
|
"@vitejs/plugin-react-swc": "^4.1.0",
|
||||||
"i18next": "^25.7.1",
|
"i18next": "^25.7.3",
|
||||||
"i18next-browser-languagedetector": "^8.2.0",
|
"i18next-browser-languagedetector": "^8.2.0",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.3",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.3",
|
||||||
"react-i18next": "^16.1.4",
|
"react-i18next": "^16.5.0",
|
||||||
"tailwindcss": "^4.1.14",
|
"tailwindcss": "^4.1.14",
|
||||||
"wouter": "^3.7.1"
|
"wouter": "^3.8.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export const CDN_URL = 'https://cdn.jsdelivr.net/npm/page-agent@latest/dist/umd/page-agent.js'
|
||||||
|
export const CDN_CN_URL =
|
||||||
|
'https://registry.npmmirror.com/page-agent/latest/files/dist/umd/page-agent.js'
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import BetaNotice from '@/components/BetaNotice'
|
import BetaNotice from '@/components/BetaNotice'
|
||||||
import CodeEditor from '@/components/CodeEditor'
|
import CodeEditor from '@/components/CodeEditor'
|
||||||
|
import { CDN_CN_URL, CDN_URL } from '@/constants'
|
||||||
|
|
||||||
export default function CdnSetup() {
|
export default function CdnSetup() {
|
||||||
return (
|
return (
|
||||||
@@ -15,11 +16,16 @@ export default function CdnSetup() {
|
|||||||
<CodeEditor
|
<CodeEditor
|
||||||
className="mb-8"
|
className="mb-8"
|
||||||
code={`
|
code={`
|
||||||
// 仅供测试使用,稳定 CDN 待定
|
// CDN: \t${CDN_URL}
|
||||||
<script src="https://hwcxiuzfylggtcktqgij.supabase.co/storage/v1/object/public/demo-public/v0.0.4/page-agent.js" crossorigin="true" type="text/javascript"></script>
|
// Mirror: \t${CDN_CN_URL}
|
||||||
|
<script
|
||||||
|
src="${CDN_URL}"
|
||||||
|
crossorigin="true"
|
||||||
|
type="text/javascript"
|
||||||
|
></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
window.pageAgent.panel.show()
|
// window.pageAgent
|
||||||
</script>`}
|
</script>`}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export default function Overview() {
|
|||||||
{/* 头图 */}
|
{/* 头图 */}
|
||||||
<figure className="mb-8 rounded-xl overflow-hidden">
|
<figure className="mb-8 rounded-xl overflow-hidden">
|
||||||
<img
|
<img
|
||||||
src="https://img.alicdn.com/imgextra/i1/O1CN01RY0Wvh26ATVeDIX7v_!!6000000007621-0-tps-1672-512.jpg"
|
src="https://img.alicdn.com/imgextra/i3/O1CN014xLeic1gnF2iWdzjJ_!!6000000004186-49-tps-1280-353.webp"
|
||||||
alt="page-agent"
|
alt="page-agent"
|
||||||
className="w-full h-64 object-cover"
|
className="w-full h-64 object-cover"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { useTranslation } from 'react-i18next'
|
|||||||
|
|
||||||
import BetaNotice from '@/components/BetaNotice'
|
import BetaNotice from '@/components/BetaNotice'
|
||||||
import CodeEditor from '@/components/CodeEditor'
|
import CodeEditor from '@/components/CodeEditor'
|
||||||
|
import { CDN_CN_URL, CDN_URL } from '@/constants'
|
||||||
|
|
||||||
export default function QuickStart() {
|
export default function QuickStart() {
|
||||||
const { t } = useTranslation('docs')
|
const { t } = useTranslation('docs')
|
||||||
@@ -23,8 +24,9 @@ export default function QuickStart() {
|
|||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-medium mb-2">{t('quick_start.step1_cdn')}</p>
|
<p className="text-sm font-medium mb-2">{t('quick_start.step1_cdn')}</p>
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
code={`// 仅供测试使用
|
code={`// CDN: \t${CDN_URL}
|
||||||
<script src="https://hwcxiuzfylggtcktqgij.supabase.co/storage/v1/object/public/demo-public/v0.0.4/page-agent.js" crossorigin="true" type="text/javascript"></script>`}
|
// Mirror: \t${CDN_CN_URL}
|
||||||
|
<script src="${CDN_URL}" crossorigin="true" type="text/javascript"></script>`}
|
||||||
language="html"
|
language="html"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ export default {
|
|||||||
step1_content: 'Show your bookmarks bar',
|
step1_content: 'Show your bookmarks bar',
|
||||||
step2_title: 'Step 2:',
|
step2_title: 'Step 2:',
|
||||||
step2_content: 'Drag this button to your bookmarks',
|
step2_content: 'Drag this button to your bookmarks',
|
||||||
|
cdn_label: 'CDN Source',
|
||||||
|
cdn_international: 'International',
|
||||||
|
cdn_china: 'China Mirror',
|
||||||
step3_title: 'Step 3:',
|
step3_title: 'Step 3:',
|
||||||
step3_content: 'Click the bookmark on any site to activate',
|
step3_content: 'Click the bookmark on any site to activate',
|
||||||
notice_title: '⚠️ Heads Up',
|
notice_title: '⚠️ Heads Up',
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ export default {
|
|||||||
step1_content: '显示收藏夹栏',
|
step1_content: '显示收藏夹栏',
|
||||||
step2_title: '步骤 2:',
|
step2_title: '步骤 2:',
|
||||||
step2_content: '拖拽下面按钮到收藏夹栏',
|
step2_content: '拖拽下面按钮到收藏夹栏',
|
||||||
|
cdn_label: 'CDN 源',
|
||||||
|
cdn_international: '国际',
|
||||||
|
cdn_china: '国内镜像',
|
||||||
step3_title: '步骤 3:',
|
step3_title: '步骤 3:',
|
||||||
step3_content: '在其他网站点击收藏夹中的按钮即可使用',
|
step3_content: '在其他网站点击收藏夹中的按钮即可使用',
|
||||||
notice_title: '⚠️ 注意',
|
notice_title: '⚠️ 注意',
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import { useHashLocation } from 'wouter/use-hash-location'
|
|||||||
|
|
||||||
import './i18n/config'
|
import './i18n/config'
|
||||||
import './i18n/types'
|
import './i18n/types'
|
||||||
import { default as PagesRouter } from './router.tsx'
|
import { default as PagesRouter } from './router'
|
||||||
import { default as TestPagesRouter } from './test-pages/router.tsx'
|
import { default as TestPagesRouter } from './test-pages/router'
|
||||||
|
|
||||||
import './index.css'
|
import './index.css'
|
||||||
|
|
||||||
|
|||||||
@@ -6,23 +6,27 @@ import { Link, useSearchParams } from 'wouter'
|
|||||||
|
|
||||||
import Footer from './components/Footer'
|
import Footer from './components/Footer'
|
||||||
import Header from './components/Header'
|
import Header from './components/Header'
|
||||||
|
import { CDN_CN_URL, CDN_URL } from './constants'
|
||||||
|
|
||||||
const injection = encodeURI(
|
function getInjection(useCN?: boolean) {
|
||||||
"javascript:(function(){var s=document.createElement('script');s.src=`https://hwcxiuzfylggtcktqgij.supabase.co/storage/v1/object/public/demo-public/v0.0.4/page-agent.js?t=${Math.random()}`;s.setAttribute('crossorigin', true);s.type=`text/javascript`;s.onload=()=>console.log('PageAgent script loaded!');document.body.appendChild(s);})();"
|
const cdn = useCN ? CDN_CN_URL : CDN_URL
|
||||||
)
|
|
||||||
|
|
||||||
const injectionA = `
|
const injection = encodeURI(
|
||||||
<a
|
`javascript:(function(){var s=document.createElement('script');s.src=\`${cdn}?t=\${Math.random()}\`;s.setAttribute('crossorigin', true);s.type="text/javascript";s.onload=()=>console.log('PageAgent script loaded!');document.body.appendChild(s);})();`
|
||||||
href=${injection}
|
)
|
||||||
class="inline-flex items-center text-xs px-3 py-2 bg-blue-500 text-white font-medium rounded-lg hover:shadow-md transform hover:scale-105 transition-all duration-200 cursor-move border-2 border-dashed border-green-300"
|
|
||||||
draggable="true"
|
|
||||||
onclick="return false;"
|
|
||||||
title="Drag me to your bookmarks bar!"
|
|
||||||
>
|
|
||||||
✨PageAgent
|
|
||||||
</a>
|
|
||||||
|
|
||||||
`
|
return `
|
||||||
|
<a
|
||||||
|
href=${injection}
|
||||||
|
class="inline-flex items-center text-xs px-3 py-2 bg-blue-500 text-white font-medium rounded-lg hover:shadow-md transform hover:scale-105 transition-all duration-200 cursor-move border-2 border-dashed border-green-300"
|
||||||
|
draggable="true"
|
||||||
|
onclick="return false;"
|
||||||
|
title="Drag me to your bookmarks bar!"
|
||||||
|
>
|
||||||
|
✨PageAgent
|
||||||
|
</a>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
export default function HomePage() {
|
export default function HomePage() {
|
||||||
const { t, i18n } = useTranslation(['home', 'common'])
|
const { t, i18n } = useTranslation(['home', 'common'])
|
||||||
@@ -38,6 +42,7 @@ export default function HomePage() {
|
|||||||
const isOther = params.has('try_other')
|
const isOther = params.has('try_other')
|
||||||
|
|
||||||
const [activeTab, setActiveTab] = useState<'try' | 'other'>(isOther ? 'other' : 'try')
|
const [activeTab, setActiveTab] = useState<'try' | 'other'>(isOther ? 'other' : 'try')
|
||||||
|
const [cdnSource, setCdnSource] = useState<'international' | 'china'>('international')
|
||||||
|
|
||||||
const handleExecute = async () => {
|
const handleExecute = async () => {
|
||||||
if (!task.trim()) return
|
if (!task.trim()) return
|
||||||
@@ -194,10 +199,25 @@ export default function HomePage() {
|
|||||||
</span>{' '}
|
</span>{' '}
|
||||||
{t('home:try_other.step2_content')}
|
{t('home:try_other.step2_content')}
|
||||||
</p>
|
</p>
|
||||||
<div
|
<div className="flex items-center justify-center gap-3">
|
||||||
className="flex items-center justify-center gap-2 text-gray-500 dark:text-gray-400"
|
<select
|
||||||
dangerouslySetInnerHTML={{ __html: injectionA }}
|
value={cdnSource}
|
||||||
></div>
|
onChange={(e) =>
|
||||||
|
setCdnSource(e.target.value as 'international' | 'china')
|
||||||
|
}
|
||||||
|
className="px-2 py-1.5 text-xs border border-gray-300 dark:border-gray-500 rounded bg-white dark:bg-gray-600 text-gray-700 dark:text-gray-200"
|
||||||
|
>
|
||||||
|
<option value="international">
|
||||||
|
{t('home:try_other.cdn_international')}
|
||||||
|
</option>
|
||||||
|
<option value="china">{t('home:try_other.cdn_china')}</option>
|
||||||
|
</select>
|
||||||
|
<div
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: getInjection(cdnSource === 'china'),
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Usage Instructions */}
|
{/* Usage Instructions */}
|
||||||
|
|||||||
@@ -11,11 +11,19 @@
|
|||||||
"@/*": ["src/*"],
|
"@/*": ["src/*"],
|
||||||
|
|
||||||
// Simplified monorepo solution (raw npm workspace with hoisting)
|
// Simplified monorepo solution (raw npm workspace with hoisting)
|
||||||
|
"page-agent": ["../page-agent/src/PageAgent.ts"],
|
||||||
|
"@page-agent/llms": ["../llms/src/index.ts"],
|
||||||
"@page-agent/page-controller": ["../page-controller/src/PageController.ts"],
|
"@page-agent/page-controller": ["../page-controller/src/PageController.ts"],
|
||||||
"page-agent": ["../page-agent/src/PageAgent.ts"]
|
"@page-agent/ui": ["../ui/src/index.ts"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["**/*.ts", "**/*.tsx"],
|
"include": ["**/*.ts", "**/*.tsx"],
|
||||||
"exclude": ["dist", "node_modules"],
|
"exclude": ["dist", "node_modules"],
|
||||||
"references": [{ "path": "../page-controller" }, { "path": "../page-agent" }]
|
"references": [
|
||||||
|
//
|
||||||
|
{ "path": "../llms" },
|
||||||
|
{ "path": "../page-agent" },
|
||||||
|
{ "path": "../page-controller" },
|
||||||
|
{ "path": "../ui" }
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import tailwindcss from '@tailwindcss/vite'
|
import tailwindcss from '@tailwindcss/vite'
|
||||||
import react from '@vitejs/plugin-react-swc'
|
import react from '@vitejs/plugin-react-swc'
|
||||||
import 'dotenv/config'
|
import { config as dotenvConfig } from 'dotenv'
|
||||||
import process from 'node:process'
|
import process from 'node:process'
|
||||||
import { dirname, resolve } from 'path'
|
import { dirname, resolve } from 'path'
|
||||||
import { fileURLToPath } from 'url'
|
import { fileURLToPath } from 'url'
|
||||||
@@ -8,6 +8,9 @@ import { defineConfig } from 'vite'
|
|||||||
|
|
||||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||||
|
|
||||||
|
// Load .env from repo root
|
||||||
|
dotenvConfig({ path: resolve(__dirname, '../../.env') })
|
||||||
|
|
||||||
// Website Config (React Documentation Site)
|
// Website Config (React Documentation Site)
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
base: './',
|
base: './',
|
||||||
@@ -19,7 +22,9 @@ export default defineConfig({
|
|||||||
'@': resolve(__dirname, 'src'),
|
'@': resolve(__dirname, 'src'),
|
||||||
|
|
||||||
// Monorepo packages (always bundle local code instead of npm versions)
|
// Monorepo packages (always bundle local code instead of npm versions)
|
||||||
|
'@page-agent/llms': resolve(__dirname, '../llms/src/index.ts'),
|
||||||
'@page-agent/page-controller': resolve(__dirname, '../page-controller/src/PageController.ts'),
|
'@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'),
|
'page-agent': resolve(__dirname, '../page-agent/src/PageAgent.ts'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ console.log(chalk.green.bold(`\n✓ Version synced: ${newVersion}\n`))
|
|||||||
if (hasChanges) {
|
if (hasChanges) {
|
||||||
const tagName = `v${newVersion}`
|
const tagName = `v${newVersion}`
|
||||||
console.log(chalk.cyan.bold('📋 Next steps:\n'))
|
console.log(chalk.cyan.bold('📋 Next steps:\n'))
|
||||||
|
console.log(chalk.blueBright(`npm i`))
|
||||||
console.log(
|
console.log(
|
||||||
chalk.blueBright(`git add . && git commit -m "chore(version): bump version to ${newVersion}"`)
|
chalk.blueBright(`git add . && git commit -m "chore(version): bump version to ${newVersion}"`)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
"extends": "./tsconfig.base.json",
|
"extends": "./tsconfig.base.json",
|
||||||
"references": [
|
"references": [
|
||||||
{ "path": "./packages/page-controller" },
|
{ "path": "./packages/page-controller" },
|
||||||
|
{ "path": "./packages/ui" },
|
||||||
|
{ "path": "./packages/llms" },
|
||||||
{ "path": "./packages/page-agent" },
|
{ "path": "./packages/page-agent" },
|
||||||
{ "path": "./packages/website" }
|
{ "path": "./packages/website" }
|
||||||
],
|
],
|
||||||
|
|||||||
Reference in New Issue
Block a user