Compare commits
7 Commits
v1.3.0
...
EXT_v0.1.7
| Author | SHA1 | Date | |
|---|---|---|---|
| 6abe75f199 | |||
| e27ebda960 | |||
| 9b603d90ba | |||
| b89228693d | |||
| 66cc9e0a3c | |||
| 53144a8021 | |||
| 43b7c1b136 |
Generated
+2
-1
@@ -11074,13 +11074,14 @@
|
|||||||
},
|
},
|
||||||
"packages/extension": {
|
"packages/extension": {
|
||||||
"name": "@page-agent/ext",
|
"name": "@page-agent/ext",
|
||||||
"version": "0.1.5",
|
"version": "0.1.7",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@page-agent/core": "1.3.0",
|
"@page-agent/core": "1.3.0",
|
||||||
"@page-agent/llms": "1.3.0",
|
"@page-agent/llms": "1.3.0",
|
||||||
"@page-agent/page-controller": "1.3.0",
|
"@page-agent/page-controller": "1.3.0",
|
||||||
"@page-agent/ui": "1.3.0",
|
"@page-agent/ui": "1.3.0",
|
||||||
|
"ai-motion": "^0.4.8",
|
||||||
"chalk": "^5.6.2",
|
"chalk": "^5.6.2",
|
||||||
"zod": "^4.3.5"
|
"zod": "^4.3.5"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import type { BrowserState, PageController } from '@page-agent/page-controller'
|
|||||||
import chalk from 'chalk'
|
import chalk from 'chalk'
|
||||||
import * as zod from 'zod'
|
import * as zod from 'zod'
|
||||||
|
|
||||||
import { type PageAgentConfig } from './config'
|
import { type PageAgentConfig, type SupportedLanguage } from './config'
|
||||||
import { DEFAULT_MAX_STEPS } from './config/constants'
|
import { DEFAULT_MAX_STEPS } from './config/constants'
|
||||||
import SYSTEM_PROMPT from './prompts/system_prompt.md?raw'
|
import SYSTEM_PROMPT from './prompts/system_prompt.md?raw'
|
||||||
import { tools } from './tools'
|
import { tools } from './tools'
|
||||||
@@ -24,6 +24,7 @@ import type {
|
|||||||
import { assert, normalizeResponse, uid, waitFor } from './utils'
|
import { assert, normalizeResponse, uid, waitFor } from './utils'
|
||||||
|
|
||||||
export { type PageAgentConfig }
|
export { type PageAgentConfig }
|
||||||
|
export type { SupportedLanguage }
|
||||||
export { tool, type PageAgentTool } from './tools'
|
export { tool, type PageAgentTool } from './tools'
|
||||||
export type * from './types'
|
export type * from './types'
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Privacy Policy for Page Agent Extension
|
# Privacy Policy for Page Agent Extension
|
||||||
|
|
||||||
**Last updated:** January 2026
|
**Last updated:** February 2026
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
@@ -10,24 +10,27 @@ Page Agent Extension is a browser automation tool that uses AI to help you inter
|
|||||||
|
|
||||||
### Local Processing
|
### Local Processing
|
||||||
|
|
||||||
The extension performs DOM analysis and automation actions **locally in your browser**. Your browsing history, passwords, and form data are not accessed or collected by the extension itself.
|
The extension performs DOM analysis and automation actions **locally in your browser**. Your browsing history, passwords, and form data are not accessed or collected by the extension developer.
|
||||||
|
|
||||||
### Data Transmission
|
### Data Transmission
|
||||||
|
|
||||||
Data is transmitted to external servers **only when you initiate an automation task**. When this occurs:
|
Data is transmitted to external servers **only when you initiate an automation task**. When this occurs:
|
||||||
|
|
||||||
- Your task instructions (natural language commands)
|
- Your task instructions (natural language commands)
|
||||||
- Sanitized page structure (simplified DOM, excluding sensitive form values)
|
- Simplified page structure (cleaned DOM) of all pages under the extension's control
|
||||||
|
|
||||||
are sent to the LLM API endpoint configured in **your settings**.
|
are sent to the LLM API endpoint configured in **your settings**.
|
||||||
|
|
||||||
|
> **Note:** The DOM cleaning process simplifies page structure for AI readability but **does not guarantee removal of sensitive information** (e.g., visible text, form values, or personal data on the page). Please be mindful of the page content when initiating tasks.
|
||||||
|
|
||||||
**If you configure a third-party LLM provider** (e.g., OpenAI, Anthropic, or others), data is sent directly to that provider. Their privacy policies apply.
|
**If you configure a third-party LLM provider** (e.g., OpenAI, Anthropic, or others), data is sent directly to that provider. Their privacy policies apply.
|
||||||
|
|
||||||
**If you use our testing endpoint**, your requests are proxied to [DeepSeek](https://deepseek.com) for AI processing. Regarding this test endpoint:
|
**If you use our testing endpoint**, your requests are proxied to [DeepSeek](https://deepseek.com) for AI processing. Regarding this test endpoint:
|
||||||
|
|
||||||
- This endpoint is provided for evaluation purposes only and is not recommended for production or daily use
|
- This endpoint is provided for evaluation purposes only and is not recommended for production or daily use
|
||||||
|
- The free model and their service providers may change at any time without prior notice
|
||||||
- We do **not** store your task content, page content, or visited URLs
|
- We do **not** store your task content, page content, or visited URLs
|
||||||
- Minimal logging (timestamps, request metadata, IP addresses) may occur for abuse prevention and service stability
|
- Minimal logging (timestamps, request metadata, IP addresses) may be collected for abuse prevention and service stability
|
||||||
- DeepSeek's [Privacy Policy](https://cdn.deepseek.com/policies/en-US/deepseek-privacy-policy.html) applies to their processing of your requests
|
- DeepSeek's [Privacy Policy](https://cdn.deepseek.com/policies/en-US/deepseek-privacy-policy.html) applies to their processing of your requests
|
||||||
|
|
||||||
## Data Storage
|
## Data Storage
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
"tsx": true,
|
"tsx": true,
|
||||||
"tailwind": {
|
"tailwind": {
|
||||||
"config": "",
|
"config": "",
|
||||||
"css": "src/index.css",
|
"css": "src/assets/index.css",
|
||||||
"baseColor": "neutral",
|
"baseColor": "neutral",
|
||||||
"cssVariables": true,
|
"cssVariables": true,
|
||||||
"prefix": ""
|
"prefix": ""
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
"utils": "@/lib/utils",
|
"utils": "@/lib/utils",
|
||||||
"ui": "@/components/ui",
|
"ui": "@/components/ui",
|
||||||
"lib": "@/lib",
|
"lib": "@/lib",
|
||||||
"hooks": "@/hooks"
|
"hooks": "@/lib/hooks"
|
||||||
},
|
},
|
||||||
"registries": {
|
"registries": {
|
||||||
"@magicui": "https://magicui.design/r/{name}.json"
|
"@magicui": "https://magicui.design/r/{name}.json"
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
AI-powered browser automation. Control web pages with natural language.
|
||||||
|
|
||||||
|
Page Agent Ext — AI-Powered Browser Automation
|
||||||
|
|
||||||
|
🌟 What is Page Agent Ext?
|
||||||
|
|
||||||
|
Page Agent Ext brings AI-powered automation to your browser. Built on the open-source Page Agent framework, it lets you control web pages across multiple tabs using natural language — no scripting required.
|
||||||
|
|
||||||
|
🌟 Key Features:
|
||||||
|
|
||||||
|
- Natural Language Control — Command your browser in plain language, no code needed
|
||||||
|
- Cross-Tab Automation — Seamlessly operate across multiple tabs and pages
|
||||||
|
- Smart HTML Cleaning — Intelligently extracts and simplifies page structure for accurate AI understanding
|
||||||
|
- Bring Your Own LLM — Use OpenAI, Anthropic, or any compatible API with full data control
|
||||||
|
- Privacy-First — Zero data collection; all data flows directly to your chosen LLM provider
|
||||||
|
- Open Source — MIT licensed, built on the Page Agent framework with full transparency
|
||||||
|
|
||||||
|
🌟 How It Works & Privacy:
|
||||||
|
|
||||||
|
Page Agent Ext performs DOM analysis locally in your browser. When you initiate a task, sanitized page structure is sent to the LLM API you configure. Your data is never collected or stored by us.
|
||||||
|
|
||||||
|
- Your API Key — Configure your own LLM API (OpenAI, Anthropic, etc.). Data goes directly to your provider
|
||||||
|
- Test API — A free test endpoint is available for evaluation; we recommend your own key for regular use
|
||||||
|
|
||||||
|
Privacy policy: https://github.com/alibaba/page-agent/blob/main/packages/extension/PRIVACY.md
|
||||||
|
|
||||||
|
🌟 Open Source:
|
||||||
|
|
||||||
|
This project is MIT licensed. Review the code, verify privacy claims, or extend it for your needs:
|
||||||
|
https://github.com/alibaba/page-agent
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
AI 驱动的浏览器自动化助手,用自然语言控制网页。
|
||||||
|
|
||||||
|
Page Agent Ext — AI 驱动的浏览器自动化助手
|
||||||
|
|
||||||
|
🌟 什么是 Page Agent Ext?
|
||||||
|
|
||||||
|
Page Agent Ext 为浏览器带来 AI 自动化能力。基于开源的 Page Agent 框架,你可以用自然语言跨标签页控制网页,无需编写任何脚本。
|
||||||
|
|
||||||
|
🌟 核心特性:
|
||||||
|
|
||||||
|
- 自然语言控制 — 用日常语言指挥浏览器,无需编程
|
||||||
|
- 跨标签页操作 — 在多个标签页之间无缝切换和操控
|
||||||
|
- HTML 智能清洗 — 智能提取和精简页面结构,让 AI 准确理解网页内容
|
||||||
|
- 使用你自己的模型 — 支持 OpenAI、Anthropic 或任何兼容 API,数据完全自主可控
|
||||||
|
- 隐私优先 — 零数据收集,所有数据直接发送到你配置的 LLM 服务商
|
||||||
|
- 开源透明 — MIT 协议,基于 Page Agent 开源框架,代码完全公开
|
||||||
|
|
||||||
|
🌟 工作原理与隐私:
|
||||||
|
|
||||||
|
Page Agent Ext 在浏览器本地进行 DOM 分析。当你发起任务时,经清洗的页面结构会发送到你配置的 LLM API。我们不会收集或存储你的任何数据。
|
||||||
|
|
||||||
|
- 你的 API Key — 配置你自己的 LLM API(OpenAI、Anthropic 等),数据直接发送到你的服务商
|
||||||
|
- 测试 API — 提供免费测试端点供体验,日常使用建议配置自己的 Key
|
||||||
|
|
||||||
|
隐私政策:https://github.com/alibaba/page-agent/blob/main/packages/extension/PRIVACY.md
|
||||||
|
|
||||||
|
🌟 开源项目:
|
||||||
|
|
||||||
|
MIT 协议开源。查看源码、验证隐私承诺,或按需扩展:
|
||||||
|
https://github.com/alibaba/page-agent
|
||||||
@@ -1,226 +0,0 @@
|
|||||||
# Page Agent 浏览器插件 API
|
|
||||||
|
|
||||||
在你的网页应用中接入 Page Agent 插件,并通过页面 JavaScript 发起多页面浏览器任务。
|
|
||||||
|
|
||||||
## 安装
|
|
||||||
|
|
||||||
### 1. 安装浏览器插件
|
|
||||||
|
|
||||||
首选渠道:
|
|
||||||
|
|
||||||
- Chrome 应用商店:https://chromewebstore.google.com/detail/page-agent-ext/akldabonmimlicnjlflnapfeklbfemhj
|
|
||||||
|
|
||||||
通常更快提供最新构建的渠道:
|
|
||||||
|
|
||||||
- GitHub Releases:https://github.com/alibaba/page-agent/releases
|
|
||||||
|
|
||||||
### 2. 安装类型定义(推荐)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install @page-agent/core --save-dev
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 授权(Token)
|
|
||||||
|
|
||||||
token 用于让页面 JS 调用扩展 API(`window.PAGE_AGENT_EXT`)并执行多页面任务。
|
|
||||||
|
|
||||||
为什么必须使用 token:
|
|
||||||
|
|
||||||
- 插件具备较广的浏览器权限(页面访问、导航、多标签控制)。
|
|
||||||
- 若被滥用,可能危害用户隐私与安全。
|
|
||||||
- 用户必须主动将 token 提供给其信任的应用。
|
|
||||||
|
|
||||||
配置步骤:
|
|
||||||
|
|
||||||
1. 在扩展侧边栏中复制 auth token。
|
|
||||||
2. 在页面中设置 token:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
localStorage.setItem('PageAgentExtUserAuthToken', 'your-token')
|
|
||||||
```
|
|
||||||
|
|
||||||
## 快速开始
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import type {
|
|
||||||
AgentActivity,
|
|
||||||
AgentStatus,
|
|
||||||
ExecutionResult,
|
|
||||||
HistoricalEvent,
|
|
||||||
} from '@page-agent/core'
|
|
||||||
|
|
||||||
// 等待插件注入(最多 1 秒)
|
|
||||||
async function waitForExtension(timeout = 1000): Promise<boolean> {
|
|
||||||
const start = Date.now()
|
|
||||||
while (Date.now() - start < timeout) {
|
|
||||||
if (window.PAGE_AGENT_EXT) return true
|
|
||||||
await new Promise((r) => setTimeout(r, 100))
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用
|
|
||||||
if (await waitForExtension()) {
|
|
||||||
const result = await window.PAGE_AGENT_EXT!.execute('点击登录按钮', {
|
|
||||||
baseURL: 'https://api.openai.com/v1',
|
|
||||||
apiKey: 'your-api-key',
|
|
||||||
model: 'gpt-5.2',
|
|
||||||
onStatusChange: (status) => console.log('状态:', status),
|
|
||||||
onActivity: (activity) => console.log('活动:', activity),
|
|
||||||
})
|
|
||||||
console.log('结果:', result)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 全局 API
|
|
||||||
|
|
||||||
token 匹配后,插件会在 `window` 上注入 API。
|
|
||||||
|
|
||||||
### `window.PAGE_AGENT_EXT_VERSION`
|
|
||||||
|
|
||||||
插件版本号字符串,可用于在访问主 API 前做能力检查。
|
|
||||||
|
|
||||||
### `window.PAGE_AGENT_EXT`
|
|
||||||
|
|
||||||
主命名空间对象。
|
|
||||||
|
|
||||||
#### `PAGE_AGENT_EXT.execute(task, config)`
|
|
||||||
|
|
||||||
执行 Agent 任务。
|
|
||||||
|
|
||||||
参数:
|
|
||||||
|
|
||||||
| 名称 | 类型 | 必填 | 说明 |
|
|
||||||
| ---- | ---- | ---- | ---- |
|
|
||||||
| `task` | `string` | 是 | 任务描述 |
|
|
||||||
| `config` | `ExecuteConfig` | 是 | LLM 设置、执行选项和回调 |
|
|
||||||
|
|
||||||
返回:`Promise<ExecutionResult>`
|
|
||||||
|
|
||||||
#### `PAGE_AGENT_EXT.stop()`
|
|
||||||
|
|
||||||
停止当前任务。
|
|
||||||
|
|
||||||
## 类型定义
|
|
||||||
|
|
||||||
安装 `@page-agent/core` 获取完整类型:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import type {
|
|
||||||
AgentActivity,
|
|
||||||
AgentStatus,
|
|
||||||
ExecutionResult,
|
|
||||||
HistoricalEvent,
|
|
||||||
} from '@page-agent/core'
|
|
||||||
|
|
||||||
export interface ExecuteConfig {
|
|
||||||
baseURL: string
|
|
||||||
apiKey: string
|
|
||||||
model: string
|
|
||||||
|
|
||||||
// 是否包含启动脚本所在标签页。默认 true。
|
|
||||||
includeInitialTab?: boolean
|
|
||||||
|
|
||||||
onStatusChange?: (status: AgentStatus) => void
|
|
||||||
onActivity?: (activity: AgentActivity) => void
|
|
||||||
onHistoryUpdate?: (history: HistoricalEvent[]) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Execute = (task: string, config: ExecuteConfig) => Promise<ExecutionResult>
|
|
||||||
```
|
|
||||||
|
|
||||||
`AgentStatus`
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
type AgentStatus = 'idle' | 'running' | 'completed' | 'error'
|
|
||||||
```
|
|
||||||
|
|
||||||
`AgentActivity`
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
type AgentActivity =
|
|
||||||
| { type: 'thinking' }
|
|
||||||
| { type: 'executing'; tool: string; input: unknown }
|
|
||||||
| { type: 'executed'; tool: string; input: unknown; output: string; duration: number }
|
|
||||||
| { type: 'retrying'; attempt: number; maxAttempts: number }
|
|
||||||
| { type: 'error'; message: string }
|
|
||||||
```
|
|
||||||
|
|
||||||
`HistoricalEvent`
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
type HistoricalEvent =
|
|
||||||
| { type: 'step'; stepIndex: number; reflection: AgentReflection; action: Action }
|
|
||||||
| { type: 'observation'; content: string }
|
|
||||||
| { type: 'user_takeover' }
|
|
||||||
| { type: 'retry'; message: string; attempt: number; maxAttempts: number }
|
|
||||||
| { type: 'error'; message: string; rawResponse?: unknown }
|
|
||||||
```
|
|
||||||
|
|
||||||
`ExecutionResult`
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
interface ExecutionResult {
|
|
||||||
success: boolean
|
|
||||||
data: string
|
|
||||||
history: HistoricalEvent[]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 使用示例
|
|
||||||
|
|
||||||
### 基础执行
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const result = await window.PAGE_AGENT_EXT!.execute(
|
|
||||||
'在邮箱输入框填入 test@example.com 然后点击提交',
|
|
||||||
{
|
|
||||||
baseURL: 'https://api.openai.com/v1',
|
|
||||||
apiKey: process.env.OPENAI_API_KEY!,
|
|
||||||
model: 'gpt-5.2',
|
|
||||||
includeInitialTab: false, // 可选:排除当前标签页
|
|
||||||
onStatusChange: (status) => console.log(status),
|
|
||||||
onActivity: (activity) => console.log(activity),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 停止当前任务
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
window.PAGE_AGENT_EXT!.stop()
|
|
||||||
```
|
|
||||||
|
|
||||||
## Window 类型声明
|
|
||||||
|
|
||||||
如果你不直接引入 `@page-agent/core`,可添加以下声明:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import type {
|
|
||||||
AgentActivity,
|
|
||||||
AgentStatus,
|
|
||||||
ExecutionResult,
|
|
||||||
HistoricalEvent,
|
|
||||||
} from '@page-agent/core'
|
|
||||||
|
|
||||||
interface ExecuteConfig {
|
|
||||||
baseURL: string
|
|
||||||
apiKey: string
|
|
||||||
model: string
|
|
||||||
includeInitialTab?: boolean
|
|
||||||
onStatusChange?: (status: AgentStatus) => void
|
|
||||||
onActivity?: (activity: AgentActivity) => void
|
|
||||||
onHistoryUpdate?: (history: HistoricalEvent[]) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface Window {
|
|
||||||
PAGE_AGENT_EXT_VERSION?: string
|
|
||||||
PAGE_AGENT_EXT?: {
|
|
||||||
version: string
|
|
||||||
execute: Execute
|
|
||||||
stop: () => void
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@page-agent/ext",
|
"name": "@page-agent/ext",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.1.5",
|
"version": "0.1.7",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "wxt",
|
"dev": "wxt",
|
||||||
@@ -42,6 +42,7 @@
|
|||||||
"@page-agent/llms": "1.3.0",
|
"@page-agent/llms": "1.3.0",
|
||||||
"@page-agent/page-controller": "1.3.0",
|
"@page-agent/page-controller": "1.3.0",
|
||||||
"@page-agent/ui": "1.3.0",
|
"@page-agent/ui": "1.3.0",
|
||||||
|
"ai-motion": "^0.4.8",
|
||||||
"chalk": "^5.6.2",
|
"chalk": "^5.6.2",
|
||||||
"zod": "^4.3.5"
|
"zod": "^4.3.5"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"extName": {
|
||||||
|
"message": "Page Agent Ext"
|
||||||
|
},
|
||||||
|
"extDescription": {
|
||||||
|
"message": "AI-powered browser automation assistant. Control web pages with natural language."
|
||||||
|
},
|
||||||
|
"extActionTitle": {
|
||||||
|
"message": "Open Page Agent"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"extName": {
|
||||||
|
"message": "Page Agent Ext"
|
||||||
|
},
|
||||||
|
"extDescription": {
|
||||||
|
"message": "AI 驱动的浏览器自动化助手,用自然语言控制网页。"
|
||||||
|
},
|
||||||
|
"extActionTitle": {
|
||||||
|
"message": "打开 Page Agent"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,22 +1,34 @@
|
|||||||
/**
|
/**
|
||||||
* React hook for using AgentController
|
* React hook for using AgentController
|
||||||
*/
|
*/
|
||||||
import type { AgentActivity, AgentStatus, HistoricalEvent } from '@page-agent/core'
|
import type {
|
||||||
|
AgentActivity,
|
||||||
|
AgentStatus,
|
||||||
|
HistoricalEvent,
|
||||||
|
SupportedLanguage,
|
||||||
|
} from '@page-agent/core'
|
||||||
import type { LLMConfig } from '@page-agent/llms'
|
import type { LLMConfig } from '@page-agent/llms'
|
||||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||||
|
|
||||||
import { MultiPageAgent } from './MultiPageAgent'
|
import { MultiPageAgent } from './MultiPageAgent'
|
||||||
import { DEMO_CONFIG } from './constants'
|
import { DEMO_CONFIG } from './constants'
|
||||||
|
|
||||||
|
/** Language preference: undefined means follow system */
|
||||||
|
export type LanguagePreference = SupportedLanguage | undefined
|
||||||
|
|
||||||
|
export interface ExtConfig extends LLMConfig {
|
||||||
|
language?: LanguagePreference
|
||||||
|
}
|
||||||
|
|
||||||
export interface UseAgentResult {
|
export interface UseAgentResult {
|
||||||
status: AgentStatus
|
status: AgentStatus
|
||||||
history: HistoricalEvent[]
|
history: HistoricalEvent[]
|
||||||
activity: AgentActivity | null
|
activity: AgentActivity | null
|
||||||
currentTask: string
|
currentTask: string
|
||||||
config: LLMConfig | null
|
config: ExtConfig | null
|
||||||
execute: (task: string) => Promise<void>
|
execute: (task: string) => Promise<void>
|
||||||
stop: () => void
|
stop: () => void
|
||||||
configure: (config: LLMConfig) => Promise<void>
|
configure: (config: ExtConfig) => Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useAgent(): UseAgentResult {
|
export function useAgent(): UseAgentResult {
|
||||||
@@ -25,16 +37,17 @@ export function useAgent(): UseAgentResult {
|
|||||||
const [history, setHistory] = useState<HistoricalEvent[]>([])
|
const [history, setHistory] = useState<HistoricalEvent[]>([])
|
||||||
const [activity, setActivity] = useState<AgentActivity | null>(null)
|
const [activity, setActivity] = useState<AgentActivity | null>(null)
|
||||||
const [currentTask, setCurrentTask] = useState('')
|
const [currentTask, setCurrentTask] = useState('')
|
||||||
const [config, setConfig] = useState<LLMConfig | null>(null)
|
const [config, setConfig] = useState<ExtConfig | null>(null)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
chrome.storage.local.get('llmConfig').then((result) => {
|
chrome.storage.local.get(['llmConfig', 'language']).then((result) => {
|
||||||
if (result.llmConfig) {
|
const llmConfig = (result.llmConfig as LLMConfig) ?? DEMO_CONFIG
|
||||||
setConfig(result.llmConfig as LLMConfig)
|
const language = (result.language as SupportedLanguage) || undefined
|
||||||
} else {
|
const full = { ...llmConfig, language }
|
||||||
|
if (!result.llmConfig) {
|
||||||
chrome.storage.local.set({ llmConfig: DEMO_CONFIG })
|
chrome.storage.local.set({ llmConfig: DEMO_CONFIG })
|
||||||
setConfig(DEMO_CONFIG)
|
|
||||||
}
|
}
|
||||||
|
setConfig(full)
|
||||||
})
|
})
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
@@ -87,9 +100,14 @@ export function useAgent(): UseAgentResult {
|
|||||||
agentRef.current?.stop()
|
agentRef.current?.stop()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const configure = useCallback(async (newConfig: LLMConfig) => {
|
const configure = useCallback(async ({ language, ...llmConfig }: ExtConfig) => {
|
||||||
await chrome.storage.local.set({ llmConfig: newConfig })
|
await chrome.storage.local.set({ llmConfig })
|
||||||
setConfig(newConfig)
|
if (language) {
|
||||||
|
await chrome.storage.local.set({ language })
|
||||||
|
} else {
|
||||||
|
await chrome.storage.local.remove('language')
|
||||||
|
}
|
||||||
|
setConfig({ ...llmConfig, language })
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -111,6 +111,41 @@
|
|||||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||||
--color-sidebar-border: var(--sidebar-border);
|
--color-sidebar-border: var(--sidebar-border);
|
||||||
--color-sidebar-ring: var(--sidebar-ring);
|
--color-sidebar-ring: var(--sidebar-ring);
|
||||||
|
--animate-blink-cursor: blink-cursor 1.2s step-end infinite;
|
||||||
|
@keyframes blink-cursor {
|
||||||
|
0%,
|
||||||
|
49% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50%,
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes glow-a {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
opacity: 0.45;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes glow-b {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.45;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
|
|||||||
@@ -21,8 +21,9 @@ function InputGroup({ className, ...props }: React.ComponentProps<'div'>) {
|
|||||||
'has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-3',
|
'has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-3',
|
||||||
'has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3',
|
'has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3',
|
||||||
|
|
||||||
// Focus state.
|
// Focus state — soft multi-color glow matching ai-motion palette
|
||||||
'has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot=input-group-control]:focus-visible]:ring-[3px]',
|
'has-[[data-slot=input-group-control]:focus-visible]:border-blue-400/60',
|
||||||
|
'has-[[data-slot=input-group-control]:focus-visible]:shadow-[0_0_0_1px_rgba(57,182,255,0.2),0_0_8px_rgba(57,182,255,0.15),0_0_16px_rgba(189,69,251,0.1)]',
|
||||||
|
|
||||||
// Error state.
|
// Error state.
|
||||||
'has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[[data-slot][aria-invalid=true]]:border-destructive dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40',
|
'has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[[data-slot][aria-invalid=true]]:border-destructive dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40',
|
||||||
|
|||||||
@@ -0,0 +1,164 @@
|
|||||||
|
import { MotionProps, motion, useInView } from 'motion/react'
|
||||||
|
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||||
|
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
interface TypingAnimationProps extends MotionProps {
|
||||||
|
children?: string
|
||||||
|
words?: string[]
|
||||||
|
className?: string
|
||||||
|
duration?: number
|
||||||
|
typeSpeed?: number
|
||||||
|
deleteSpeed?: number
|
||||||
|
delay?: number
|
||||||
|
pauseDelay?: number
|
||||||
|
loop?: boolean
|
||||||
|
as?: React.ElementType
|
||||||
|
startOnView?: boolean
|
||||||
|
showCursor?: boolean
|
||||||
|
blinkCursor?: boolean
|
||||||
|
cursorStyle?: 'line' | 'block' | 'underscore'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TypingAnimation({
|
||||||
|
children,
|
||||||
|
words,
|
||||||
|
className,
|
||||||
|
duration = 100,
|
||||||
|
typeSpeed,
|
||||||
|
deleteSpeed,
|
||||||
|
delay = 0,
|
||||||
|
pauseDelay = 1000,
|
||||||
|
loop = false,
|
||||||
|
as: Component = 'span',
|
||||||
|
startOnView = true,
|
||||||
|
showCursor = true,
|
||||||
|
blinkCursor = true,
|
||||||
|
cursorStyle = 'line',
|
||||||
|
...props
|
||||||
|
}: TypingAnimationProps) {
|
||||||
|
const MotionComponent = motion.create(Component, {
|
||||||
|
forwardMotionProps: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
const [displayedText, setDisplayedText] = useState<string>('')
|
||||||
|
const [currentWordIndex, setCurrentWordIndex] = useState(0)
|
||||||
|
const [currentCharIndex, setCurrentCharIndex] = useState(0)
|
||||||
|
const [phase, setPhase] = useState<'typing' | 'pause' | 'deleting'>('typing')
|
||||||
|
const elementRef = useRef<HTMLElement | null>(null)
|
||||||
|
const isInView = useInView(elementRef as React.RefObject<Element>, {
|
||||||
|
amount: 0.3,
|
||||||
|
once: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
const wordsToAnimate = useMemo(() => words || (children ? [children] : []), [words, children])
|
||||||
|
const hasMultipleWords = wordsToAnimate.length > 1
|
||||||
|
|
||||||
|
const typingSpeed = typeSpeed || duration
|
||||||
|
const deletingSpeed = deleteSpeed || typingSpeed / 2
|
||||||
|
|
||||||
|
const shouldStart = startOnView ? isInView : true
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!shouldStart || wordsToAnimate.length === 0) return
|
||||||
|
|
||||||
|
const timeoutDelay =
|
||||||
|
delay > 0 && displayedText === ''
|
||||||
|
? delay
|
||||||
|
: phase === 'typing'
|
||||||
|
? typingSpeed
|
||||||
|
: phase === 'deleting'
|
||||||
|
? deletingSpeed
|
||||||
|
: pauseDelay
|
||||||
|
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
const currentWord = wordsToAnimate[currentWordIndex] || ''
|
||||||
|
const graphemes = Array.from(currentWord)
|
||||||
|
|
||||||
|
switch (phase) {
|
||||||
|
case 'typing':
|
||||||
|
if (currentCharIndex < graphemes.length) {
|
||||||
|
setDisplayedText(graphemes.slice(0, currentCharIndex + 1).join(''))
|
||||||
|
setCurrentCharIndex(currentCharIndex + 1)
|
||||||
|
} else {
|
||||||
|
if (hasMultipleWords || loop) {
|
||||||
|
const isLastWord = currentWordIndex === wordsToAnimate.length - 1
|
||||||
|
if (!isLastWord || loop) {
|
||||||
|
setPhase('pause')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'pause':
|
||||||
|
setPhase('deleting')
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'deleting':
|
||||||
|
if (currentCharIndex > 0) {
|
||||||
|
setDisplayedText(graphemes.slice(0, currentCharIndex - 1).join(''))
|
||||||
|
setCurrentCharIndex(currentCharIndex - 1)
|
||||||
|
} else {
|
||||||
|
const nextIndex = (currentWordIndex + 1) % wordsToAnimate.length
|
||||||
|
setCurrentWordIndex(nextIndex)
|
||||||
|
setPhase('typing')
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}, timeoutDelay)
|
||||||
|
|
||||||
|
return () => clearTimeout(timeout)
|
||||||
|
}, [
|
||||||
|
shouldStart,
|
||||||
|
phase,
|
||||||
|
currentCharIndex,
|
||||||
|
currentWordIndex,
|
||||||
|
displayedText,
|
||||||
|
wordsToAnimate,
|
||||||
|
hasMultipleWords,
|
||||||
|
loop,
|
||||||
|
typingSpeed,
|
||||||
|
deletingSpeed,
|
||||||
|
pauseDelay,
|
||||||
|
delay,
|
||||||
|
])
|
||||||
|
|
||||||
|
const currentWordGraphemes = Array.from(wordsToAnimate[currentWordIndex] || '')
|
||||||
|
const isComplete =
|
||||||
|
!loop &&
|
||||||
|
currentWordIndex === wordsToAnimate.length - 1 &&
|
||||||
|
currentCharIndex >= currentWordGraphemes.length &&
|
||||||
|
phase !== 'deleting'
|
||||||
|
|
||||||
|
const shouldShowCursor =
|
||||||
|
showCursor &&
|
||||||
|
!isComplete &&
|
||||||
|
(hasMultipleWords || loop || currentCharIndex < currentWordGraphemes.length)
|
||||||
|
|
||||||
|
const getCursorChar = () => {
|
||||||
|
switch (cursorStyle) {
|
||||||
|
case 'block':
|
||||||
|
return '▌'
|
||||||
|
case 'underscore':
|
||||||
|
return '_'
|
||||||
|
case 'line':
|
||||||
|
default:
|
||||||
|
return '|'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MotionComponent
|
||||||
|
ref={elementRef}
|
||||||
|
className={cn('leading-[5rem] tracking-[-0.02em]', className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{displayedText}
|
||||||
|
{shouldShowCursor && (
|
||||||
|
<span className={cn('inline-block', blinkCursor && 'animate-blink-cursor')}>
|
||||||
|
{getCursorChar()}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</MotionComponent>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -15,7 +15,7 @@ import { ConfigPanel } from './components/ConfigPanel'
|
|||||||
import { HistoryDetail } from './components/HistoryDetail'
|
import { HistoryDetail } from './components/HistoryDetail'
|
||||||
import { HistoryList } from './components/HistoryList'
|
import { HistoryList } from './components/HistoryList'
|
||||||
import { ActivityCard, EventCard } from './components/cards'
|
import { ActivityCard, EventCard } from './components/cards'
|
||||||
import { EmptyState, Logo, StatusDot } from './components/misc'
|
import { EmptyState, Logo, MotionOverlay, StatusDot } from './components/misc'
|
||||||
|
|
||||||
type View =
|
type View =
|
||||||
| { name: 'chat' }
|
| { name: 'chat' }
|
||||||
@@ -117,14 +117,15 @@ export default function App() {
|
|||||||
const showEmptyState = !currentTask && history.length === 0 && !isRunning
|
const showEmptyState = !currentTask && history.length === 0 && !isRunning
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-screen bg-background">
|
<div className="relative flex flex-col h-screen bg-background">
|
||||||
|
<MotionOverlay active={isRunning} />
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<header className="flex items-center justify-between border-b px-3 py-2">
|
<header className="flex items-center justify-between border-b px-3 py-2">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Logo className="size-5" />
|
<Logo className="size-5" />
|
||||||
<span className="text-sm font-medium">Page Agent Ext</span>
|
<span className="text-sm font-medium">Page Agent Ext</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-1">
|
||||||
<StatusDot status={status} />
|
<StatusDot status={status} />
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import type { LLMConfig } from '@page-agent/llms'
|
|
||||||
import { Copy, CornerUpLeft, Eye, EyeOff, HatGlasses, Home, Loader2, Scale } from 'lucide-react'
|
import { Copy, CornerUpLeft, Eye, EyeOff, HatGlasses, Home, Loader2, Scale } from 'lucide-react'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { siGithub } from 'simple-icons'
|
import { siGithub } from 'simple-icons'
|
||||||
|
|
||||||
import { DEMO_API_KEY, DEMO_BASE_URL, DEMO_MODEL } from '@/agent/constants'
|
import { DEMO_API_KEY, DEMO_BASE_URL, DEMO_MODEL } from '@/agent/constants'
|
||||||
|
import type { ExtConfig, LanguagePreference } from '@/agent/useAgent'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Input } from '@/components/ui/input'
|
import { Input } from '@/components/ui/input'
|
||||||
|
|
||||||
interface ConfigPanelProps {
|
interface ConfigPanelProps {
|
||||||
config: LLMConfig | null
|
config: ExtConfig | null
|
||||||
onSave: (config: LLMConfig) => Promise<void>
|
onSave: (config: ExtConfig) => Promise<void>
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -17,6 +17,7 @@ export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) {
|
|||||||
const [apiKey, setApiKey] = useState(config?.apiKey || DEMO_API_KEY)
|
const [apiKey, setApiKey] = useState(config?.apiKey || DEMO_API_KEY)
|
||||||
const [baseURL, setBaseURL] = useState(config?.baseURL || DEMO_BASE_URL)
|
const [baseURL, setBaseURL] = useState(config?.baseURL || DEMO_BASE_URL)
|
||||||
const [model, setModel] = useState(config?.model || DEMO_MODEL)
|
const [model, setModel] = useState(config?.model || DEMO_MODEL)
|
||||||
|
const [language, setLanguage] = useState<LanguagePreference>(config?.language)
|
||||||
const [saving, setSaving] = useState(false)
|
const [saving, setSaving] = useState(false)
|
||||||
const [userAuthToken, setUserAuthToken] = useState<string>('')
|
const [userAuthToken, setUserAuthToken] = useState<string>('')
|
||||||
const [copied, setCopied] = useState(false)
|
const [copied, setCopied] = useState(false)
|
||||||
@@ -28,6 +29,7 @@ export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) {
|
|||||||
setApiKey(config?.apiKey || DEMO_API_KEY)
|
setApiKey(config?.apiKey || DEMO_API_KEY)
|
||||||
setBaseURL(config?.baseURL || DEMO_BASE_URL)
|
setBaseURL(config?.baseURL || DEMO_BASE_URL)
|
||||||
setModel(config?.model || DEMO_MODEL)
|
setModel(config?.model || DEMO_MODEL)
|
||||||
|
setLanguage(config?.language)
|
||||||
}, [config])
|
}, [config])
|
||||||
|
|
||||||
// Poll for user auth token every second until found
|
// Poll for user auth token every second until found
|
||||||
@@ -65,7 +67,7 @@ export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) {
|
|||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
setSaving(true)
|
setSaving(true)
|
||||||
try {
|
try {
|
||||||
await onSave({ apiKey, baseURL, model })
|
await onSave({ apiKey, baseURL, model, language })
|
||||||
} finally {
|
} finally {
|
||||||
setSaving(false)
|
setSaving(false)
|
||||||
}
|
}
|
||||||
@@ -165,6 +167,19 @@ export function ConfigPanel({ config, onSave, onClose }: ConfigPanelProps) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-1.5">
|
||||||
|
<label className="text-xs text-muted-foreground">Language</label>
|
||||||
|
<select
|
||||||
|
value={language ?? ''}
|
||||||
|
onChange={(e) => setLanguage((e.target.value || undefined) as LanguagePreference)}
|
||||||
|
className="h-8 text-xs rounded-md border border-input bg-background px-2 cursor-pointer"
|
||||||
|
>
|
||||||
|
<option value="">System</option>
|
||||||
|
<option value="en-US">English</option>
|
||||||
|
<option value="zh-CN">中文</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-2 mt-2">
|
<div className="flex gap-2 mt-2">
|
||||||
<Button variant="outline" onClick={onClose} className="flex-1 h-8 text-xs cursor-pointer">
|
<Button variant="outline" onClick={onClose} className="flex-1 h-8 text-xs cursor-pointer">
|
||||||
Cancel
|
Cancel
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import type { AgentStatus } from '@page-agent/core'
|
import type { AgentStatus } from '@page-agent/core'
|
||||||
|
import { Motion } from 'ai-motion'
|
||||||
|
import { BookOpen, Globe } from 'lucide-react'
|
||||||
|
import { useEffect, useRef } from 'react'
|
||||||
|
import { siGithub } from 'simple-icons'
|
||||||
|
|
||||||
|
import { TypingAnimation } from '@/components/ui/typing-animation'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
// Status dot indicator
|
// Status dot indicator
|
||||||
@@ -19,7 +24,7 @@ export function StatusDot({ status }: { status: AgentStatus }) {
|
|||||||
}[status]
|
}[status]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5 mr-2">
|
||||||
<span
|
<span
|
||||||
className={cn('size-2 rounded-full', colorClass, status === 'running' && 'animate-pulse')}
|
className={cn('size-2 rounded-full', colorClass, status === 'running' && 'animate-pulse')}
|
||||||
/>
|
/>
|
||||||
@@ -32,14 +37,111 @@ export function Logo({ className }: { className?: string }) {
|
|||||||
return <img src="/assets/page-agent-256.webp" alt="Page Agent" className={cn('', className)} />
|
return <img src="/assets/page-agent-256.webp" alt="Page Agent" className={cn('', className)} />
|
||||||
}
|
}
|
||||||
|
|
||||||
// Empty state with logo
|
// Full-screen ai-motion glow overlay, shown only while running
|
||||||
|
export function MotionOverlay({ active }: { active: boolean }) {
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null)
|
||||||
|
const motionRef = useRef<Motion | null>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const mode = document.documentElement.classList.contains('dark') ? 'dark' : 'light'
|
||||||
|
const motion = new Motion({
|
||||||
|
mode,
|
||||||
|
borderWidth: 4,
|
||||||
|
borderRadius: 14,
|
||||||
|
glowWidth: mode === 'dark' ? 120 : 60,
|
||||||
|
styles: { position: 'absolute', inset: '0' },
|
||||||
|
})
|
||||||
|
motionRef.current = motion
|
||||||
|
containerRef.current!.appendChild(motion.element)
|
||||||
|
motion.autoResize(containerRef.current!)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
motion.dispose()
|
||||||
|
motionRef.current = null
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const motion = motionRef.current
|
||||||
|
if (!motion) return
|
||||||
|
|
||||||
|
let disposed = false
|
||||||
|
if (active) {
|
||||||
|
motion.start()
|
||||||
|
motion.fadeIn()
|
||||||
|
} else {
|
||||||
|
motion.fadeOut().then(() => !disposed && motion.pause())
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
disposed = true
|
||||||
|
}
|
||||||
|
}, [active])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={containerRef}
|
||||||
|
className="pointer-events-none absolute inset-0 z-10 opacity-60"
|
||||||
|
style={{ display: active ? undefined : 'none' }}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty state with logo and breathing glow
|
||||||
export function EmptyState() {
|
export function EmptyState() {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center justify-center h-full gap-3 text-center px-6">
|
<div className="flex flex-col items-center justify-center h-full gap-4 text-center px-6">
|
||||||
<Logo className="size-20 opacity-80" />
|
<div className="relative select-none pointer-events-none">
|
||||||
|
<div className="absolute inset-0 -m-6 rounded-full bg-[conic-gradient(from_180deg,oklch(0.55_0.2_280),oklch(0.5_0.15_230),oklch(0.6_0.18_310),oklch(0.55_0.2_280))] blur-2xl animate-[glow-a_5s_ease-in-out_infinite]" />
|
||||||
|
<div className="absolute inset-0 -m-6 rounded-full bg-[conic-gradient(from_0deg,oklch(0.55_0.18_160),oklch(0.5_0.2_200),oklch(0.6_0.15_120),oklch(0.55_0.18_160))] blur-2xl animate-[glow-b_5s_ease-in-out_infinite]" />
|
||||||
|
<Logo className="relative size-20 opacity-80" />
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-sm font-medium text-foreground">Page Agent Ext</h2>
|
<h2 className="text-base font-medium text-foreground mb-1">Page Agent Ext</h2>
|
||||||
<p className="text-xs text-muted-foreground mt-1">Enter a task to automate this page</p>
|
<TypingAnimation
|
||||||
|
className="text-sm text-muted-foreground"
|
||||||
|
words={[
|
||||||
|
'Enter a task to automate this page',
|
||||||
|
'Execute multi-page tasks',
|
||||||
|
'Call this extension from your web page',
|
||||||
|
'Use this extension in your own agents',
|
||||||
|
]}
|
||||||
|
cursorStyle="underscore"
|
||||||
|
loop
|
||||||
|
typeSpeed={20}
|
||||||
|
deleteSpeed={10}
|
||||||
|
pauseDelay={3000}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-3 mt-1 text-muted-foreground">
|
||||||
|
<a
|
||||||
|
href="https://github.com/alibaba/page-agent"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="hover:text-foreground transition-colors"
|
||||||
|
title="GitHub"
|
||||||
|
>
|
||||||
|
<svg role="img" viewBox="0 0 24 24" className="size-4 fill-current">
|
||||||
|
<path d={siGithub.path} />
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="https://alibaba.github.io/page-agent/#/docs/features/chrome-extension"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="hover:text-foreground transition-colors"
|
||||||
|
title="Documentation"
|
||||||
|
>
|
||||||
|
<BookOpen className="size-4" />
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="https://alibaba.github.io/page-agent"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="hover:text-foreground transition-colors"
|
||||||
|
title="Website"
|
||||||
|
>
|
||||||
|
<Globe className="size-4" />
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -38,9 +38,9 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
manifest: {
|
manifest: {
|
||||||
name: 'Page Agent Ext',
|
default_locale: 'en',
|
||||||
description:
|
name: '__MSG_extName__',
|
||||||
'AI-powered browser automation assistant. Control web pages with natural language.',
|
description: '__MSG_extDescription__',
|
||||||
homepage_url: 'https://alibaba.github.io/page-agent/',
|
homepage_url: 'https://alibaba.github.io/page-agent/',
|
||||||
permissions: ['tabs', 'tabGroups', 'sidePanel', 'storage'],
|
permissions: ['tabs', 'tabGroups', 'sidePanel', 'storage'],
|
||||||
host_permissions: ['<all_urls>'],
|
host_permissions: ['<all_urls>'],
|
||||||
@@ -48,7 +48,7 @@ export default defineConfig({
|
|||||||
64: 'assets/page-agent-64.png',
|
64: 'assets/page-agent-64.png',
|
||||||
},
|
},
|
||||||
action: {
|
action: {
|
||||||
default_title: 'Open Page Agent',
|
default_title: '__MSG_extActionTitle__',
|
||||||
},
|
},
|
||||||
web_accessible_resources: [
|
web_accessible_resources: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ export default function ChromeExtension() {
|
|||||||
href={chromeWebStoreUrl}
|
href={chromeWebStoreUrl}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="inline-flex items-center gap-2 px-6 py-3 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-lg transition-colors"
|
className="inline-flex items-center gap-2 px-6 py-3 bg-blue-600 hover:bg-blue-700 text-white! font-medium rounded-lg transition-colors"
|
||||||
>
|
>
|
||||||
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
|
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
|
||||||
<path d={siChromewebstore.path} />
|
<path d={siChromewebstore.path} />
|
||||||
@@ -74,7 +74,7 @@ export default function ChromeExtension() {
|
|||||||
href={githubReleasesUrl}
|
href={githubReleasesUrl}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="inline-flex items-center gap-2 px-6 py-3 bg-gray-900 hover:bg-gray-800 dark:bg-gray-700 dark:hover:bg-gray-600 text-white font-medium rounded-lg transition-colors"
|
className="inline-flex items-center gap-2 px-6 py-3 bg-gray-900 hover:bg-gray-800 dark:bg-gray-700 dark:hover:bg-gray-600 text-white! font-medium rounded-lg transition-colors"
|
||||||
>
|
>
|
||||||
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
|
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
|
||||||
<path d={siGithub.path} />
|
<path d={siGithub.path} />
|
||||||
@@ -164,15 +164,7 @@ localStorage.setItem('PageAgentExtUserAuthToken', '<your-token-from-extension>')
|
|||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="block text-sm text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300 hover:underline"
|
className="block text-sm text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300 hover:underline"
|
||||||
>
|
>
|
||||||
📄 {isZh ? '英文版 API 文档' : 'API Documentation (English)'}
|
📄 {isZh ? 'API 文档' : 'API Documentation'}
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href="https://github.com/alibaba/page-agent/blob/main/packages/extension/docs/extension_api_zh.md"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="block text-sm text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300 hover:underline"
|
|
||||||
>
|
|
||||||
📄 {isZh ? '中文版 API 文档' : 'API Documentation (Chinese)'}
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -317,6 +309,7 @@ window.PAGE_AGENT_EXT.stop()`
|
|||||||
? '将 MultiPageAgent 集成你自己的插件'
|
? '将 MultiPageAgent 集成你自己的插件'
|
||||||
: 'Integrate MultiPageAgent into Your Extension'}
|
: 'Integrate MultiPageAgent into Your Extension'}
|
||||||
</h2>
|
</h2>
|
||||||
|
<p>@TODO</p>
|
||||||
<p className="text-gray-600 dark:text-gray-300 mb-4">
|
<p className="text-gray-600 dark:text-gray-300 mb-4">
|
||||||
{isZh
|
{isZh
|
||||||
? '建议先阅读扩展 API 文档,再参考 background entry implementation。'
|
? '建议先阅读扩展 API 文档,再参考 background entry implementation。'
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { Link } from 'wouter'
|
||||||
|
|
||||||
import { useLanguage } from '@/i18n/context'
|
import { useLanguage } from '@/i18n/context'
|
||||||
|
|
||||||
export default function LimitationsPage() {
|
export default function LimitationsPage() {
|
||||||
@@ -11,184 +13,144 @@ export default function LimitationsPage() {
|
|||||||
</h1>
|
</h1>
|
||||||
<p className="text-xl text-gray-600 dark:text-gray-300">
|
<p className="text-xl text-gray-600 dark:text-gray-300">
|
||||||
{isZh
|
{isZh
|
||||||
? '了解 page-agent 当前的功能边界和技术限制'
|
? 'Page Agent 基于 DOM 理解网页并执行操作。这决定了它的能力边界。'
|
||||||
: "Understand page-agent's current capabilities and technical constraints"}
|
: 'Page Agent understands web pages via DOM and performs actions accordingly. This defines its capability boundary.'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="prose prose-lg dark:prose-invert max-w-none">
|
<div className="prose prose-lg dark:prose-invert max-w-none">
|
||||||
|
{/* PageAgent.js vs PageAgentExt */}
|
||||||
<h2 className="text-2xl font-bold mb-3">
|
<h2 className="text-2xl font-bold mb-3">
|
||||||
{isZh ? '页面支持限制' : 'Page Support Limitations'}
|
{isZh ? 'PageAgent.js vs PageAgentExt' : 'PageAgent.js vs PageAgentExt'}
|
||||||
</h2>
|
</h2>
|
||||||
<div className="bg-blue-50 dark:bg-blue-900/20 border-l-4 border-blue-400 p-4 mb-6">
|
<p className="text-gray-600 dark:text-gray-300 mb-4">
|
||||||
<h3 className="font-semibold text-blue-800 dark:text-blue-200 mb-2">
|
{isZh
|
||||||
{isZh ? '单页应用限制' : 'Single Page Application Limits'}
|
? 'PageAgent.js 是核心库,运行在页面内。PageAgentExt 是可选的浏览器扩展,提供额外的浏览器级控制能力。'
|
||||||
</h3>
|
: 'PageAgent.js is the core library running inside a page. PageAgentExt is an optional browser extension that adds browser-level control.'}
|
||||||
<ul className="text-blue-700 dark:text-blue-300 space-y-2">
|
</p>
|
||||||
<li>
|
<div className="overflow-x-auto mb-6">
|
||||||
{isZh
|
<table className="w-full text-sm border-collapse">
|
||||||
? '• 仅支持单页应用(SPA):目前只能在单个页面内进行操作'
|
<thead>
|
||||||
: '• SPA only: Currently operates within a single page'}
|
<tr className="border-b border-gray-200 dark:border-gray-700">
|
||||||
</li>
|
<th className="text-left py-3 pr-4"></th>
|
||||||
<li>
|
<th className="text-left py-3 px-4 font-semibold">PageAgent.js</th>
|
||||||
{isZh
|
<th className="text-left py-3 pl-4 font-semibold">
|
||||||
? '• 多页接力功能正在设计中:暂时无法跨页面执行连续任务'
|
PageAgentExt{' '}
|
||||||
: '• Multi-page relay in design: Cannot execute continuous tasks across pages yet'}
|
<Link
|
||||||
</li>
|
href="/docs/features/chrome-extension"
|
||||||
<li>
|
className="text-xs font-normal text-blue-600 dark:text-blue-400 hover:underline"
|
||||||
{isZh
|
>
|
||||||
? '• 无法操作未接入该能力的网站:需要目标网站主动集成 page-agent'
|
{isZh ? '了解更多' : 'learn more'}
|
||||||
: '• Requires integration: Cannot operate on sites without page-agent'}
|
</Link>
|
||||||
</li>
|
</th>
|
||||||
</ul>
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody className="text-gray-600 dark:text-gray-300">
|
||||||
|
<tr className="border-b border-gray-100 dark:border-gray-800">
|
||||||
|
<td className="py-3 pr-4 font-medium text-gray-900 dark:text-white">
|
||||||
|
{isZh ? '接入方式' : 'Integration'}
|
||||||
|
</td>
|
||||||
|
<td className="py-3 px-4">
|
||||||
|
{isZh ? '网站开发者主动集成' : 'Site developer integrates the library'}
|
||||||
|
</td>
|
||||||
|
<td className="py-3 pl-4">
|
||||||
|
{isZh ? '用户安装浏览器扩展' : 'User installs a browser extension'}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr className="border-b border-gray-100 dark:border-gray-800">
|
||||||
|
<td className="py-3 pr-4 font-medium text-gray-900 dark:text-white">
|
||||||
|
{isZh ? '可操作范围' : 'Scope'}
|
||||||
|
</td>
|
||||||
|
<td className="py-3 px-4">
|
||||||
|
{isZh ? '当前页面(为 SPA 设计)' : 'Current page (designed for SPAs)'}
|
||||||
|
</td>
|
||||||
|
<td className="py-3 pl-4">
|
||||||
|
{isZh ? '任意网页、多标签页' : 'Any web page, multi-tab'}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td className="py-3 pr-4 font-medium text-gray-900 dark:text-white">
|
||||||
|
{isZh ? '额外能力' : 'Extra capabilities'}
|
||||||
|
</td>
|
||||||
|
<td className="py-3 px-4">—</td>
|
||||||
|
<td className="py-3 pl-4">
|
||||||
|
{isZh ? '新建/切换/关闭标签页' : 'Open / switch / close tabs'}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Interaction Limitations */}
|
||||||
<h2 className="text-2xl font-bold mb-3">
|
<h2 className="text-2xl font-bold mb-3">
|
||||||
{isZh ? '交互行为限制' : 'Interaction Limitations'}
|
{isZh ? '交互能力' : 'Interaction Capabilities'}
|
||||||
</h2>
|
</h2>
|
||||||
<div className="bg-gray-50 dark:bg-gray-800 rounded-lg p-6 mb-6">
|
<div className="bg-gray-50 dark:bg-gray-800 rounded-lg p-6 mb-6">
|
||||||
<h3 className="font-semibold mb-4">{isZh ? '支持的操作' : 'Supported Operations'}</h3>
|
<div className="grid md:grid-cols-2 gap-6">
|
||||||
<div className="grid md:grid-cols-2 gap-4 mb-6">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex items-center text-green-600 dark:text-green-400">
|
|
||||||
<span className="mr-2">✅</span>
|
|
||||||
<span>{isZh ? '点击操作' : 'Click'}</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center text-green-600 dark:text-green-400">
|
|
||||||
<span className="mr-2">✅</span>
|
|
||||||
<span>{isZh ? '文本输入' : 'Text input'}</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center text-green-600 dark:text-green-400">
|
|
||||||
<span className="mr-2">✅</span>
|
|
||||||
<span>{isZh ? '页面滚动' : 'Scroll'}</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center text-green-600 dark:text-green-400">
|
|
||||||
<span className="mr-2">✅</span>
|
|
||||||
<span>{isZh ? '表单提交' : 'Form submit'}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex items-center text-green-600 dark:text-green-400">
|
|
||||||
<span className="mr-2">✅</span>
|
|
||||||
<span>{isZh ? '选择操作' : 'Select'}</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center text-green-600 dark:text-green-400">
|
|
||||||
<span className="mr-2">✅</span>
|
|
||||||
<span>{isZh ? '焦点切换' : 'Focus'}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3 className="font-semibold mb-4">{isZh ? '不支持的操作' : 'Unsupported Operations'}</h3>
|
|
||||||
<div className="grid md:grid-cols-2 gap-4">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex items-center text-red-600 dark:text-red-400">
|
|
||||||
<span className="mr-2">❌</span>
|
|
||||||
<span>{isZh ? '鼠标悬停(hover)' : 'Mouse hover'}</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center text-red-600 dark:text-red-400">
|
|
||||||
<span className="mr-2">❌</span>
|
|
||||||
<span>{isZh ? '拖拽操作' : 'Drag & drop'}</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center text-red-600 dark:text-red-400">
|
|
||||||
<span className="mr-2">❌</span>
|
|
||||||
<span>{isZh ? '右键菜单' : 'Right-click menu'}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex items-center text-red-600 dark:text-red-400">
|
|
||||||
<span className="mr-2">❌</span>
|
|
||||||
<span>{isZh ? '图形绘制' : 'Drawing'}</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center text-red-600 dark:text-red-400">
|
|
||||||
<span className="mr-2">❌</span>
|
|
||||||
<span>{isZh ? '键盘快捷键' : 'Keyboard shortcuts'}</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center text-red-600 dark:text-red-400">
|
|
||||||
<span className="mr-2">❌</span>
|
|
||||||
<span>{isZh ? '基于点击区域或鼠标位置的控制' : 'Position-based control'}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h2 className="text-2xl font-bold mb-3">
|
|
||||||
{isZh ? '网页理解限制' : 'Understanding Limitations'}
|
|
||||||
</h2>
|
|
||||||
<div className="bg-red-50 dark:bg-red-900/20 border-l-4 border-red-400 p-4 mb-6">
|
|
||||||
<h3 className="font-semibold text-red-800 dark:text-red-200 mb-2">
|
|
||||||
{isZh ? '无视觉能力' : 'No Visual Recognition'}
|
|
||||||
</h3>
|
|
||||||
<p className="text-red-700 dark:text-red-300 mb-3">
|
|
||||||
{isZh
|
|
||||||
? 'page-agent 基于 DOM 结构进行理解和操作,没有视觉识别能力,无法理解以下内容:'
|
|
||||||
: 'page-agent operates based on DOM structure with no visual recognition. Cannot understand:'}
|
|
||||||
</p>
|
|
||||||
<ul className="text-red-700 dark:text-red-300 space-y-1">
|
|
||||||
<li>
|
|
||||||
{isZh
|
|
||||||
? '• 图片内容:无法识别图片中的文字、图标或视觉元素'
|
|
||||||
: '• Image content: Cannot recognize text, icons, or visual elements in images'}
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
{isZh
|
|
||||||
? '• Canvas 画布:无法理解 Canvas 中绘制的图形和内容'
|
|
||||||
: '• Canvas: Cannot understand graphics drawn on Canvas'}
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
{isZh
|
|
||||||
? '• WebGL 3D 内容:无法操作 3D 场景中的元素'
|
|
||||||
: '• WebGL 3D: Cannot operate elements in 3D scenes'}
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
{isZh
|
|
||||||
? '• SVG 图形:无法理解 SVG 中的视觉内容和路径'
|
|
||||||
: '• SVG graphics: Cannot understand visual content and paths in SVG'}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h2 className="text-2xl font-bold mb-3">
|
|
||||||
{isZh ? '被操作网站要求' : 'Website Requirements'}
|
|
||||||
</h2>
|
|
||||||
<div className="bg-gray-50 dark:bg-gray-800 rounded-lg p-6 mb-6">
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div>
|
<div>
|
||||||
<h3 className="font-semibold mb-2">
|
<h3 className="font-semibold mb-3 text-green-700 dark:text-green-400">
|
||||||
{isZh ? '语义化和易用性' : 'Semantic & Usability'}
|
{isZh ? '支持' : 'Supported'}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-gray-600 dark:text-gray-300">
|
<ul className="space-y-1.5 text-sm">
|
||||||
{isZh
|
{[
|
||||||
? '所有操作都基于 DOM 元素的语义化标签和属性。如果页面结构不够语义化,或者没有任何 accessibility 特性,可能影响 AI 的理解准确性。'
|
isZh ? '点击、文本输入、选择' : 'Click, text input, select',
|
||||||
: 'All operations rely on semantic tags and attributes. Poor semantic structure or lack of accessibility features may affect AI understanding accuracy.'}
|
isZh ? '页面滚动(垂直 / 水平)' : 'Scroll (vertical / horizontal)',
|
||||||
</p>
|
isZh ? '表单提交、焦点切换' : 'Form submit, focus',
|
||||||
|
isZh ? '执行 JavaScript(可选)' : 'Execute JavaScript (opt-in)',
|
||||||
|
].map((text) => (
|
||||||
|
<li key={text} className="flex items-center text-gray-700 dark:text-gray-300">
|
||||||
|
<span className="mr-2 text-green-600 dark:text-green-400">✓</span>
|
||||||
|
{text}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="font-semibold mb-2">{isZh ? 'UI/UX' : 'UI/UX'}</h3>
|
<h3 className="font-semibold mb-3 text-red-700 dark:text-red-400">
|
||||||
<p className="text-gray-600 dark:text-gray-300">
|
{isZh ? '不支持' : 'Not supported'}
|
||||||
{isZh
|
</h3>
|
||||||
? '反常识的交互规则、基于视觉的操作提示、复杂的鼠标交互、快速出现快速消失的元素等,都会影响 AI 的理解和操作。'
|
<ul className="space-y-1.5 text-sm">
|
||||||
: 'Counter-intuitive interaction rules, visual-only operation hints, complex mouse interactions, or rapidly appearing/disappearing elements can affect AI understanding and operation.'}
|
{[
|
||||||
</p>
|
isZh ? '悬停、拖拽、右键菜单' : 'Hover, drag & drop, right-click',
|
||||||
</div>
|
isZh ? '键盘快捷键' : 'Keyboard shortcuts',
|
||||||
<div>
|
isZh ? '坐标定位操作' : 'Position-based control',
|
||||||
<h3 className="font-semibold mb-2">{isZh ? '环境要求' : 'Environment'}</h3>
|
isZh ? '绘图操作' : 'Drawing',
|
||||||
<p className="text-gray-600 dark:text-gray-300">modern browser</p>
|
].map((text) => (
|
||||||
|
<li key={text} className="flex items-center text-gray-700 dark:text-gray-300">
|
||||||
|
<span className="mr-2 text-red-600 dark:text-red-400">✗</span>
|
||||||
|
{text}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2>{isZh ? '未来规划' : 'Future Plans'}</h2>
|
{/* Understanding Limitations */}
|
||||||
<div className="bg-green-50 dark:bg-green-900/20 border-l-4 border-green-400 p-4">
|
<h2 className="text-2xl font-bold mb-3">{isZh ? '理解能力' : 'Understanding'}</h2>
|
||||||
<h3 className="font-semibold text-green-800 dark:text-green-200 mb-2">
|
<div className="bg-amber-50 dark:bg-amber-900/20 border-l-4 border-amber-400 p-4 mb-6">
|
||||||
{isZh ? '即将支持' : 'Coming Soon'}
|
<p className="text-amber-800 dark:text-amber-200 mb-2 font-medium">
|
||||||
</h3>
|
{isZh
|
||||||
<ul className="text-green-700 dark:text-green-300 space-y-1">
|
? 'Page Agent 不使用多模态模型,不截图,没有视觉能力。仅通过 DOM 结构理解页面。'
|
||||||
<li>{isZh ? '• 多页面接力操作能力' : '• Multi-page relay capabilities'}</li>
|
: 'Page Agent does not use multimodal models, does not take screenshots, and has no visual capability. It reads pages through DOM structure only.'}
|
||||||
<li>{isZh ? '• 更丰富的鼠标交互支持' : '• Richer mouse interaction support'}</li>
|
</p>
|
||||||
<li>{isZh ? '• 基础的视觉理解能力' : '• Basic visual understanding'}</li>
|
<p className="text-amber-700 dark:text-amber-300 text-sm">
|
||||||
<li>{isZh ? '• 更智能的错误恢复机制' : '• Smarter error recovery'}</li>
|
{isZh
|
||||||
</ul>
|
? '图片、Canvas、WebGL、SVG 等视觉内容无法被识别。页面的语义化程度和可访问性直接影响 AI 的理解准确性。'
|
||||||
|
: 'Images, Canvas, WebGL, SVG and other visual content cannot be recognized. Page semantic quality and accessibility directly affect AI accuracy.'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Website Quality */}
|
||||||
|
<h2 className="text-2xl font-bold mb-3">
|
||||||
|
{isZh ? '网页质量影响' : 'Page Quality Matters'}
|
||||||
|
</h2>
|
||||||
|
<div className="bg-gray-50 dark:bg-gray-800 rounded-lg p-6">
|
||||||
|
<p className="text-gray-600 dark:text-gray-300 text-sm">
|
||||||
|
{isZh
|
||||||
|
? '反常识的交互逻辑、纯视觉的操作提示、快速出现消失的元素等都会降低自动化成功率。语义化的 HTML 和良好的可访问性会显著提升效果。'
|
||||||
|
: 'Counter-intuitive interactions, visual-only cues, and rapidly appearing/disappearing elements reduce automation success. Semantic HTML and good accessibility significantly improve results.'}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user