Compare commits

...

53 Commits

Author SHA1 Message Date
Simon 2d66915570 chore: simplify published pkg 2025-12-01 21:05:10 +08:00
Simon 25098f8772 chore(version): bump version to 0.0.5 2025-12-01 20:53:56 +08:00
Simon 8a67b96fdb chore: add version sync script 2025-12-01 20:52:26 +08:00
Simon 682a08e0dc chore: automatically copy readme when publish 2025-12-01 20:33:10 +08:00
Simon b53d4dd8a5 chore: clean up 2025-12-01 20:26:52 +08:00
Simon a4e48d32a4 chore: fix github actions 2025-12-01 20:20:27 +08:00
Simon cdd1602f79 chore: issue template wording 2025-12-01 20:16:05 +08:00
Simon adec9d8197 refactor: monorepo 2025-12-01 20:11:12 +08:00
Simon 1b9970da14 chore: lint warnings 2025-11-26 20:41:55 +08:00
Simon aa8f5ae235 chore: lint 2025-11-26 20:39:37 +08:00
Simon 60a6b5e5b6 chore: wording; tailwind lint 2025-11-26 19:56:47 +08:00
Simon 7ae8fe753a feat: update logo 2025-11-25 21:00:41 +08:00
Simon a1dfe879c1 chore: update issue templates 2025-11-25 19:46:18 +08:00
Simon 6e7619760f chore: update lock 2025-11-24 20:33:57 +08:00
Simon efc85da91c Merge pull request #41 from alibaba/dependabot/npm_and_yarn/development-dependencies-8c725c2457
chore(deps-dev): bump the development-dependencies group across 1 directory with 17 updates
2025-11-24 20:31:21 +08:00
Simon 8e11fcb918 chore: i18n improve 2025-11-24 20:30:12 +08:00
dependabot[bot] 4a9bd9aed0 chore(deps-dev): bump the development-dependencies group across 1 directory with 17 updates
Bumps the development-dependencies group with 16 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [@eslint/js](https://github.com/eslint/eslint/tree/HEAD/packages/js) | `9.38.0` | `9.39.1` |
| [@microsoft/api-extractor](https://github.com/microsoft/rushstack/tree/HEAD/apps/api-extractor) | `7.53.1` | `7.55.0` |
| [@tailwindcss/vite](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-vite) | `4.1.15` | `4.1.17` |
| [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) | `19.2.2` | `19.2.5` |
| [@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom) | `19.2.2` | `19.2.3` |
| [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react-swc) | `4.1.0` | `4.2.2` |
| [eslint](https://github.com/eslint/eslint) | `9.38.0` | `9.39.1` |
| [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/HEAD/packages/plugins/eslint-plugin-react-dom) | `2.2.2` | `2.3.5` |
| [eslint-plugin-react-hooks](https://github.com/facebook/react/tree/HEAD/packages/eslint-plugin-react-hooks) | `7.0.0` | `7.0.1` |
| [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/HEAD/packages/plugins/eslint-plugin-react-x) | `2.2.2` | `2.3.5` |
| [globals](https://github.com/sindresorhus/globals) | `16.4.0` | `16.5.0` |
| [i18next](https://github.com/i18next/i18next) | `25.6.0` | `25.6.2` |
| [lint-staged](https://github.com/lint-staged/lint-staged) | `16.2.5` | `16.2.6` |
| [react-i18next](https://github.com/i18next/react-i18next) | `16.1.4` | `16.3.3` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.46.2` | `8.47.0` |
| [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) | `7.1.11` | `7.2.2` |



Updates `@eslint/js` from 9.38.0 to 9.39.1
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/commits/v9.39.1/packages/js)

Updates `@microsoft/api-extractor` from 7.53.1 to 7.55.0
- [Changelog](https://github.com/microsoft/rushstack/blob/main/apps/api-extractor/CHANGELOG.md)
- [Commits](https://github.com/microsoft/rushstack/commits/@microsoft/api-extractor_v7.55.0/apps/api-extractor)

Updates `@tailwindcss/vite` from 4.1.15 to 4.1.17
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.1.17/packages/@tailwindcss-vite)

Updates `@types/react` from 19.2.2 to 19.2.5
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react)

Updates `@types/react-dom` from 19.2.2 to 19.2.3
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom)

Updates `@vitejs/plugin-react-swc` from 4.1.0 to 4.2.2
- [Release notes](https://github.com/vitejs/vite-plugin-react/releases)
- [Changelog](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite-plugin-react/commits/plugin-react-swc@4.2.2/packages/plugin-react-swc)

Updates `eslint` from 9.38.0 to 9.39.1
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v9.38.0...v9.39.1)

Updates `eslint-plugin-react-dom` from 2.2.2 to 2.3.5
- [Release notes](https://github.com/Rel1cx/eslint-react/releases)
- [Changelog](https://github.com/Rel1cx/eslint-react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Rel1cx/eslint-react/commits/v2.3.5/packages/plugins/eslint-plugin-react-dom)

Updates `eslint-plugin-react-hooks` from 7.0.0 to 7.0.1
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/facebook/react/blob/main/packages/eslint-plugin-react-hooks/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/HEAD/packages/eslint-plugin-react-hooks)

Updates `eslint-plugin-react-x` from 2.2.2 to 2.3.5
- [Release notes](https://github.com/Rel1cx/eslint-react/releases)
- [Changelog](https://github.com/Rel1cx/eslint-react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Rel1cx/eslint-react/commits/v2.3.5/packages/plugins/eslint-plugin-react-x)

Updates `globals` from 16.4.0 to 16.5.0
- [Release notes](https://github.com/sindresorhus/globals/releases)
- [Commits](https://github.com/sindresorhus/globals/compare/v16.4.0...v16.5.0)

Updates `i18next` from 25.6.0 to 25.6.2
- [Release notes](https://github.com/i18next/i18next/releases)
- [Changelog](https://github.com/i18next/i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/i18next/compare/v25.6.0...v25.6.2)

Updates `lint-staged` from 16.2.5 to 16.2.6
- [Release notes](https://github.com/lint-staged/lint-staged/releases)
- [Changelog](https://github.com/lint-staged/lint-staged/blob/main/CHANGELOG.md)
- [Commits](https://github.com/lint-staged/lint-staged/compare/v16.2.5...v16.2.6)

Updates `react-i18next` from 16.1.4 to 16.3.3
- [Changelog](https://github.com/i18next/react-i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/react-i18next/compare/v16.1.4...v16.3.3)

Updates `tailwindcss` from 4.1.15 to 4.1.17
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.1.17/packages/tailwindcss)

Updates `typescript-eslint` from 8.46.2 to 8.47.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.47.0/packages/typescript-eslint)

Updates `vite` from 7.1.11 to 7.2.2
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v7.2.2/packages/vite)

---
updated-dependencies:
- dependency-name: "@eslint/js"
  dependency-version: 9.39.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development-dependencies
- dependency-name: "@microsoft/api-extractor"
  dependency-version: 7.55.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development-dependencies
- dependency-name: "@tailwindcss/vite"
  dependency-version: 4.1.17
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: "@types/react"
  dependency-version: 19.2.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: "@types/react-dom"
  dependency-version: 19.2.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: "@vitejs/plugin-react-swc"
  dependency-version: 4.2.2
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development-dependencies
- dependency-name: eslint
  dependency-version: 9.39.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development-dependencies
- dependency-name: eslint-plugin-react-dom
  dependency-version: 2.3.5
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development-dependencies
- dependency-name: eslint-plugin-react-hooks
  dependency-version: 7.0.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: eslint-plugin-react-x
  dependency-version: 2.3.5
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development-dependencies
- dependency-name: globals
  dependency-version: 16.5.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development-dependencies
- dependency-name: i18next
  dependency-version: 25.6.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: lint-staged
  dependency-version: 16.2.6
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: react-i18next
  dependency-version: 16.3.3
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development-dependencies
- dependency-name: tailwindcss
  dependency-version: 4.1.17
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: typescript-eslint
  dependency-version: 8.47.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development-dependencies
- dependency-name: vite
  dependency-version: 7.2.2
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-17 19:50:43 +00:00
Simon 0f697c21bd chore: update lock 2025-10-24 21:47:25 +08:00
Simon e0d6d0d51b docs: plan 2025-10-24 17:50:57 +08:00
Simon d92ba7d882 fix: update aria-label for theme switcher button 2025-10-24 17:21:39 +08:00
Simon 3e8e584065 fix: demo video cn 2025-10-24 17:06:32 +08:00
Simon 9bdd565feb fix: demo video cn 2025-10-24 17:05:32 +08:00
Simon b0a31e777b fix: demo video
Updated demo video source in README.
2025-10-24 17:03:56 +08:00
Simon 78a54d0d3d docs: 演示视频 2025-10-24 16:56:26 +08:00
Simon 7f04f3564a fix: gemini response format 2025-10-24 16:54:05 +08:00
Simon 22a25b17f5 Merge pull request #36 from gaomeng1900/main
docs update
2025-10-24 01:21:01 +08:00
Simon 8a63c1b716 feat: Enhance code highlighting 2025-10-24 00:50:51 +08:00
Simon ab710bcaf5 stye: optimize mobile header with icon navigation 2025-10-24 00:07:15 +08:00
Simon df449166d5 docs: wording 2025-10-23 21:20:48 +08:00
Simon da822c5fe1 docs: wording 2025-10-23 20:48:57 +08:00
Simon 78412323a7 docs: wording 2025-10-23 20:45:03 +08:00
Simon ad06cd1869 docs: wording 2025-10-23 20:42:27 +08:00
Simon 86682c3a1e docs: wording 2025-10-23 20:41:12 +08:00
Simon fee3bbdf85 docs: Authentication suggestion 2025-10-23 20:25:55 +08:00
Simon 4d3c878809 feat: add experimentalScriptExecutionTool 2025-10-23 19:59:17 +08:00
Simon e704398c43 feat: add Google Analytics tracking script 2025-10-23 16:16:20 +08:00
Simon 56d20904b9 docs: demo 2025-10-23 16:07:59 +08:00
Simon bdddcb860e chore: add Open Graph meta tags for better sharing 2025-10-23 15:43:12 +08:00
Simon e6e8ebf4e3 docs: wording 2025-10-23 15:37:22 +08:00
Simon 9b1131ce13 docs: wording 2025-10-23 15:32:56 +08:00
Simon 462d76dbb6 docs: update CDN links to version 0.0.4 2025-10-23 15:27:31 +08:00
Simon 3791d1e157 chore: wording 2025-10-23 15:17:05 +08:00
Simon 2e3355f52c fix: update language setting to use i18n configuration 2025-10-22 23:07:23 +08:00
Simon b85a82785f chore: bump version to 0.0.4 2025-10-22 23:02:49 +08:00
Simon 2314d28e60 fix: mobile panel styles 2025-10-22 23:02:17 +08:00
Simon 58475c63da feat: enhance mobile navigation and update i18n labels 2025-10-22 22:58:52 +08:00
Simon 55f07d971a docs: update i18n status in ROADMAP 2025-10-22 22:41:48 +08:00
Simon cd84269427 feat: i18n for website 2025-10-22 22:35:25 +08:00
Simon fedeb57f48 chore: add homepage 2025-10-22 19:04:19 +08:00
Simon 386167e99b chore: wording 2025-10-22 17:48:06 +08:00
Simon 190d264bda docs: update ROADMAP 2025-10-22 17:18:39 +08:00
Simon 7af3b580d5 docs: updating ROADMAP 2025-10-22 17:16:15 +08:00
Simon 4af927f218 fix: action 2025-10-22 16:59:10 +08:00
117 changed files with 4221 additions and 2305 deletions
+22 -28
View File
@@ -6,32 +6,11 @@ body:
- type: markdown
attributes:
value: |
Please follow our community communication guidelines when filing issues.
- Be polite and respectful.
- Prefer constructive, actionable feedback over venting.
- Include clear reproduction steps and environment details.
- Consider contributing a fix if possible — PRs are welcome.
Thanks for your interest in improving the project! Before submitting, please read our guidelines.
感谢您对改进项目的兴趣!提交前请阅读我们的指南。
提交 Issue 时请遵守社区沟通规范:
- 保持礼貌与尊重。
- 提供建设性、可行动的反馈,避免情绪化表达。
- 请包含清晰的复现步骤与环境信息。
- 如果可以,欢迎直接提交修复 PR。
See: CODE_OF_CONDUCT.md / CODE_OF_CONDUCT_zh.md, CONTRIBUTING.md
- type: checkboxes
id: community
attributes:
label: Community Communication / 社区沟通
description: Confirm you will communicate respectfully and constructively / 确认将以礼貌、建设性的方式沟通
options:
- label: I will be polite and respectful. / 我会保持礼貌与尊重。
required: true
- label: I will share constructive, actionable details. / 我会提供建设性、可行动的细节。
required: true
- label: I have read the Code of Conduct. / 我已阅读行为准则。
required: true
- [Code of Conduct](https://github.com/alibaba/page-agent/blob/main/CODE_OF_CONDUCT.md)
- [Contributing Guide](https://github.com/alibaba/page-agent/blob/main/CONTRIBUTING.md)
- type: textarea
id: description
@@ -48,7 +27,7 @@ body:
render: typescript
placeholder: Minimal reproduction code
validations:
required: true
required: false
- type: input
id: browser
@@ -56,7 +35,7 @@ body:
label: Browser
placeholder: 'Chrome 120, Firefox 119, etc.'
validations:
required: true
required: false
- type: input
id: version
@@ -64,4 +43,19 @@ body:
label: version
placeholder: '0.0.0'
validations:
required: true
required: false
- type: checkboxes
id: community
attributes:
label: Community Communication / 社区沟通
description: Confirm you will communicate respectfully and constructively / 确认将以礼貌、建设性的方式沟通
options:
- label: I will be polite and respectful. / 我会保持礼貌与尊重。
required: true
- label: I will share constructive, actionable suggestions. / 我会提供建设性、可行动的建议。
required: true
- label: I have read the Code of Conduct. / 我已阅读行为准则。
required: true
validations:
required: true
-3
View File
@@ -12,6 +12,3 @@ contact_links:
- name: Code of Conduct / 行为准则
url: https://github.com/alibaba/page-agent/blob/main/CODE_OF_CONDUCT.md
about: Community expectations and standards. 社区行为期望与标准。
- name: 行为准则(中文)
url: https://github.com/alibaba/page-agent/blob/main/CODE_OF_CONDUCT_zh.md
about: 中文版社区行为准则。
+23 -34
View File
@@ -6,19 +6,30 @@ body:
- type: markdown
attributes:
value: |
Please follow our community communication guidelines when proposing features.
- Be polite and respectful.
- Explain the problem first, then propose solutions.
- Prefer constructive, actionable suggestions.
- Contributions are encouraged — consider opening a PR.
Thanks for your interest in improving the project! Before submitting, please read our guidelines.
感谢您对改进项目的兴趣!提交前请阅读我们的指南。
提交功能建议时请遵守社区沟通规范:
- 保持礼貌与尊重。
- 先阐述问题,再提出解决方案。
- 提供建设性、可行动的建议。
- 鼓励代码贡献,欢迎直接提交 PR。
- [Code of Conduct](https://github.com/alibaba/page-agent/blob/main/CODE_OF_CONDUCT.md)
- [Contributing Guide](https://github.com/alibaba/page-agent/blob/main/CONTRIBUTING.md)
See: CODE_OF_CONDUCT.md / CODE_OF_CONDUCT_zh.md, CONTRIBUTING.md
- type: textarea
id: description
attributes:
label: Feature Description / 功能描述
description: Describe the problem, solution, and any API changes. / 描述问题、解决方案以及 API 变更。
placeholder: |
**Problem**:
What problem does this solve?
**Solution**:
How should this work?
**Proposed API**:
```typescript
// code here
```
validations:
required: true
- type: checkboxes
id: community
@@ -32,27 +43,5 @@ body:
required: true
- label: I have read the CODE_OF_CONDUCT.md and CONTRIBUTING.md. / 我已阅读行为准则。
required: true
- type: textarea
id: problem
attributes:
label: Problem
placeholder: What problem does this solve?
validations:
required: true
- type: textarea
id: solution
attributes:
label: Solution
placeholder: How should this work?
validations:
required: true
- type: textarea
id: api
attributes:
label: Proposed API
render: typescript
validations:
required: false
required: true
+5 -8
View File
@@ -5,9 +5,12 @@ Brief description of changes.
## Type
- [ ] Bug fix
- [ ] New feature
- [ ] Breaking change
- [ ] Feature / Improvement
- [ ] Refactor
- [ ] Documentation
- [ ] Website
- [ ] Demo / Testing
- [ ] Breaking change
## Testing
@@ -19,10 +22,4 @@ Closes #(issue)
## Requirements / 要求
- [ ] I will be polite and respectful. / 我会保持礼貌与尊重。
- [ ] My comments and replies are constructive and actionable. / 我的评论与回复具有建设性。
- [ ] I have read and follow the [Code of Conduct](CODE_OF_CONDUCT.md) and [Contributing Guide](CONTRIBUTING.md) . / 我已阅读并遵守行为准则。
## Contributing / 贡献
Constructive suggestions and code contributions are encouraged. If this PR originated from a discussion or issue, please link it above. 欢迎建设性意见与代码贡献;如源自讨论或 Issue,请在上方关联链接。
+1 -1
View File
@@ -33,7 +33,7 @@ jobs:
- name: Upload artifact
uses: actions/upload-pages-artifact@v4
with:
path: './dist'
path: './packages/website/dist'
- name: Deploy to GitHub Pages
id: deployment
+5 -2
View File
@@ -19,9 +19,12 @@ jobs:
uses: actions/setup-node@v6
with:
node-version: 20
cache: 'npm'
registry-url: 'https://registry.npmjs.org'
# Ensure npm 11.5.1 or later is installed
- name: Update npm
run: npm install -g npm@latest
- name: Install dependencies
run: npm ci
@@ -29,4 +32,4 @@ jobs:
run: npm run build:lib
- name: Publish to npm
run: npm publish
run: npm publish --workspace=page-agent
+6 -1
View File
@@ -26,4 +26,9 @@ dist-ssr
#
.env
.env
# Copied files for npm publish (generated by prepublishOnly)
packages/page-agent/README.md
packages/page-agent/README-zh.md
packages/page-agent/LICENSE
+1 -1
View File
@@ -1,6 +1,6 @@
{
"editor.fontLigatures": true,
"cSpell.words": ["HITL", "opensource", "retryable"],
"cSpell.words": ["HITL", "innerhtml", "opensource", "retryable", "wouter"],
"markdownlint.config": {
// "comment": "Relaxed rules",
"default": true,
+53 -32
View File
@@ -2,36 +2,56 @@
## Project Overview
This is a dual-architecture project with **two separate parts**:
This is a **monorepo** with npm workspaces containing **two main packages**:
1. **Core Library** (`src/`) - Pure JavaScript/TypeScript AI agent library for browser DOM automation
2. **Demo&docs Website** (`pages/`) - React documentation and landing page
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`
## Development Commands
### Core Commands
```bash
npm start # Start React website development server
npm run build # Build both library AND website
npm run build:lib # Build pure JS library only (src/ → dist/lib/)
npm run build:lib:watch # Library development with auto-rebuild
npm start # Start website dev server
npm run dev # Same as start
npm run build # Build all packages
npm run build:lib # Build page-agent library only
npm run lint # ESLint with TypeScript strict rules
```
### 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
### Dual Build System
### Monorepo Structure
- **Website build**: `vite.config.js` → React SPA with hash routing → `dist/`
- **Library build**: `vite.lib.config.js` → UMD/ES modules → `dist/lib/`
- **Entry points**: `src/entry.ts` (library), `pages/main.tsx` (website)
```
packages/
├── page-agent/ # npm: "page-agent"
│ ├── src/ # Core library source
│ ├── vite.config.js # Library build (ES + UMD)
│ └── package.json
└── website/ # npm: "@page-agent/website" (private)
├── src/ # Website source (formerly pages/)
├── index.html
├── vite.config.js # Website build
└── package.json
```
### Module Boundaries (Critical)
- **Core library** (`src/`): NEVER import from `pages/` - must remain pure JavaScript
- **Website** (`pages/`): CAN import from `src/` via `@/` alias for demos
- **Import aliases**: `@/``src/`, `@pages/``pages/`
- **Core library** (`packages/page-agent/`): NEVER import from website - must remain pure JavaScript
- **Website** (`packages/website/`): CAN import from `page-agent` for demos. Alias `@/` `website/src/`
### DOM Pipeline
@@ -73,7 +93,7 @@ Query params configure `PageAgentConfig` automatically in `src/entry.ts`.
## File Organization
### Core Library (`src/`)
### Core Library (`packages/page-agent/src/`)
- `entry.ts` - CDN/UMD entry point with auto-initialization
- `PageAgent.ts` - **Main AI agent class** orchestrating DOM operations
@@ -85,7 +105,7 @@ Query params configure `PageAgentConfig` automatically in `src/entry.ts`.
- `dom/` - HTML serialization and page analysis utilities
- `config/` - Configuration constants and settings
### Website (`pages/`)
### Website (`packages/website/src/`)
- `main.tsx` - Site entry with hash routing setup
- `router.tsx` - **Manual route definitions** (requires explicit registration)
@@ -97,21 +117,20 @@ Query params configure `PageAgentConfig` automatically in `src/entry.ts`.
### New Documentation Page
1. Create `pages/docs/<section>/<slug>/page.tsx`
2. Add route to `pages/router.tsx` with `<Header /> + <DocsLayout>` wrapper
1. Create `packages/website/src/docs/<section>/<slug>/page.tsx`
2. Add route to `packages/website/src/router.tsx` with `<Header /> + <DocsLayout>` wrapper
3. Add navigation item to `DocsLayout.tsx`
### New Agent Tool
1. Implement under `src/tools/`
2. Export via `src/tools/index.ts`
1. Implement under `packages/page-agent/src/tools/`
2. Export via `packages/page-agent/src/tools/index.ts`
3. Wire into `PageAgent.ts` if needed
### New UI Component
1. Create in `src/ui/` with colocated CSS modules
1. Create in `packages/page-agent/src/ui/` with colocated CSS modules
2. Use event bus for PageAgent communication
3. Test via `pages/test-pages/`
## Code Standards
@@ -136,26 +155,28 @@ Query params configure `PageAgentConfig` automatically in `src/entry.ts`.
## Critical Files to Understand
- `pages/router.tsx` - Central routing definition (manual registration required)
- `pages/components/DocsLayout.tsx` - Navigation structure
- `src/PageAgent.ts` - Core AI agent class with DOM manipulation
- `src/dom/dom_tree/index.js` - DOM extraction engine
- `src/utils/bus.ts` - Type-safe event bus system
- `src/entry.ts` - Library entry point for CDN usage
- `vite.config.js` / `vite.lib.config.js` - Dual build configuration
- `packages/page-agent/src/PageAgent.ts` - Core AI agent class with DOM manipulation
- `packages/page-agent/src/dom/dom_tree/index.js` - DOM extraction engine
- `packages/page-agent/src/utils/bus.ts` - Type-safe event bus system
- `packages/page-agent/src/entry.ts` - Library entry point for CDN usage
- `packages/page-agent/vite.config.js` - Library build configuration
- `packages/website/src/router.tsx` - Central routing definition (manual registration required)
- `packages/website/src/components/DocsLayout.tsx` - Navigation structure
- `packages/website/vite.config.js` - Website build configuration
## Debugging Common Issues
### Blank Documentation Pages
1. Verify route exists in `pages/router.tsx`
1. Verify route exists in `packages/website/src/router.tsx`
2. Check component import path
3. Verify CSS isn't hiding content (check dark mode classes)
4. Test with minimal component first
### Library Integration Issues
1. Check `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
3. Verify event bus communications are properly typed
4. Use `pages/test-pages/` for isolated testing
4. Use `packages/website/src/test-pages/` for isolated testing
+5 -3
View File
@@ -22,10 +22,12 @@ Thank you for your interest in contributing to Page-Agent! We welcome contributi
### Project Structure
This project has **two separate parts**:
This is a **monorepo** with npm workspaces containing **two main packages**:
- **Core Library** (`src/`) - Pure JavaScript AI agent library
- **Documentation Website** (`pages/`) - React web app for landing page and docs
1. **Core Library** (`packages/page-agent/`) - Pure JavaScript/TypeScript AI agent library for browser DOM automation, published as `page-agent` on npm
2. **Website** (`packages/website/`) - React documentation and landing page. Also as demo and test page for the core lib. private package `@page-agent/website`
We use a simplified monorepo solution with native npm-workspace. No fancy tooling. Hoisting is required.
## 🤝 How to Contribute
+5 -5
View File
@@ -4,13 +4,13 @@
[![npm version](https://badge.fury.io/js/page-agent.svg)](https://badge.fury.io/js/page-agent) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![TypeScript](https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg)](http://www.typescriptlang.org/) [![Downloads](https://img.shields.io/npm/dt/page-agent.svg)](https://www.npmjs.com/package/page-agent) [![Bundle Size](https://img.shields.io/bundlephobia/minzip/page-agent)](https://bundlephobia.com/package/page-agent) [![GitHub stars](https://img.shields.io/github/stars/alibaba/page-agent.svg)](https://github.com/alibaba/page-agent)
**让你的网页支持 AI 自动化。**
运行在页面内的 UI agent. 使用自然语言操作 Web 应用。
纯 JS 实现的 GUI agent。使用自然语言操作你的 Web 应用。无须后端、客户端、浏览器插件。
🌐 [English](./README.md) | **中文**
👉 [🚀 **Demo**](https://alibaba.github.io/page-agent/) | [📖 **Documentation**](https://alibaba.github.io/page-agent/#/docs/introduction/overview)
👉 <a href="https://alibaba.github.io/page-agent/" target="_blank"><b>🚀 Demo</b></a> | <a href="https://alibaba.github.io/page-agent/#/docs/introduction/overview" target="_blank"><b>📖 Documentation</b></a>
<video id="demo-video" src="https://github.com/user-attachments/assets/141bbb01-8022-4d1f-919d-9efc9a1dc1cf" width="640" crossorigin muted autoplay loop></video>
---
@@ -33,7 +33,7 @@
```html
<!-- 临时 CDN URL. 未来会变更 -->
<script
src="https://hwcxiuzfylggtcktqgij.supabase.co/storage/v1/object/public/demo-public/v0.0.2/page-agent.js"
src="https://hwcxiuzfylggtcktqgij.supabase.co/storage/v1/object/public/demo-public/v0.0.4/page-agent.js"
crossorigin="true"
type="text/javascript"
></script>
+6 -6
View File
@@ -4,19 +4,19 @@
[![npm version](https://badge.fury.io/js/page-agent.svg)](https://badge.fury.io/js/page-agent) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![TypeScript](https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg)](http://www.typescriptlang.org/) [![Downloads](https://img.shields.io/npm/dt/page-agent.svg)](https://www.npmjs.com/package/page-agent) [![Bundle Size](https://img.shields.io/bundlephobia/minzip/page-agent)](https://bundlephobia.com/package/page-agent) [![GitHub stars](https://img.shields.io/github/stars/alibaba/page-agent.svg)](https://github.com/alibaba/page-agent)
**Transform your webpage into an AI-powered application with a single script tag.**
An in-page UI agent in javascript. Control web interfaces with natural language.
The GUI Agent Living in Your Webpage. Control web interfaces with natural language.
🌐 **English** | [中文](./README-zh.md)
👉 [🚀 **Demo**](https://alibaba.github.io/page-agent/) | [📖 **Documentation**](https://alibaba.github.io/page-agent/#/docs/introduction/overview)
👉 <a href="https://alibaba.github.io/page-agent/" target="_blank"><b>🚀 Demo</b></a> | <a href="https://alibaba.github.io/page-agent/#/docs/introduction/overview" target="_blank"><b>📖 Documentation</b></a>
<video id="demo-video" src="https://github.com/user-attachments/assets/de8d1964-8bde-494f-a52f-2975469557a5" width="640" crossorigin muted autoplay loop></video>
---
## ✨ Features
- **🎯 Easy Integration**
- **🎯 Easy Integration** - Transform your webpage into an agent with a single script tag.
- **🔐 Client-Side Processing**
- **🧠 DOM Extraction**
- **💬 Natural Language Interface**
@@ -33,7 +33,7 @@ An in-page UI agent in javascript. Control web interfaces with natural language.
```html
<!-- temporary CDN URL. May change in the future -->
<script
src="https://hwcxiuzfylggtcktqgij.supabase.co/storage/v1/object/public/demo-public/v0.0.2/page-agent.js"
src="https://hwcxiuzfylggtcktqgij.supabase.co/storage/v1/object/public/demo-public/v0.0.4/page-agent.js"
crossorigin="true"
type="text/javascript"
></script>
+28 -9
View File
@@ -4,32 +4,49 @@ The development progress and future plans for PageAgent.
## 🚀 Current Works
- [x] **MVP** - Core functionality implemented
- [x] **SPA interaction** - Single Page Application support
- [x] **MVP**
- Core functionality implemented.
- [x] **SPA interaction**
- [x] **Reasoning and (short) memory**
- [x] **Multi model provider integration and testing**
- [x] **UI with HITL** - Human-in-the-loop user interface
- [x] **UI with HITL**
- Human-in-the-loop user interface. Agent can ask user questions.
- [x] **Landing and doc pages**
- [x] **Remove ai-sdk** - Only one function is being used
- [x] **Remove `ai-sdk`**
- Only one function of AI-ADK is being used.
- Our agent memory and thinking mechanism does not suite ai-sdk.
- [x] **Robust LLM output**
- Auto-fix incomplete output format of DeepSeek and QWen.
- [x] **Working homepage with live LLM API**
- [x] **~~free~~ CDN**
- [x] **Free evaluation plan?**
- [x] **Free evaluation plan**
- [x] **Custom actions and HITL**
- [ ] **Hooks and Events**
- [x] **lifecycle hooks**
- [ ] **lifecycle events**
- [ ] **❗Pause and intervene**
- [ ] **❗Pause and intervene**
- Current pause does not allow user intervention.
- [ ] **❗Hijack `page_open/page_change/page_unload` behavior**
- [ ] **Custom knowledge base and instructions**
- [ ] **Black/white-list safeguard**
- [ ] **Data-masking**
- [ ] **Improve Memory**
- Current phrasing can cause logic-loop for some models.
- Test adding `Action` to memory.
- [ ] **Tools for more complex tasks**
- todo list
- file sys
- [ ] **Optimize for popular UI frameworks**
- [x] **i18n of the website**
- [x] Chinese version
- [x] English version
- [ ] **Testing suits**
- [ ] **Support custom llm fetch**
- [ ] **Refactor: Separate Agent and Page-Controller** - Agent should run w/o dom
- [ ] **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
@@ -39,4 +56,6 @@ The development progress and future plans for PageAgent.
## 🤔 To Be Decided
- [ ] **Cross-origin multi-page?** - Tricky
- [ ] **Cross-origin multi-page?**
- Tricky
- Need some kind of "memory rally"
+2 -2
View File
@@ -8,7 +8,7 @@ import globals from 'globals'
import tseslint from 'typescript-eslint'
export default defineConfig([
globalIgnores(['dist', 'test-pages']),
globalIgnores(['**/dist', '**/test-pages', '**/node_modules']),
{
plugins: {
'react-hooks': reactHooks,
@@ -37,7 +37,7 @@ export default defineConfig([
],
languageOptions: {
parserOptions: {
project: ['./tsconfig.json'],
project: ['./packages/*/tsconfig.json'],
tsconfigRootDir: import.meta.dirname,
},
ecmaVersion: 2020,
-32
View File
@@ -1,32 +0,0 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<link
rel="icon"
type="image/svg+xml"
href="https://img.alicdn.com/imgextra/i3/O1CN01uo9VaC20mamWNM2Zn_!!6000000006892-55-tps-64-64.svg"
/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>PageAgent - 网页内的交互助手</title>
<meta
name="description"
content="PageAgent.js:一行 CDN 引入,为任何网站添加智能 UI Agent。现代网页 AI 自动化,极简集成,开发者专用。"
/>
<meta
name="keywords"
content="PageAgent, AI Agent, Web Automation, UI 自动化, 前端, CDN, JavaScript, React, Vite"
/>
<meta name="theme-color" content="#58c0fc" />
<meta name="color-scheme" content="light dark" />
<meta name="author" content="PageAgent.js Team" />
<meta property="og:title" content="PageAgent.js" />
<meta property="og:description" content="一行 CDN 引入,为任何网站添加智能 UI Agent。" />
<meta property="og:type" content="website" />
<meta property="og:locale" content="zh_CN" />
</head>
<body>
<div id="root"></div>
<script type="module" src="./pages/main.tsx"></script>
</body>
</html>
+937 -940
View File
File diff suppressed because it is too large Load Diff
+19 -57
View File
@@ -1,89 +1,51 @@
{
"name": "page-agent",
"private": false,
"version": "0.0.3",
"name": "root",
"private": true,
"version": "0.0.6",
"type": "module",
"main": "./dist/lib/page-agent.js",
"module": "./dist/lib/page-agent.js",
"types": "./dist/lib/PageAgent.d.ts",
"exports": {
".": {
"types": "./dist/lib/PageAgent.d.ts",
"import": "./dist/lib/page-agent.js",
"default": "./dist/lib/page-agent.js"
}
},
"files": [
"dist/lib/",
"README.md",
"LICENSE",
"NOTICE"
],
"description": "AI-powered UI agent for web applications - add intelligent automation to any webpage with a single script tag",
"keywords": [
"ai",
"automation",
"ui-agent",
"browser-automation",
"web-agent",
"llm",
"dom-interaction",
"intelligent-ui"
"workspaces": [
"packages/*"
],
"description": "AI-powered UI agent for web applications",
"author": "Simon<gaomeng1900>",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/alibaba/page-agent.git"
},
"homepage": "https://alibaba.github.io/page-agent/",
"engines": {
"node": ">=20.0.0"
"node": ">=20.0.0",
"npm": ">=10.0.0"
},
"scripts": {
"dev": "vite",
"start": "vite",
"build": "tsc -b && vite build && npm run build:lib && npm run build:umd",
"build:lib": "MODE=lib vite build",
"build:lib:watch": "MODE=lib vite build --watch",
"build:umd": "MODE=umd vite build",
"dev": "npm run dev --workspace=@page-agent/website",
"start": "npm run dev --workspace=@page-agent/website",
"build": "npm run build --workspaces --if-present",
"build:lib": "npm run build --workspace=page-agent",
"version": "node scripts/sync-version.js",
"lint": "eslint .",
"prepare": "husky"
},
"dependencies": {
"ai-motion": "^0.4.7",
"chalk": "^5.6.2",
"zod": "^4.1.12"
},
"devDependencies": {
"@commitlint/cli": "^20.1.0",
"@commitlint/config-conventional": "^20.0.0",
"@eslint/js": "^9.37.0",
"@microsoft/api-extractor": "^7.53.1",
"@tailwindcss/vite": "^4.1.14",
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
"@types/react": "^19.2.2",
"@types/react-dom": "^19.2.1",
"@vitejs/plugin-react-swc": "^4.1.0",
"dotenv": "^17.2.3",
"eslint": "^9.37.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-react-dom": "^2.0.6",
"eslint-plugin-react-hooks": "^7.0.0",
"eslint-plugin-react-refresh": "^0.4.23",
"eslint-plugin-react-x": "^2.0.6",
"eslint-plugin-react-dom": "^2.3.9",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.4.24",
"eslint-plugin-react-x": "^2.3.9",
"globals": "^16.4.0",
"husky": "^9.1.7",
"lint-staged": "^16.2.4",
"prettier": "^3.6.2",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"tailwindcss": "^4.1.14",
"typescript": "^5.9.3",
"typescript-eslint": "^8.46.0",
"unplugin-dts": "^1.0.0-beta.6",
"vite": "^7.1.9",
"vite-plugin-css-injected-by-js": "^3.5.2",
"wouter": "^3.7.1"
"vite": "^7.1.9"
},
"lint-staged": {
"*.{js,ts,cjs,cts,mjs,mts}": [
-5
View File
@@ -11,15 +11,10 @@ declare module '*.md?raw' {
export default content
}
/**
* for local dev and umd demo
*/
declare global {
interface Window {
pageAgent?: PageAgent
PageAgent: typeof PageAgent
__PAGE_AGENT_IDS__: string[]
}
}
+59
View File
@@ -0,0 +1,59 @@
{
"name": "page-agent",
"private": false,
"version": "0.0.6",
"type": "module",
"main": "./dist/lib/page-agent.js",
"module": "./dist/lib/page-agent.js",
"types": "./dist/lib/PageAgent.d.ts",
"exports": {
".": {
"types": "./dist/lib/PageAgent.d.ts",
"import": "./dist/lib/page-agent.js",
"default": "./dist/lib/page-agent.js"
}
},
"files": [
"dist/",
"README.md",
"LICENSE"
],
"description": "GUI agent for web applications - add intelligent automation to any webpage with a single script",
"keywords": [
"ai",
"automation",
"ui-agent",
"GUI-agent",
"browser-automation",
"web-agent",
"llm",
"dom-interaction",
"web-automation",
"GUI-simulation"
],
"author": "Simon<gaomeng1900>",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/alibaba/page-agent.git"
},
"homepage": "https://alibaba.github.io/page-agent/",
"scripts": {
"build": "MODE=lib vite build && MODE=umd vite build",
"build:lib": "MODE=lib vite build",
"build:umd": "MODE=umd vite build",
"build:watch": "MODE=lib vite build --watch",
"prepublishOnly": "node -e \"const fs=require('fs');['README.md','LICENSE'].forEach(f=>fs.copyFileSync('../../'+f,f))\"",
"postpublish": "node -e \"['README.md','LICENSE'].forEach(f=>{try{require('fs').unlinkSync(f)}catch{}})\""
},
"dependencies": {
"ai-motion": "^0.4.7",
"chalk": "^5.6.2",
"zod": "^4.1.12"
},
"devDependencies": {
"@microsoft/api-extractor": "^7.55.1",
"unplugin-dts": "^1.0.0-beta.6",
"vite-plugin-css-injected-by-js": "^3.5.2"
}
}
@@ -125,6 +125,10 @@ export class PageAgent extends EventTarget {
}
}
if (!this.config.experimentalScriptExecutionTool) {
this.tools.delete('execute_javascript')
}
patchReact(this)
window.addEventListener('beforeunload', (e) => {
@@ -1,8 +1,7 @@
import type { AgentHistory, ExecutionResult, PageAgent } from '@/PageAgent'
import type { DomConfig } from '@/dom'
import type { SupportedLanguage } from '@/i18n'
import type { PageAgentTool } from '@/tools'
import type { AgentHistory, ExecutionResult, PageAgent } from '../PageAgent'
import type { DomConfig } from '../dom'
import type { SupportedLanguage } from '../i18n'
import type { PageAgentTool } from '../tools'
import {
DEFAULT_API_KEY,
DEFAULT_BASE_URL,
@@ -72,6 +71,14 @@ export interface AgentConfig {
// page behavior hooks
/**
* @experimental
* Enable the experimental script execution tool that allows executing generated JavaScript code on the page.
* @note Can cause unpredictable side effects.
* @note May bypass some safe guards and data-masking mechanisms.
*/
experimentalScriptExecutionTool?: boolean
/**
* TODO: @unimplemented
* hook when action causes a new page to be opened
@@ -1,11 +1,11 @@
import { VIEWPORT_EXPANSION } from '@/config/constants'
import domTree from '@/dom/dom_tree/index'
import { VIEWPORT_EXPANSION } from '../config/constants'
import domTree from './dom_tree/index'
import {
ElementDomNode,
FlatDomTree,
InteractiveElementDomNode,
TextDomNode,
} from '@/dom/dom_tree/type'
} from './dom_tree/type'
export interface DomConfig {
interactiveBlacklist?: (Element | (() => Element))[]
@@ -1,8 +1,7 @@
/**
* OpenAI Client implementation
*/
import type { MacroToolInput } from '@/PageAgent'
import type { MacroToolInput } from '../PageAgent'
import { InvokeError, InvokeErrorType } from './errors'
import type { InvokeResult, LLMClient, Message, OpenAIClientConfig, Tool } from './types'
import { lenientParseMacroToolCall, modelPatch, zodToOpenAITool } from './utils'
@@ -31,10 +31,9 @@
* - 使 tool call
* - tool 使 tool call
*/
import type { LLMConfig } from '@/config'
import { parseLLMConfig } from '@/config'
import { EventBus, getEventBus } from '@/utils/bus'
import type { LLMConfig } from '../config'
import { parseLLMConfig } from '../config'
import { EventBus, getEventBus } from '../utils/bus'
import { OpenAIClient } from './OpenAILenientClient'
import { InvokeError } from './errors'
import type { InvokeResult, LLMClient, Message, Tool } from './types'
@@ -4,8 +4,7 @@
import chalk from 'chalk'
import { z } from 'zod'
import type { MacroToolInput } from '@/PageAgent'
import type { MacroToolInput } from '../PageAgent'
import { InvokeError, InvokeErrorType } from './errors'
import type { Tool } from './types'
@@ -42,6 +41,7 @@ export function lenientParseMacroToolCall(
// check
switch (choice.finish_reason) {
case 'tool_calls':
case 'function_call': // gemini
case 'stop': // will try a robust parse
// ✅ Normal
break
@@ -1,4 +1,4 @@
import type { PageAgent } from '@/PageAgent'
import type { PageAgent } from '../PageAgent'
const clearFunctions = [] as (() => void)[]
@@ -1,4 +1,4 @@
import type { PageAgent } from '@/PageAgent'
import type { PageAgent } from '../PageAgent'
// Find common React root elements and add data-page-agent-not-interactive attribute
export function patchReact(pageAgent: PageAgent) {
@@ -4,8 +4,7 @@
*/
import zod, { type z } from 'zod'
import type { PageAgent } from '@/PageAgent'
import type { PageAgent } from '../PageAgent'
import {
clickElement,
getElementByIndex,
@@ -215,6 +214,27 @@ tools.set(
})
)
tools.set(
'execute_javascript',
tool({
description:
'Execute JavaScript code on the current page. Supports async/await syntax. Use with caution!',
inputSchema: zod.object({
script: zod.string(),
}),
execute: async function (this: PageAgent, input) {
try {
// Wrap script in async function to support await
const asyncFunction = eval(`(async () => { ${input.script} })`)
const result = await asyncFunction()
return `✅ Executed JavaScript. Result: ${result}` + (await getSystemInfo())
} catch (error) {
return `❌ Error executing JavaScript: ${error}` + (await getSystemInfo())
}
},
})
)
// @todo get_dropdown_options
// @todo select_dropdown_option
// @todo send_keys
@@ -33,8 +33,7 @@
/* 响应式设计 */
@media (max-width: 480px) {
width: calc(100vw - 40px);
left: 20px;
transform: none;
--width: calc(100vw - 40px);
}
.background {
@@ -1,8 +1,7 @@
import type { PageAgent } from '@/PageAgent'
import type { I18n } from '@/i18n'
import { truncate } from '@/utils'
import type { EventBus } from '@/utils/bus'
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 styles from './Panel.module.css'
@@ -1,6 +1,6 @@
import { Motion } from 'ai-motion'
import { isPageDark } from '@/utils/checkDarkMode'
import { isPageDark } from '../utils/checkDarkMode'
import styles from './SimulatorMask.module.css'
import cursorStyles from './cursor.module.css'
@@ -1,7 +1,7 @@
/**
* Type-safe event bus for decoupling PageAgent and Panel
*/
import type { Step } from '@/ui/UIState'
import type { Step } from '../ui/UIState'
/**
* Event mapping definitions
@@ -69,10 +69,7 @@ class EventBus extends EventTarget {
/**
* Listen to built-in events
*/
on<T extends keyof PageAgentEventMap>(
event: T,
handler: EventHandler<T & keyof PageAgentEventMap>
): void {
on<T extends keyof PageAgentEventMap>(event: T, handler: EventHandler<T>): void {
const wrappedHandler = (e: Event) => {
const customEvent = e as CustomEvent
const params = customEvent.detail?.[0]
@@ -84,10 +81,7 @@ class EventBus extends EventTarget {
/**
* Listen to built-in events (one-time)
*/
once<T extends keyof PageAgentEventMap>(
event: T,
handler: EventHandler<T & keyof PageAgentEventMap>
): void {
once<T extends keyof PageAgentEventMap>(event: T, handler: EventHandler<T>): void {
const wrappedHandler = (e: Event) => {
const customEvent = e as CustomEvent
const params = customEvent.detail?.[0]
+10
View File
@@ -0,0 +1,10 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"composite": true,
"noEmit": false,
"outDir": "./dist",
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo"
},
"include": ["src", "env.d.ts"]
}
@@ -1,9 +1,4 @@
// @ts-check
// ============================================================================
// Export Configuration Based on MODE Environment Variable
// ============================================================================
import tailwindcss from '@tailwindcss/vite'
import react from '@vitejs/plugin-react-swc'
import chalk from 'chalk'
import 'dotenv/config'
import process from 'node:process'
@@ -13,44 +8,18 @@ import { fileURLToPath } from 'url'
import { defineConfig } from 'vite'
import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
// Website Config (React Documentation Site)
/** @type {import('vite').UserConfig} */
const websiteConfig = {
// https://vite.dev/config/
base: './',
plugins: [react(), tailwindcss()],
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
'@pages': resolve(__dirname, 'pages'),
},
},
define: {
'import.meta.env.LLM_MODEL_NAME': JSON.stringify(process.env.LLM_MODEL_NAME),
'import.meta.env.LLM_API_KEY': JSON.stringify(process.env.LLM_API_KEY),
'import.meta.env.LLM_BASE_URL': JSON.stringify(process.env.LLM_BASE_URL),
},
}
const __dirname = dirname(fileURLToPath(import.meta.url))
// ============================================================================
// Library Config (ES Module for NPM Package)
// ============================================================================
/** @type {import('vite').UserConfig} */
const libConfig = {
// Library build configuration
clearScreen: false,
plugins: [
dts({ tsconfigPath: './tsconfig.json', bundleTypes: true }),
cssInjectedByJsPlugin({ relativeCSSInjection: true }),
],
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
},
},
publicDir: false,
esbuild: {
keepNames: true,
@@ -66,7 +35,6 @@ const libConfig = {
rollupOptions: {
external: ['ai', 'ai-motion', 'chalk', 'zod'],
},
// minify: 'terser',
minify: false,
sourcemap: true,
cssCodeSplit: true,
@@ -81,13 +49,7 @@ const libConfig = {
// ============================================================================
/** @type {import('vite').UserConfig} */
const umdConfig = {
// Library build configuration
plugins: [cssInjectedByJsPlugin({ relativeCSSInjection: true })],
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
},
},
publicDir: false,
esbuild: {
keepNames: true,
@@ -109,19 +71,15 @@ const umdConfig = {
// ============================================================================
// ============================================================================
const MODE = process.env.MODE
console.log(chalk.cyan(`📦 Build mode: ${chalk.bold(MODE || 'website')}`))
console.log(chalk.cyan(`📦 Build mode: ${chalk.bold(MODE || 'lib')}`))
let config
if (MODE === 'lib') {
config = libConfig
} else if (MODE === 'umd') {
if (MODE === 'umd') {
config = umdConfig
} else {
config = websiteConfig
config = libConfig
}
export default defineConfig(config)
+1
View File
@@ -0,0 +1 @@
# Landing Page & Docs
+6
View File
@@ -0,0 +1,6 @@
/// <reference types="vite/client" />
declare module '*.module.css' {
const classes: Record<string, string>
export default classes
}
+60
View File
@@ -0,0 +1,60 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<link
rel="icon"
type="image/svg+xml"
href="https://img.alicdn.com/imgextra/i2/O1CN012eGDRI1X6nnMt9clU_!!6000000002875-49-tps-64-64.webp"
/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>PageAgent - The GUI Agent Living in Your Webpage</title>
<meta
name="description"
content="PageAgent.js: Intelligent GUI Agent for any website. Modern web AI automation with minimal integration."
/>
<meta
name="keywords"
content="PageAgent, AI Agent, GUI Agent, Web Automation, GUI Automation, Frontend, CDN, JavaScript, React, Vite, LLM"
/>
<meta
property="og:image"
content="https://img.alicdn.com/imgextra/i3/O1CN01JPT4Fj1FJTfmHfNxO_!!6000000000466-49-tps-512-512.webp"
/>
<meta property="og:url" content="https://alibaba.github.io/page-agent" />
<meta property="og:type" content="website" />
<meta name="theme-color" content="#58c0fc" />
<meta name="color-scheme" content="light dark" />
<meta name="author" content="PageAgent.js Team" />
<meta property="og:title" content="PageAgent.js - AI-powered GUI Agent" />
<meta property="og:description" content="The GUI Agent living in your website." />
<meta property="og:type" content="website" />
<meta property="og:locale" content="en_US" />
<meta property="og:locale:alternate" content="zh_CN" />
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-HCGRJTN3HM"></script>
<script>
window.dataLayer = window.dataLayer || []
function gtag() {
dataLayer.push(arguments)
}
gtag('js', new Date())
gtag('config', 'G-HCGRJTN3HM')
</script>
</head>
<body>
<div id="root"></div>
<script type="module" src="./src/main.tsx"></script>
<script>
// Dynamically update html lang attribute based on i18n detection
const updateHtmlLang = () => {
const lang = localStorage.getItem('i18nextLng') || navigator.language || 'zh-CN'
document.documentElement.lang = lang
}
updateHtmlLang()
window.addEventListener('storage', updateHtmlLang)
</script>
</body>
</html>
+28
View File
@@ -0,0 +1,28 @@
{
"name": "@page-agent/website",
"private": true,
"version": "0.0.6",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"page-agent": "*"
},
"devDependencies": {
"@tailwindcss/vite": "^4.1.14",
"@types/react": "^19.2.2",
"@types/react-dom": "^19.2.1",
"@vitejs/plugin-react-swc": "^4.1.0",
"i18next": "^25.6.0",
"i18next-browser-languagedetector": "^8.2.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-i18next": "^16.1.4",
"tailwindcss": "^4.1.14",
"wouter": "^3.7.1"
}
}
@@ -1,17 +1,19 @@
import { useTranslation } from 'react-i18next'
export default function BetaNotice() {
const { t } = useTranslation('common')
return (
<div className="bg-orange-50 dark:bg-orange-900/20 border border-orange-200 dark:border-orange-800 rounded-lg p-4 mb-8">
<div className="flex items-start">
<div className="flex-shrink-0">
<div className="shrink-0">
<span className="text-xl">🚧</span>
</div>
<div className="ml-3">
<h3 className="text-sm font-medium text-orange-800 dark:text-orange-200 mb-1">
Beta
{t('beta_notice.title')}
</h3>
<p className="text-sm text-orange-700 dark:text-orange-300">
</p>
<p className="text-sm text-orange-700 dark:text-orange-300">{t('beta_notice.content')}</p>
</div>
</div>
</div>
@@ -28,7 +28,7 @@ const CodeEditor: React.FC<CodeEditorProps> = ({
// 使用 Tailwind 的 dark: 前缀实现自动主题切换
const containerClasses =
'bg-gradient-to-br from-gray-50 to-gray-100 dark:from-gray-900 dark:to-gray-800 border-gray-300 dark:border-gray-700'
'bg-linear-to-br from-gray-50 to-gray-100 dark:from-gray-900 dark:to-gray-800 border-gray-300 dark:border-gray-700'
const headerClasses = 'bg-gray-100 dark:bg-gray-800 border-gray-300 dark:border-gray-700'
const headerTextClasses = 'text-gray-700 dark:text-gray-300'
const languageTextClasses = 'text-gray-600 dark:text-gray-400'
@@ -72,7 +72,7 @@ const CodeEditor: React.FC<CodeEditorProps> = ({
<div className="flex">
{/* 行号 */}
{showLineNumbers && (
<div className={`flex-shrink-0 px-4 py-4 ${lineNumbersClasses} border-r select-none`}>
<div className={`shrink-0 px-4 py-4 ${lineNumbersClasses} border-r select-none`}>
<div className="text-xs font-mono leading-6">
{lines.map((line, lineIdx) => {
const lineNum = lineIdx + 1
@@ -0,0 +1,98 @@
import { ReactNode } from 'react'
import { useTranslation } from 'react-i18next'
import { Link, useLocation } from 'wouter'
interface DocsLayoutProps {
children: ReactNode
}
interface NavItem {
title: string
path: string
}
interface NavSection {
title: string
items: NavItem[]
}
export default function DocsLayout({ children }: DocsLayoutProps) {
const { t } = useTranslation('common')
const [location] = useLocation()
const navigationSections: NavSection[] = [
{
title: t('nav.introduction'),
items: [
{ title: t('nav.overview'), path: '/docs/introduction/overview' },
{ title: t('nav.quick_start'), path: '/docs/introduction/quick-start' },
{ title: t('nav.limitations'), path: '/docs/introduction/limitations' },
],
},
{
title: t('nav.features'),
items: [
{ title: t('nav.model_integration'), path: '/docs/features/model-integration' },
{ title: t('nav.custom_tools'), path: '/docs/features/custom-tools' },
{ title: t('nav.knowledge_injection'), path: '/docs/features/knowledge-injection' },
{ title: t('nav.security_permissions'), path: '/docs/features/security-permissions' },
{ title: t('nav.data_masking'), path: '/docs/features/data-masking' },
],
},
{
title: t('nav.integration'),
items: [
{ title: t('nav.cdn_setup'), path: '/docs/integration/cdn-setup' },
{ title: t('nav.configuration'), path: '/docs/integration/configuration' },
{ title: t('nav.best_practices'), path: '/docs/integration/best-practices' },
{ title: t('nav.third_party_agent'), path: '/docs/integration/third-party-agent' },
],
},
]
return (
<div className="max-w-7xl mx-auto px-6 py-8 overflow-x-auto">
<div className="flex gap-8 min-w-[900px]">
{/* Sidebar */}
<aside className="w-64 shrink-0" role="complementary" aria-label="文档导航">
<div className="sticky top-8">
<nav className="space-y-8" role="navigation" aria-label="文档章节">
{navigationSections.map((section) => (
<section key={section.title}>
<h3 className="font-semibold text-gray-600 dark:text-gray-400 uppercase tracking-wider mb-3">
{section.title}
</h3>
<ul className="space-y-2" role="list">
{section.items.map((item) => {
const isActive = location === item.path
return (
<li key={item.path}>
<Link
href={item.path}
className={`block px-3 py-2 rounded-lg transition-colors duration-200 ${
isActive
? 'bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 font-medium'
: 'text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-800'
}`}
aria-current={isActive ? 'page' : undefined}
>
{item.title}
</Link>
</li>
)
})}
</ul>
</section>
))}
</nav>
</div>
</aside>
{/* Main Content */}
<main className="flex-1 min-w-0" id="main-content" role="main">
<div className="prose prose-lg dark:prose-invert max-w-none">{children}</div>
</main>
</div>
</div>
)
}
@@ -1,4 +1,8 @@
import { useTranslation } from 'react-i18next'
export default function Footer() {
const { t } = useTranslation('common')
return (
<footer
className="bg-gray-50 dark:bg-gray-900 border-t border-gray-200 dark:border-gray-700"
@@ -6,14 +10,14 @@ export default function Footer() {
>
<div className="max-w-7xl mx-auto px-6 py-6">
<div className="flex flex-col md:flex-row justify-between items-center space-y-4 md:space-y-0">
<p className="text-foreground/80 text-sm">© 2025 page-agent. All rights reserved.</p>
<p className="text-gray-600 dark:text-gray-300 text-sm">{t('footer.copyright')}</p>
<div className="flex items-center space-x-6">
<a
href="https://github.com/alibaba/page-agent"
target="_blank"
rel="noopener noreferrer"
className="text-foreground/80 hover:text-foreground transition-colors duration-200"
aria-label="访问 GitHub 仓库"
className="text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition-colors duration-200"
aria-label={t('footer.github_label')}
>
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
+142
View File
@@ -0,0 +1,142 @@
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Link } from 'wouter'
import LanguageSwitcher from './LanguageSwitcher'
import ThemeSwitcher from './ThemeSwitcher'
import { BookIcon, CloseIcon, GithubIcon, MenuIcon } from './icons'
export default function Header() {
const { t } = useTranslation('common')
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
return (
<>
<header
className="relative z-50 bg-white/80 dark:bg-gray-900/80 backdrop-blur-md border-b border-gray-200 dark:border-gray-700"
role="banner"
>
<div className="max-w-7xl mx-auto px-6 py-4">
<div className="flex items-center justify-between gap-2">
{/* Logo */}
<Link
href="/"
className="flex items-center gap-2 sm:gap-3 group shrink-0"
aria-label={t('header.logo_alt')}
onClick={() => setMobileMenuOpen(false)}
>
<img
src="https://img.alicdn.com/imgextra/i2/O1CN01HB8ylu1uozANEMZw2_!!6000000006085-49-tps-128-128.webp"
alt="PageAgent Logo"
className="w-10 h-10 rounded-xl group-hover:scale-110 transition-transform duration-200"
/>
<div>
<span className="text-base sm:text-xl font-bold text-gray-900 dark:text-white block leading-tight">
page-agent
</span>
<p className="hidden sm:block text-xs text-gray-600 dark:text-gray-300">
{t('header.slogan')}
</p>
</div>
</Link>
{/* Mobile Icon Navigation (横向滚动) */}
<nav
className="md:hidden flex items-center gap-1 overflow-x-auto scrollbar-hide flex-1"
role="navigation"
aria-label="Mobile navigation"
>
<Link
href="/docs/introduction/overview"
className="p-2 rounded-lg text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-blue-600 dark:hover:text-blue-400 transition-colors duration-200 shrink-0"
aria-label={t('header.nav_docs')}
>
<BookIcon className="w-5 h-5" />
</Link>
<a
href="https://github.com/alibaba/page-agent"
target="_blank"
rel="noopener noreferrer"
className="p-2 rounded-lg text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-blue-600 dark:hover:text-blue-400 transition-colors duration-200 shrink-0"
aria-label={t('header.nav_source')}
>
<GithubIcon className="w-5 h-5" />
</a>
</nav>
{/* Desktop Navigation */}
<nav
className="hidden md:flex items-center space-x-6"
role="navigation"
aria-label={t('header.nav_docs')}
>
<Link
href="/docs/introduction/overview"
className="flex items-center gap-1.5 text-gray-600 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 transition-colors duration-200"
>
<BookIcon />
{t('header.nav_docs')}
</Link>
<a
href="https://github.com/alibaba/page-agent"
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-1.5 text-gray-600 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 transition-colors duration-200"
aria-label={t('header.nav_source')}
>
<GithubIcon />
{t('header.nav_source')}
</a>
<ThemeSwitcher />
<LanguageSwitcher />
</nav>
{/* Mobile menu button */}
<button
type="button"
className="md:hidden p-2 rounded-lg text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors duration-200 shrink-0"
aria-label={t('header.mobile_menu')}
aria-expanded={mobileMenuOpen}
aria-controls="mobile-menu"
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
>
{mobileMenuOpen ? <CloseIcon /> : <MenuIcon />}
</button>
</div>
{/* Mobile Navigation */}
{mobileMenuOpen && (
<nav
id="mobile-menu"
className="md:hidden pt-4 pb-2 space-y-3 border-t border-gray-200 dark:border-gray-700 mt-4"
role="navigation"
>
<Link
href="/docs/introduction/overview"
className="flex items-center gap-2 px-3 py-2 rounded-lg text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-blue-600 dark:hover:text-blue-400 transition-colors duration-200"
onClick={() => setMobileMenuOpen(false)}
>
<BookIcon className="w-5 h-5" />
{t('header.nav_docs')}
</Link>
<a
href="https://github.com/alibaba/page-agent"
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-2 px-3 py-2 rounded-lg text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-blue-600 dark:hover:text-blue-400 transition-colors duration-200"
aria-label={t('header.nav_source')}
>
<GithubIcon className="w-5 h-5" />
{t('header.nav_source')}
</a>
<div className="flex items-center gap-3 px-3 py-2">
<ThemeSwitcher />
<LanguageSwitcher />
</div>
</nav>
)}
</div>
</header>
</>
)
}
@@ -0,0 +1,126 @@
.syntax {
white-space: pre-wrap;
word-break: break-word;
overflow-wrap: break-word;
font-family: monospace;
color: #171717;
}
:global(.dark) .syntax {
color: #e0e0e0;
}
/* JavaScript/TypeScript 关键字 */
.keyword {
color: #d73a49;
font-weight: 600;
}
:global(.dark) .keyword {
color: #ff6b6b;
}
/* TypeScript 特定关键字 (interface, type, enum, etc.) */
.tsKeyword {
color: #af00db;
font-weight: 600;
}
:global(.dark) .tsKeyword {
color: #c792ea;
}
/* TypeScript 内置类型 */
.type {
color: #267f99;
font-weight: 500;
}
:global(.dark) .type {
color: #4ec9b0;
}
/* 字符串 */
.string {
color: #1d6eca;
}
:global(.dark) .string {
color: #4fc3f7;
}
/* 数字 */
.number {
color: #00c583;
}
:global(.dark) .number {
color: #66bb6a;
}
/* 布尔值和字面量 (true, false, null, undefined) */
.literal {
color: #0000ff;
font-weight: 500;
}
:global(.dark) .literal {
color: #569cd6;
}
/* 注释 */
.comment {
color: #6a737d;
font-style: italic;
}
:global(.dark) .comment {
color: #9e9e9e;
}
/* 装饰器 (@decorator) */
.decorator {
color: #e0aa00;
font-weight: 500;
}
:global(.dark) .decorator {
color: #dcdcaa;
}
/* 箭头函数 (=>) */
.arrow {
color: #d73a49;
font-weight: bold;
}
:global(.dark) .arrow {
color: #ff6b6b;
}
/* 标识符(变量名、函数名等) */
.identifier {
color: #171717;
}
:global(.dark) .identifier {
color: #e0e0e0;
}
/* 属性访问 (.property) */
.property {
color: #0550ae;
}
:global(.dark) .property {
color: #9cdcfe;
}
/* 运算符 */
.operator {
color: #5a5a5a;
}
:global(.dark) .operator {
color: #d4d4d4;
}
@@ -0,0 +1,176 @@
/**
* js 语法高亮组件,适合在文章中演示代码片段
*/
import React from 'react'
import styles from './HighlightSyntax.module.css'
interface HighlightSyntaxProps {
code: string
}
// JavaScript/TypeScript 关键字
const keywords =
'async|await|function|const|let|var|if|else|for|while|return|try|catch|finally|class|extends|from|import|export|default|undefined|throw|break|continue|switch|case|do|with|yield|delete|typeof|void|static|get|set|super|debugger'
// TypeScript 特定关键字
const tsKeywords =
'interface|type|enum|namespace|module|declare|abstract|implements|public|private|protected|readonly|as|satisfies|infer|keyof|is'
// 布尔值和空值
const literals = 'true|false|null|undefined|NaN|Infinity'
// TypeScript 内置类型
const tsTypes =
'string|number|boolean|any|unknown|never|void|object|symbol|bigint|Array|Promise|Record|Partial|Required|Readonly|Pick|Omit|Exclude|Extract|NonNullable|ReturnType|Parameters|ConstructorParameters|InstanceType|ThisType|Uppercase|Lowercase|Capitalize|Uncapitalize'
// 辅助函数:转义 HTML 特殊字符
function escapeHtml(text: string): string {
return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
}
// 语法高亮函数,先提取 token 再转义和高亮
function highlightSyntax(code: string): string {
// 构建正则模式,包含更多 token 类型(在原始文本上匹配)
const pattern = new RegExp(
'(' +
// 1. 字符串(双引号、单引号、模板字符串)
'"([^"\\\\]|\\\\.)*"|' +
"'([^'\\\\]|\\\\.)*'|" +
'`([^`\\\\]|\\\\.)*`|' +
// 2. 注释(单行和多行)
'//[^\\n]*|' +
'/\\*[\\s\\S]*?\\*/|' +
// 3. 装饰器
'@[a-zA-Z_$][\\w$]*|' +
// 4. 数字(包括小数、十六进制、科学计数法)
'\\b0[xX][0-9a-fA-F]+\\b|' +
'\\b\\d+\\.?\\d*(?:[eE][+-]?\\d+)?\\b|' +
// 5. TypeScript/JavaScript 关键字
'\\b(?:' +
keywords +
'|' +
tsKeywords +
'|' +
literals +
')\\b|' +
// 6. TypeScript 内置类型
'\\b(?:' +
tsTypes +
')\\b|' +
// 7. 箭头函数
'=>|' +
// 8. 函数调用(函数名后跟括号)
'\\b[a-zA-Z_$][\\w$]*(?=\\()|' +
// 9. 属性访问
'\\.[a-zA-Z_$][\\w$]*|' +
// 10. 运算符和特殊符号
'[+\\-*/%&|^!~<>=?:]+|' +
'[{}\\[\\]();,.]' +
')',
'g'
)
const tokens: string[] = []
let lastIndex = 0
let match: RegExpExecArray | null
while ((match = pattern.exec(code)) !== null) {
if (match.index > lastIndex) {
const gap = code.slice(lastIndex, match.index)
// 将间隙按空白符分割,保留空白符
tokens.push(...gap.split(/(\s+)/))
}
tokens.push(match[0])
lastIndex = pattern.lastIndex
}
if (lastIndex < code.length) {
tokens.push(...code.slice(lastIndex).split(/(\s+)/))
}
const highlighted = tokens
.map((token) => {
// 空白符直接返回
if (/^\s+$/.test(token)) {
return token
}
// 1. 注释(单行和多行)
if (/^\/\/.*$/.test(token) || /^\/\*[\s\S]*?\*\/$/.test(token)) {
return `<span class="${styles.comment}">${escapeHtml(token)}</span>`
}
// 2. 字符串
if (
/^"([^"\\]|\\.)*"$/.test(token) ||
/^'([^'\\]|\\.)*'$/.test(token) ||
/^`([^`\\]|\\.)*`$/.test(token)
) {
return `<span class="${styles.string}">${escapeHtml(token)}</span>`
}
// 3. 数字
if (/^(0[xX][0-9a-fA-F]+|\d+\.?\d*(?:[eE][+-]?\d+)?)$/.test(token)) {
return `<span class="${styles.number}">${escapeHtml(token)}</span>`
}
// 4. 布尔值和特殊字面量
if (new RegExp(`^(?:${literals})$`).test(token)) {
return `<span class="${styles.literal}">${escapeHtml(token)}</span>`
}
// 5. JavaScript/TypeScript 关键字
if (new RegExp(`^(?:${keywords})$`).test(token)) {
return `<span class="${styles.keyword}">${escapeHtml(token)}</span>`
}
// 6. TypeScript 特定关键字
if (new RegExp(`^(?:${tsKeywords})$`).test(token)) {
return `<span class="${styles.tsKeyword}">${escapeHtml(token)}</span>`
}
// 7. TypeScript 内置类型
if (new RegExp(`^(?:${tsTypes})$`).test(token)) {
return `<span class="${styles.type}">${escapeHtml(token)}</span>`
}
// 8. 装饰器
if (/^@[a-zA-Z_$][\w$]*$/.test(token)) {
return `<span class="${styles.decorator}">${escapeHtml(token)}</span>`
}
// 9. 箭头函数
if (token === '=>') {
return `<span class="${styles.arrow}">${escapeHtml(token)}</span>`
}
// 10. 函数调用和标识符
if (/^[a-zA-Z_$][\w$]*$/.test(token)) {
return `<span class="${styles.identifier}">${escapeHtml(token)}</span>`
}
// 11. 属性访问
if (/^\.[a-zA-Z_$][\w$]*$/.test(token)) {
return `<span class="${styles.property}">${escapeHtml(token)}</span>`
}
// 12. 运算符
if (/^[+\-*/%&|^!~<>=?:]+$/.test(token)) {
return `<span class="${styles.operator}">${escapeHtml(token)}</span>`
}
// 13. 其他符号,需要转义
return escapeHtml(token)
})
.join('')
return highlighted
}
const HighlightSyntaxClient: React.FC<HighlightSyntaxProps> = ({ code }) => {
const htmlContent = highlightSyntax(code)
// eslint-disable-next-line react-dom/no-dangerously-set-innerhtml
return <code className={styles.syntax} dangerouslySetInnerHTML={{ __html: htmlContent }} />
}
export default HighlightSyntaxClient
@@ -0,0 +1,115 @@
import { useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
export default function LanguageSwitcher() {
const { i18n, t } = useTranslation('common')
const [isOpen, setIsOpen] = useState(false)
const dropdownRef = useRef<HTMLDivElement>(null)
const currentLang = i18n.language
const languages = [
{ code: 'zh-CN', label: '中文' },
{ code: 'en-US', label: 'English' },
]
const currentLanguage = languages.find((lang) => lang.code === currentLang) || languages[0]
const handleLanguageChange = (langCode: string) => {
i18n.changeLanguage(langCode)
setIsOpen(false)
}
// Close dropdown when clicking outside
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
setIsOpen(false)
}
}
if (isOpen) {
document.addEventListener('mousedown', handleClickOutside)
}
return () => {
document.removeEventListener('mousedown', handleClickOutside)
}
}, [isOpen])
return (
<div className="relative" ref={dropdownRef}>
<button
onClick={() => setIsOpen(!isOpen)}
className="flex items-center gap-2 px-3 py-1.5 rounded-lg bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors text-sm font-medium border border-gray-200 dark:border-gray-700 text-gray-700 dark:text-gray-300"
aria-label={t('language.switch_label')}
aria-expanded={isOpen}
aria-haspopup="true"
>
<svg
className="w-4 h-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M3 5h12M9 3v2m1.048 9.5A18.022 18.022 0 016.412 9m6.088 9h7M11 21l5-10 5 10M12.751 5C11.783 10.77 8.07 15.61 3 18.129"
/>
</svg>
<span>{currentLanguage.label}</span>
<svg
className={`w-4 h-4 transition-transform duration-200 ${isOpen ? 'rotate-180' : ''}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</button>
{isOpen && (
<div
className="absolute right-0 mt-2 w-40 bg-white dark:bg-gray-800 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700 py-1 z-50"
role="menu"
aria-orientation="vertical"
>
{languages.map((lang) => (
<button
key={lang.code}
onClick={() => handleLanguageChange(lang.code)}
className={`w-full flex items-center gap-2 px-4 py-2 text-sm text-left hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors ${
currentLang === lang.code
? 'bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-300'
: 'text-gray-700 dark:text-gray-300'
}`}
role="menuitem"
>
<span>{lang.label}</span>
{currentLang === lang.code && (
<svg
className="w-4 h-4 ml-auto"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M5 13l4 4L19 7"
/>
</svg>
)}
</button>
))}
</div>
)}
</div>
)
}
@@ -0,0 +1,133 @@
import { useEffect, useState } from 'react'
type Theme = 'light' | 'dark'
export default function ThemeSwitcher() {
const [theme, setTheme] = useState<Theme>(() => {
// 初始化时读取保存的主题
if (typeof window !== 'undefined') {
const savedTheme = localStorage.getItem('theme') as Theme | null
if (savedTheme === 'light' || savedTheme === 'dark') {
return savedTheme
}
// 默认跟随系统
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
}
return 'light'
})
useEffect(() => {
// 应用主题
if (theme === 'dark') {
document.documentElement.classList.add('dark')
document.documentElement.style.colorScheme = 'dark'
} else {
document.documentElement.classList.remove('dark')
document.documentElement.style.colorScheme = 'light'
}
// 保存到 localStorage
localStorage.setItem('theme', theme)
}, [theme])
// 监听系统主题变化
useEffect(() => {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
const handleSystemThemeChange = (e: MediaQueryListEvent) => {
// 只有在用户未手动设置时才自动跟随系统
const savedTheme = localStorage.getItem('theme')
if (!savedTheme) {
setTheme(e.matches ? 'dark' : 'light')
}
}
mediaQuery.addEventListener('change', handleSystemThemeChange)
return () => mediaQuery.removeEventListener('change', handleSystemThemeChange)
}, [])
const toggleTheme = () => {
setTheme((prev) => (prev === 'light' ? 'dark' : 'light'))
}
return (
<button
onClick={toggleTheme}
className="relative inline-flex items-center h-8 w-16 rounded-full transition-colors duration-300 ease-in-out focus:outline-none"
style={{
backgroundColor: theme === 'dark' ? '#1e293b' : '#e0f2fe',
}}
aria-label={theme === 'light' ? 'Switch to dark mode' : 'Switch to light mode'}
role="switch"
aria-checked={theme === 'dark'}
>
{/* 滑块 */}
<span
className="inline-block h-6 w-6 transform rounded-full transition-all duration-300 ease-in-out shadow-md"
style={{
backgroundColor: theme === 'dark' ? '#475569' : '#fbbf24',
transform: theme === 'dark' ? 'translateX(2.25rem)' : 'translateX(0.25rem)',
}}
>
{/* 图标 */}
<span className="flex items-center justify-center h-full w-full">
{theme === 'light' ? (
// 太阳图标
<svg
className="w-4 h-4 text-white"
fill="currentColor"
viewBox="0 0 20 20"
aria-hidden="true"
>
<path
fillRule="evenodd"
d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z"
clipRule="evenodd"
/>
</svg>
) : (
// 月亮图标
<svg
className="w-4 h-4 text-slate-200"
fill="currentColor"
viewBox="0 0 20 20"
aria-hidden="true"
>
<path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z" />
</svg>
)}
</span>
</span>
{/* 背景装饰 */}
<span
className="absolute inset-0 flex items-center justify-between px-2 pointer-events-none"
aria-hidden="true"
>
{/* 左侧太阳(浅色模式时显示) */}
<span
className={`transition-opacity duration-300 ${
theme === 'light' ? 'opacity-0' : 'opacity-40'
}`}
>
<svg className="w-4 h-4 text-sky-400" fill="currentColor" viewBox="0 0 20 20">
<path
fillRule="evenodd"
d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z"
clipRule="evenodd"
/>
</svg>
</span>
{/* 右侧月亮(深色模式时显示) */}
<span
className={`transition-opacity duration-300 ${
theme === 'dark' ? 'opacity-0' : 'opacity-40'
}`}
>
<svg className="w-4 h-4 text-slate-400" fill="currentColor" viewBox="0 0 20 20">
<path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z" />
</svg>
</span>
</span>
</button>
)
}
+75
View File
@@ -0,0 +1,75 @@
// SVG图标组件集合,用于Header等地方复用
interface IconProps {
className?: string
'aria-hidden'?: boolean
}
export function BookIcon({ className = 'w-4 h-4', ...props }: IconProps) {
return (
<svg
className={className}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
{...props}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"
/>
</svg>
)
}
export function GithubIcon({ className = 'w-4 h-4', ...props }: IconProps) {
return (
<svg
className={className}
fill="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
{...props}
>
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
</svg>
)
}
export function MenuIcon({ className = 'w-6 h-6', ...props }: IconProps) {
return (
<svg
className={className}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
{...props}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M4 6h16M4 12h16M4 18h16"
/>
</svg>
)
}
export function CloseIcon({ className = 'w-6 h-6', ...props }: IconProps) {
return (
<svg
className={className}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
{...props}
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
)
}
@@ -1,21 +1,24 @@
import BetaNotice from '@pages/components/BetaNotice'
import CodeEditor from '@pages/components/CodeEditor'
import { useTranslation } from 'react-i18next'
import BetaNotice from '@/components/BetaNotice'
import CodeEditor from '@/components/CodeEditor'
export default function CustomTools() {
const { t } = useTranslation('docs')
return (
<div>
<h1 className="text-4xl font-bold mb-6"></h1>
<h1 className="text-4xl font-bold mb-6">{t('custom_tools.title')}</h1>
<p className="text-xl text-foreground/80 mb-8 leading-relaxed">
AI Agent 使 Zod AI
<p className="text-xl text-gray-600 dark:text-gray-300 mb-8 leading-relaxed">
{t('custom_tools.subtitle')}
</p>
<div className="space-y-8">
<section>
<h2 className="text-2xl font-bold mb-4"></h2>
<p className="text-foreground/80 mb-4">
namedescriptioninput schema execute
<h2 className="text-2xl font-bold mb-4">{t('custom_tools.registration')}</h2>
<p className="text-gray-600 dark:text-gray-300 mb-4">
{t('custom_tools.registration_desc')}
</p>
<CodeEditor
@@ -49,13 +52,12 @@ const pageAgent = new PageAgent({customTools})
</section>
<section>
<h2 className="text-2xl font-bold mb-4"></h2>
<h2 className="text-2xl font-bold mb-4">{t('custom_tools.page_filter')}</h2>
<BetaNotice />
<p className="text-foreground/80 mb-4">
<code className="bg-gray-200 dark:bg-gray-700 px-2 py-1 rounded">pageFilter</code>{' '}
<p className="text-gray-600 dark:text-gray-300 mb-4">
{t('custom_tools.page_filter_desc')}
</p>
<CodeEditor
@@ -82,16 +84,16 @@ const pageAgent = new PageAgent({customTools})
</section>
<section>
<h2 className="text-2xl font-bold mb-4"></h2>
<h2 className="text-2xl font-bold mb-4">{t('custom_tools.best_practices')}</h2>
<div className="space-y-4">
<div className="p-4 bg-yellow-50 dark:bg-yellow-900/20 rounded-lg">
<h3 className="text-lg font-semibold text-yellow-900 dark:text-yellow-300 mb-2">
{t('custom_tools.bp_performance')}
</h3>
<ul className="text-foreground/80 space-y-1 text-sm">
<li> 使 pageFilter </li>
<li> execute </li>
<li> </li>
<ul className="text-gray-600 dark:text-gray-300 space-y-1 text-sm">
<li>{t('custom_tools.bp_1')}</li>
<li>{t('custom_tools.bp_2')}</li>
<li>{t('custom_tools.bp_3')}</li>
</ul>
</div>
</div>
@@ -1,5 +1,5 @@
import BetaNotice from '@pages/components/BetaNotice'
import CodeEditor from '@pages/components/CodeEditor'
import BetaNotice from '@/components/BetaNotice'
import CodeEditor from '@/components/CodeEditor'
export default function DataMasking() {
return (
@@ -8,7 +8,7 @@ export default function DataMasking() {
<BetaNotice />
<p className="text-xl text-foreground/80 mb-6 leading-relaxed">
<p className="text-xl text-gray-600 dark:text-gray-300 mb-6 leading-relaxed">
AI
</p>
@@ -19,14 +19,18 @@ export default function DataMasking() {
<h3 className="text-lg font-semibold mb-2 text-blue-900 dark:text-blue-300">
🔒
</h3>
<p className="text-foreground/80"></p>
<p className="text-gray-600 dark:text-gray-300">
</p>
</div>
<div className="p-4 bg-purple-50 dark:bg-purple-900/20 rounded-lg">
<h3 className="text-lg font-semibold mb-2 text-purple-900 dark:text-purple-300">
</h3>
<p className="text-foreground/80"></p>
<p className="text-gray-600 dark:text-gray-300">
</p>
</div>
</div>
@@ -1,5 +1,5 @@
import BetaNotice from '@pages/components/BetaNotice'
import CodeEditor from '@pages/components/CodeEditor'
import BetaNotice from '@/components/BetaNotice'
import CodeEditor from '@/components/CodeEditor'
export default function KnowledgeInjection() {
return (
@@ -8,7 +8,7 @@ export default function KnowledgeInjection() {
<BetaNotice />
<p className="text-xl text-foreground/80 mb-8 leading-relaxed">
<p className="text-xl text-gray-600 dark:text-gray-300 mb-8 leading-relaxed">
AI
</p>
@@ -20,8 +20,10 @@ export default function KnowledgeInjection() {
<h3 className="text-xl font-semibold mb-3 text-purple-900 dark:text-purple-300">
🎯
</h3>
<p className="text-foreground/80 mb-4"> AI </p>
<ul className="list-disc list-inside space-y-2 text-foreground/70">
<p className="text-gray-600 dark:text-gray-300 mb-4">
AI
</p>
<ul className="list-disc list-inside space-y-2 text-gray-500 dark:text-gray-400">
<li> AI </li>
<li></li>
<li></li>
@@ -57,13 +59,13 @@ const pageAgent = new PageAgent({
<h3 className="text-xl font-semibold mb-3 text-blue-900 dark:text-blue-300">
</h3>
<p className="text-foreground/80 mb-4">
<p className="text-gray-600 dark:text-gray-300 mb-4">
AI
</p>
<div className="grid md:grid-cols-2 gap-4">
<div className="space-y-2">
<h4 className="font-semibold text-blue-800 dark:text-blue-200"></h4>
<ul className="list-disc list-inside text-sm text-foreground/70 space-y-1">
<ul className="list-disc list-inside text-sm text-gray-500 dark:text-gray-400 space-y-1">
<li></li>
<li></li>
<li></li>
@@ -71,7 +73,7 @@ const pageAgent = new PageAgent({
</div>
<div className="space-y-2">
<h4 className="font-semibold text-blue-800 dark:text-blue-200"></h4>
<ul className="list-disc list-inside text-sm text-foreground/70 space-y-1">
<ul className="list-disc list-inside text-sm text-gray-500 dark:text-gray-400 space-y-1">
<li></li>
<li></li>
<li></li>
@@ -111,21 +113,25 @@ pageAgent.knowledge.setAppKnowledge(\`
<h3 className="text-xl font-semibold mb-3 text-green-900 dark:text-green-300">
📄
</h3>
<p className="text-foreground/80 mb-4">
<p className="text-gray-600 dark:text-gray-300 mb-4">
AI
</p>
<div className="grid md:grid-cols-3 gap-4">
<div className="space-y-2">
<h4 className="font-semibold text-green-800 dark:text-green-200"></h4>
<p className="text-sm text-foreground/70"></p>
<p className="text-sm text-gray-500 dark:text-gray-400"></p>
</div>
<div className="space-y-2">
<h4 className="font-semibold text-green-800 dark:text-green-200"></h4>
<p className="text-sm text-foreground/70"></p>
<p className="text-sm text-gray-500 dark:text-gray-400">
</p>
</div>
<div className="space-y-2">
<h4 className="font-semibold text-green-800 dark:text-green-200"></h4>
<p className="text-sm text-foreground/70"></p>
<p className="text-sm text-gray-500 dark:text-gray-400">
</p>
</div>
</div>
</div>
@@ -0,0 +1,164 @@
import { useTranslation } from 'react-i18next'
import BetaNotice from '@/components/BetaNotice'
import CodeEditor from '@/components/CodeEditor'
export default function ModelIntegration() {
const { t } = useTranslation('docs')
return (
<div>
<h1 className="text-4xl font-bold mb-6">{t('model_integration.title')}</h1>
<p className="text-xl text-gray-600 dark:text-gray-300 mb-6 leading-relaxed">
{t('model_integration.subtitle')}
</p>
<h2 className="text-2xl font-bold mb-3">{t('model_integration.recommended')}</h2>
<div className="grid md:grid-cols-3 gap-4 mb-6">
<div className="p-4 bg-green-50 dark:bg-green-900/20 rounded-lg">
<h3 className="text-lg font-semibold mb-2 text-green-900 dark:text-green-300">
{t('model_integration.model_gpt4_title')}
</h3>
<p className="text-sm text-gray-600 dark:text-gray-300 mb-2">
{t('model_integration.model_gpt4_badge')}
</p>
<ul className="text-sm text-gray-500 dark:text-gray-400 space-y-1">
<li>{t('model_integration.model_gpt4_1')}</li>
<li>{t('model_integration.model_gpt4_2')}</li>
<li>{t('model_integration.model_gpt4_3')}</li>
</ul>
</div>
<div className="p-4 bg-purple-50 dark:bg-purple-900/20 rounded-lg">
<h3 className="text-lg font-semibold mb-2 text-purple-900 dark:text-purple-300">
{t('model_integration.model_deepseek_title')}
</h3>
<p className="text-sm text-gray-600 dark:text-gray-300 mb-2">
{t('model_integration.model_deepseek_badge')}
</p>
<ul className="text-sm text-gray-500 dark:text-gray-400 space-y-1">
<li>{t('model_integration.model_deepseek_1')}</li>
<li>{t('model_integration.model_deepseek_2')}</li>
<li>{t('model_integration.model_deepseek_3')}</li>
</ul>
</div>
<div className="p-4 bg-orange-50 dark:bg-orange-900/20 rounded-lg">
<h3 className="text-lg font-semibold mb-2 text-orange-900 dark:text-orange-300">
{t('model_integration.model_qwen_title')}
</h3>
<p className="text-sm text-gray-600 dark:text-gray-300 mb-2">
{t('model_integration.model_qwen_badge')}
</p>
<ul className="text-sm text-gray-500 dark:text-gray-400 space-y-1">
<li>{t('model_integration.model_qwen_1')}</li>
<li>{t('model_integration.model_qwen_2')}</li>
<li>{t('model_integration.model_qwen_3')}</li>
</ul>
</div>
<div className="p-4 bg-orange-50 dark:bg-orange-900/20 rounded-lg">
<h3 className="text-lg font-semibold mb-2 text-orange-900 dark:text-orange-300">
{t('model_integration.model_gemini_title')}
</h3>
<p className="text-sm text-gray-600 dark:text-gray-300 mb-2">
{t('model_integration.model_gemini_badge')}
</p>
</div>
</div>
<h2 className="text-2xl font-bold mb-3">{t('model_integration.available')}</h2>
<div className="p-4 bg-emerald-50 dark:bg-emerald-900/20 rounded-lg mb-6">
<h3 className="text-lg font-semibold mb-3 text-emerald-900 dark:text-emerald-300">
{t('model_integration.available_verified')}
</h3>
<div className="flex flex-wrap gap-2">
<span className="inline-flex items-center rounded-full bg-emerald-100 dark:bg-emerald-900/40 text-emerald-900 dark:text-emerald-200 px-3 py-1 text-sm">
gpt-4.1-mini/4.1/5
</span>
<span className="inline-flex items-center rounded-full bg-emerald-100 dark:bg-emerald-900/40 text-emerald-900 dark:text-emerald-200 px-3 py-1 text-sm">
grok-4/grok-code-fast
</span>
<span className="inline-flex items-center rounded-full bg-emerald-100 dark:bg-emerald-900/40 text-emerald-900 dark:text-emerald-200 px-3 py-1 text-sm">
qwen3
</span>
<span className="inline-flex items-center rounded-full bg-emerald-100 dark:bg-emerald-900/40 text-emerald-900 dark:text-emerald-200 px-3 py-1 text-sm">
deepseek-v3.1/3.2
</span>
<span className="inline-flex items-center rounded-full bg-emerald-100 dark:bg-emerald-900/40 text-emerald-900 dark:text-emerald-200 px-3 py-1 text-sm">
claude-sonnet-4/4.5/haiku-4.5
</span>
<span className="inline-flex items-center rounded-full bg-emerald-100 dark:bg-emerald-900/40 text-emerald-900 dark:text-emerald-200 px-3 py-1 text-sm">
gemini-2.5
</span>
</div>
</div>
<h2 className="text-2xl font-bold mb-3">{t('model_integration.tips')}</h2>
<div className="p-4 bg-red-50 dark:bg-red-900/20 rounded-lg mb-6">
<ul className="text-sm text-gray-600 dark:text-gray-300 space-y-1 list-disc pl-5">
<li>{t('model_integration.tip_1')}</li>
<li>{t('model_integration.tip_2')}</li>
<li>{t('model_integration.tip_3')}</li>
</ul>
</div>
<h2 className="text-2xl font-bold mb-3">{t('model_integration.security')}</h2>
<div className="p-4 bg-yellow-50 dark:bg-yellow-900/20 border-l-4 border-yellow-500 mb-4">
<p className="text-sm font-semibold text-yellow-900 dark:text-yellow-200">
{t('model_integration.security_warning')}
</p>
</div>
<p className="text-gray-600 dark:text-gray-300 mb-4">
{t('model_integration.security_desc')}
</p>
<div className="p-4 bg-blue-50 dark:bg-blue-900/20 rounded-lg mb-6">
<h3 className="text-lg font-semibold mb-3 text-blue-900 dark:text-blue-300">
{t('model_integration.security_backend_proxy')}
</h3>
<p className="text-sm text-gray-600 dark:text-gray-300 mb-3">
{t('model_integration.security_backend_desc')}
</p>
<ul className="text-sm text-gray-600 dark:text-gray-300 space-y-1 list-none pl-0">
<li>{t('model_integration.security_method_1')}</li>
<li>{t('model_integration.security_method_2')}</li>
<li>{t('model_integration.security_method_3')}</li>
</ul>
</div>
<h2 className="text-2xl font-bold mb-3">{t('model_integration.configuration')}</h2>
<CodeEditor
code={`
// 百炼等其他兼容服务
const pageAgent = new PageAgent({
baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
apiKey: 'your-api-key',
model: 'qwen-plus'
});
// 私有部署模型
const pageAgent = new PageAgent({
baseURL: 'http://localhost:11434/v1',
apiKey: 'N/A', // Ollama 通常使用任意值
model: 'qwen3:latest'
});
// 测试接口
// @note: 限流,限制 prompt 内容,限制来源,随时变更,请替换成你自己的
// @note: 使用 DeepSeek-chat(3.2) 官方版本,使用协议和隐私策略见 DeepSeek 网站
const DEMO_MODEL = 'PAGE-AGENT-FREE-TESTING-RANDOM'
const DEMO_BASE_URL = 'https://hwcxiuzfylggtcktqgij.supabase.co/functions/v1/llm-testing-proxy'
const DEMO_API_KEY = 'PAGE-AGENT-FREE-TESTING-RANDOM'
`}
/>
</div>
)
}
@@ -1,4 +1,4 @@
import BetaNotice from '@pages/components/BetaNotice'
import BetaNotice from '@/components/BetaNotice'
export default function SecurityPermissions() {
return (
@@ -7,7 +7,7 @@ export default function SecurityPermissions() {
<BetaNotice />
<p className="text-xl text-foreground/80 mb-8 leading-relaxed">
<p className="text-xl text-gray-600 dark:text-gray-300 mb-8 leading-relaxed">
page-agent AI
</p>
@@ -19,13 +19,15 @@ export default function SecurityPermissions() {
<h3 className="text-lg font-semibold text-red-900 dark:text-red-300">
🚫
</h3>
<p className="text-foreground/80"> AI </p>
<p className="text-gray-600 dark:text-gray-300">
AI
</p>
</div>
<div className="p-4 bg-green-50 dark:bg-green-900/20 rounded-lg">
<h3 className="text-lg font-semibold text-green-900 dark:text-green-300">
</h3>
<p className="text-foreground/80"> AI </p>
<p className="text-gray-600 dark:text-gray-300"> AI </p>
</div>
</div>
</section>
@@ -37,13 +39,13 @@ export default function SecurityPermissions() {
<h3 className="text-lg font-semibold text-red-900 dark:text-red-300">
🚫 URL
</h3>
<p className="text-foreground/80"> AI 访</p>
<p className="text-gray-600 dark:text-gray-300"> AI 访</p>
</div>
<div className="p-4 bg-green-50 dark:bg-green-900/20 rounded-lg">
<h3 className="text-lg font-semibold text-green-900 dark:text-green-300">
URL
</h3>
<p className="text-foreground/80"> AI 访</p>
<p className="text-gray-600 dark:text-gray-300"> AI 访</p>
</div>
</div>
</section>
@@ -54,17 +56,21 @@ export default function SecurityPermissions() {
<h3 className="text-lg font-semibold mb-2 text-yellow-900 dark:text-yellow-300">
</h3>
<p className="text-foreground/80 mb-3">
<p className="text-gray-600 dark:text-gray-300 mb-3">
AI
</p>
<div className="space-y-2">
<div className="pl-3 border-l-2 border-red-400">
<p className="font-medium text-red-700 dark:text-red-300"></p>
<p className="text-sm text-foreground/70"></p>
<p className="text-sm text-gray-500 dark:text-gray-400">
</p>
</div>
<div className="pl-3 border-l-2 border-orange-400">
<p className="font-medium text-orange-700 dark:text-orange-300"></p>
<p className="text-sm text-foreground/70"></p>
<p className="text-sm text-gray-500 dark:text-gray-400">
</p>
</div>
</div>
</div>
@@ -1,5 +1,5 @@
import BetaNotice from '@pages/components/BetaNotice'
import CodeEditor from '@pages/components/CodeEditor'
import BetaNotice from '@/components/BetaNotice'
import CodeEditor from '@/components/CodeEditor'
export default function BestPractices() {
return (
@@ -19,7 +19,9 @@ export default function BestPractices() {
<h3 className="text-lg font-semibold mb-2 text-green-900 dark:text-green-300">
API
</h3>
<p className="text-foreground/80 mb-3"> AI </p>
<p className="text-gray-600 dark:text-gray-300 mb-3">
AI
</p>
<CodeEditor
code={`// 推荐:合并操作
@@ -36,7 +38,9 @@ await pageAgent.execute('点击提交按钮');`}
<h3 className="text-lg font-semibold mb-2 text-blue-900 dark:text-blue-300">
🎯
</h3>
<p className="text-foreground/80">使</p>
<p className="text-gray-600 dark:text-gray-300">
使
</p>
</div>
</div>
@@ -45,12 +49,12 @@ await pageAgent.execute('点击提交按钮');`}
<div className="space-y-3 mb-6">
<div className="p-3 bg-red-50 dark:bg-red-900/20 rounded-lg border-l-4 border-red-500">
<h3 className="font-semibold mb-1 text-red-900 dark:text-red-300"></h3>
<p className="text-foreground/80"></p>
<p className="text-gray-600 dark:text-gray-300"></p>
</div>
<div className="p-3 bg-yellow-50 dark:bg-yellow-900/20 rounded-lg border-l-4 border-yellow-500">
<h3 className="font-semibold mb-1 text-yellow-900 dark:text-yellow-300"></h3>
<p className="text-foreground/80"></p>
<p className="text-gray-600 dark:text-gray-300"></p>
</div>
</div>
@@ -1,5 +1,5 @@
import BetaNotice from '@pages/components/BetaNotice'
import CodeEditor from '@pages/components/CodeEditor'
import BetaNotice from '@/components/BetaNotice'
import CodeEditor from '@/components/CodeEditor'
export default function CdnSetup() {
return (
@@ -16,7 +16,7 @@ export default function CdnSetup() {
className="mb-8"
code={`
// 仅供测试使用,稳定 CDN 待定
<script src="https://hwcxiuzfylggtcktqgij.supabase.co/storage/v1/object/public/demo-public/v0.0.2/page-agent.js" crossorigin="true" type="text/javascript"></script>
<script src="https://hwcxiuzfylggtcktqgij.supabase.co/storage/v1/object/public/demo-public/v0.0.4/page-agent.js" crossorigin="true" type="text/javascript"></script>
<script>
window.pageAgent.panel.show()
@@ -27,7 +27,7 @@ export default function CdnSetup() {
<h3 className="text-lg font-semibold mb-2 text-yellow-900 dark:text-yellow-300">
</h3>
<ul className="text-foreground/80 space-y-1">
<ul className="text-gray-600 dark:text-gray-300 space-y-1">
<li> 使</li>
<li> HTTPS 使</li>
<li> CSP </li>
@@ -1,4 +1,4 @@
import CodeEditor from '@pages/components/CodeEditor'
import CodeEditor from '@/components/CodeEditor'
export default function Configuration() {
return (
@@ -70,6 +70,14 @@ interface AgentConfig {
// page behavior hooks
/**
* @experimental
* Enable the experimental script execution tool that allows executing generated JavaScript code on the page.
* @note Can cause unpredictable side effects.
* @note May bypass some safe guards and data-masking mechanisms.
*/
experimentalScriptExecutionTool?: boolean
/**
* TODO: @unimplemented
* hook when action causes a new page to be opened
@@ -1,17 +1,17 @@
import CodeEditor from '@pages/components/CodeEditor'
import CodeEditor from '@/components/CodeEditor'
export default function ThirdPartyAgentPage() {
return (
<div>
<h1 className="text-4xl font-bold mb-6"> Agent</h1>
<p className="mb-6 leading-relaxed">
<p className="mb-6 leading-relaxed text-gray-600 dark:text-gray-300">
pageAgent Agent Agent
</p>
<div className="bg-blue-50 dark:bg-blue-900/20 rounded-lg p-4 mb-6">
<h3 className="text-lg font-semibold mb-2 text-blue-900 dark:text-blue-300">💡 </h3>
<p className="text-blue-800 dark:text-blue-200">
Agent "嘴巴""眼睛""手"
"嘴巴""眼睛""手"
</p>
</div>
@@ -48,26 +48,26 @@ const pageAgentTool = {
<h2 className="text-2xl font-bold mb-4"></h2>
<div className="grid md:grid-cols-2 gap-4 mb-6">
<div className="bg-gradient-to-br from-blue-50 to-purple-50 dark:from-gray-800 dark:to-gray-700 p-4 rounded-lg">
<h4 className="font-semibold mb-2">🤖 </h4>
<div className="bg-linear-to-br from-blue-50 to-purple-50 dark:from-gray-800 dark:to-gray-700 p-4 rounded-lg">
<h4 className="font-semibold mb-2 text-gray-900 dark:text-white">🤖 </h4>
<p className="text-sm text-gray-600 dark:text-gray-300">
"帮我提交工单"
</p>
</div>
<div className="bg-gradient-to-br from-green-50 to-blue-50 dark:from-gray-800 dark:to-gray-700 p-4 rounded-lg">
<h4 className="font-semibold mb-2">📋 </h4>
<div className="bg-linear-to-br from-green-50 to-blue-50 dark:from-gray-800 dark:to-gray-700 p-4 rounded-lg">
<h4 className="font-semibold mb-2 text-gray-900 dark:text-white">📋 </h4>
<p className="text-sm text-gray-600 dark:text-gray-300">
"完成客户入职"
</p>
</div>
<div className="bg-gradient-to-br from-purple-50 to-pink-50 dark:from-gray-800 dark:to-gray-700 p-4 rounded-lg">
<h4 className="font-semibold mb-2">🎯 </h4>
<div className="bg-linear-to-br from-purple-50 to-pink-50 dark:from-gray-800 dark:to-gray-700 p-4 rounded-lg">
<h4 className="font-semibold mb-2 text-gray-900 dark:text-white">🎯 </h4>
<p className="text-sm text-gray-600 dark:text-gray-300">
"预订会议室"
</p>
</div>
<div className="bg-gradient-to-br from-orange-50 to-red-50 dark:from-gray-800 dark:to-gray-700 p-4 rounded-lg">
<h4 className="font-semibold mb-2">🔧 </h4>
<div className="bg-linear-to-br from-orange-50 to-red-50 dark:from-gray-800 dark:to-gray-700 p-4 rounded-lg">
<h4 className="font-semibold mb-2 text-gray-900 dark:text-white">🔧 </h4>
<p className="text-sm text-gray-600 dark:text-gray-300">
"重启服务器"
</p>
@@ -77,12 +77,12 @@ const pageAgentTool = {
<h2 className="text-2xl font-bold mb-4"></h2>
<div className="space-y-4 mb-6">
<div className="p-4 bg-gray-50 dark:bg-gray-800 rounded-lg">
<h3 className="text-lg font-semibold mb-2"></h3>
<h3 className="text-lg font-semibold mb-2 text-gray-900 dark:text-white"></h3>
<CodeEditor code={`// @TODO`} language="javascript" />
</div>
<div className="p-4 bg-gray-50 dark:bg-gray-800 rounded-lg">
<h3 className="text-lg font-semibold mb-2"></h3>
<h3 className="text-lg font-semibold mb-2 text-gray-900 dark:text-white"></h3>
<CodeEditor code={`// @TODO`} language="javascript" />
</div>
</div>
@@ -98,9 +98,11 @@ const pageAgentTool = {
</ul>
</div>
<div className="bg-gradient-to-r from-green-50 to-blue-50 dark:from-green-900/20 dark:to-blue-900/20 p-4 rounded-lg">
<h3 className="text-lg font-semibold mb-2">🎉 </h3>
<p className="mb-3"> Agent </p>
<div className="bg-linear-to-r from-green-50 to-blue-50 dark:from-green-900/20 dark:to-blue-900/20 p-4 rounded-lg">
<h3 className="text-lg font-semibold mb-2 text-gray-900 dark:text-white">🎉 </h3>
<p className="mb-3 text-gray-700 dark:text-gray-300">
Agent
</p>
<a
href="/docs/integration/configuration"
className="inline-flex items-center px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors duration-200"
@@ -1,153 +1,141 @@
import { useTranslation } from 'react-i18next'
export default function LimitationsPage() {
const { t } = useTranslation('docs')
return (
<div className="max-w-4xl mx-auto">
<div className="mb-8">
<h1 className="text-4xl font-bold mb-4 text-gray-900 dark:text-white">使</h1>
<p className="text-xl text-gray-600 dark:text-gray-300">
page-agent
</p>
<h1 className="text-4xl font-bold mb-4 text-gray-900 dark:text-white">
{t('limitations.title')}
</h1>
<p className="text-xl text-gray-600 dark:text-gray-300">{t('limitations.subtitle')}</p>
</div>
<div className="prose prose-lg dark:prose-invert max-w-none">
<h2 className="text-2xl font-bold mb-3"></h2>
<h2 className="text-2xl font-bold mb-3">{t('limitations.page_support')}</h2>
<div className="bg-blue-50 dark:bg-blue-900/20 border-l-4 border-blue-400 p-4 mb-6">
<h3 className="font-semibold text-blue-800 dark:text-blue-200 mb-2"></h3>
<h3 className="font-semibold text-blue-800 dark:text-blue-200 mb-2">
{t('limitations.spa_limit_title')}
</h3>
<ul className="text-blue-700 dark:text-blue-300 space-y-2">
<li>
<strong>SPA</strong>
</li>
<li>
<strong></strong>
</li>
<li>
<strong></strong> page-agent
</li>
<li>{t('limitations.spa_limit_1')}</li>
<li>{t('limitations.spa_limit_2')}</li>
<li>{t('limitations.spa_limit_3')}</li>
</ul>
</div>
<h2 className="text-2xl font-bold mb-3"></h2>
<h2 className="text-2xl font-bold mb-3">{t('limitations.interaction_limits')}</h2>
<div className="bg-gray-50 dark:bg-gray-800 rounded-lg p-6 mb-6">
<h3 className="font-semibold mb-4"></h3>
<h3 className="font-semibold mb-4">{t('limitations.supported_ops')}</h3>
<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></span>
<span>{t('limitations.op_click')}</span>
</div>
<div className="flex items-center text-green-600 dark:text-green-400">
<span className="mr-2"></span>
<span></span>
<span>{t('limitations.op_input')}</span>
</div>
<div className="flex items-center text-green-600 dark:text-green-400">
<span className="mr-2"></span>
<span></span>
<span>{t('limitations.op_scroll')}</span>
</div>
<div className="flex items-center text-green-600 dark:text-green-400">
<span className="mr-2"></span>
<span></span>
<span>{t('limitations.op_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></span>
<span>{t('limitations.op_select')}</span>
</div>
<div className="flex items-center text-green-600 dark:text-green-400">
<span className="mr-2"></span>
<span></span>
<span>{t('limitations.op_focus')}</span>
</div>
</div>
</div>
<h3 className="font-semibold mb-4"></h3>
<h3 className="font-semibold mb-4">{t('limitations.unsupported_ops')}</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>hover</span>
<span>{t('limitations.op_hover')}</span>
</div>
<div className="flex items-center text-red-600 dark:text-red-400">
<span className="mr-2"></span>
<span></span>
<span>{t('limitations.op_drag')}</span>
</div>
<div className="flex items-center text-red-600 dark:text-red-400">
<span className="mr-2"></span>
<span></span>
<span>{t('limitations.op_context')}</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></span>
<span>{t('limitations.op_draw')}</span>
</div>
<div className="flex items-center text-red-600 dark:text-red-400">
<span className="mr-2"></span>
<span></span>
<span>{t('limitations.op_keyboard')}</span>
</div>
<div className="flex items-center text-red-600 dark:text-red-400">
<span className="mr-2"></span>
<span></span>
<span>{t('limitations.op_position')}</span>
</div>
</div>
</div>
</div>
<h2 className="text-2xl font-bold mb-3"></h2>
<h2 className="text-2xl font-bold mb-3">{t('limitations.understanding_limits')}</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"></h3>
<p className="text-red-700 dark:text-red-300 mb-3">
page-agent DOM <strong></strong>
</p>
<h3 className="font-semibold text-red-800 dark:text-red-200 mb-2">
{t('limitations.no_vision_title')}
</h3>
<p className="text-red-700 dark:text-red-300 mb-3">{t('limitations.no_vision_desc')}</p>
<ul className="text-red-700 dark:text-red-300 space-y-1">
<li>
<strong></strong>
</li>
<li>
<strong>Canvas </strong> Canvas
</li>
<li>
<strong>WebGL 3D </strong> 3D
</li>
<li>
<strong>SVG </strong> SVG
</li>
<li>{t('limitations.no_vision_1')}</li>
<li>{t('limitations.no_vision_2')}</li>
<li>{t('limitations.no_vision_3')}</li>
<li>{t('limitations.no_vision_4')}</li>
</ul>
</div>
<h2 className="text-2xl font-bold mb-3"></h2>
<h2 className="text-2xl font-bold mb-3">{t('limitations.website_requirements')}</h2>
<div className="bg-gray-50 dark:bg-gray-800 rounded-lg p-6 mb-6">
<div className="space-y-4">
<div>
<h3 className="font-semibold mb-2"></h3>
<h3 className="font-semibold mb-2">{t('limitations.req_semantic_title')}</h3>
<p className="text-gray-600 dark:text-gray-300">
DOM
accessibility AI
{t('limitations.req_semantic_desc')}
</p>
</div>
<div>
<h3 className="font-semibold mb-2">UI/UX</h3>
<p className="text-gray-600 dark:text-gray-300">
AI
</p>
<h3 className="font-semibold mb-2">{t('limitations.req_ux_title')}</h3>
<p className="text-gray-600 dark:text-gray-300">{t('limitations.req_ux_desc')}</p>
</div>
<div>
<h3 className="font-semibold mb-2"></h3>
<p className="text-gray-600 dark:text-gray-300">modern browser</p>
<h3 className="font-semibold mb-2">{t('limitations.req_env_title')}</h3>
<p className="text-gray-600 dark:text-gray-300">{t('limitations.req_env_desc')}</p>
</div>
</div>
</div>
<h2></h2>
<h2>{t('limitations.future')}</h2>
<div className="bg-green-50 dark:bg-green-900/20 border-l-4 border-green-400 p-4">
<h3 className="font-semibold text-green-800 dark:text-green-200 mb-2"></h3>
<h3 className="font-semibold text-green-800 dark:text-green-200 mb-2">
{t('limitations.future_title')}
</h3>
<ul className="text-green-700 dark:text-green-300 space-y-1">
<li> </li>
<li> </li>
<li> </li>
<li> </li>
<li>{t('limitations.future_1')}</li>
<li>{t('limitations.future_2')}</li>
<li>{t('limitations.future_3')}</li>
<li>{t('limitations.future_4')}</li>
</ul>
</div>
</div>
@@ -0,0 +1,212 @@
import { useTranslation } from 'react-i18next'
export default function Overview() {
const { t } = useTranslation('docs')
return (
<article>
{/* 头图 */}
<figure className="mb-8 rounded-xl overflow-hidden">
<img
src="https://img.alicdn.com/imgextra/i1/O1CN01RY0Wvh26ATVeDIX7v_!!6000000007621-0-tps-1672-512.jpg"
alt="page-agent"
className="w-full h-64 object-cover"
/>
</figure>
<div className="mb-8">
<h1 className="text-4xl font-bold mb-4">{t('overview.title')}</h1>
<p className="text-xl text-gray-600 dark:text-gray-300 mb-4 leading-relaxed">
{t('overview.subtitle')}
</p>
{/* Status Badges */}
<div className="flex flex-wrap gap-2 items-center">
<a
href="https://www.npmjs.com/package/page-agent"
target="_blank"
rel="noopener noreferrer"
>
<img src="https://badge.fury.io/js/page-agent.svg" alt="npm version" />
</a>
<a href="https://opensource.org/licenses/MIT" target="_blank" rel="noopener noreferrer">
<img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="MIT License" />
</a>
<a href="http://www.typescriptlang.org/" target="_blank" rel="noopener noreferrer">
<img
src="https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg"
alt="TypeScript"
/>
</a>
<a
href="https://www.npmjs.com/package/page-agent"
target="_blank"
rel="noopener noreferrer"
>
<img src="https://img.shields.io/npm/dt/page-agent.svg" alt="Downloads" />
</a>
<a
href="https://bundlephobia.com/package/page-agent"
target="_blank"
rel="noopener noreferrer"
>
<img src="https://img.shields.io/bundlephobia/minzip/page-agent" alt="Bundle Size" />
</a>
<a href="https://github.com/alibaba/page-agent" target="_blank" rel="noopener noreferrer">
<img
src="https://img.shields.io/github/stars/alibaba/page-agent.svg"
alt="GitHub stars"
/>
</a>
</div>
</div>
<section>
<h2 className="text-2xl font-bold mb-4">{t('overview.what_is')}</h2>
<p className="text-gray-600 dark:text-gray-300 mb-8 leading-relaxed ">
{t('overview.what_is_desc')}
</p>
</section>
<section>
<h2 className="text-2xl font-bold mb-3">{t('overview.features_title')}</h2>
<div className="grid md:grid-cols-2 gap-4 mb-8" role="list">
<div className="p-4 bg-blue-50 dark:bg-blue-900/20 rounded-lg">
<h3 className="text-lg font-semibold mb-2 text-blue-900 dark:text-blue-300">
{t('overview.feature_dom.title')}
</h3>
<p className="text-gray-700 dark:text-gray-300">{t('overview.feature_dom.desc')}</p>
</div>
<div className="p-4 bg-purple-50 dark:bg-purple-900/20 rounded-lg">
<h3 className="text-lg font-semibold mb-2 text-purple-900 dark:text-purple-300">
{t('overview.feature_secure.title')}
</h3>
<p className="text-gray-700 dark:text-gray-300">{t('overview.feature_secure.desc')}</p>
</div>
<div className="p-4 bg-green-50 dark:bg-green-900/20 rounded-lg">
<h3 className="text-lg font-semibold mb-2 text-green-900 dark:text-green-300">
{t('overview.feature_backend.title')}
</h3>
<p className="text-gray-700 dark:text-gray-300">{t('overview.feature_backend.desc')}</p>
</div>
<div className="p-4 bg-orange-50 dark:bg-orange-900/20 rounded-lg">
<h3 className="text-lg font-semibold mb-2 text-orange-900 dark:text-orange-300">
{t('overview.feature_accessible.title')}
</h3>
<p className="text-gray-700 dark:text-gray-300">
{t('overview.feature_accessible.desc')}
</p>
</div>
</div>
<h2 className="text-2xl font-bold mb-4">{t('overview.vs_browser_use')}</h2>
<div className="overflow-x-auto mb-8">
<table className="w-full border-collapse border border-gray-300 dark:border-gray-600">
<thead>
<tr className="bg-gray-50 dark:bg-gray-800">
<th className="border border-gray-300 dark:border-gray-600 px-4 py-3 text-left">
{t('overview.table_feature')}
</th>
<th className="border border-gray-300 dark:border-gray-600 px-4 py-3 text-left">
page-agent
</th>
<th className="border border-gray-300 dark:border-gray-600 px-4 py-3 text-left">
browser-use
</th>
</tr>
</thead>
<tbody>
<tr>
<td className="border border-gray-300 dark:border-gray-600 px-4 py-3 font-medium">
{t('overview.table_deployment')}
</td>
<td className="border border-gray-300 dark:border-gray-600 px-4 py-3">
{t('overview.table_deployment_pa')}
</td>
<td className="border border-gray-300 dark:border-gray-600 px-4 py-3">
{t('overview.table_deployment_bu')}
</td>
</tr>
<tr>
<td className="border border-gray-300 dark:border-gray-600 px-4 py-3 font-medium">
{t('overview.table_scope')}
</td>
<td className="border border-gray-300 dark:border-gray-600 px-4 py-3">
{t('overview.table_scope_pa')}
</td>
<td className="border border-gray-300 dark:border-gray-600 px-4 py-3">
{t('overview.table_scope_bu')}
</td>
</tr>
<tr>
<td className="border border-gray-300 dark:border-gray-600 px-4 py-3 font-medium">
{t('overview.table_user')}
</td>
<td className="border border-gray-300 dark:border-gray-600 px-4 py-3">
{t('overview.table_user_pa')}
</td>
<td className="border border-gray-300 dark:border-gray-600 px-4 py-3">
{t('overview.table_user_bu')}
</td>
</tr>
<tr>
<td className="border border-gray-300 dark:border-gray-600 px-4 py-3 font-medium">
{t('overview.table_scenario')}
</td>
<td className="border border-gray-300 dark:border-gray-600 px-4 py-3">
{t('overview.table_scenario_pa')}
</td>
<td className="border border-gray-300 dark:border-gray-600 px-4 py-3">
{t('overview.table_scenario_bu')}
</td>
</tr>
</tbody>
</table>
</div>
<h2 className="text-2xl font-bold mb-4">{t('overview.use_cases_title')}</h2>
<ul className="space-y-4 mb-8">
<li className="flex items-start space-x-3">
<span className="w-6 h-6 min-w-6 bg-blue-500 text-white rounded-full flex items-center justify-center font-bold text-sm mt-0.5 shrink-0">
1
</span>
<div className="text-gray-700 dark:text-gray-300">
<strong>{t('overview.use_case1_title')}</strong> {t('overview.use_case1_desc')}
</div>
</li>
<li className="flex items-start space-x-3">
<span className="w-6 h-6 min-w-6 bg-green-500 text-white rounded-full flex items-center justify-center font-bold text-sm mt-0.5 shrink-0">
2
</span>
<div className="text-gray-700 dark:text-gray-300">
<strong>{t('overview.use_case2_title')}</strong> {t('overview.use_case2_desc')}
</div>
</li>
<li className="flex items-start space-x-3">
<span className="w-6 h-6 min-w-6 bg-purple-500 text-white rounded-full flex items-center justify-center font-bold text-sm mt-0.5 shrink-0">
3
</span>
<div className="text-gray-700 dark:text-gray-300">
<strong>{t('overview.use_case3_title')}</strong> {t('overview.use_case3_desc')}
</div>
</li>
<li className="flex items-start space-x-3">
<span className="w-6 h-6 min-w-6 bg-orange-500 text-white rounded-full flex items-center justify-center font-bold text-sm mt-0.5 shrink-0">
4
</span>
<div className="text-gray-700 dark:text-gray-300">
<strong>{t('overview.use_case4_title')}</strong> {t('overview.use_case4_desc')}
</div>
</li>
</ul>
</section>
</article>
)
}
@@ -1,31 +1,35 @@
import BetaNotice from '@pages/components/BetaNotice'
import CodeEditor from '@pages/components/CodeEditor'
import { useTranslation } from 'react-i18next'
import BetaNotice from '@/components/BetaNotice'
import CodeEditor from '@/components/CodeEditor'
export default function QuickStart() {
const { t } = useTranslation('docs')
return (
<div>
<h1 className="text-4xl font-bold mb-6">Quick Start</h1>
<h1 className="text-4xl font-bold mb-6">{t('quick_start.title')}</h1>
<p className=" mb-6 leading-relaxed"> page-agent </p>
<p className=" mb-6 leading-relaxed">{t('quick_start.subtitle')}</p>
<h2 className="text-2xl font-bold mb-3"></h2>
<h2 className="text-2xl font-bold mb-3">{t('quick_start.installation')}</h2>
<div className="space-y-4 mb-6">
<div className="p-4 bg-blue-50 dark:bg-blue-900/20 rounded-lg">
<h3 className="text-lg font-semibold mb-2 text-blue-900 dark:text-blue-300">
1.
{t('quick_start.step1_title')}
</h3>
<div className="space-y-3">
<div>
<p className="text-sm font-medium mb-2">CDN </p>
<p className="text-sm font-medium mb-2">{t('quick_start.step1_cdn')}</p>
<CodeEditor
code={`// 仅供测试使用
<script src="https://hwcxiuzfylggtcktqgij.supabase.co/storage/v1/object/public/demo-public/v0.0.2/page-agent.js" crossorigin="true" type="text/javascript"></script>`}
<script src="https://hwcxiuzfylggtcktqgij.supabase.co/storage/v1/object/public/demo-public/v0.0.4/page-agent.js" crossorigin="true" type="text/javascript"></script>`}
language="html"
/>
</div>
<div>
<p className="text-sm font-medium mb-2">NPM </p>
<p className="text-sm font-medium mb-2">{t('quick_start.step1_npm')}</p>
<CodeEditor
code={`// npm install page-agent
import PageAgent from 'page-agent'`}
@@ -37,7 +41,7 @@ import PageAgent from 'page-agent'`}
<div className="p-4 bg-green-50 dark:bg-green-900/20 rounded-lg">
<h3 className="text-lg font-semibold mb-2 text-green-900 dark:text-green-300">
2.
{t('quick_start.step2_title')}
</h3>
<CodeEditor
code={`// 仅供测试使用,生产环境需要配置 LLM 接入点,本工具不提供 LLM 服务
@@ -60,7 +64,7 @@ const agent = new PageAgent({
<div className="p-4 bg-purple-50 dark:bg-purple-900/20 rounded-lg">
<h3 className="text-lg font-semibold mb-2 text-purple-900 dark:text-purple-300">
3. 使
{t('quick_start.step3_title')}
</h3>
<CodeEditor
code={`// 程序化执行自然语言指令
+74
View File
@@ -0,0 +1,74 @@
# 国际化配置说明
本项目使用 `react-i18next` 实现国际化支持。
## 目录结构
```
pages/i18n/
├── config.ts # i18next 配置和初始化
├── types.ts # TypeScript 类型声明
├── locales/
│ ├── zh-CN/ # 中文翻译
│ │ ├── common.json # 通用组件(Header, Footer等)
│ │ ├── home.json # 首页
│ │ └── docs.json # 文档页(待完善)
│ └── en-US/ # 英文翻译
│ ├── common.json
│ ├── home.json
│ └── docs.json
└── README.md # 本文件
```
## 使用方法
### 在组件中使用
```tsx
import { useTranslation } from 'react-i18next'
function MyComponent() {
const { t } = useTranslation('common') // 指定命名空间
return <h1>{t('header.nav_docs')}</h1>
}
```
### 使用多个命名空间
```tsx
const { t } = useTranslation(['home', 'common'])
// 使用时指定命名空间
t('home:hero.title')
t('common:header.nav_docs')
```
## 语言切换
用户可以通过以下方式切换语言:
1. **自动检测**:首次访问根据浏览器语言自动选择
2. **手动切换**:点击页面右上角的语言切换按钮
3. **持久化**:语言选择保存在 `localStorage` 中,刷新后保持
## 添加新翻译
1. 在对应的 JSON 文件中添加翻译条目(如 `zh-CN/home.json`
2. 在对应的英文文件中添加翻译(如 `en-US/home.json`
3. 在组件中使用 `t('namespace:key')` 获取翻译
## TypeScript 支持
`types.ts` 文件提供了类型声明,使得翻译 key 具有:
- 自动补全
- 编译期类型检查
- 防止拼写错误
## 待完成
- [ ] 文档页翻译(`docs.json`
- [ ] DocsLayout 导航结构国际化
- [ ] 404 页面国际化
+45
View File
@@ -0,0 +1,45 @@
import i18n from 'i18next'
import LanguageDetector from 'i18next-browser-languagedetector'
import { initReactI18next } from 'react-i18next'
import commonEn from './locales/en-US/common'
import docsEn from './locales/en-US/docs'
import homeEn from './locales/en-US/home'
import commonZh from './locales/zh-CN/common'
import docsZh from './locales/zh-CN/docs'
import homeZh from './locales/zh-CN/home'
const resources = {
'zh-CN': {
common: commonZh,
home: homeZh,
docs: docsZh,
},
'en-US': {
common: commonEn,
home: homeEn,
docs: docsEn,
},
}
i18n
.use(LanguageDetector)
.use(initReactI18next)
.init({
resources,
fallbackLng: 'en-US',
defaultNS: 'common',
// 语言检测配置
detection: {
// localStorage 优先(用户手动选择),其次检测浏览器语言
order: ['localStorage', 'navigator'],
caches: ['localStorage'],
},
interpolation: {
escapeValue: false, // React 已经做了 XSS 防护
},
})
export default i18n
@@ -0,0 +1,40 @@
export default {
header: {
logo_alt: 'page-agent home',
slogan: 'GUI Agent in your webpage',
nav_docs: 'Docs',
nav_source: 'GitHub',
mobile_menu: 'Open navigation',
},
footer: {
copyright: '© 2025 page-agent. All rights reserved.',
github_label: 'Visit GitHub repository',
},
beta_notice: {
title: 'Beta Stage',
content:
'Current features are incomplete and the API may change at any time. Please do not use in production environments before the official release.',
},
language: {
zh: '中文',
en: 'English',
switch_label: 'Switch language',
},
nav: {
introduction: 'Introduction',
features: 'Features',
integration: 'Integration',
overview: 'Overview',
quick_start: 'Quick Start',
limitations: 'Limitations',
model_integration: 'Model Integration',
custom_tools: 'Custom Tools',
knowledge_injection: 'Knowledge Injection',
security_permissions: 'Security & Permissions',
data_masking: 'Data Masking',
cdn_setup: 'CDN Setup',
configuration: 'Configuration',
best_practices: 'Best Practices',
third_party_agent: 'Third-party Agent',
},
}
@@ -0,0 +1,172 @@
export default {
overview: {
title: 'Overview',
subtitle:
'page-agent is a purely web-based GUI Agent. Gives your website an AI operator in simple steps.',
what_is: 'What is page-agent?',
what_is_desc:
'page-agent is an embedded GUI Agent. Unlike traditional browser automation tools, page-agent targets web developers, not scrapers or agent clients builders. Integrate it into your site to let users interact with pages through natural language.',
features_title: 'Core Features',
feature_dom: {
title: '🧠 Smart DOM Analysis',
desc: 'DOM-based analysis with high-intensity dehydration. No visual recognition needed. Pure text for fast and precise operations.',
},
feature_secure: {
title: '🔒 Secure & Controllable',
desc: 'Supports operation allowlists, data masking protection. Inject custom knowledge to make AI work by your rules.',
},
feature_backend: {
title: '⚡ Zero Backend',
desc: 'CDN or NPM import with custom LLM endpoints.',
},
feature_accessible: {
title: '♿ Accessible Intelligence',
desc: 'Provides natural language interface for complex B2B systems and admin panels. Makes software easy for everyone.',
},
vs_browser_use: 'vs. browser-use',
table_feature: 'Feature',
table_deployment: 'Deployment',
table_deployment_pa: 'Embedded component',
table_deployment_bu: 'External tool',
table_scope: 'Scope',
table_scope_pa: 'Current page',
table_scope_bu: 'Entire browser',
table_user: 'Target Users',
table_user_pa: 'Web developers',
table_user_bu: 'Scraper/Agent developers',
table_scenario: 'Use Case',
table_scenario_pa: 'UX enhancement',
table_scenario_bu: 'Automation tasks',
use_cases_title: 'Use Cases',
use_case1_title: 'Connect Support Bots:',
use_case1_desc:
"Turn your support assistant into a full agent. Customer service bots no longer just say 'Please click the settings button then click...'—they operate for users directly.",
use_case2_title: 'Modernize Legacy Apps:',
use_case2_desc:
'One line of code transforms old apps into agents. Product experts help users navigate complex B2B software. Reduce support costs and improve satisfaction.',
use_case3_title: 'Interactive Training:',
use_case3_desc:
"Demonstrate workflows in real-time. Let AI show the complete process of 'how to submit an expense report.'",
use_case4_title: 'Accessibility:',
use_case4_desc:
'Provide natural language interaction for visually impaired and elderly users. Connect screen readers or voice assistants to make software accessible to everyone.',
get_started_title: '🚀 Get Started',
get_started_desc:
'Ready to add an AI operator to your website? Check our quick start guide for integration in minutes.',
get_started_button: 'Quick Start →',
},
quick_start: {
title: 'Quick Start',
subtitle: 'Integrate page-agent in minutes.',
installation: 'Installation Steps',
step1_title: '1. Import Options',
step1_cdn: 'CDN Import',
step1_npm: 'NPM Install',
step2_title: '2. Initialize Configuration',
step3_title: '3. Start Using',
},
limitations: {
title: 'Limitations',
subtitle: "Understand page-agent's current capabilities and technical constraints",
page_support: 'Page Support Limitations',
spa_limit_title: 'Single Page Application Limits',
spa_limit_1: '• SPA only: Currently operates within a single page',
spa_limit_2: '• Multi-page relay in design: Cannot execute continuous tasks across pages yet',
spa_limit_3: '• Requires integration: Cannot operate on sites without page-agent',
interaction_limits: 'Interaction Limitations',
supported_ops: 'Supported Operations',
op_click: 'Click',
op_input: 'Text input',
op_scroll: 'Scroll',
op_submit: 'Form submit',
op_select: 'Select',
op_focus: 'Focus',
unsupported_ops: 'Unsupported Operations',
op_hover: 'Mouse hover',
op_drag: 'Drag & drop',
op_context: 'Right-click menu',
op_draw: 'Drawing',
op_keyboard: 'Keyboard shortcuts',
op_position: 'Position-based control',
understanding_limits: 'Understanding Limitations',
no_vision_title: 'No Visual Recognition',
no_vision_desc:
'page-agent operates based on DOM structure with no visual recognition. Cannot understand:',
no_vision_1: '• Image content: Cannot recognize text, icons, or visual elements in images',
no_vision_2: '• Canvas: Cannot understand graphics drawn on Canvas',
no_vision_3: '• WebGL 3D: Cannot operate elements in 3D scenes',
no_vision_4: '• SVG graphics: Cannot understand visual content and paths in SVG',
website_requirements: 'Website Requirements',
req_semantic_title: 'Semantic & Usability',
req_semantic_desc:
'All operations rely on semantic tags and attributes. Poor semantic structure or lack of accessibility features may affect AI understanding accuracy.',
req_ux_title: 'UI/UX',
req_ux_desc:
'Counter-intuitive interaction rules, visual-only operation hints, complex mouse interactions, or rapidly appearing/disappearing elements can affect AI understanding and operation.',
req_env_title: 'Environment',
req_env_desc: 'modern browser',
future: 'Future Plans',
future_title: 'Coming Soon',
future_1: '• Multi-page relay capabilities',
future_2: '• Richer mouse interaction support',
future_3: '• Basic visual understanding',
future_4: '• Smarter error recovery',
},
model_integration: {
title: 'Model Integration',
subtitle:
'Supports OpenAI-compatible models with tool call support, including public cloud services and private deployments.',
recommended: 'Recommended Models',
model_gpt4_title: '⚡ gpt-4.1-mini',
model_gpt4_badge: 'Evaluation Baseline ✅',
model_gpt4_1: '• Cost-effective',
model_gpt4_2: '• Fast',
model_gpt4_3: '• High success rate',
model_deepseek_title: '💰 DeepSeek-3.2',
model_deepseek_badge: 'Economical',
model_deepseek_1: '• Much cheaper than similar models',
model_deepseek_2: '• ToolCall may error but usually auto-recovers',
model_deepseek_3: "• This site's free demo uses DeepSeek",
model_qwen_title: '🛡️ qwen3',
model_qwen_badge: 'Secure & Compliant',
model_qwen_1: '• Controllable, decent results, reasonable price',
model_qwen_2: '• ToolCall may error but usually auto-recovers',
model_qwen_3: '• Best for scenarios with detailed steps',
model_gemini_title: '⚡ gemini-2.5-flash',
model_gemini_badge: 'Highly efficient, high success rate, reasonable price',
available: 'Available Models',
available_verified: '✅ Verified Working',
tips: 'Tips',
tip_1: 'Reasoning models (like GPT-5) are slower with no advantage',
tip_2:
"Non-OpenAI models don't guarantee JSON schema compliance—tool call may error but usually recovers. Higher temperature recommended",
tip_3: 'Small/nano models perform poorly',
security: '🔐 Production Authentication',
security_warning: '⚠️ Never commit real LLM API Keys to your frontend codebase',
security_desc:
'In production environments, to hide the real LLM API Keys, we recommend the following architecture:',
security_backend_proxy: 'Backend Proxy Pattern',
security_backend_desc:
'Set up a backend LLM proxy endpoint that uses the same authentication method as other APIs in your website, such as:',
security_method_1: '• Session/Cookie-based authentication',
security_method_2: '• OIDC (OpenID Connect) single sign-on',
security_method_3: '• Temporary Access Key or JWT Token',
configuration: 'Configuration',
},
custom_tools: {
title: 'Custom Tools',
subtitle:
'Extend AI Agent capabilities by registering custom tools. Use Zod to define strict input schemas for safe business logic calls.',
registration: 'Tool Registration',
registration_desc:
'Each custom tool requires four core properties: name, description, input schema, and execute function.',
page_filter: 'Page Filter',
page_filter_desc:
'Control tool visibility on specific pages via the pageFilter property to enhance security and UX.',
best_practices: 'Best Practices',
bp_performance: '⚡ Performance Optimization',
bp_1: '• Use pageFilter to reduce unnecessary tool loading',
bp_2: '• Implement appropriate caching in execute functions',
bp_3: '• Avoid long-running sync operations in tools',
},
}
@@ -0,0 +1,79 @@
export default {
hero: {
badge: 'GUI Agent in your webpage',
title_line1: 'The AI Operator',
title_line2: 'Living in Your Web App',
subtitle_emoji: '🪄 One line of CDN',
subtitle_main: ' adds intelligent GUI Agents to your website.',
subtitle_detail: 'Users give natural language commands, AI handles the rest.',
tab_try: '🚀 Try It Now',
tab_other: '🌐 Try on Other Sites',
input_placeholder: 'Describe what you want AI to do...',
execute_button: 'Run',
default_task:
'Goto docs in navigation bar, find Quick-Start section, and summarize in markdown',
},
try_other: {
step1_title: 'Step 1:',
step1_content: 'Show your bookmarks bar',
step2_title: 'Step 2:',
step2_content: 'Drag this button to your bookmarks',
step3_title: 'Step 3:',
step3_content: 'Click the bookmark on any site to activate',
notice_title: '⚠️ Heads Up',
notice_items: {
item1: 'Demo only—link may expire without notice',
item2: 'This free demo uses DeepSeek API (see their terms and privacy policy)',
item3: 'Some sites block script injection (CSP policies)',
item4: 'Works on single-page apps only—reload required after navigation',
item5: 'Text-only understanding—no image recognition or drag-and-drop',
item6_prefix: 'Full limitations in',
item6_link: 'Docs',
},
},
benefits: {
no_backend: 'Pure Front-end Solution',
private_model: 'Your Own Models',
data_masking: 'Built-in Privacy',
open_source: 'MIT Open Source',
},
features: {
section_title: 'Why PageAgent',
in_page: {
title: 'In-page Solution',
desc: 'Runs entirely within your page. No browser extensions, no headless browsers, and no backend required.',
},
secure_integration: {
title: 'Secure by Design',
desc: 'Control what AI can access with allowlists, data masking, and custom knowledge injection. Your rules, your data.',
},
zero_backend: {
title: 'Zero Backend Setup',
desc: 'Just drop in a script. Works with any LLM provider—OpenAI, Anthropic, or your own models.',
},
accessible: {
title: 'Natural Language UI',
desc: 'Transform complex admin panels into chat interfaces. Make powerful tools accessible to everyone, not just experts.',
},
},
use_cases: {
section_title: 'Where It Shines',
section_subtitle: 'From simple forms to complex workflows, AI understands and executes',
case1: {
title: 'Supercharge Support Bots',
desc: 'Stop telling users where to click—let AI do it for them. Turn your chatbot from a guide into an operator that actually completes tasks.',
},
case2: {
title: 'Modernize Legacy Apps',
desc: 'Add AI superpowers to old software without rebuilding. One script tag transforms complex enterprise tools into chat-driven interfaces.',
},
case3: {
title: 'Interactive Walkthroughs',
desc: "Show, don't tell. Let AI demonstrate workflows in real-time—perfect for onboarding or training new users on complex systems.",
},
case4: {
title: 'Accessibility First',
desc: 'Make web apps accessible through natural language. Perfect for screen readers, voice control, or users who find traditional interfaces challenging.',
},
},
}
@@ -0,0 +1,39 @@
export default {
header: {
logo_alt: 'page-agent 首页',
slogan: 'GUI Agent in your webpage',
nav_docs: '文档',
nav_source: 'GitHub',
mobile_menu: '打开导航栏',
},
footer: {
copyright: '© 2025 page-agent. All rights reserved.',
github_label: '访问 GitHub 仓库',
},
beta_notice: {
title: 'Beta 阶段',
content: '当前功能未完成,接口可能随时变更。正式版本发布前请勿用于生产环境。',
},
language: {
zh: '中文',
en: 'English',
switch_label: '切换语言',
},
nav: {
introduction: '介绍',
features: '功能特性',
integration: '集成指南',
overview: '概览',
quick_start: '快速开始',
limitations: '使用限制',
model_integration: '模型接入',
custom_tools: '自定义工具',
knowledge_injection: '知识库注入',
security_permissions: '安全与权限',
data_masking: '数据脱敏',
cdn_setup: 'CDN 引入',
configuration: '配置选项',
best_practices: '最佳实践',
third_party_agent: '接入第三方 Agent',
},
}
@@ -0,0 +1,168 @@
export default {
overview: {
title: 'Overview',
subtitle:
'page-agent 是一个完全基于 Web 技术的 GUI Agent,简单几步,让你的网站拥有 AI 操作员。',
what_is: '什么是 page-agent',
what_is_desc:
'page-agent 是一个页面内嵌式 GUI Agent。与传统的浏览器自动化工具不同,page-agent 面向网站开发者,而非爬虫或Agent客户端开发者;将 Agent 集成到你的网站中,让用户可以通过自然语言与页面进行交互。',
features_title: '核心特性',
feature_dom: {
title: '🧠 智能 DOM 理解',
desc: '基于 DOM 分析,高强度脱水。无需视觉识别,纯文本实现精准操作。',
},
feature_secure: {
title: '🔒 安全可控',
desc: '支持操作黑白名单、数据脱敏保护。注入自定义知识库,让 AI 按你的规则工作。',
},
feature_backend: {
title: '⚡ 零后端部署',
desc: 'CDN 或 NPM 引入,自定义 LLM 接入点。',
},
feature_accessible: {
title: '♿ 普惠智能',
desc: '为复杂 B端系统、管理后台提供自然语言入口。让每个用户都能轻松上手。',
},
vs_browser_use: '与 browser-use 的区别',
table_feature: '特性',
table_deployment: '部署方式',
table_deployment_pa: '页面内嵌组件',
table_deployment_bu: '外部工具',
table_scope: '操作范围',
table_scope_pa: '当前页面',
table_scope_bu: '整个浏览器',
table_user: '目标用户',
table_user_pa: '网站开发者',
table_user_bu: '爬虫/Agent 开发者',
table_scenario: '使用场景',
table_scenario_pa: '用户体验增强',
table_scenario_bu: '自动化任务',
use_cases_title: '应用场景',
use_case1_title: '对接答疑机器人:',
use_case1_desc:
'把你的答疑助手变成全能Agent。客服机器人不再只说「请先点击设置按钮然后点击...」,而是直接帮用户现场操作。',
use_case2_title: '交互升级/智能化改造:',
use_case2_desc:
'一行代码,老应用变身Agent,产品专家帮用户操作复杂 B 端软件。降低人工支持成本,提高用户满意度。',
use_case3_title: '产品教学:',
use_case3_desc:
'向用户演示交互过程,边做边教。例如让AI演示「如何提交报销申请」的完整操作流程。',
use_case4_title: '无障碍支持:',
use_case4_desc:
'为视障用户、老年用户提供自然语言交互,对接屏幕阅读器或语音助理,让软件人人可用。',
get_started_title: '🚀 开始使用',
get_started_desc:
'准备好为你的网站添加 AI 操作员了吗?查看我们的快速开始指南,几分钟内完成集成。',
get_started_button: '快速开始 →',
},
quick_start: {
title: 'Quick Start',
subtitle: '几分钟内完成 page-agent 的集成。',
installation: '安装步骤',
step1_title: '1. 引入方式',
step1_cdn: 'CDN 引入',
step1_npm: 'NPM 安装',
step2_title: '2. 初始化配置',
step3_title: '3. 开始使用',
},
limitations: {
title: '使用限制',
subtitle: '了解 page-agent 当前的功能边界和技术限制',
page_support: '页面支持限制',
spa_limit_title: '单页应用限制',
spa_limit_1: '• 仅支持单页应用(SPA):目前只能在单个页面内进行操作',
spa_limit_2: '• 多页接力功能正在设计中:暂时无法跨页面执行连续任务',
spa_limit_3: '• 无法操作未接入该能力的网站:需要目标网站主动集成 page-agent',
interaction_limits: '交互行为限制',
supported_ops: '支持的操作',
op_click: '点击操作',
op_input: '文本输入',
op_scroll: '页面滚动',
op_submit: '表单提交',
op_select: '选择操作',
op_focus: '焦点切换',
unsupported_ops: '不支持的操作',
op_hover: '鼠标悬停(hover',
op_drag: '拖拽操作',
op_context: '右键菜单',
op_draw: '图形绘制',
op_keyboard: '键盘快捷键',
op_position: '基于点击区域或鼠标位置的控制',
understanding_limits: '网页理解限制',
no_vision_title: '无视觉能力',
no_vision_desc: 'page-agent 基于 DOM 结构进行理解和操作,没有视觉识别能力,无法理解以下内容:',
no_vision_1: '• 图片内容:无法识别图片中的文字、图标或视觉元素',
no_vision_2: '• Canvas 画布:无法理解 Canvas 中绘制的图形和内容',
no_vision_3: '• WebGL 3D 内容:无法操作 3D 场景中的元素',
no_vision_4: '• SVG 图形:无法理解 SVG 中的视觉内容和路径',
website_requirements: '被操作网站要求',
req_semantic_title: '语义化和易用性',
req_semantic_desc:
'所有操作都基于 DOM 元素的语义化标签和属性。如果页面结构不够语义化,或者没有任何 accessibility 特性,可能影响 AI 的理解准确性。',
req_ux_title: 'UI/UX',
req_ux_desc:
'反常识的交互规则、基于视觉的操作提示、复杂的鼠标交互、快速出现快速消失的元素等,都会影响 AI 的理解和操作。',
req_env_title: '环境要求',
req_env_desc: 'modern browser',
future: '未来规划',
future_title: '即将支持',
future_1: '• 多页面接力操作能力',
future_2: '• 更丰富的鼠标交互支持',
future_3: '• 基础的视觉理解能力',
future_4: '• 更智能的错误恢复机制',
},
model_integration: {
title: '模型接入',
subtitle: '当前支持符合 OpenAI 接口规范且支持 tool call 的模型,包括公有云服务和私有部署方案。',
recommended: '推荐模型',
model_gpt4_title: '⚡ gpt-4.1-mini',
model_gpt4_badge: '评估基准 ✅',
model_gpt4_1: '• 性价比高',
model_gpt4_2: '• 速度快',
model_gpt4_3: '• 成功率高',
model_deepseek_title: '💰 DeepSeek-3.2',
model_deepseek_badge: '经济实惠',
model_deepseek_1: '• 价格远低于同等级其他模型',
model_deepseek_2: '• ToolCall 有出错率,通常能够自动修复',
model_deepseek_3: '• 本网站提供的免费试用为 DeepSeek',
model_qwen_title: '🛡️ qwen3',
model_qwen_badge: '安全合规',
model_qwen_1: '• 可控、效果尚可,价格合理',
model_qwen_2: '• ToolCall 有出错率,通常能够自动修复',
model_qwen_3: '• 适合能给出详细步骤的场景',
model_gemini_title: '⚡ gemini-2.5-flash',
model_gemini_badge: '极其高效,成功率高,价格合理',
available: '可用模型',
available_verified: '✅ 已验证可用',
tips: '提示',
tip_1: 'reasoning 模型(如 GPT-5),速度偏慢,没有必要',
tip_2:
'不保证 json schema 的模型(openAI 以外的几乎所有模型),tool call 有概率出错,通常能自动修复,建议 temperature 设置高一些',
tip_3: '小模型、nano 模型,效果不佳',
security: '🔐 生产环境鉴权建议',
security_warning: '⚠️ 永远不要把真实的 LLM API Key 发布到前端代码库',
security_desc: '在实际应用中,为了隐藏真实的 LLM API Key,建议采用以下架构:',
security_backend_proxy: '后端代理转发',
security_backend_desc:
'在后端搭建一个 LLM 流量转发接口,该接口使用与你网站上其他接口相同的鉴权方式,例如:',
security_method_1: '• Session/Cookie 会话认证',
security_method_2: '• OIDC (OpenID Connect) 单点登录',
security_method_3: '• 临时 Access Key 或 JWT Token',
configuration: '配置方式',
},
custom_tools: {
title: '自定义工具',
subtitle:
'通过注册自定义工具,扩展 AI Agent 的能力边界。使用 Zod 定义严格的输入接口,让 AI 安全调用你的业务逻辑。',
registration: '工具注册',
registration_desc:
'每个自定义工具需要定义四个核心属性:name、description、input schema 和 execute 函数。',
page_filter: '页面过滤器',
page_filter_desc: '通过 pageFilter 属性控制工具在哪些页面可见,提升安全性和用户体验。',
best_practices: '最佳实践',
bp_performance: '⚡ 性能优化',
bp_1: '• 使用 pageFilter 减少不必要的工具加载',
bp_2: '• 在 execute 函数中实现适当的缓存机制',
bp_3: '• 避免在工具中执行耗时的同步操作',
},
}
@@ -0,0 +1,78 @@
export default {
hero: {
badge: 'GUI Agent in your webpage',
title_line1: '让你的 Web 应用',
title_line2: '拥有 AI 操作员',
subtitle_emoji: '🪄 一行 CDN 引入',
subtitle_main: ',为你的网站添加 GUI Agent。',
subtitle_detail: '用户/答疑机器人给出文字指示,AI 帮你操作页面。',
tab_try: '🚀 立即尝试',
tab_other: '🌐 其他网页尝试',
input_placeholder: '输入您想要 AI 执行的任务...',
execute_button: '执行',
default_task: '从导航栏中进入文档页,打开“快速开始”相关的文档,帮我总结成 markdown',
},
try_other: {
step1_title: '步骤 1:',
step1_content: '显示收藏夹栏',
step2_title: '步骤 2:',
step2_content: '拖拽下面按钮到收藏夹栏',
step3_title: '步骤 3:',
step3_content: '在其他网站点击收藏夹中的按钮即可使用',
notice_title: '⚠️ 注意',
notice_items: {
item1: '仅做技术评估,链接定期失效',
item2: '使用 DeepSeek 模型,参考 DeepSeek 用户协议和隐私政策',
item3: '部分网站屏蔽了链接嵌入,将无反应',
item4: '仅支持单页应用,页面跳转后需要重新注入',
item5: '仅识别文本,不识别图像,不支持拖拽等复杂交互',
item6_prefix: '详细使用限制参照',
item6_link: '《文档》',
},
},
benefits: {
no_backend: '纯前端方案',
private_model: '支持私有模型',
data_masking: '无痛脱敏',
open_source: 'MIT 开源',
},
features: {
section_title: '核心特性',
in_page: {
title: '纯页面内方案',
desc: '完全运行在你的页面内。不需要浏览器插件、不需要无头浏览器,不需要后端。',
},
secure_integration: {
title: '安全可控集成',
desc: '支持操作黑白名单、数据脱敏保护。注入自定义知识库,让 AI 按你的规则工作。',
},
zero_backend: {
title: '零后端部署',
desc: '前端脚本引入,自定义 LLM 接入点。从 OpenAI 到 qwen3,完全由你掌控。',
},
accessible: {
title: '普惠智能交互',
desc: '为复杂 B端系统、管理后台提供自然语言入口。让每个用户都能轻松上手。',
},
},
use_cases: {
section_title: '应用场景',
section_subtitle: '从简单的表单填写到复杂的业务流程,AI 都能理解并执行',
case1: {
title: '对接答疑机器人',
desc: '把你的答疑助手变成全能Agent。客服机器人不再只说「请先点击设置按钮然后点击...」,而是直接帮用户现场操作。',
},
case2: {
title: '交互升级/智能化改造',
desc: '一行代码,老应用变身Agent,产品专家帮用户操作复杂 B 端软件。降低人工支持成本,提高用户满意度。',
},
case3: {
title: '产品教学',
desc: '向用户演示交互过程,边做边教。例如让AI演示「如何提交报销申请」的完整操作流程。',
},
case4: {
title: '无障碍支持',
desc: '为视障用户、老年用户提供自然语言交互,对接屏幕阅读器或语音助理,让软件人人可用。',
},
},
}
+16
View File
@@ -0,0 +1,16 @@
import 'react-i18next'
import type commonZh from './locales/zh-CN/common'
import type docsZh from './locales/zh-CN/docs'
import type homeZh from './locales/zh-CN/home'
declare module 'react-i18next' {
interface CustomTypeOptions {
defaultNS: 'common'
resources: {
common: typeof commonZh
home: typeof homeZh
docs: typeof docsZh
}
}
}
+204
View File
@@ -0,0 +1,204 @@
@import 'tailwindcss';
/* 启用 class-based dark mode for Tailwind v4 */
@variant dark (.dark &);
:root {
--background: #ffffff;
--foreground: #171717;
/* 主题色渐变 */
--theme-color-1: rgb(88, 192, 252);
--theme-color-2: rgb(189, 69, 251);
}
/* class-based dark mode - 应用到 html.dark */
html.dark,
:root.dark {
--background: #0a0a0a;
--foreground: #ededed;
}
/* 同时支持系统偏好 */
/* @media (prefers-color-scheme: dark) {
html:not(.light),
:root:not(.light) {
--background: #0a0a0a;
--foreground: #ededed;
}
} */
/* 添加 Tailwind 自定义颜色 */
@theme {
--color-background: var(--background);
--color-foreground: var(--foreground);
}
body {
background: var(--background);
color: var(--foreground);
font-family: Arial, Helvetica, sans-serif;
}
/* 确保文档页面标题在暗色模式下可见 - 只针对 prose 内的标题 */
.prose h1,
.prose h2,
.prose h3,
.prose h4,
.prose h5,
.prose h6 {
color: rgba(23, 23, 23, 0.85);
}
.dark .prose h1,
.dark .prose h2,
.dark .prose h3,
.dark .prose h4,
.dark .prose h5,
.dark .prose h6 {
color: rgba(255, 255, 255, 0.9);
}
table,
th,
td {
color: #171717;
}
.dark table,
.dark th,
.dark td {
color: #ededed;
}
/* 文档页深色模式优化 */
.dark .prose {
color: rgba(255, 255, 255, 0.7);
}
/* @media (prefers-color-scheme: dark) {
:root:not(.light) .prose {
color: rgba(255, 255, 255, 0.7);
}
} */
.dark .dark\:prose-invert {
--tw-prose-body: rgba(255, 255, 255, 0.7);
--tw-prose-headings: rgba(255, 255, 255, 0.95);
--tw-prose-lead: rgba(255, 255, 255, 0.7);
--tw-prose-links: rgba(147, 197, 253, 0.9);
--tw-prose-bold: rgba(255, 255, 255, 0.9);
--tw-prose-counters: rgba(255, 255, 255, 0.6);
--tw-prose-bullets: rgba(255, 255, 255, 0.5);
--tw-prose-hr: rgba(255, 255, 255, 0.2);
--tw-prose-quotes: rgba(255, 255, 255, 0.8);
--tw-prose-quote-borders: rgba(255, 255, 255, 0.3);
--tw-prose-captions: rgba(255, 255, 255, 0.6);
--tw-prose-code: rgba(255, 255, 255, 0.9);
--tw-prose-pre-code: rgba(255, 255, 255, 0.95);
--tw-prose-pre-bg: rgba(0, 0, 0, 0.5);
--tw-prose-th-borders: rgba(255, 255, 255, 0.3);
--tw-prose-td-borders: rgba(255, 255, 255, 0.2);
}
/* @media (prefers-color-scheme: dark) {
:root:not(.light) .dark\:prose-invert {
--tw-prose-body: rgba(255, 255, 255, 0.7);
--tw-prose-headings: rgba(255, 255, 255, 0.95);
--tw-prose-lead: rgba(255, 255, 255, 0.7);
--tw-prose-links: rgba(147, 197, 253, 0.9);
--tw-prose-bold: rgba(255, 255, 255, 0.9);
--tw-prose-counters: rgba(255, 255, 255, 0.6);
--tw-prose-bullets: rgba(255, 255, 255, 0.5);
--tw-prose-hr: rgba(255, 255, 255, 0.2);
--tw-prose-quotes: rgba(255, 255, 255, 0.8);
--tw-prose-quote-borders: rgba(255, 255, 255, 0.3);
--tw-prose-captions: rgba(255, 255, 255, 0.6);
--tw-prose-code: rgba(255, 255, 255, 0.9);
--tw-prose-pre-code: rgba(255, 255, 255, 0.95);
--tw-prose-pre-bg: rgba(0, 0, 0, 0.5);
--tw-prose-th-borders: rgba(255, 255, 255, 0.3);
--tw-prose-td-borders: rgba(255, 255, 255, 0.2);
}
} */
/* 标题更清晰 */
.dark .prose h1,
.dark .prose h2,
.dark .prose h3,
.dark .prose h4 {
color: rgba(255, 255, 255, 0.95);
}
/* @media (prefers-color-scheme: dark) {
:root:not(.light) .prose h1,
:root:not(.light) .prose h2,
:root:not(.light) .prose h3,
:root:not(.light) .prose h4 {
color: rgba(255, 255, 255, 0.95);
}
} */
/* 链接更清晰 */
.dark .prose a {
color: rgba(147, 197, 253, 0.9);
}
/* @media (prefers-color-scheme: dark) {
:root:not(.light) .prose a {
color: rgba(147, 197, 253, 0.9);
}
} */
/* 代码块背景更黑 */
.dark .prose pre {
background-color: rgba(0, 0, 0, 0.6);
}
/* @media (prefers-color-scheme: dark) {
:root:not(.light) .prose pre {
background-color: rgba(0, 0, 0, 0.6);
}
} */
/* 表格样式 */
.dark .prose table {
color: rgba(255, 255, 255, 0.7);
}
/* @media (prefers-color-scheme: dark) {
:root:not(.light) .prose table {
color: rgba(255, 255, 255, 0.7);
}
} */
.dark .prose thead {
color: rgba(255, 255, 255, 0.9);
border-bottom-color: rgba(255, 255, 255, 0.3);
}
/* @media (prefers-color-scheme: dark) {
:root:not(.light) .prose thead {
color: rgba(255, 255, 255, 0.9);
border-bottom-color: rgba(255, 255, 255, 0.3);
}
} */
.dark .prose tbody tr {
border-bottom-color: rgba(255, 255, 255, 0.2);
}
/* @media (prefers-color-scheme: dark) {
:root:not(.light) .prose tbody tr {
border-bottom-color: rgba(255, 255, 255, 0.2);
}
} */
/* 隐藏滚动条,但保持滚动功能 */
.scrollbar-hide {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
.scrollbar-hide::-webkit-scrollbar {
display: none; /* Chrome, Safari and Opera */
}
@@ -2,6 +2,8 @@ import { createRoot } from 'react-dom/client'
import { Route, Router, Switch } from 'wouter'
import { useHashLocation } from 'wouter/use-hash-location'
import './i18n/config'
import './i18n/types'
import { default as PagesRouter } from './router.tsx'
import { default as TestPagesRouter } from './test-pages/router.tsx'
+113 -95
View File
@@ -1,14 +1,14 @@
/* eslint-disable react-dom/no-dangerously-set-innerhtml */
import { useState } from 'react'
import { PageAgent } from 'page-agent'
import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Link, useSearchParams } from 'wouter'
import { PageAgent } from '@/PageAgent.js'
import Footer from './components/Footer'
import Header from './components/Header'
const injection = encodeURI(
"javascript:(function(){var s=document.createElement('script');s.src=`https://hwcxiuzfylggtcktqgij.supabase.co/storage/v1/object/public/demo-public/v0.0.2/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);})();"
"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 injectionA = `
@@ -17,7 +17,7 @@ const injectionA = `
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="拖拽我到收藏夹栏"
title="Drag me to your bookmarks bar!"
>
PageAgent
</a>
@@ -25,7 +25,14 @@ const injectionA = `
`
export default function HomePage() {
const [task, setTask] = useState('进入文档页,打开数据脱敏相关的文档,帮我总结成 markdown')
const { t, i18n } = useTranslation(['home', 'common'])
const [task, setTask] = useState(() => t('home:hero.default_task'))
// Update task when language changes
const defaultTask = t('home:hero.default_task')
useEffect(() => {
setTask(defaultTask)
}, [defaultTask])
const [params, setParams] = useSearchParams()
const isOther = params.has('try_other')
@@ -36,14 +43,17 @@ export default function HomePage() {
if (!task.trim()) return
let pageAgent: PageAgent
const win = window as any
if (window.pageAgent && !window.pageAgent.disposed) {
pageAgent = window.pageAgent
if (win.pageAgent && !win.pageAgent.disposed) {
pageAgent = win.pageAgent
} else {
pageAgent = new PageAgent({
// 把 react 根元素排除掉,挂了很多冒泡时间导致假阳
interactiveBlacklist: [document.getElementById('root')!],
language: 'zh-CN',
language: i18n.language as any,
// experimentalScriptExecutionTool: true,
// testing server
// @note: rate limit. prompt limit.
@@ -51,7 +61,7 @@ export default function HomePage() {
// baseURL: DEMO_BASE_URL,
// apiKey: DEMO_API_KEY,
})
window.pageAgent = pageAgent
win.pageAgent = pageAgent
}
const result = await pageAgent.execute(task)
@@ -60,7 +70,7 @@ export default function HomePage() {
}
return (
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-purple-50 dark:from-gray-900 dark:to-gray-800">
<div className="min-h-screen bg-linear-to-br from-blue-50 to-purple-50 dark:from-gray-900 dark:to-gray-800">
<Header />
{/* Hero Section */}
@@ -69,8 +79,8 @@ export default function HomePage() {
<div className="max-w-7xl mx-auto text-center">
{/* Background Pattern */}
<div className="absolute inset-0 opacity-30" aria-hidden="true">
<div className="absolute inset-0 bg-gradient-to-r from-blue-400/20 to-purple-400/20 rounded-3xl transform rotate-1"></div>
<div className="absolute inset-0 bg-gradient-to-l from-purple-400/20 to-blue-400/20 rounded-3xl transform -rotate-1"></div>
<div className="absolute inset-0 bg-linear-to-r from-blue-400/20 to-purple-400/20 rounded-3xl transform rotate-1"></div>
<div className="absolute inset-0 bg-linear-to-l from-purple-400/20 to-blue-400/20 rounded-3xl transform -rotate-1"></div>
</div>
<div className="relative z-10">
@@ -79,25 +89,25 @@ export default function HomePage() {
className="w-2 h-2 bg-blue-500 rounded-full mr-2 animate-pulse"
aria-hidden="true"
></span>
UI Agent in your webpage
{t('home:hero.badge')}
</div>
<h1
id="hero-heading"
className="text-5xl lg:text-7xl font-bold mb-8 bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent"
className="text-5xl lg:text-7xl font-bold mb-8 bg-linear-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent pb-1"
>
Web
{t('home:hero.title_line1')}
<br />
AI
{t('home:hero.title_line2')}
</h1>
<p className="text-xl lg:text-2xl text-gray-600 dark:text-gray-300 mb-12 max-w-4xl mx-auto leading-relaxed">
<span className="bg-gradient-to-r from-blue-500 to-purple-500 bg-clip-text text-transparent font-bold">
🪄 CDN
<span className="bg-linear-to-r from-blue-500 to-purple-500 bg-clip-text text-transparent font-bold">
{t('home:hero.subtitle_emoji')}
</span>
UI Agent
{t('home:hero.subtitle_main')}
<br />
/AI
{t('home:hero.subtitle_detail')}
</p>
{/* Try It Now Section - Tab Card */}
@@ -110,21 +120,21 @@ export default function HomePage() {
onClick={() => setActiveTab('try')}
className={`flex-1 px-4 py-4 text-lg font-medium transition-colors duration-200 ${
activeTab === 'try'
? 'bg-gradient-to-r from-blue-50 to-purple-50 dark:from-blue-900/30 dark:to-purple-900/30 text-blue-700 dark:text-blue-300 border-b-2 border-blue-500'
? 'bg-linear-to-r from-blue-50 to-purple-50 dark:from-blue-900/30 dark:to-purple-900/30 text-blue-700 dark:text-blue-300 border-b-2 border-blue-500'
: 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:bg-gray-50 dark:hover:bg-gray-700'
}`}
>
🚀
{t('home:hero.tab_try')}
</button>
<button
onClick={() => setActiveTab('other')}
className={`flex-1 px-4 py-4 text-lg font-medium transition-colors duration-200 ${
activeTab === 'other'
? 'bg-gradient-to-r from-green-50 to-blue-50 dark:from-green-900/30 dark:to-blue-900/30 text-green-700 dark:text-green-300 border-b-2 border-green-500'
? 'bg-linear-to-r from-green-50 to-blue-50 dark:from-green-900/30 dark:to-blue-900/30 text-green-700 dark:text-green-300 border-b-2 border-green-500'
: 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:bg-gray-50 dark:hover:bg-gray-700'
}`}
>
🌐
{t('home:hero.tab_other')}
</button>
</div>
@@ -136,7 +146,7 @@ export default function HomePage() {
<input
value={task}
onChange={(e) => setTask(e.target.value)}
placeholder="输入您想要 AI 执行的任务..."
placeholder={t('home:hero.input_placeholder')}
className="w-full px-4 py-3 pr-20 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-500 dark:placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-none text-sm mb-0"
data-page-agent-not-interactive
/>
@@ -144,10 +154,10 @@ export default function HomePage() {
onClick={handleExecute}
// disabled
// disabled={!task.trim()}
className="absolute right-2 top-2 px-5 py-1.5 bg-gradient-to-r from-blue-600 to-purple-600 text-white font-medium rounded-md hover:shadow-md transform hover:scale-105 transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none text-sm"
className="absolute right-2 top-2 px-5 py-1.5 bg-linear-to-r from-blue-600 to-purple-600 text-white font-medium rounded-md hover:shadow-md transform hover:scale-105 transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none text-sm"
data-page-agent-not-interactive
>
{t('home:hero.execute_button')}
</button>
</div>
</div>
@@ -160,7 +170,10 @@ export default function HomePage() {
{/* Keyboard Shortcut Hint */}
<div className="bg-blue-50 dark:bg-gray-700 p-4 rounded-lg">
<p className="text-gray-700 dark:text-gray-300 text-sm mb-3">
<span className="font-semibold"> 1:</span>
<span className="font-semibold">
{t('home:try_other.step1_title')}
</span>{' '}
{t('home:try_other.step1_content')}
</p>
<div className="flex items-center justify-center gap-2">
<kbd className="px-2 py-1 bg-white dark:bg-gray-600 border border-gray-300 dark:border-gray-500 rounded text-xs font-mono">
@@ -176,8 +189,10 @@ export default function HomePage() {
{/* Draggable Bookmarklet */}
<div className="bg-green-50 dark:bg-gray-700 p-4 rounded-lg">
<p className="text-gray-700 dark:text-gray-300 text-sm mb-3">
<span className="font-semibold"> 2:</span>{' '}
<span className="font-semibold">
{t('home:try_other.step2_title')}
</span>{' '}
{t('home:try_other.step2_content')}
</p>
<div
className="flex items-center justify-center gap-2 text-gray-500 dark:text-gray-400"
@@ -188,8 +203,10 @@ export default function HomePage() {
{/* Usage Instructions */}
<div className="bg-purple-50 dark:bg-gray-700 p-4 rounded-lg">
<p className="text-gray-700 dark:text-gray-300 text-sm">
<span className="font-semibold"> 3:</span>{' '}
使
<span className="font-semibold">
{t('home:try_other.step3_title')}
</span>{' '}
{t('home:try_other.step3_content')}
</p>
</div>
</div>
@@ -197,37 +214,37 @@ export default function HomePage() {
{/* 右侧:注意事项 */}
<div className="bg-yellow-50 dark:bg-gray-700 p-4 rounded-lg">
<h4 className="font-semibold text-gray-900 dark:text-white mb-3 text-sm">
{t('home:try_other.notice_title')}
</h4>
<ul className="space-y-2 text-sm text-gray-700 dark:text-gray-300">
<li className="flex items-start text-left">
<span className="w-1.5 h-1.5 bg-yellow-500 rounded-full mt-2 mr-2 flex-shrink-0 "></span>
<span className="w-1.5 h-1.5 bg-yellow-500 rounded-full mt-2 mr-2 shrink-0 "></span>
{t('home:try_other.notice_items.item1')}
</li>
<li className="flex items-start text-left">
<span className="w-1.5 h-1.5 bg-yellow-500 rounded-full mt-2 mr-2 flex-shrink-0 "></span>
使 DeepSeek DeepSeek
<span className="w-1.5 h-1.5 bg-yellow-500 rounded-full mt-2 mr-2 shrink-0 "></span>
{t('home:try_other.notice_items.item2')}
</li>
<li className="flex items-start text-left">
<span className="w-1.5 h-1.5 bg-yellow-500 rounded-full mt-2 mr-2 flex-shrink-0 "></span>
<span className="w-1.5 h-1.5 bg-yellow-500 rounded-full mt-2 mr-2 shrink-0 "></span>
{t('home:try_other.notice_items.item3')}
</li>
<li className="flex items-start text-left">
<span className="w-1.5 h-1.5 bg-yellow-500 rounded-full mt-2 mr-2 flex-shrink-0 "></span>
<span className="w-1.5 h-1.5 bg-yellow-500 rounded-full mt-2 mr-2 shrink-0 "></span>
{t('home:try_other.notice_items.item4')}
</li>
<li className="flex items-start text-left">
<span className="w-1.5 h-1.5 bg-yellow-500 rounded-full mt-2 mr-2 flex-shrink-0 "></span>
<span className="w-1.5 h-1.5 bg-yellow-500 rounded-full mt-2 mr-2 shrink-0 "></span>
{t('home:try_other.notice_items.item5')}
</li>
<li className="flex items-start text-left">
<span className="w-1.5 h-1.5 bg-yellow-500 rounded-full mt-2 mr-2 flex-shrink-0 "></span>
使{' '}
<span className="w-1.5 h-1.5 bg-yellow-500 rounded-full mt-2 mr-2 shrink-0 "></span>
{t('home:try_other.notice_items.item6_prefix')}{' '}
<Link
href="/docs/introduction/limitations"
className="text-blue-600 dark:text-blue-400 hover:underline"
>
{t('home:try_other.notice_items.item6_link')}
</Link>
</li>
</ul>
@@ -248,28 +265,28 @@ export default function HomePage() {
className="w-2 h-2 bg-green-500 rounded-full mr-2"
aria-hidden="true"
></span>
{t('home:benefits.no_backend')}
</li>
<li className="flex items-center">
<span
className="w-2 h-2 bg-green-500 rounded-full mr-2"
aria-hidden="true"
></span>
{t('home:benefits.private_model')}
</li>
<li className="flex items-center">
<span
className="w-2 h-2 bg-green-500 rounded-full mr-2"
aria-hidden="true"
></span>
{t('home:benefits.data_masking')}
</li>
<li className="flex items-center">
<span
className="w-2 h-2 bg-green-500 rounded-full mr-2"
aria-hidden="true"
></span>
DOM
{t('home:benefits.open_source')}
</li>
</ul>
</div>
@@ -285,75 +302,77 @@ export default function HomePage() {
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-8" role="list">
{/* Feature 1 */}
<article
className="group p-8 bg-gradient-to-br from-blue-100 to-purple-100 dark:from-gray-700 dark:to-gray-800 rounded-2xl shadow-lg hover:shadow-xl transition-all duration-300 border border-gray-100 dark:border-gray-700"
className="group p-8 bg-linear-to-br from-blue-100 to-purple-100 dark:from-gray-700 dark:to-gray-800 rounded-2xl shadow-lg hover:shadow-xl transition-all duration-300 border border-gray-100 dark:border-gray-700"
role="listitem"
>
<div
className="w-12 h-12 bg-gradient-to-br from-blue-500 to-blue-600 rounded-xl flex items-center justify-center mb-6 group-hover:scale-110 transition-transform duration-300"
className="w-12 h-12 bg-linear-to-br from-blue-500 to-blue-600 rounded-xl flex items-center justify-center mb-6 group-hover:scale-110 transition-transform duration-300"
aria-hidden="true"
>
<span className="text-white text-xl">🧠</span>
<span className="text-white text-xl">📦</span>
</div>
<h3 className="text-xl font-bold mb-4 text-gray-900 dark:text-white">
DOM
{t('home:features.in_page.title')}
</h3>
<p className="text-gray-600 dark:text-gray-300 leading-relaxed">
DOM
{t('home:features.in_page.desc')}
</p>
</article>
{/* Feature 2 */}
<article
className="group p-8 bg-gradient-to-br from-green-100 to-blue-100 dark:from-gray-700 dark:to-gray-800 rounded-2xl shadow-lg hover:shadow-xl transition-all duration-300 border border-gray-100 dark:border-gray-700"
className="group p-8 bg-linear-to-br from-purple-100 to-pink-100 dark:from-gray-700 dark:to-gray-800 rounded-2xl shadow-lg hover:shadow-xl transition-all duration-300 border border-gray-100 dark:border-gray-700"
role="listitem"
>
<div
className="w-12 h-12 bg-gradient-to-br from-purple-500 to-purple-600 rounded-xl flex items-center justify-center mb-6 group-hover:scale-110 transition-transform duration-300"
className="w-12 h-12 bg-linear-to-br from-green-500 to-green-600 rounded-xl flex items-center justify-center mb-6 group-hover:scale-110 transition-transform duration-300"
aria-hidden="true"
>
<span className="text-white text-xl">🔒</span>
<span className="text-white text-xl"></span>
</div>
<h3 className="text-xl font-bold mb-4 text-gray-900 dark:text-white">
{t('home:features.zero_backend.title')}
</h3>
<p className="text-gray-600 dark:text-gray-300 leading-relaxed">
AI
{t('home:features.zero_backend.desc')}
</p>
</article>
{/* Feature 3 */}
<article
className="group p-8 bg-gradient-to-br from-purple-100 to-pink-100 dark:from-gray-700 dark:to-gray-800 rounded-2xl shadow-lg hover:shadow-xl transition-all duration-300 border border-gray-100 dark:border-gray-700"
className="group p-8 bg-linear-to-br from-orange-100 to-red-100 dark:from-gray-700 dark:to-gray-800 rounded-2xl shadow-lg hover:shadow-xl transition-all duration-300 border border-gray-100 dark:border-gray-700"
role="listitem"
>
<div
className="w-12 h-12 bg-gradient-to-br from-green-500 to-green-600 rounded-xl flex items-center justify-center mb-6 group-hover:scale-110 transition-transform duration-300"
className="w-12 h-12 bg-linear-to-br from-purple-500 to-purple-600 rounded-xl flex items-center justify-center mb-6 group-hover:scale-110 transition-transform duration-300"
aria-hidden="true"
>
<span className="text-white text-xl"></span>
<span className="text-white text-xl">🌈</span>
</div>
<h3 className="text-xl font-bold mb-4 text-gray-900 dark:text-white"></h3>
<h3 className="text-xl font-bold mb-4 text-gray-900 dark:text-white">
{t('home:features.accessible.title')}
</h3>
<p className="text-gray-600 dark:text-gray-300 leading-relaxed">
CDN LLM OpenAI qwen3
{t('home:features.accessible.desc')}
</p>
</article>
{/* Feature 4 */}
<article
className="group p-8 bg-gradient-to-br from-orange-100 to-red-100 dark:from-gray-700 dark:to-gray-800 rounded-2xl shadow-lg hover:shadow-xl transition-all duration-300 border border-gray-100 dark:border-gray-700"
className="group p-8 bg-linear-to-br from-green-100 to-blue-100 dark:from-gray-700 dark:to-gray-800 rounded-2xl shadow-lg hover:shadow-xl transition-all duration-300 border border-gray-100 dark:border-gray-700"
role="listitem"
>
<div
className="w-12 h-12 bg-gradient-to-br from-orange-500 to-orange-600 rounded-xl flex items-center justify-center mb-6 group-hover:scale-110 transition-transform duration-300"
className="w-12 h-12 bg-linear-to-br from-orange-500 to-orange-600 rounded-xl flex items-center justify-center mb-6 group-hover:scale-110 transition-transform duration-300"
aria-hidden="true"
>
<span className="text-white text-xl"></span>
<span className="text-white text-xl">🔒</span>
</div>
<h3 className="text-xl font-bold mb-4 text-gray-900 dark:text-white">
{t('home:features.secure_integration.title')}
</h3>
<p className="text-gray-600 dark:text-gray-300 leading-relaxed">
B端系统
{t('home:features.secure_integration.desc')}
</p>
</article>
</div>
@@ -366,81 +385,80 @@ export default function HomePage() {
<div className="text-center mb-16">
<h2
id="use-cases-heading"
className="text-4xl lg:text-5xl mb-6 text-gray-900 dark:text-white"
className="text-4xl lg:text-5xl font-bold mb-6 bg-linear-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent"
>
{t('home:use_cases.section_title')}
</h2>
<p className="text-xl text-gray-600 dark:text-gray-300 max-w-3xl mx-auto">
AI
{t('home:use_cases.section_subtitle')}
</p>
</div>
<div className="grid lg:grid-cols-2 gap-12" role="list">
{/* Use Case 1 */}
<div className="bg-gradient-to-br from-blue-100 to-purple-100 dark:from-gray-700 dark:to-gray-800 p-8 rounded-2xl">
<div className="flex items-start space-x-4 h-20">
<div className="w-10 h-10 bg-blue-500 rounded-lg flex items-center justify-center flex-shrink-0">
<div className="bg-linear-to-br from-blue-100 to-purple-100 dark:from-gray-700 dark:to-gray-800 p-8 rounded-2xl">
<div className="flex items-start space-x-4">
<div className="w-10 h-10 bg-blue-500 rounded-lg flex items-center justify-center shrink-0">
<span className="text-white font-bold">1</span>
</div>
<div>
<h3 className="text-xl font-bold mb-2 text-gray-900 dark:text-white">
{t('home:use_cases.case1.title')}
</h3>
<p className="text-gray-600 dark:text-gray-300">
Agent"请先点击设置按钮然后点击..."
{t('home:use_cases.case1.desc')}
</p>
</div>
</div>
</div>
{/* Use Case 2 */}
<div className="bg-gradient-to-br from-green-100 to-blue-100 dark:from-gray-700 dark:to-gray-800 p-8 rounded-2xl">
<div className="flex items-start space-x-4 h-20">
<div className="w-10 h-10 bg-green-500 rounded-lg flex items-center justify-center flex-shrink-0">
<div className="bg-linear-to-br from-green-100 to-blue-100 dark:from-gray-700 dark:to-gray-800 p-8 rounded-2xl">
<div className="flex items-start space-x-4">
<div className="w-10 h-10 bg-green-500 rounded-lg flex items-center justify-center shrink-0">
<span className="text-white font-bold">2</span>
</div>
<div>
<h3 className="text-xl font-bold mb-2 text-gray-900 dark:text-white">
/
{t('home:use_cases.case2.title')}
</h3>
<p className="text-gray-600 dark:text-gray-300">
Agent B
{t('home:use_cases.case2.desc')}
</p>
</div>
</div>
</div>
{/* Use Case 3 */}
<div className="bg-gradient-to-br from-purple-100 to-pink-100 dark:from-gray-700 dark:to-gray-800 p-8 rounded-2xl">
<div className="flex items-start space-x-4 h-20">
<div className="w-10 h-10 bg-purple-500 rounded-lg flex items-center justify-center flex-shrink-0">
<div className="bg-linear-to-br from-purple-100 to-pink-100 dark:from-gray-700 dark:to-gray-800 p-8 rounded-2xl">
<div className="flex items-start space-x-4">
<div className="w-10 h-10 bg-purple-500 rounded-lg flex items-center justify-center shrink-0">
<span className="text-white font-bold">3</span>
</div>
<div>
<h3 className="text-xl font-bold mb-2 text-gray-900 dark:text-white">
{t('home:use_cases.case3.title')}
</h3>
<p className="text-gray-600 dark:text-gray-300">
AI演示"如何提交报销申请"
{t('home:use_cases.case3.desc')}
</p>
</div>
</div>
</div>
{/* Use Case 4 */}
<div className="bg-gradient-to-br from-orange-100 to-red-100 dark:from-gray-700 dark:to-gray-800 p-8 rounded-2xl">
<div className="flex items-start space-x-4 h-20">
<div className="w-10 h-10 bg-orange-500 rounded-lg flex items-center justify-center flex-shrink-0">
<div className="bg-linear-to-br from-orange-100 to-red-100 dark:from-gray-700 dark:to-gray-800 p-8 rounded-2xl">
<div className="flex items-start space-x-4">
<div className="w-10 h-10 bg-orange-500 rounded-lg flex items-center justify-center shrink-0">
<span className="text-white font-bold">4</span>
</div>
<div>
<h3 className="text-xl font-bold mb-2 text-gray-900 dark:text-white">
{t('home:use_cases.case4.title')}
</h3>
<p className="text-gray-600 dark:text-gray-300">
{t('home:use_cases.case4.desc')}
</p>
</div>
</div>
@@ -19,19 +19,31 @@ interface WizardStep {
export default function ComplexTestPage() {
const [currentStep, setCurrentStep] = useState(1)
const [cartItems, setCartItems] = useState<CartItem[]>([
{ id: 1, name: 'iPhone 15 Pro', price: 7999, quantity: 1, image: 'https://picsum.photos/100/100?random=1' },
{ id: 2, name: 'MacBook Air', price: 8999, quantity: 1, image: 'https://picsum.photos/100/100?random=2' }
{
id: 1,
name: 'iPhone 15 Pro',
price: 7999,
quantity: 1,
image: 'https://picsum.photos/100/100?random=1',
},
{
id: 2,
name: 'MacBook Air',
price: 8999,
quantity: 1,
image: 'https://picsum.photos/100/100?random=2',
},
])
const [wizardData, setWizardData] = useState({
personalInfo: { name: '', email: '', phone: '' },
address: { street: '', city: '', zipCode: '' },
payment: { cardNumber: '', expiryDate: '', cvv: '' }
payment: { cardNumber: '', expiryDate: '', cvv: '' },
})
const [wizardSteps, setWizardSteps] = useState<WizardStep[]>([
{ id: 1, title: '个人信息', description: '填写基本信息', completed: false },
{ id: 2, title: '收货地址', description: '填写收货地址', completed: false },
{ id: 3, title: '支付方式', description: '选择支付方式', completed: false },
{ id: 4, title: '确认订单', description: '确认订单信息', completed: false }
{ id: 4, title: '确认订单', description: '确认订单信息', completed: false },
])
const [showConfirmDialog, setShowConfirmDialog] = useState(false)
const [isProcessing, setIsProcessing] = useState(false)
@@ -43,15 +55,13 @@ export default function ComplexTestPage() {
removeItem(id)
return
}
setCartItems(prev =>
prev.map(item =>
item.id === id ? { ...item, quantity: newQuantity } : item
)
setCartItems((prev) =>
prev.map((item) => (item.id === id ? { ...item, quantity: newQuantity } : item))
)
}
const removeItem = (id: number) => {
setCartItems(prev => prev.filter(item => item.id !== id))
setCartItems((prev) => prev.filter((item) => item.id !== id))
}
const addItem = () => {
@@ -60,9 +70,9 @@ export default function ComplexTestPage() {
name: `新产品 ${cartItems.length + 1}`,
price: Math.floor(Math.random() * 5000) + 1000,
quantity: 1,
image: `https://picsum.photos/100/100?random=${Date.now()}`
image: `https://picsum.photos/100/100?random=${Date.now()}`,
}
setCartItems(prev => [...prev, newItem])
setCartItems((prev) => [...prev, newItem])
}
const getTotalPrice = () => {
@@ -73,11 +83,23 @@ export default function ComplexTestPage() {
const validateStep = (step: number): boolean => {
switch (step) {
case 1:
return !!(wizardData.personalInfo.name && wizardData.personalInfo.email && wizardData.personalInfo.phone)
return !!(
wizardData.personalInfo.name &&
wizardData.personalInfo.email &&
wizardData.personalInfo.phone
)
case 2:
return !!(wizardData.address.street && wizardData.address.city && wizardData.address.zipCode)
return !!(
wizardData.address.street &&
wizardData.address.city &&
wizardData.address.zipCode
)
case 3:
return !!(wizardData.payment.cardNumber && wizardData.payment.expiryDate && wizardData.payment.cvv)
return !!(
wizardData.payment.cardNumber &&
wizardData.payment.expiryDate &&
wizardData.payment.cvv
)
default:
return true
}
@@ -92,10 +114,8 @@ export default function ComplexTestPage() {
// 更新步骤完成状态
if (step > currentStep) {
setWizardSteps(prev =>
prev.map(s =>
s.id === currentStep ? { ...s, completed: true } : s
)
setWizardSteps((prev) =>
prev.map((s) => (s.id === currentStep ? { ...s, completed: true } : s))
)
}
@@ -103,21 +123,21 @@ export default function ComplexTestPage() {
}
const handleInputChange = (section: string, field: string, value: string) => {
setWizardData(prev => ({
setWizardData((prev) => ({
...prev,
[section]: {
...prev[section as keyof typeof prev],
[field]: value
}
[field]: value,
},
}))
}
const handleSubmitOrder = async () => {
setIsProcessing(true)
// 模拟处理时间
await new Promise(resolve => setTimeout(resolve, 3000))
await new Promise((resolve) => setTimeout(resolve, 3000))
// 模拟随机失败
if (Math.random() < 0.2) {
setIsProcessing(false)
@@ -135,9 +155,9 @@ export default function ComplexTestPage() {
setWizardData({
personalInfo: { name: '', email: '', phone: '' },
address: { street: '', city: '', zipCode: '' },
payment: { cardNumber: '', expiryDate: '', cvv: '' }
payment: { cardNumber: '', expiryDate: '', cvv: '' },
})
setWizardSteps(prev => prev.map(s => ({ ...s, completed: false })))
setWizardSteps((prev) => prev.map((s) => ({ ...s, completed: false })))
setOrderComplete(false)
setShowConfirmDialog(false)
}
@@ -162,7 +182,10 @@ export default function ComplexTestPage() {
>
</button>
<Link href="/test-pages" className="block w-full bg-gray-600 hover:bg-gray-700 text-white py-2 px-4 rounded-md transition-colors text-center">
<Link
href="/test-pages"
className="block w-full bg-gray-600 hover:bg-gray-700 text-white py-2 px-4 rounded-md transition-colors text-center"
>
</Link>
</div>
@@ -176,12 +199,8 @@ export default function ComplexTestPage() {
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 py-8">
<div className="max-w-6xl mx-auto px-4">
<div className="mb-8">
<h1 className="text-3xl font-bold text-gray-900 dark:text-white mb-2">
</h1>
<p className="text-gray-600 dark:text-gray-300">
</p>
<h1 className="text-3xl font-bold text-gray-900 dark:text-white mb-2"></h1>
<p className="text-gray-600 dark:text-gray-300"></p>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
@@ -191,10 +210,13 @@ export default function ComplexTestPage() {
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-4">
({cartItems.length})
</h3>
<div className="space-y-4 mb-6">
{cartItems.map(item => (
<div key={item.id} className="flex items-center space-x-3 p-3 border border-gray-200 dark:border-gray-600 rounded-lg">
{cartItems.map((item) => (
<div
key={item.id}
className="flex items-center space-x-3 p-3 border border-gray-200 dark:border-gray-600 rounded-lg"
>
<img
src={item.image}
alt={item.name}
@@ -215,9 +237,7 @@ export default function ComplexTestPage() {
>
-
</button>
<span className="text-sm font-medium w-8 text-center">
{item.quantity}
</span>
<span className="text-sm font-medium w-8 text-center">{item.quantity}</span>
<button
onClick={() => updateQuantity(item.id, item.quantity + 1)}
className="w-6 h-6 flex items-center justify-center bg-gray-200 dark:bg-gray-600 rounded text-sm hover:bg-gray-300 dark:hover:bg-gray-500"
@@ -265,16 +285,18 @@ export default function ComplexTestPage() {
step.completed
? 'bg-green-500 text-white'
: step.id === currentStep
? 'bg-blue-500 text-white'
: 'bg-gray-200 dark:bg-gray-600 text-gray-500 dark:text-gray-400'
? 'bg-blue-500 text-white'
: 'bg-gray-200 dark:bg-gray-600 text-gray-500 dark:text-gray-400'
}`}
>
{step.completed ? '✓' : step.id}
</button>
{index < wizardSteps.length - 1 && (
<div className={`w-16 h-1 mx-2 ${
step.completed ? 'bg-green-500' : 'bg-gray-200 dark:bg-gray-600'
}`} />
<div
className={`w-16 h-1 mx-2 ${
step.completed ? 'bg-green-500' : 'bg-gray-200 dark:bg-gray-600'
}`}
/>
)}
</div>
))}
@@ -395,7 +417,9 @@ export default function ComplexTestPage() {
<input
type="text"
value={wizardData.payment.expiryDate}
onChange={(e) => handleInputChange('payment', 'expiryDate', e.target.value)}
onChange={(e) =>
handleInputChange('payment', 'expiryDate', e.target.value)
}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-white"
placeholder="MM/YY"
/>
@@ -419,25 +443,35 @@ export default function ComplexTestPage() {
{currentStep === 4 && (
<div className="space-y-6">
<div>
<h4 className="text-lg font-medium text-gray-900 dark:text-white mb-4"></h4>
<h4 className="text-lg font-medium text-gray-900 dark:text-white mb-4">
</h4>
<div className="space-y-4">
<div className="bg-gray-50 dark:bg-gray-700 p-4 rounded-lg">
<h5 className="font-medium text-gray-900 dark:text-white mb-2"></h5>
<h5 className="font-medium text-gray-900 dark:text-white mb-2">
</h5>
<p className="text-sm text-gray-600 dark:text-gray-300">
{wizardData.personalInfo.name} | {wizardData.personalInfo.email} | {wizardData.personalInfo.phone}
{wizardData.personalInfo.name} | {wizardData.personalInfo.email} |{' '}
{wizardData.personalInfo.phone}
</p>
</div>
<div className="bg-gray-50 dark:bg-gray-700 p-4 rounded-lg">
<h5 className="font-medium text-gray-900 dark:text-white mb-2"></h5>
<h5 className="font-medium text-gray-900 dark:text-white mb-2">
</h5>
<p className="text-sm text-gray-600 dark:text-gray-300">
{wizardData.address.street}, {wizardData.address.city} {wizardData.address.zipCode}
{wizardData.address.street}, {wizardData.address.city}{' '}
{wizardData.address.zipCode}
</p>
</div>
<div className="bg-gray-50 dark:bg-gray-700 p-4 rounded-lg">
<h5 className="font-medium text-gray-900 dark:text-white mb-2"></h5>
<h5 className="font-medium text-gray-900 dark:text-white mb-2">
</h5>
<p className="text-sm text-gray-600 dark:text-gray-300">
**** **** **** {wizardData.payment.cardNumber.slice(-4)}
</p>
@@ -457,7 +491,7 @@ export default function ComplexTestPage() {
>
</button>
{currentStep < 4 ? (
<button
onClick={() => goToStep(currentStep + 1)}
@@ -505,9 +539,25 @@ export default function ComplexTestPage() {
>
{isProcessing ? (
<>
<svg className="animate-spin -ml-1 mr-3 h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
<svg
className="animate-spin -ml-1 mr-3 h-5 w-5 text-white"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
></circle>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
...
</>
@@ -17,7 +17,7 @@ export default function ErrorTestPage() {
username: '',
password: '',
email: '',
file: null as File | null
file: null as File | null,
})
const errorScenarios: ErrorScenario[] = [
@@ -25,32 +25,32 @@ export default function ErrorTestPage() {
id: 'network-error',
title: '网络连接错误',
description: '模拟网络连接失败,测试重试机制',
type: 'network'
type: 'network',
},
{
id: 'validation-error',
title: '表单验证错误',
description: '模拟表单验证失败,测试错误提示',
type: 'validation'
type: 'validation',
},
{
id: 'permission-error',
title: '权限不足错误',
description: '模拟权限验证失败,测试权限处理',
type: 'permission'
type: 'permission',
},
{
id: 'timeout-error',
title: '请求超时错误',
description: '模拟请求超时,测试超时处理',
type: 'timeout'
type: 'timeout',
},
{
id: 'server-error',
title: '服务器内部错误',
description: '模拟服务器500错误,测试错误恢复',
type: 'server'
}
type: 'server',
},
]
const simulateError = async (scenario: ErrorScenario): Promise<void> => {
@@ -59,7 +59,7 @@ export default function ErrorTestPage() {
setSuccess(null)
// 模拟网络延迟
await new Promise(resolve => setTimeout(resolve, 1000 + Math.random() * 2000))
await new Promise((resolve) => setTimeout(resolve, 1000 + Math.random() * 2000))
switch (scenario.type) {
case 'network':
@@ -91,7 +91,7 @@ export default function ErrorTestPage() {
case 'timeout':
// 模拟超时
await new Promise(resolve => setTimeout(resolve, 8000))
await new Promise((resolve) => setTimeout(resolve, 8000))
throw new Error('请求超时:服务器响应时间过长,请稍后重试')
case 'server':
@@ -117,7 +117,7 @@ export default function ErrorTestPage() {
} catch (err) {
const errorMessage = err instanceof Error ? err.message : '未知错误'
setError(errorMessage)
setRetryCount(prev => prev + 1)
setRetryCount((prev) => prev + 1)
} finally {
setIsLoading(false)
}
@@ -154,7 +154,7 @@ export default function ErrorTestPage() {
}
// 模拟上传过程
await new Promise(resolve => setTimeout(resolve, 2000))
await new Promise((resolve) => setTimeout(resolve, 2000))
// 模拟随机失败
if (Math.random() < 0.3) {
@@ -178,12 +178,18 @@ export default function ErrorTestPage() {
const getErrorIcon = (type: string) => {
switch (type) {
case 'network': return '🌐'
case 'validation': return '⚠️'
case 'permission': return '🔒'
case 'timeout': return ''
case 'server': return '🔧'
default: return ''
case 'network':
return '🌐'
case 'validation':
return '⚠️'
case 'permission':
return '🔒'
case 'timeout':
return '⏰'
case 'server':
return '🔧'
default:
return '❌'
}
}
@@ -191,9 +197,7 @@ export default function ErrorTestPage() {
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 py-8">
<div className="max-w-4xl mx-auto px-4">
<div className="mb-8">
<h1 className="text-3xl font-bold text-gray-900 dark:text-white mb-2">
</h1>
<h1 className="text-3xl font-bold text-gray-900 dark:text-white mb-2"></h1>
<p className="text-gray-600 dark:text-gray-300">
Agent
</p>
@@ -205,18 +209,18 @@ export default function ErrorTestPage() {
{error && (
<div className="bg-red-50 dark:bg-red-900 border border-red-200 dark:border-red-700 rounded-lg p-4 mb-4">
<div className="flex items-start">
<div className="flex-shrink-0">
<div className="shrink-0">
<svg className="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor">
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clipRule="evenodd" />
<path
fillRule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z"
clipRule="evenodd"
/>
</svg>
</div>
<div className="ml-3 flex-1">
<h3 className="text-sm font-medium text-red-800 dark:text-red-200">
</h3>
<p className="mt-1 text-sm text-red-700 dark:text-red-300">
{error}
</p>
<h3 className="text-sm font-medium text-red-800 dark:text-red-200"></h3>
<p className="mt-1 text-sm text-red-700 dark:text-red-300">{error}</p>
{retryCount > 0 && (
<p className="mt-2 text-xs text-red-600 dark:text-red-400">
{retryCount} {retryCount >= 3 && '(已达最大重试次数)'}
@@ -228,7 +232,11 @@ export default function ErrorTestPage() {
className="ml-3 text-red-400 hover:text-red-600 dark:hover:text-red-300"
>
<svg className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path fillRule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clipRule="evenodd" />
<path
fillRule="evenodd"
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
clipRule="evenodd"
/>
</svg>
</button>
</div>
@@ -238,25 +246,31 @@ export default function ErrorTestPage() {
{success && (
<div className="bg-green-50 dark:bg-green-900 border border-green-200 dark:border-green-700 rounded-lg p-4 mb-4">
<div className="flex items-start">
<div className="flex-shrink-0">
<div className="shrink-0">
<svg className="h-5 w-5 text-green-400" viewBox="0 0 20 20" fill="currentColor">
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
<path
fillRule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clipRule="evenodd"
/>
</svg>
</div>
<div className="ml-3 flex-1">
<h3 className="text-sm font-medium text-green-800 dark:text-green-200">
</h3>
<p className="mt-1 text-sm text-green-700 dark:text-green-300">
{success}
</p>
<p className="mt-1 text-sm text-green-700 dark:text-green-300">{success}</p>
</div>
<button
onClick={clearMessages}
className="ml-3 text-green-400 hover:text-green-600 dark:hover:text-green-300"
>
<svg className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path fillRule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clipRule="evenodd" />
<path
fillRule="evenodd"
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
clipRule="evenodd"
/>
</svg>
</button>
</div>
@@ -268,9 +282,7 @@ export default function ErrorTestPage() {
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
{/* 错误场景测试 */}
<div className="space-y-6">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
</h2>
<h2 className="text-xl font-semibold text-gray-900 dark:text-white"></h2>
{errorScenarios.map((scenario) => (
<div key={scenario.id} className="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
@@ -309,9 +321,7 @@ export default function ErrorTestPage() {
{/* 表单验证测试 */}
<div className="space-y-6">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
</h2>
<h2 className="text-xl font-semibold text-gray-900 dark:text-white"></h2>
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
<h3 className="text-lg font-medium text-gray-900 dark:text-white mb-4">
@@ -325,7 +335,7 @@ export default function ErrorTestPage() {
<input
type="text"
value={formData.username}
onChange={(e) => setFormData(prev => ({ ...prev, username: e.target.value }))}
onChange={(e) => setFormData((prev) => ({ ...prev, username: e.target.value }))}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-white"
placeholder="请输入用户名"
/>
@@ -337,7 +347,7 @@ export default function ErrorTestPage() {
<input
type="password"
value={formData.password}
onChange={(e) => setFormData(prev => ({ ...prev, password: e.target.value }))}
onChange={(e) => setFormData((prev) => ({ ...prev, password: e.target.value }))}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-white"
placeholder="请输入密码"
/>
@@ -349,13 +359,15 @@ export default function ErrorTestPage() {
<input
type="email"
value={formData.email}
onChange={(e) => setFormData(prev => ({ ...prev, email: e.target.value }))}
onChange={(e) => setFormData((prev) => ({ ...prev, email: e.target.value }))}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-white"
placeholder="请输入邮箱地址"
/>
</div>
<button
onClick={() => handleScenarioTest(errorScenarios.find(s => s.type === 'validation')!)}
onClick={() =>
handleScenarioTest(errorScenarios.find((s) => s.type === 'validation')!)
}
disabled={isLoading}
className="w-full px-4 py-2 bg-blue-600 hover:bg-blue-700 disabled:bg-blue-400 text-white rounded-md transition-colors"
>
@@ -376,14 +388,17 @@ export default function ErrorTestPage() {
</label>
<input
type="file"
onChange={(e) => setFormData(prev => ({ ...prev, file: e.target.files?.[0] || null }))}
onChange={(e) =>
setFormData((prev) => ({ ...prev, file: e.target.files?.[0] || null }))
}
accept="image/*,.pdf"
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-white"
/>
</div>
{formData.file && (
<div className="text-sm text-gray-600 dark:text-gray-300">
: {formData.file.name} ({(formData.file.size / 1024 / 1024).toFixed(2)} MB)
: {formData.file.name} ({(formData.file.size / 1024 / 1024).toFixed(2)}{' '}
MB)
</div>
)}
<button
@@ -412,9 +427,25 @@ export default function ErrorTestPage() {
{isLoading && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white dark:bg-gray-800 rounded-lg p-6 flex items-center space-x-4">
<svg className="animate-spin h-8 w-8 text-blue-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
<svg
className="animate-spin h-8 w-8 text-blue-600"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
></circle>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
<span className="text-gray-900 dark:text-white">...</span>
</div>

Some files were not shown because too many files have changed in this diff Show More