Compare commits
53 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2d66915570 | |||
| 25098f8772 | |||
| 8a67b96fdb | |||
| 682a08e0dc | |||
| b53d4dd8a5 | |||
| a4e48d32a4 | |||
| cdd1602f79 | |||
| adec9d8197 | |||
| 1b9970da14 | |||
| aa8f5ae235 | |||
| 60a6b5e5b6 | |||
| 7ae8fe753a | |||
| a1dfe879c1 | |||
| 6e7619760f | |||
| efc85da91c | |||
| 8e11fcb918 | |||
| 4a9bd9aed0 | |||
| 0f697c21bd | |||
| e0d6d0d51b | |||
| d92ba7d882 | |||
| 3e8e584065 | |||
| 9bdd565feb | |||
| b0a31e777b | |||
| 78a54d0d3d | |||
| 7f04f3564a | |||
| 22a25b17f5 | |||
| 8a63c1b716 | |||
| ab710bcaf5 | |||
| df449166d5 | |||
| da822c5fe1 | |||
| 78412323a7 | |||
| ad06cd1869 | |||
| 86682c3a1e | |||
| fee3bbdf85 | |||
| 4d3c878809 | |||
| e704398c43 | |||
| 56d20904b9 | |||
| bdddcb860e | |||
| e6e8ebf4e3 | |||
| 9b1131ce13 | |||
| 462d76dbb6 | |||
| 3791d1e157 | |||
| 2e3355f52c | |||
| b85a82785f | |||
| 2314d28e60 | |||
| 58475c63da | |||
| 55f07d971a | |||
| cd84269427 | |||
| fedeb57f48 | |||
| 386167e99b | |||
| 190d264bda | |||
| 7af3b580d5 | |||
| 4af927f218 |
@@ -6,32 +6,11 @@ body:
|
|||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
value: |
|
||||||
Please follow our community communication guidelines when filing issues.
|
Thanks for your interest in improving the project! Before submitting, please read our guidelines.
|
||||||
- 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.
|
|
||||||
|
|
||||||
提交 Issue 时请遵守社区沟通规范:
|
- [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)
|
||||||
- 提供建设性、可行动的反馈,避免情绪化表达。
|
|
||||||
- 请包含清晰的复现步骤与环境信息。
|
|
||||||
- 如果可以,欢迎直接提交修复 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
|
|
||||||
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: description
|
id: description
|
||||||
@@ -48,7 +27,7 @@ body:
|
|||||||
render: typescript
|
render: typescript
|
||||||
placeholder: Minimal reproduction code
|
placeholder: Minimal reproduction code
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: false
|
||||||
|
|
||||||
- type: input
|
- type: input
|
||||||
id: browser
|
id: browser
|
||||||
@@ -56,7 +35,7 @@ body:
|
|||||||
label: Browser
|
label: Browser
|
||||||
placeholder: 'Chrome 120, Firefox 119, etc.'
|
placeholder: 'Chrome 120, Firefox 119, etc.'
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: false
|
||||||
|
|
||||||
- type: input
|
- type: input
|
||||||
id: version
|
id: version
|
||||||
@@ -64,4 +43,19 @@ body:
|
|||||||
label: version
|
label: version
|
||||||
placeholder: '0.0.0'
|
placeholder: '0.0.0'
|
||||||
validations:
|
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
|
||||||
@@ -12,6 +12,3 @@ contact_links:
|
|||||||
- name: Code of Conduct / 行为准则
|
- name: Code of Conduct / 行为准则
|
||||||
url: https://github.com/alibaba/page-agent/blob/main/CODE_OF_CONDUCT.md
|
url: https://github.com/alibaba/page-agent/blob/main/CODE_OF_CONDUCT.md
|
||||||
about: Community expectations and standards. 社区行为期望与标准。
|
about: Community expectations and standards. 社区行为期望与标准。
|
||||||
- name: 行为准则(中文)
|
|
||||||
url: https://github.com/alibaba/page-agent/blob/main/CODE_OF_CONDUCT_zh.md
|
|
||||||
about: 中文版社区行为准则。
|
|
||||||
|
|||||||
@@ -6,19 +6,30 @@ body:
|
|||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
value: |
|
||||||
Please follow our community communication guidelines when proposing features.
|
Thanks for your interest in improving the project! Before submitting, please read our guidelines.
|
||||||
- Be polite and respectful.
|
感谢您对改进项目的兴趣!提交前请阅读我们的指南。
|
||||||
- Explain the problem first, then propose solutions.
|
|
||||||
- Prefer constructive, actionable suggestions.
|
|
||||||
- Contributions are encouraged — consider opening a 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)
|
||||||
- 先阐述问题,再提出解决方案。
|
|
||||||
- 提供建设性、可行动的建议。
|
|
||||||
- 鼓励代码贡献,欢迎直接提交 PR。
|
|
||||||
|
|
||||||
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
|
- type: checkboxes
|
||||||
id: community
|
id: community
|
||||||
@@ -32,27 +43,5 @@ body:
|
|||||||
required: true
|
required: true
|
||||||
- label: I have read the CODE_OF_CONDUCT.md and CONTRIBUTING.md. / 我已阅读行为准则。
|
- label: I have read the CODE_OF_CONDUCT.md and CONTRIBUTING.md. / 我已阅读行为准则。
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: problem
|
|
||||||
attributes:
|
|
||||||
label: Problem
|
|
||||||
placeholder: What problem does this solve?
|
|
||||||
validations:
|
validations:
|
||||||
required: true
|
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
|
|
||||||
@@ -5,9 +5,12 @@ Brief description of changes.
|
|||||||
## Type
|
## Type
|
||||||
|
|
||||||
- [ ] Bug fix
|
- [ ] Bug fix
|
||||||
- [ ] New feature
|
- [ ] Feature / Improvement
|
||||||
- [ ] Breaking change
|
- [ ] Refactor
|
||||||
- [ ] Documentation
|
- [ ] Documentation
|
||||||
|
- [ ] Website
|
||||||
|
- [ ] Demo / Testing
|
||||||
|
- [ ] Breaking change
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
@@ -19,10 +22,4 @@ Closes #(issue)
|
|||||||
|
|
||||||
## Requirements / 要求
|
## 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) . / 我已阅读并遵守行为准则。
|
- [ ] 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,请在上方关联链接。
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ jobs:
|
|||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-pages-artifact@v4
|
uses: actions/upload-pages-artifact@v4
|
||||||
with:
|
with:
|
||||||
path: './dist'
|
path: './packages/website/dist'
|
||||||
|
|
||||||
- name: Deploy to GitHub Pages
|
- name: Deploy to GitHub Pages
|
||||||
id: deployment
|
id: deployment
|
||||||
|
|||||||
@@ -19,9 +19,12 @@ jobs:
|
|||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
cache: 'npm'
|
|
||||||
registry-url: 'https://registry.npmjs.org'
|
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
|
- name: Install dependencies
|
||||||
run: npm ci
|
run: npm ci
|
||||||
|
|
||||||
@@ -29,4 +32,4 @@ jobs:
|
|||||||
run: npm run build:lib
|
run: npm run build:lib
|
||||||
|
|
||||||
- name: Publish to npm
|
- name: Publish to npm
|
||||||
run: npm publish
|
run: npm publish --workspace=page-agent
|
||||||
+6
-1
@@ -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
|
||||||
Vendored
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"editor.fontLigatures": true,
|
"editor.fontLigatures": true,
|
||||||
"cSpell.words": ["HITL", "opensource", "retryable"],
|
"cSpell.words": ["HITL", "innerhtml", "opensource", "retryable", "wouter"],
|
||||||
"markdownlint.config": {
|
"markdownlint.config": {
|
||||||
// "comment": "Relaxed rules",
|
// "comment": "Relaxed rules",
|
||||||
"default": true,
|
"default": true,
|
||||||
|
|||||||
@@ -2,36 +2,56 @@
|
|||||||
|
|
||||||
## Project Overview
|
## 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
|
1. **Core Library** (`packages/page-agent/`) - Pure JavaScript/TypeScript AI agent library for browser DOM automation, published as `page-agent` on npm
|
||||||
2. **Demo&docs Website** (`pages/`) - React documentation and landing page
|
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
|
## Development Commands
|
||||||
|
|
||||||
### Core Commands
|
### Core Commands
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm start # Start React website development server
|
npm start # Start website dev server
|
||||||
npm run build # Build both library AND website
|
npm run dev # Same as start
|
||||||
npm run build:lib # Build pure JS library only (src/ → dist/lib/)
|
npm run build # Build all packages
|
||||||
npm run build:lib:watch # Library development with auto-rebuild
|
npm run build:lib # Build page-agent library only
|
||||||
npm run lint # ESLint with TypeScript strict rules
|
npm run lint # ESLint with TypeScript strict rules
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Package-specific Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Core library
|
||||||
|
npm run build --workspace=page-agent
|
||||||
|
npm run build:watch --workspace=page-agent
|
||||||
|
|
||||||
|
# Website
|
||||||
|
npm run dev --workspace=@page-agent/website
|
||||||
|
npm run build --workspace=@page-agent/website
|
||||||
|
```
|
||||||
|
|
||||||
## Architecture & Critical Patterns
|
## Architecture & Critical Patterns
|
||||||
|
|
||||||
### 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/`
|
packages/
|
||||||
- **Entry points**: `src/entry.ts` (library), `pages/main.tsx` (website)
|
├── 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)
|
### Module Boundaries (Critical)
|
||||||
|
|
||||||
- **Core library** (`src/`): NEVER import from `pages/` - must remain pure JavaScript
|
- **Core library** (`packages/page-agent/`): NEVER import from website - must remain pure JavaScript
|
||||||
- **Website** (`pages/`): CAN import from `src/` via `@/` alias for demos
|
- **Website** (`packages/website/`): CAN import from `page-agent` for demos. Alias `@/` → `website/src/`
|
||||||
- **Import aliases**: `@/` → `src/`, `@pages/` → `pages/`
|
|
||||||
|
|
||||||
### DOM Pipeline
|
### DOM Pipeline
|
||||||
|
|
||||||
@@ -73,7 +93,7 @@ Query params configure `PageAgentConfig` automatically in `src/entry.ts`.
|
|||||||
|
|
||||||
## File Organization
|
## File Organization
|
||||||
|
|
||||||
### Core Library (`src/`)
|
### Core Library (`packages/page-agent/src/`)
|
||||||
|
|
||||||
- `entry.ts` - CDN/UMD entry point with auto-initialization
|
- `entry.ts` - CDN/UMD entry point with auto-initialization
|
||||||
- `PageAgent.ts` - **Main AI agent class** orchestrating DOM operations
|
- `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
|
- `dom/` - HTML serialization and page analysis utilities
|
||||||
- `config/` - Configuration constants and settings
|
- `config/` - Configuration constants and settings
|
||||||
|
|
||||||
### Website (`pages/`)
|
### Website (`packages/website/src/`)
|
||||||
|
|
||||||
- `main.tsx` - Site entry with hash routing setup
|
- `main.tsx` - Site entry with hash routing setup
|
||||||
- `router.tsx` - **Manual route definitions** (requires explicit registration)
|
- `router.tsx` - **Manual route definitions** (requires explicit registration)
|
||||||
@@ -97,21 +117,20 @@ Query params configure `PageAgentConfig` automatically in `src/entry.ts`.
|
|||||||
|
|
||||||
### New Documentation Page
|
### New Documentation Page
|
||||||
|
|
||||||
1. Create `pages/docs/<section>/<slug>/page.tsx`
|
1. Create `packages/website/src/docs/<section>/<slug>/page.tsx`
|
||||||
2. Add route to `pages/router.tsx` with `<Header /> + <DocsLayout>` wrapper
|
2. Add route to `packages/website/src/router.tsx` with `<Header /> + <DocsLayout>` wrapper
|
||||||
3. Add navigation item to `DocsLayout.tsx`
|
3. Add navigation item to `DocsLayout.tsx`
|
||||||
|
|
||||||
### New Agent Tool
|
### New Agent Tool
|
||||||
|
|
||||||
1. Implement under `src/tools/`
|
1. Implement under `packages/page-agent/src/tools/`
|
||||||
2. Export via `src/tools/index.ts`
|
2. Export via `packages/page-agent/src/tools/index.ts`
|
||||||
3. Wire into `PageAgent.ts` if needed
|
3. Wire into `PageAgent.ts` if needed
|
||||||
|
|
||||||
### New UI Component
|
### 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
|
2. Use event bus for PageAgent communication
|
||||||
3. Test via `pages/test-pages/`
|
|
||||||
|
|
||||||
## Code Standards
|
## Code Standards
|
||||||
|
|
||||||
@@ -136,26 +155,28 @@ Query params configure `PageAgentConfig` automatically in `src/entry.ts`.
|
|||||||
|
|
||||||
## Critical Files to Understand
|
## Critical Files to Understand
|
||||||
|
|
||||||
- `pages/router.tsx` - Central routing definition (manual registration required)
|
- `packages/page-agent/src/PageAgent.ts` - Core AI agent class with DOM manipulation
|
||||||
- `pages/components/DocsLayout.tsx` - Navigation structure
|
- `packages/page-agent/src/dom/dom_tree/index.js` - DOM extraction engine
|
||||||
- `src/PageAgent.ts` - Core AI agent class with DOM manipulation
|
- `packages/page-agent/src/utils/bus.ts` - Type-safe event bus system
|
||||||
- `src/dom/dom_tree/index.js` - DOM extraction engine
|
- `packages/page-agent/src/entry.ts` - Library entry point for CDN usage
|
||||||
- `src/utils/bus.ts` - Type-safe event bus system
|
- `packages/page-agent/vite.config.js` - Library build configuration
|
||||||
- `src/entry.ts` - Library entry point for CDN usage
|
|
||||||
- `vite.config.js` / `vite.lib.config.js` - Dual 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
|
## Debugging Common Issues
|
||||||
|
|
||||||
### Blank Documentation Pages
|
### 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
|
2. Check component import path
|
||||||
3. Verify CSS isn't hiding content (check dark mode classes)
|
3. Verify CSS isn't hiding content (check dark mode classes)
|
||||||
4. Test with minimal component first
|
4. Test with minimal component first
|
||||||
|
|
||||||
### Library Integration Issues
|
### 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
|
2. Test CDN injection with query params
|
||||||
3. Verify event bus communications are properly typed
|
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
@@ -22,10 +22,12 @@ Thank you for your interest in contributing to Page-Agent! We welcome contributi
|
|||||||
|
|
||||||
### Project Structure
|
### 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
|
1. **Core Library** (`packages/page-agent/`) - Pure JavaScript/TypeScript AI agent library for browser DOM automation, published as `page-agent` on npm
|
||||||
- **Documentation Website** (`pages/`) - React web app for landing page and docs
|
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
|
## 🤝 How to Contribute
|
||||||
|
|
||||||
|
|||||||
+5
-5
@@ -4,13 +4,13 @@
|
|||||||
|
|
||||||
[](https://badge.fury.io/js/page-agent) [](https://opensource.org/licenses/MIT) [](http://www.typescriptlang.org/) [](https://www.npmjs.com/package/page-agent) [](https://bundlephobia.com/package/page-agent) [](https://github.com/alibaba/page-agent)
|
[](https://badge.fury.io/js/page-agent) [](https://opensource.org/licenses/MIT) [](http://www.typescriptlang.org/) [](https://www.npmjs.com/package/page-agent) [](https://bundlephobia.com/package/page-agent) [](https://github.com/alibaba/page-agent)
|
||||||
|
|
||||||
**让你的网页支持 AI 自动化。**
|
纯 JS 实现的 GUI agent。使用自然语言操作你的 Web 应用。无须后端、客户端、浏览器插件。
|
||||||
|
|
||||||
运行在页面内的 UI agent. 使用自然语言操作 Web 应用。
|
|
||||||
|
|
||||||
🌐 [English](./README.md) | **中文**
|
🌐 [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
|
```html
|
||||||
<!-- 临时 CDN URL. 未来会变更 -->
|
<!-- 临时 CDN URL. 未来会变更 -->
|
||||||
<script
|
<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"
|
crossorigin="true"
|
||||||
type="text/javascript"
|
type="text/javascript"
|
||||||
></script>
|
></script>
|
||||||
|
|||||||
@@ -4,19 +4,19 @@
|
|||||||
|
|
||||||
[](https://badge.fury.io/js/page-agent) [](https://opensource.org/licenses/MIT) [](http://www.typescriptlang.org/) [](https://www.npmjs.com/package/page-agent) [](https://bundlephobia.com/package/page-agent) [](https://github.com/alibaba/page-agent)
|
[](https://badge.fury.io/js/page-agent) [](https://opensource.org/licenses/MIT) [](http://www.typescriptlang.org/) [](https://www.npmjs.com/package/page-agent) [](https://bundlephobia.com/package/page-agent) [](https://github.com/alibaba/page-agent)
|
||||||
|
|
||||||
**Transform your webpage into an AI-powered application with a single script tag.**
|
The GUI Agent Living in Your Webpage. Control web interfaces with natural language.
|
||||||
|
|
||||||
An in-page UI agent in javascript. Control web interfaces with natural language.
|
|
||||||
|
|
||||||
🌐 **English** | [中文](./README-zh.md)
|
🌐 **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
|
## ✨ Features
|
||||||
|
|
||||||
- **🎯 Easy Integration**
|
- **🎯 Easy Integration** - Transform your webpage into an agent with a single script tag.
|
||||||
- **🔐 Client-Side Processing**
|
- **🔐 Client-Side Processing**
|
||||||
- **🧠 DOM Extraction**
|
- **🧠 DOM Extraction**
|
||||||
- **💬 Natural Language Interface**
|
- **💬 Natural Language Interface**
|
||||||
@@ -33,7 +33,7 @@ An in-page UI agent in javascript. Control web interfaces with natural language.
|
|||||||
```html
|
```html
|
||||||
<!-- temporary CDN URL. May change in the future -->
|
<!-- temporary CDN URL. May change in the future -->
|
||||||
<script
|
<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"
|
crossorigin="true"
|
||||||
type="text/javascript"
|
type="text/javascript"
|
||||||
></script>
|
></script>
|
||||||
|
|||||||
+28
-9
@@ -4,32 +4,49 @@ The development progress and future plans for PageAgent.
|
|||||||
|
|
||||||
## 🚀 Current Works
|
## 🚀 Current Works
|
||||||
|
|
||||||
- [x] **MVP** - Core functionality implemented
|
- [x] **MVP**
|
||||||
- [x] **SPA interaction** - Single Page Application support
|
- Core functionality implemented.
|
||||||
|
- [x] **SPA interaction**
|
||||||
- [x] **Reasoning and (short) memory**
|
- [x] **Reasoning and (short) memory**
|
||||||
- [x] **Multi model provider integration and testing**
|
- [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] **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**
|
- [x] **Robust LLM output**
|
||||||
|
- Auto-fix incomplete output format of DeepSeek and QWen.
|
||||||
- [x] **Working homepage with live LLM API**
|
- [x] **Working homepage with live LLM API**
|
||||||
- [x] **~~free~~ CDN**
|
- [x] **~~free~~ CDN**
|
||||||
- [x] **Free evaluation plan?**
|
- [x] **Free evaluation plan**
|
||||||
- [x] **Custom actions and HITL**
|
- [x] **Custom actions and HITL**
|
||||||
- [ ] **Hooks and Events**
|
- [ ] **Hooks and Events**
|
||||||
- [x] **lifecycle hooks**
|
- [x] **lifecycle hooks**
|
||||||
- [ ] **lifecycle events**
|
- [ ] **lifecycle events**
|
||||||
- [ ] **❗Pause and intervene**
|
- [ ] **❗Pause and intervene**
|
||||||
|
- Current pause does not allow user intervention.
|
||||||
- [ ] **❗Hijack `page_open/page_change/page_unload` behavior**
|
- [ ] **❗Hijack `page_open/page_change/page_unload` behavior**
|
||||||
- [ ] **Custom knowledge base and instructions**
|
- [ ] **Custom knowledge base and instructions**
|
||||||
- [ ] **Black/white-list safeguard**
|
- [ ] **Black/white-list safeguard**
|
||||||
- [ ] **Data-masking**
|
- [ ] **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**
|
- [ ] **Optimize for popular UI frameworks**
|
||||||
|
- [x] **i18n of the website**
|
||||||
|
- [x] Chinese version
|
||||||
|
- [x] English version
|
||||||
- [ ] **Testing suits**
|
- [ ] **Testing suits**
|
||||||
- [ ] **Support custom llm fetch**
|
- [ ] **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
|
## 📋 Pending Features
|
||||||
|
|
||||||
@@ -39,4 +56,6 @@ The development progress and future plans for PageAgent.
|
|||||||
|
|
||||||
## 🤔 To Be Decided
|
## 🤔 To Be Decided
|
||||||
|
|
||||||
- [ ] **Cross-origin multi-page?** - Tricky
|
- [ ] **Cross-origin multi-page?**
|
||||||
|
- Tricky
|
||||||
|
- Need some kind of "memory rally"
|
||||||
|
|||||||
+2
-2
@@ -8,7 +8,7 @@ import globals from 'globals'
|
|||||||
import tseslint from 'typescript-eslint'
|
import tseslint from 'typescript-eslint'
|
||||||
|
|
||||||
export default defineConfig([
|
export default defineConfig([
|
||||||
globalIgnores(['dist', 'test-pages']),
|
globalIgnores(['**/dist', '**/test-pages', '**/node_modules']),
|
||||||
{
|
{
|
||||||
plugins: {
|
plugins: {
|
||||||
'react-hooks': reactHooks,
|
'react-hooks': reactHooks,
|
||||||
@@ -37,7 +37,7 @@ export default defineConfig([
|
|||||||
],
|
],
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
project: ['./tsconfig.json'],
|
project: ['./packages/*/tsconfig.json'],
|
||||||
tsconfigRootDir: import.meta.dirname,
|
tsconfigRootDir: import.meta.dirname,
|
||||||
},
|
},
|
||||||
ecmaVersion: 2020,
|
ecmaVersion: 2020,
|
||||||
|
|||||||
-32
@@ -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>
|
|
||||||
Generated
+937
-940
File diff suppressed because it is too large
Load Diff
+19
-57
@@ -1,89 +1,51 @@
|
|||||||
{
|
{
|
||||||
"name": "page-agent",
|
"name": "root",
|
||||||
"private": false,
|
"private": true,
|
||||||
"version": "0.0.3",
|
"version": "0.0.6",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/lib/page-agent.js",
|
"workspaces": [
|
||||||
"module": "./dist/lib/page-agent.js",
|
"packages/*"
|
||||||
"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"
|
|
||||||
],
|
],
|
||||||
|
"description": "AI-powered UI agent for web applications",
|
||||||
"author": "Simon<gaomeng1900>",
|
"author": "Simon<gaomeng1900>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/alibaba/page-agent.git"
|
"url": "https://github.com/alibaba/page-agent.git"
|
||||||
},
|
},
|
||||||
|
"homepage": "https://alibaba.github.io/page-agent/",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20.0.0"
|
"node": ">=20.0.0",
|
||||||
|
"npm": ">=10.0.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "npm run dev --workspace=@page-agent/website",
|
||||||
"start": "vite",
|
"start": "npm run dev --workspace=@page-agent/website",
|
||||||
"build": "tsc -b && vite build && npm run build:lib && npm run build:umd",
|
"build": "npm run build --workspaces --if-present",
|
||||||
"build:lib": "MODE=lib vite build",
|
"build:lib": "npm run build --workspace=page-agent",
|
||||||
"build:lib:watch": "MODE=lib vite build --watch",
|
"version": "node scripts/sync-version.js",
|
||||||
"build:umd": "MODE=umd vite build",
|
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"prepare": "husky"
|
"prepare": "husky"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
|
||||||
"ai-motion": "^0.4.7",
|
|
||||||
"chalk": "^5.6.2",
|
|
||||||
"zod": "^4.1.12"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commitlint/cli": "^20.1.0",
|
"@commitlint/cli": "^20.1.0",
|
||||||
"@commitlint/config-conventional": "^20.0.0",
|
"@commitlint/config-conventional": "^20.0.0",
|
||||||
"@eslint/js": "^9.37.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",
|
"@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",
|
"dotenv": "^17.2.3",
|
||||||
"eslint": "^9.37.0",
|
"eslint": "^9.37.0",
|
||||||
"eslint-config-prettier": "^10.1.8",
|
"eslint-config-prettier": "^10.1.8",
|
||||||
"eslint-plugin-react-dom": "^2.0.6",
|
"eslint-plugin-react-dom": "^2.3.9",
|
||||||
"eslint-plugin-react-hooks": "^7.0.0",
|
"eslint-plugin-react-hooks": "^7.0.1",
|
||||||
"eslint-plugin-react-refresh": "^0.4.23",
|
"eslint-plugin-react-refresh": "^0.4.24",
|
||||||
"eslint-plugin-react-x": "^2.0.6",
|
"eslint-plugin-react-x": "^2.3.9",
|
||||||
"globals": "^16.4.0",
|
"globals": "^16.4.0",
|
||||||
"husky": "^9.1.7",
|
"husky": "^9.1.7",
|
||||||
"lint-staged": "^16.2.4",
|
"lint-staged": "^16.2.4",
|
||||||
"prettier": "^3.6.2",
|
"prettier": "^3.6.2",
|
||||||
"react": "^19.2.0",
|
|
||||||
"react-dom": "^19.2.0",
|
|
||||||
"tailwindcss": "^4.1.14",
|
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
"typescript-eslint": "^8.46.0",
|
"typescript-eslint": "^8.46.0",
|
||||||
"unplugin-dts": "^1.0.0-beta.6",
|
"vite": "^7.1.9"
|
||||||
"vite": "^7.1.9",
|
|
||||||
"vite-plugin-css-injected-by-js": "^3.5.2",
|
|
||||||
"wouter": "^3.7.1"
|
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"*.{js,ts,cjs,cts,mjs,mts}": [
|
"*.{js,ts,cjs,cts,mjs,mts}": [
|
||||||
|
|||||||
-5
@@ -11,15 +11,10 @@ declare module '*.md?raw' {
|
|||||||
export default content
|
export default content
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* for local dev and umd demo
|
|
||||||
*/
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
pageAgent?: PageAgent
|
pageAgent?: PageAgent
|
||||||
PageAgent: typeof PageAgent
|
PageAgent: typeof PageAgent
|
||||||
|
|
||||||
__PAGE_AGENT_IDS__: string[]
|
__PAGE_AGENT_IDS__: string[]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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)
|
patchReact(this)
|
||||||
|
|
||||||
window.addEventListener('beforeunload', (e) => {
|
window.addEventListener('beforeunload', (e) => {
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
import type { AgentHistory, ExecutionResult, PageAgent } from '@/PageAgent'
|
import type { AgentHistory, ExecutionResult, PageAgent } from '../PageAgent'
|
||||||
import type { DomConfig } from '@/dom'
|
import type { DomConfig } from '../dom'
|
||||||
import type { SupportedLanguage } from '@/i18n'
|
import type { SupportedLanguage } from '../i18n'
|
||||||
import type { PageAgentTool } from '@/tools'
|
import type { PageAgentTool } from '../tools'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DEFAULT_API_KEY,
|
DEFAULT_API_KEY,
|
||||||
DEFAULT_BASE_URL,
|
DEFAULT_BASE_URL,
|
||||||
@@ -72,6 +71,14 @@ export interface AgentConfig {
|
|||||||
|
|
||||||
// page behavior hooks
|
// 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
|
* TODO: @unimplemented
|
||||||
* hook when action causes a new page to be opened
|
* hook when action causes a new page to be opened
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
import { VIEWPORT_EXPANSION } from '@/config/constants'
|
import { VIEWPORT_EXPANSION } from '../config/constants'
|
||||||
import domTree from '@/dom/dom_tree/index'
|
import domTree from './dom_tree/index'
|
||||||
import {
|
import {
|
||||||
ElementDomNode,
|
ElementDomNode,
|
||||||
FlatDomTree,
|
FlatDomTree,
|
||||||
InteractiveElementDomNode,
|
InteractiveElementDomNode,
|
||||||
TextDomNode,
|
TextDomNode,
|
||||||
} from '@/dom/dom_tree/type'
|
} from './dom_tree/type'
|
||||||
|
|
||||||
export interface DomConfig {
|
export interface DomConfig {
|
||||||
interactiveBlacklist?: (Element | (() => Element))[]
|
interactiveBlacklist?: (Element | (() => Element))[]
|
||||||
+1
-2
@@ -1,8 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* OpenAI Client implementation
|
* OpenAI Client implementation
|
||||||
*/
|
*/
|
||||||
import type { MacroToolInput } from '@/PageAgent'
|
import type { MacroToolInput } from '../PageAgent'
|
||||||
|
|
||||||
import { InvokeError, InvokeErrorType } from './errors'
|
import { InvokeError, InvokeErrorType } from './errors'
|
||||||
import type { InvokeResult, LLMClient, Message, OpenAIClientConfig, Tool } from './types'
|
import type { InvokeResult, LLMClient, Message, OpenAIClientConfig, Tool } from './types'
|
||||||
import { lenientParseMacroToolCall, modelPatch, zodToOpenAITool } from './utils'
|
import { lenientParseMacroToolCall, modelPatch, zodToOpenAITool } from './utils'
|
||||||
@@ -31,10 +31,9 @@
|
|||||||
* - 永远使用 tool call 来返回结构化数据,禁止模型直接返回(视为出错)
|
* - 永远使用 tool call 来返回结构化数据,禁止模型直接返回(视为出错)
|
||||||
* - 不能假设 tool 参数合法,必须有修复机制,而且修复也应该使用 tool call 返回
|
* - 不能假设 tool 参数合法,必须有修复机制,而且修复也应该使用 tool call 返回
|
||||||
*/
|
*/
|
||||||
import type { LLMConfig } from '@/config'
|
import type { LLMConfig } from '../config'
|
||||||
import { parseLLMConfig } from '@/config'
|
import { parseLLMConfig } from '../config'
|
||||||
import { EventBus, getEventBus } from '@/utils/bus'
|
import { EventBus, getEventBus } from '../utils/bus'
|
||||||
|
|
||||||
import { OpenAIClient } from './OpenAILenientClient'
|
import { OpenAIClient } from './OpenAILenientClient'
|
||||||
import { InvokeError } from './errors'
|
import { InvokeError } from './errors'
|
||||||
import type { InvokeResult, LLMClient, Message, Tool } from './types'
|
import type { InvokeResult, LLMClient, Message, Tool } from './types'
|
||||||
@@ -4,8 +4,7 @@
|
|||||||
import chalk from 'chalk'
|
import chalk from 'chalk'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
|
|
||||||
import type { MacroToolInput } from '@/PageAgent'
|
import type { MacroToolInput } from '../PageAgent'
|
||||||
|
|
||||||
import { InvokeError, InvokeErrorType } from './errors'
|
import { InvokeError, InvokeErrorType } from './errors'
|
||||||
import type { Tool } from './types'
|
import type { Tool } from './types'
|
||||||
|
|
||||||
@@ -42,6 +41,7 @@ export function lenientParseMacroToolCall(
|
|||||||
// check
|
// check
|
||||||
switch (choice.finish_reason) {
|
switch (choice.finish_reason) {
|
||||||
case 'tool_calls':
|
case 'tool_calls':
|
||||||
|
case 'function_call': // gemini
|
||||||
case 'stop': // will try a robust parse
|
case 'stop': // will try a robust parse
|
||||||
// ✅ Normal
|
// ✅ Normal
|
||||||
break
|
break
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { PageAgent } from '@/PageAgent'
|
import type { PageAgent } from '../PageAgent'
|
||||||
|
|
||||||
const clearFunctions = [] as (() => void)[]
|
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
|
// Find common React root elements and add data-page-agent-not-interactive attribute
|
||||||
export function patchReact(pageAgent: PageAgent) {
|
export function patchReact(pageAgent: PageAgent) {
|
||||||
@@ -4,8 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
import zod, { type z } from 'zod'
|
import zod, { type z } from 'zod'
|
||||||
|
|
||||||
import type { PageAgent } from '@/PageAgent'
|
import type { PageAgent } from '../PageAgent'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
clickElement,
|
clickElement,
|
||||||
getElementByIndex,
|
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 get_dropdown_options
|
||||||
// @todo select_dropdown_option
|
// @todo select_dropdown_option
|
||||||
// @todo send_keys
|
// @todo send_keys
|
||||||
@@ -33,8 +33,7 @@
|
|||||||
/* 响应式设计 */
|
/* 响应式设计 */
|
||||||
@media (max-width: 480px) {
|
@media (max-width: 480px) {
|
||||||
width: calc(100vw - 40px);
|
width: calc(100vw - 40px);
|
||||||
left: 20px;
|
--width: calc(100vw - 40px);
|
||||||
transform: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.background {
|
.background {
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
import type { PageAgent } from '@/PageAgent'
|
import type { PageAgent } from '../PageAgent'
|
||||||
import type { I18n } from '@/i18n'
|
import type { I18n } from '../i18n'
|
||||||
import { truncate } from '@/utils'
|
import { truncate } from '../utils'
|
||||||
import type { EventBus } from '@/utils/bus'
|
import type { EventBus } from '../utils/bus'
|
||||||
|
|
||||||
import { type Step, UIState } from './UIState'
|
import { type Step, UIState } from './UIState'
|
||||||
|
|
||||||
import styles from './Panel.module.css'
|
import styles from './Panel.module.css'
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Motion } from 'ai-motion'
|
import { Motion } from 'ai-motion'
|
||||||
|
|
||||||
import { isPageDark } from '@/utils/checkDarkMode'
|
import { isPageDark } from '../utils/checkDarkMode'
|
||||||
|
|
||||||
import styles from './SimulatorMask.module.css'
|
import styles from './SimulatorMask.module.css'
|
||||||
import cursorStyles from './cursor.module.css'
|
import cursorStyles from './cursor.module.css'
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* Type-safe event bus for decoupling PageAgent and Panel
|
* Type-safe event bus for decoupling PageAgent and Panel
|
||||||
*/
|
*/
|
||||||
import type { Step } from '@/ui/UIState'
|
import type { Step } from '../ui/UIState'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Event mapping definitions
|
* Event mapping definitions
|
||||||
@@ -69,10 +69,7 @@ class EventBus extends EventTarget {
|
|||||||
/**
|
/**
|
||||||
* Listen to built-in events
|
* Listen to built-in events
|
||||||
*/
|
*/
|
||||||
on<T extends keyof PageAgentEventMap>(
|
on<T extends keyof PageAgentEventMap>(event: T, handler: EventHandler<T>): void {
|
||||||
event: T,
|
|
||||||
handler: EventHandler<T & keyof PageAgentEventMap>
|
|
||||||
): void {
|
|
||||||
const wrappedHandler = (e: Event) => {
|
const wrappedHandler = (e: Event) => {
|
||||||
const customEvent = e as CustomEvent
|
const customEvent = e as CustomEvent
|
||||||
const params = customEvent.detail?.[0]
|
const params = customEvent.detail?.[0]
|
||||||
@@ -84,10 +81,7 @@ class EventBus extends EventTarget {
|
|||||||
/**
|
/**
|
||||||
* Listen to built-in events (one-time)
|
* Listen to built-in events (one-time)
|
||||||
*/
|
*/
|
||||||
once<T extends keyof PageAgentEventMap>(
|
once<T extends keyof PageAgentEventMap>(event: T, handler: EventHandler<T>): void {
|
||||||
event: T,
|
|
||||||
handler: EventHandler<T & keyof PageAgentEventMap>
|
|
||||||
): void {
|
|
||||||
const wrappedHandler = (e: Event) => {
|
const wrappedHandler = (e: Event) => {
|
||||||
const customEvent = e as CustomEvent
|
const customEvent = e as CustomEvent
|
||||||
const params = customEvent.detail?.[0]
|
const params = customEvent.detail?.[0]
|
||||||
@@ -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
|
// @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 chalk from 'chalk'
|
||||||
import 'dotenv/config'
|
import 'dotenv/config'
|
||||||
import process from 'node:process'
|
import process from 'node:process'
|
||||||
@@ -13,44 +8,18 @@ import { fileURLToPath } from 'url'
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js'
|
import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js'
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url)
|
const __dirname = dirname(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),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Library Config (ES Module for NPM Package)
|
// Library Config (ES Module for NPM Package)
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
/** @type {import('vite').UserConfig} */
|
/** @type {import('vite').UserConfig} */
|
||||||
const libConfig = {
|
const libConfig = {
|
||||||
// Library build configuration
|
clearScreen: false,
|
||||||
plugins: [
|
plugins: [
|
||||||
dts({ tsconfigPath: './tsconfig.json', bundleTypes: true }),
|
dts({ tsconfigPath: './tsconfig.json', bundleTypes: true }),
|
||||||
cssInjectedByJsPlugin({ relativeCSSInjection: true }),
|
cssInjectedByJsPlugin({ relativeCSSInjection: true }),
|
||||||
],
|
],
|
||||||
resolve: {
|
|
||||||
alias: {
|
|
||||||
'@': resolve(__dirname, 'src'),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
publicDir: false,
|
publicDir: false,
|
||||||
esbuild: {
|
esbuild: {
|
||||||
keepNames: true,
|
keepNames: true,
|
||||||
@@ -66,7 +35,6 @@ const libConfig = {
|
|||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
external: ['ai', 'ai-motion', 'chalk', 'zod'],
|
external: ['ai', 'ai-motion', 'chalk', 'zod'],
|
||||||
},
|
},
|
||||||
// minify: 'terser',
|
|
||||||
minify: false,
|
minify: false,
|
||||||
sourcemap: true,
|
sourcemap: true,
|
||||||
cssCodeSplit: true,
|
cssCodeSplit: true,
|
||||||
@@ -81,13 +49,7 @@ const libConfig = {
|
|||||||
// ============================================================================
|
// ============================================================================
|
||||||
/** @type {import('vite').UserConfig} */
|
/** @type {import('vite').UserConfig} */
|
||||||
const umdConfig = {
|
const umdConfig = {
|
||||||
// Library build configuration
|
|
||||||
plugins: [cssInjectedByJsPlugin({ relativeCSSInjection: true })],
|
plugins: [cssInjectedByJsPlugin({ relativeCSSInjection: true })],
|
||||||
resolve: {
|
|
||||||
alias: {
|
|
||||||
'@': resolve(__dirname, 'src'),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
publicDir: false,
|
publicDir: false,
|
||||||
esbuild: {
|
esbuild: {
|
||||||
keepNames: true,
|
keepNames: true,
|
||||||
@@ -109,19 +71,15 @@ const umdConfig = {
|
|||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
const MODE = process.env.MODE
|
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
|
let config
|
||||||
if (MODE === 'lib') {
|
if (MODE === 'umd') {
|
||||||
config = libConfig
|
|
||||||
} else if (MODE === 'umd') {
|
|
||||||
config = umdConfig
|
config = umdConfig
|
||||||
} else {
|
} else {
|
||||||
config = websiteConfig
|
config = libConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defineConfig(config)
|
export default defineConfig(config)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
# Landing Page & Docs
|
||||||
Vendored
+6
@@ -0,0 +1,6 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
|
declare module '*.module.css' {
|
||||||
|
const classes: Record<string, string>
|
||||||
|
export default classes
|
||||||
|
}
|
||||||
@@ -0,0 +1,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>
|
||||||
@@ -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() {
|
export default function BetaNotice() {
|
||||||
|
const { t } = useTranslation('common')
|
||||||
|
|
||||||
return (
|
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="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 items-start">
|
||||||
<div className="flex-shrink-0">
|
<div className="shrink-0">
|
||||||
<span className="text-xl">🚧</span>
|
<span className="text-xl">🚧</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-3">
|
<div className="ml-3">
|
||||||
<h3 className="text-sm font-medium text-orange-800 dark:text-orange-200 mb-1">
|
<h3 className="text-sm font-medium text-orange-800 dark:text-orange-200 mb-1">
|
||||||
Beta 阶段
|
{t('beta_notice.title')}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-sm text-orange-700 dark:text-orange-300">
|
<p className="text-sm text-orange-700 dark:text-orange-300">{t('beta_notice.content')}</p>
|
||||||
当前功能未完成,接口可能随时变更。正式版本发布前请勿用于生产环境。
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -28,7 +28,7 @@ const CodeEditor: React.FC<CodeEditorProps> = ({
|
|||||||
|
|
||||||
// 使用 Tailwind 的 dark: 前缀实现自动主题切换
|
// 使用 Tailwind 的 dark: 前缀实现自动主题切换
|
||||||
const containerClasses =
|
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 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 headerTextClasses = 'text-gray-700 dark:text-gray-300'
|
||||||
const languageTextClasses = 'text-gray-600 dark:text-gray-400'
|
const languageTextClasses = 'text-gray-600 dark:text-gray-400'
|
||||||
@@ -72,7 +72,7 @@ const CodeEditor: React.FC<CodeEditorProps> = ({
|
|||||||
<div className="flex">
|
<div className="flex">
|
||||||
{/* 行号 */}
|
{/* 行号 */}
|
||||||
{showLineNumbers && (
|
{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">
|
<div className="text-xs font-mono leading-6">
|
||||||
{lines.map((line, lineIdx) => {
|
{lines.map((line, lineIdx) => {
|
||||||
const lineNum = lineIdx + 1
|
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() {
|
export default function Footer() {
|
||||||
|
const { t } = useTranslation('common')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<footer
|
<footer
|
||||||
className="bg-gray-50 dark:bg-gray-900 border-t border-gray-200 dark:border-gray-700"
|
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="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">
|
<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">
|
<div className="flex items-center space-x-6">
|
||||||
<a
|
<a
|
||||||
href="https://github.com/alibaba/page-agent"
|
href="https://github.com/alibaba/page-agent"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="text-foreground/80 hover:text-foreground transition-colors duration-200"
|
className="text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition-colors duration-200"
|
||||||
aria-label="访问 GitHub 仓库"
|
aria-label={t('footer.github_label')}
|
||||||
>
|
>
|
||||||
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
<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" />
|
<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" />
|
||||||
@@ -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, '&').replace(/</g, '<').replace(/>/g, '>')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 语法高亮函数,先提取 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
)
|
||||||
|
}
|
||||||
+21
-19
@@ -1,21 +1,24 @@
|
|||||||
import BetaNotice from '@pages/components/BetaNotice'
|
import { useTranslation } from 'react-i18next'
|
||||||
import CodeEditor from '@pages/components/CodeEditor'
|
|
||||||
|
import BetaNotice from '@/components/BetaNotice'
|
||||||
|
import CodeEditor from '@/components/CodeEditor'
|
||||||
|
|
||||||
export default function CustomTools() {
|
export default function CustomTools() {
|
||||||
|
const { t } = useTranslation('docs')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<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">
|
<p className="text-xl text-gray-600 dark:text-gray-300 mb-8 leading-relaxed">
|
||||||
通过注册自定义工具,扩展 AI Agent 的能力边界。使用 Zod 定义严格的输入接口,让 AI
|
{t('custom_tools.subtitle')}
|
||||||
安全调用你的业务逻辑。
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="space-y-8">
|
<div className="space-y-8">
|
||||||
<section>
|
<section>
|
||||||
<h2 className="text-2xl font-bold mb-4">工具注册</h2>
|
<h2 className="text-2xl font-bold mb-4">{t('custom_tools.registration')}</h2>
|
||||||
<p className="text-foreground/80 mb-4">
|
<p className="text-gray-600 dark:text-gray-300 mb-4">
|
||||||
每个自定义工具需要定义四个核心属性:name、description、input schema 和 execute 函数。
|
{t('custom_tools.registration_desc')}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
@@ -49,13 +52,12 @@ const pageAgent = new PageAgent({customTools})
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<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 />
|
<BetaNotice />
|
||||||
|
|
||||||
<p className="text-foreground/80 mb-4">
|
<p className="text-gray-600 dark:text-gray-300 mb-4">
|
||||||
通过 <code className="bg-gray-200 dark:bg-gray-700 px-2 py-1 rounded">pageFilter</code>{' '}
|
{t('custom_tools.page_filter_desc')}
|
||||||
属性控制工具在哪些页面可见,提升安全性和用户体验。
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
@@ -82,16 +84,16 @@ const pageAgent = new PageAgent({customTools})
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<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="space-y-4">
|
||||||
<div className="p-4 bg-yellow-50 dark:bg-yellow-900/20 rounded-lg">
|
<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">
|
<h3 className="text-lg font-semibold text-yellow-900 dark:text-yellow-300 mb-2">
|
||||||
⚡ 性能优化
|
{t('custom_tools.bp_performance')}
|
||||||
</h3>
|
</h3>
|
||||||
<ul className="text-foreground/80 space-y-1 text-sm">
|
<ul className="text-gray-600 dark:text-gray-300 space-y-1 text-sm">
|
||||||
<li>• 使用 pageFilter 减少不必要的工具加载</li>
|
<li>{t('custom_tools.bp_1')}</li>
|
||||||
<li>• 在 execute 函数中实现适当的缓存机制</li>
|
<li>{t('custom_tools.bp_2')}</li>
|
||||||
<li>• 避免在工具中执行耗时的同步操作</li>
|
<li>{t('custom_tools.bp_3')}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
+9
-5
@@ -1,5 +1,5 @@
|
|||||||
import BetaNotice from '@pages/components/BetaNotice'
|
import BetaNotice from '@/components/BetaNotice'
|
||||||
import CodeEditor from '@pages/components/CodeEditor'
|
import CodeEditor from '@/components/CodeEditor'
|
||||||
|
|
||||||
export default function DataMasking() {
|
export default function DataMasking() {
|
||||||
return (
|
return (
|
||||||
@@ -8,7 +8,7 @@ export default function DataMasking() {
|
|||||||
|
|
||||||
<BetaNotice />
|
<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 处理过程中的数据安全。
|
保护敏感数据,确保 AI 处理过程中的数据安全。
|
||||||
</p>
|
</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 className="text-lg font-semibold mb-2 text-blue-900 dark:text-blue-300">
|
||||||
🔒 自动脱敏
|
🔒 自动脱敏
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-foreground/80">自动识别并脱敏手机号、身份证号、银行卡号等敏感信息。</p>
|
<p className="text-gray-600 dark:text-gray-300">
|
||||||
|
自动识别并脱敏手机号、身份证号、银行卡号等敏感信息。
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="p-4 bg-purple-50 dark:bg-purple-900/20 rounded-lg">
|
<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 className="text-lg font-semibold mb-2 text-purple-900 dark:text-purple-300">
|
||||||
⚙️ 自定义规则
|
⚙️ 自定义规则
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-foreground/80">支持自定义脱敏规则,适应不同业务场景的数据保护需求。</p>
|
<p className="text-gray-600 dark:text-gray-300">
|
||||||
|
支持自定义脱敏规则,适应不同业务场景的数据保护需求。
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
+18
-12
@@ -1,5 +1,5 @@
|
|||||||
import BetaNotice from '@pages/components/BetaNotice'
|
import BetaNotice from '@/components/BetaNotice'
|
||||||
import CodeEditor from '@pages/components/CodeEditor'
|
import CodeEditor from '@/components/CodeEditor'
|
||||||
|
|
||||||
export default function KnowledgeInjection() {
|
export default function KnowledgeInjection() {
|
||||||
return (
|
return (
|
||||||
@@ -8,7 +8,7 @@ export default function KnowledgeInjection() {
|
|||||||
|
|
||||||
<BetaNotice />
|
<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 深度理解你的业务场景和应用逻辑,实现更精准的自动化操作。
|
通过多层次的知识注入,让 AI 深度理解你的业务场景和应用逻辑,实现更精准的自动化操作。
|
||||||
</p>
|
</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 className="text-xl font-semibold mb-3 text-purple-900 dark:text-purple-300">
|
||||||
🎯 系统级指令
|
🎯 系统级指令
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-foreground/80 mb-4">为 AI 设定全局行为准则和工作风格。</p>
|
<p className="text-gray-600 dark:text-gray-300 mb-4">
|
||||||
<ul className="list-disc list-inside space-y-2 text-foreground/70">
|
为 AI 设定全局行为准则和工作风格。
|
||||||
|
</p>
|
||||||
|
<ul className="list-disc list-inside space-y-2 text-gray-500 dark:text-gray-400">
|
||||||
<li>定义 AI 的工作风格和交互方式</li>
|
<li>定义 AI 的工作风格和交互方式</li>
|
||||||
<li>设置安全边界和操作限制</li>
|
<li>设置安全边界和操作限制</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 className="text-xl font-semibold mb-3 text-blue-900 dark:text-blue-300">
|
||||||
� 业务领域知识
|
� 业务领域知识
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-foreground/80 mb-4">
|
<p className="text-gray-600 dark:text-gray-300 mb-4">
|
||||||
注入应用的核心业务知识,包括产品介绍、操作流程、术语定义等,让 AI 理解业务上下文。
|
注入应用的核心业务知识,包括产品介绍、操作流程、术语定义等,让 AI 理解业务上下文。
|
||||||
</p>
|
</p>
|
||||||
<div className="grid md:grid-cols-2 gap-4">
|
<div className="grid md:grid-cols-2 gap-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<h4 className="font-semibold text-blue-800 dark:text-blue-200">产品知识</h4>
|
<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>
|
<li>用户角色和权限体系</li>
|
||||||
<li>业务规则和约束条件</li>
|
<li>业务规则和约束条件</li>
|
||||||
@@ -71,7 +73,7 @@ const pageAgent = new PageAgent({
|
|||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<h4 className="font-semibold text-blue-800 dark:text-blue-200">操作指南</h4>
|
<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>
|
<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 className="text-xl font-semibold mb-3 text-green-900 dark:text-green-300">
|
||||||
📄 页面级精准指导
|
📄 页面级精准指导
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-foreground/80 mb-4">
|
<p className="text-gray-600 dark:text-gray-300 mb-4">
|
||||||
为特定页面提供精确的操作指导和元素说明,让 AI 准确理解页面结构和交互逻辑。
|
为特定页面提供精确的操作指导和元素说明,让 AI 准确理解页面结构和交互逻辑。
|
||||||
</p>
|
</p>
|
||||||
<div className="grid md:grid-cols-3 gap-4">
|
<div className="grid md:grid-cols-3 gap-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<h4 className="font-semibold text-green-800 dark:text-green-200">元素标注</h4>
|
<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 className="space-y-2">
|
<div className="space-y-2">
|
||||||
<h4 className="font-semibold text-green-800 dark:text-green-200">交互说明</h4>
|
<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 className="space-y-2">
|
<div className="space-y-2">
|
||||||
<h4 className="font-semibold text-green-800 dark:text-green-200">页面逻辑</h4>
|
<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>
|
</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>
|
||||||
|
)
|
||||||
|
}
|
||||||
+15
-9
@@ -1,4 +1,4 @@
|
|||||||
import BetaNotice from '@pages/components/BetaNotice'
|
import BetaNotice from '@/components/BetaNotice'
|
||||||
|
|
||||||
export default function SecurityPermissions() {
|
export default function SecurityPermissions() {
|
||||||
return (
|
return (
|
||||||
@@ -7,7 +7,7 @@ export default function SecurityPermissions() {
|
|||||||
|
|
||||||
<BetaNotice />
|
<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 操作在可控范围内进行。
|
page-agent 提供四种安全机制,确保 AI 操作在可控范围内进行。
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -19,13 +19,15 @@ export default function SecurityPermissions() {
|
|||||||
<h3 className="text-lg font-semibold text-red-900 dark:text-red-300">
|
<h3 className="text-lg font-semibold text-red-900 dark:text-red-300">
|
||||||
🚫 操作黑名单
|
🚫 操作黑名单
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-foreground/80">禁止 AI 操作敏感元素,如删除按钮、支付按钮等。</p>
|
<p className="text-gray-600 dark:text-gray-300">
|
||||||
|
禁止 AI 操作敏感元素,如删除按钮、支付按钮等。
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-4 bg-green-50 dark:bg-green-900/20 rounded-lg">
|
<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 className="text-lg font-semibold text-green-900 dark:text-green-300">
|
||||||
✅ 操作白名单
|
✅ 操作白名单
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-foreground/80">明确定义 AI 可以操作的元素范围。</p>
|
<p className="text-gray-600 dark:text-gray-300">明确定义 AI 可以操作的元素范围。</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -37,13 +39,13 @@ export default function SecurityPermissions() {
|
|||||||
<h3 className="text-lg font-semibold text-red-900 dark:text-red-300">
|
<h3 className="text-lg font-semibold text-red-900 dark:text-red-300">
|
||||||
🚫 URL 黑名单
|
🚫 URL 黑名单
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-foreground/80">禁止 AI 访问敏感页面和危险链接。</p>
|
<p className="text-gray-600 dark:text-gray-300">禁止 AI 访问敏感页面和危险链接。</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-4 bg-green-50 dark:bg-green-900/20 rounded-lg">
|
<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 className="text-lg font-semibold text-green-900 dark:text-green-300">
|
||||||
✅ URL 白名单
|
✅ URL 白名单
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-foreground/80">限制 AI 只能访问预定义的安全页面。</p>
|
<p className="text-gray-600 dark:text-gray-300">限制 AI 只能访问预定义的安全页面。</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</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 className="text-lg font-semibold mb-2 text-yellow-900 dark:text-yellow-300">
|
||||||
⚠️ 高危操作控制
|
⚠️ 高危操作控制
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-foreground/80 mb-3">
|
<p className="text-gray-600 dark:text-gray-300 mb-3">
|
||||||
在 AI 指令中明确列举高危操作,通过两种策略进行控制:
|
在 AI 指令中明确列举高危操作,通过两种策略进行控制:
|
||||||
</p>
|
</p>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="pl-3 border-l-2 border-red-400">
|
<div className="pl-3 border-l-2 border-red-400">
|
||||||
<p className="font-medium text-red-700 dark:text-red-300">完全禁止操作</p>
|
<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>
|
||||||
<div className="pl-3 border-l-2 border-orange-400">
|
<div className="pl-3 border-l-2 border-orange-400">
|
||||||
<p className="font-medium text-orange-700 dark:text-orange-300">需用户确认操作</p>
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
+10
-6
@@ -1,5 +1,5 @@
|
|||||||
import BetaNotice from '@pages/components/BetaNotice'
|
import BetaNotice from '@/components/BetaNotice'
|
||||||
import CodeEditor from '@pages/components/CodeEditor'
|
import CodeEditor from '@/components/CodeEditor'
|
||||||
|
|
||||||
export default function BestPractices() {
|
export default function BestPractices() {
|
||||||
return (
|
return (
|
||||||
@@ -19,7 +19,9 @@ export default function BestPractices() {
|
|||||||
<h3 className="text-lg font-semibold mb-2 text-green-900 dark:text-green-300">
|
<h3 className="text-lg font-semibold mb-2 text-green-900 dark:text-green-300">
|
||||||
⚡ 减少 API 调用
|
⚡ 减少 API 调用
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-foreground/80 mb-3">合并多个操作指令,减少与 AI 模型的交互次数。</p>
|
<p className="text-gray-600 dark:text-gray-300 mb-3">
|
||||||
|
合并多个操作指令,减少与 AI 模型的交互次数。
|
||||||
|
</p>
|
||||||
|
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
code={`// 推荐:合并操作
|
code={`// 推荐:合并操作
|
||||||
@@ -36,7 +38,9 @@ await pageAgent.execute('点击提交按钮');`}
|
|||||||
<h3 className="text-lg font-semibold mb-2 text-blue-900 dark:text-blue-300">
|
<h3 className="text-lg font-semibold mb-2 text-blue-900 dark:text-blue-300">
|
||||||
🎯 精确的元素描述
|
🎯 精确的元素描述
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-foreground/80">使用具体、明确的元素描述,提高操作成功率。</p>
|
<p className="text-gray-600 dark:text-gray-300">
|
||||||
|
使用具体、明确的元素描述,提高操作成功率。
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -45,12 +49,12 @@ await pageAgent.execute('点击提交按钮');`}
|
|||||||
<div className="space-y-3 mb-6">
|
<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">
|
<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>
|
<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>
|
||||||
|
|
||||||
<div className="p-3 bg-yellow-50 dark:bg-yellow-900/20 rounded-lg border-l-4 border-yellow-500">
|
<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>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
+4
-4
@@ -1,5 +1,5 @@
|
|||||||
import BetaNotice from '@pages/components/BetaNotice'
|
import BetaNotice from '@/components/BetaNotice'
|
||||||
import CodeEditor from '@pages/components/CodeEditor'
|
import CodeEditor from '@/components/CodeEditor'
|
||||||
|
|
||||||
export default function CdnSetup() {
|
export default function CdnSetup() {
|
||||||
return (
|
return (
|
||||||
@@ -16,7 +16,7 @@ export default function CdnSetup() {
|
|||||||
className="mb-8"
|
className="mb-8"
|
||||||
code={`
|
code={`
|
||||||
// 仅供测试使用,稳定 CDN 待定
|
// 仅供测试使用,稳定 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>
|
<script>
|
||||||
window.pageAgent.panel.show()
|
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 className="text-lg font-semibold mb-2 text-yellow-900 dark:text-yellow-300">
|
||||||
⚠️ 注意事项
|
⚠️ 注意事项
|
||||||
</h3>
|
</h3>
|
||||||
<ul className="text-foreground/80 space-y-1">
|
<ul className="text-gray-600 dark:text-gray-300 space-y-1">
|
||||||
<li>• 生产环境建议使用固定版本号</li>
|
<li>• 生产环境建议使用固定版本号</li>
|
||||||
<li>• 确保 HTTPS 环境下使用</li>
|
<li>• 确保 HTTPS 环境下使用</li>
|
||||||
<li>• 配置 CSP 策略允许脚本执行</li>
|
<li>• 配置 CSP 策略允许脚本执行</li>
|
||||||
+9
-1
@@ -1,4 +1,4 @@
|
|||||||
import CodeEditor from '@pages/components/CodeEditor'
|
import CodeEditor from '@/components/CodeEditor'
|
||||||
|
|
||||||
export default function Configuration() {
|
export default function Configuration() {
|
||||||
return (
|
return (
|
||||||
@@ -70,6 +70,14 @@ interface AgentConfig {
|
|||||||
|
|
||||||
// page behavior hooks
|
// 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
|
* TODO: @unimplemented
|
||||||
* hook when action causes a new page to be opened
|
* hook when action causes a new page to be opened
|
||||||
+18
-16
@@ -1,17 +1,17 @@
|
|||||||
import CodeEditor from '@pages/components/CodeEditor'
|
import CodeEditor from '@/components/CodeEditor'
|
||||||
|
|
||||||
export default function ThirdPartyAgentPage() {
|
export default function ThirdPartyAgentPage() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-4xl font-bold mb-6">接入第三方 Agent</h1>
|
<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 的眼和手。
|
将 pageAgent 作为工具接入你的答疑助手或 Agent 系统,成为你 Agent 的眼和手。
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="bg-blue-50 dark:bg-blue-900/20 rounded-lg p-4 mb-6">
|
<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>
|
<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">
|
<p className="text-blue-800 dark:text-blue-200">
|
||||||
让你的 Agent 不再只是"嘴巴",而是拥有"眼睛"和"手"的完整智能体。
|
让你的答疑机器人不再只是"嘴巴",而是拥有"眼睛"和"手"的完整智能体。
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -48,26 +48,26 @@ const pageAgentTool = {
|
|||||||
|
|
||||||
<h2 className="text-2xl font-bold mb-4">应用场景</h2>
|
<h2 className="text-2xl font-bold mb-4">应用场景</h2>
|
||||||
<div className="grid md:grid-cols-2 gap-4 mb-6">
|
<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">
|
<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">🤖 智能客服系统</h4>
|
<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 className="text-sm text-gray-600 dark:text-gray-300">
|
||||||
客服机器人帮用户直接操作系统,如"帮我提交工单"
|
客服机器人帮用户直接操作系统,如"帮我提交工单"
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</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">
|
<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">📋 业务流程助手</h4>
|
<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 className="text-sm text-gray-600 dark:text-gray-300">
|
||||||
引导新员工完成复杂流程,如"完成客户入职"
|
引导新员工完成复杂流程,如"完成客户入职"
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</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">
|
<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">🎯 个人效率助手</h4>
|
<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 className="text-sm text-gray-600 dark:text-gray-300">
|
||||||
跨网站帮你完成任务,如"预订会议室"
|
跨网站帮你完成任务,如"预订会议室"
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</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">
|
<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">🔧 运维自动化</h4>
|
<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 className="text-sm text-gray-600 dark:text-gray-300">
|
||||||
通过自然语言操作管理后台,如"重启服务器"
|
通过自然语言操作管理后台,如"重启服务器"
|
||||||
</p>
|
</p>
|
||||||
@@ -77,12 +77,12 @@ const pageAgentTool = {
|
|||||||
<h2 className="text-2xl font-bold mb-4">最佳实践</h2>
|
<h2 className="text-2xl font-bold mb-4">最佳实践</h2>
|
||||||
<div className="space-y-4 mb-6">
|
<div className="space-y-4 mb-6">
|
||||||
<div className="p-4 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
<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" />
|
<CodeEditor code={`// @TODO`} language="javascript" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="p-4 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
<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" />
|
<CodeEditor code={`// @TODO`} language="javascript" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -98,9 +98,11 @@ const pageAgentTool = {
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</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">
|
<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">🎉 开始集成</h3>
|
<h3 className="text-lg font-semibold mb-2 text-gray-900 dark:text-white">🎉 开始集成</h3>
|
||||||
<p className="mb-3">通过这种方式,你的 Agent 系统就能真正成为用户的智能助手。</p>
|
<p className="mb-3 text-gray-700 dark:text-gray-300">
|
||||||
|
通过这种方式,你的 Agent 系统就能真正成为用户的智能助手。
|
||||||
|
</p>
|
||||||
<a
|
<a
|
||||||
href="/docs/integration/configuration"
|
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"
|
className="inline-flex items-center px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors duration-200"
|
||||||
+54
-66
@@ -1,153 +1,141 @@
|
|||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
export default function LimitationsPage() {
|
export default function LimitationsPage() {
|
||||||
|
const { t } = useTranslation('docs')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-4xl mx-auto">
|
<div className="max-w-4xl mx-auto">
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<h1 className="text-4xl font-bold mb-4 text-gray-900 dark:text-white">使用限制</h1>
|
<h1 className="text-4xl font-bold mb-4 text-gray-900 dark:text-white">
|
||||||
<p className="text-xl text-gray-600 dark:text-gray-300">
|
{t('limitations.title')}
|
||||||
了解 page-agent 当前的功能边界和技术限制
|
</h1>
|
||||||
</p>
|
<p className="text-xl text-gray-600 dark:text-gray-300">{t('limitations.subtitle')}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="prose prose-lg dark:prose-invert max-w-none">
|
<div className="prose prose-lg dark:prose-invert max-w-none">
|
||||||
<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">
|
<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">
|
<ul className="text-blue-700 dark:text-blue-300 space-y-2">
|
||||||
<li>
|
<li>{t('limitations.spa_limit_1')}</li>
|
||||||
• <strong>仅支持单页应用(SPA)</strong>:目前只能在单个页面内进行操作
|
<li>{t('limitations.spa_limit_2')}</li>
|
||||||
</li>
|
<li>{t('limitations.spa_limit_3')}</li>
|
||||||
<li>
|
|
||||||
• <strong>多页接力功能正在设计中</strong>:暂时无法跨页面执行连续任务
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
• <strong>无法操作未接入该能力的网站</strong>:需要目标网站主动集成 page-agent
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</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">
|
<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="grid md:grid-cols-2 gap-4 mb-6">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center text-green-600 dark:text-green-400">
|
<div className="flex items-center text-green-600 dark:text-green-400">
|
||||||
<span className="mr-2">✅</span>
|
<span className="mr-2">✅</span>
|
||||||
<span>点击操作</span>
|
<span>{t('limitations.op_click')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center text-green-600 dark:text-green-400">
|
<div className="flex items-center text-green-600 dark:text-green-400">
|
||||||
<span className="mr-2">✅</span>
|
<span className="mr-2">✅</span>
|
||||||
<span>文本输入</span>
|
<span>{t('limitations.op_input')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center text-green-600 dark:text-green-400">
|
<div className="flex items-center text-green-600 dark:text-green-400">
|
||||||
<span className="mr-2">✅</span>
|
<span className="mr-2">✅</span>
|
||||||
<span>页面滚动</span>
|
<span>{t('limitations.op_scroll')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center text-green-600 dark:text-green-400">
|
<div className="flex items-center text-green-600 dark:text-green-400">
|
||||||
<span className="mr-2">✅</span>
|
<span className="mr-2">✅</span>
|
||||||
<span>表单提交</span>
|
<span>{t('limitations.op_submit')}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center text-green-600 dark:text-green-400">
|
<div className="flex items-center text-green-600 dark:text-green-400">
|
||||||
<span className="mr-2">✅</span>
|
<span className="mr-2">✅</span>
|
||||||
<span>选择操作</span>
|
<span>{t('limitations.op_select')}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center text-green-600 dark:text-green-400">
|
<div className="flex items-center text-green-600 dark:text-green-400">
|
||||||
<span className="mr-2">✅</span>
|
<span className="mr-2">✅</span>
|
||||||
<span>焦点切换</span>
|
<span>{t('limitations.op_focus')}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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="grid md:grid-cols-2 gap-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center text-red-600 dark:text-red-400">
|
<div className="flex items-center text-red-600 dark:text-red-400">
|
||||||
<span className="mr-2">❌</span>
|
<span className="mr-2">❌</span>
|
||||||
<span>鼠标悬停(hover)</span>
|
<span>{t('limitations.op_hover')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center text-red-600 dark:text-red-400">
|
<div className="flex items-center text-red-600 dark:text-red-400">
|
||||||
<span className="mr-2">❌</span>
|
<span className="mr-2">❌</span>
|
||||||
<span>拖拽操作</span>
|
<span>{t('limitations.op_drag')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center text-red-600 dark:text-red-400">
|
<div className="flex items-center text-red-600 dark:text-red-400">
|
||||||
<span className="mr-2">❌</span>
|
<span className="mr-2">❌</span>
|
||||||
<span>右键菜单</span>
|
<span>{t('limitations.op_context')}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center text-red-600 dark:text-red-400">
|
<div className="flex items-center text-red-600 dark:text-red-400">
|
||||||
<span className="mr-2">❌</span>
|
<span className="mr-2">❌</span>
|
||||||
<span>图形绘制</span>
|
<span>{t('limitations.op_draw')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center text-red-600 dark:text-red-400">
|
<div className="flex items-center text-red-600 dark:text-red-400">
|
||||||
<span className="mr-2">❌</span>
|
<span className="mr-2">❌</span>
|
||||||
<span>键盘快捷键</span>
|
<span>{t('limitations.op_keyboard')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center text-red-600 dark:text-red-400">
|
<div className="flex items-center text-red-600 dark:text-red-400">
|
||||||
<span className="mr-2">❌</span>
|
<span className="mr-2">❌</span>
|
||||||
<span>基于点击区域或鼠标位置的控制</span>
|
<span>{t('limitations.op_position')}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<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>
|
<h3 className="font-semibold text-red-800 dark:text-red-200 mb-2">
|
||||||
<p className="text-red-700 dark:text-red-300 mb-3">
|
{t('limitations.no_vision_title')}
|
||||||
page-agent 基于 DOM 结构进行理解和操作,<strong>没有视觉识别能力</strong>
|
</h3>
|
||||||
,无法理解以下内容:
|
<p className="text-red-700 dark:text-red-300 mb-3">{t('limitations.no_vision_desc')}</p>
|
||||||
</p>
|
|
||||||
<ul className="text-red-700 dark:text-red-300 space-y-1">
|
<ul className="text-red-700 dark:text-red-300 space-y-1">
|
||||||
<li>
|
<li>{t('limitations.no_vision_1')}</li>
|
||||||
• <strong>图片内容</strong>:无法识别图片中的文字、图标或视觉元素
|
<li>{t('limitations.no_vision_2')}</li>
|
||||||
</li>
|
<li>{t('limitations.no_vision_3')}</li>
|
||||||
<li>
|
<li>{t('limitations.no_vision_4')}</li>
|
||||||
• <strong>Canvas 画布</strong>:无法理解 Canvas 中绘制的图形和内容
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
• <strong>WebGL 3D 内容</strong>:无法操作 3D 场景中的元素
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
• <strong>SVG 图形</strong>:无法理解 SVG 中的视觉内容和路径
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</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="bg-gray-50 dark:bg-gray-800 rounded-lg p-6 mb-6">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<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">
|
<p className="text-gray-600 dark:text-gray-300">
|
||||||
所有操作都基于 DOM 元素的语义化标签和属性。如果页面结构不够语义化,或者没有任何
|
{t('limitations.req_semantic_desc')}
|
||||||
accessibility 特性,可能影响 AI 的理解准确性。
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="font-semibold mb-2">UI/UX</h3>
|
<h3 className="font-semibold mb-2">{t('limitations.req_ux_title')}</h3>
|
||||||
<p className="text-gray-600 dark:text-gray-300">
|
<p className="text-gray-600 dark:text-gray-300">{t('limitations.req_ux_desc')}</p>
|
||||||
反常识的交互规则、基于视觉的操作提示、复杂的鼠标交互、快速出现快速消失的元素等,都会影响
|
|
||||||
AI 的理解和操作。
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="font-semibold mb-2">环境要求</h3>
|
<h3 className="font-semibold mb-2">{t('limitations.req_env_title')}</h3>
|
||||||
<p className="text-gray-600 dark:text-gray-300">modern browser</p>
|
<p className="text-gray-600 dark:text-gray-300">{t('limitations.req_env_desc')}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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">
|
<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">
|
<ul className="text-green-700 dark:text-green-300 space-y-1">
|
||||||
<li>• 多页面接力操作能力</li>
|
<li>{t('limitations.future_1')}</li>
|
||||||
<li>• 更丰富的鼠标交互支持</li>
|
<li>{t('limitations.future_2')}</li>
|
||||||
<li>• 基础的视觉理解能力</li>
|
<li>{t('limitations.future_3')}</li>
|
||||||
<li>• 更智能的错误恢复机制</li>
|
<li>{t('limitations.future_4')}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
|
)
|
||||||
|
}
|
||||||
+15
-11
@@ -1,31 +1,35 @@
|
|||||||
import BetaNotice from '@pages/components/BetaNotice'
|
import { useTranslation } from 'react-i18next'
|
||||||
import CodeEditor from '@pages/components/CodeEditor'
|
|
||||||
|
import BetaNotice from '@/components/BetaNotice'
|
||||||
|
import CodeEditor from '@/components/CodeEditor'
|
||||||
|
|
||||||
export default function QuickStart() {
|
export default function QuickStart() {
|
||||||
|
const { t } = useTranslation('docs')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<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="space-y-4 mb-6">
|
||||||
<div className="p-4 bg-blue-50 dark:bg-blue-900/20 rounded-lg">
|
<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">
|
<h3 className="text-lg font-semibold mb-2 text-blue-900 dark:text-blue-300">
|
||||||
1. 引入方式
|
{t('quick_start.step1_title')}
|
||||||
</h3>
|
</h3>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div>
|
<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
|
<CodeEditor
|
||||||
code={`// 仅供测试使用
|
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"
|
language="html"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<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
|
<CodeEditor
|
||||||
code={`// npm install page-agent
|
code={`// npm install page-agent
|
||||||
import PageAgent from '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">
|
<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">
|
<h3 className="text-lg font-semibold mb-2 text-green-900 dark:text-green-300">
|
||||||
2. 初始化配置
|
{t('quick_start.step2_title')}
|
||||||
</h3>
|
</h3>
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
code={`// 仅供测试使用,生产环境需要配置 LLM 接入点,本工具不提供 LLM 服务
|
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">
|
<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 className="text-lg font-semibold mb-2 text-purple-900 dark:text-purple-300">
|
||||||
3. 开始使用
|
{t('quick_start.step3_title')}
|
||||||
</h3>
|
</h3>
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
code={`// 程序化执行自然语言指令
|
code={`// 程序化执行自然语言指令
|
||||||
@@ -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 页面国际化
|
||||||
|
|
||||||
@@ -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: '为视障用户、老年用户提供自然语言交互,对接屏幕阅读器或语音助理,让软件人人可用。',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 { Route, Router, Switch } from 'wouter'
|
||||||
import { useHashLocation } from 'wouter/use-hash-location'
|
import { useHashLocation } from 'wouter/use-hash-location'
|
||||||
|
|
||||||
|
import './i18n/config'
|
||||||
|
import './i18n/types'
|
||||||
import { default as PagesRouter } from './router.tsx'
|
import { default as PagesRouter } from './router.tsx'
|
||||||
import { default as TestPagesRouter } from './test-pages/router.tsx'
|
import { default as TestPagesRouter } from './test-pages/router.tsx'
|
||||||
|
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
/* eslint-disable react-dom/no-dangerously-set-innerhtml */
|
/* 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 { Link, useSearchParams } from 'wouter'
|
||||||
|
|
||||||
import { PageAgent } from '@/PageAgent.js'
|
|
||||||
|
|
||||||
import Footer from './components/Footer'
|
import Footer from './components/Footer'
|
||||||
import Header from './components/Header'
|
import Header from './components/Header'
|
||||||
|
|
||||||
const injection = encodeURI(
|
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 = `
|
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"
|
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"
|
draggable="true"
|
||||||
onclick="return false;"
|
onclick="return false;"
|
||||||
title="拖拽我到收藏夹栏"
|
title="Drag me to your bookmarks bar!"
|
||||||
>
|
>
|
||||||
✨PageAgent
|
✨PageAgent
|
||||||
</a>
|
</a>
|
||||||
@@ -25,7 +25,14 @@ const injectionA = `
|
|||||||
`
|
`
|
||||||
|
|
||||||
export default function HomePage() {
|
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 [params, setParams] = useSearchParams()
|
||||||
const isOther = params.has('try_other')
|
const isOther = params.has('try_other')
|
||||||
@@ -36,14 +43,17 @@ export default function HomePage() {
|
|||||||
if (!task.trim()) return
|
if (!task.trim()) return
|
||||||
|
|
||||||
let pageAgent: PageAgent
|
let pageAgent: PageAgent
|
||||||
|
const win = window as any
|
||||||
|
|
||||||
if (window.pageAgent && !window.pageAgent.disposed) {
|
if (win.pageAgent && !win.pageAgent.disposed) {
|
||||||
pageAgent = window.pageAgent
|
pageAgent = win.pageAgent
|
||||||
} else {
|
} else {
|
||||||
pageAgent = new PageAgent({
|
pageAgent = new PageAgent({
|
||||||
// 把 react 根元素排除掉,挂了很多冒泡时间导致假阳
|
// 把 react 根元素排除掉,挂了很多冒泡时间导致假阳
|
||||||
interactiveBlacklist: [document.getElementById('root')!],
|
interactiveBlacklist: [document.getElementById('root')!],
|
||||||
language: 'zh-CN',
|
language: i18n.language as any,
|
||||||
|
|
||||||
|
// experimentalScriptExecutionTool: true,
|
||||||
|
|
||||||
// testing server
|
// testing server
|
||||||
// @note: rate limit. prompt limit.
|
// @note: rate limit. prompt limit.
|
||||||
@@ -51,7 +61,7 @@ export default function HomePage() {
|
|||||||
// baseURL: DEMO_BASE_URL,
|
// baseURL: DEMO_BASE_URL,
|
||||||
// apiKey: DEMO_API_KEY,
|
// apiKey: DEMO_API_KEY,
|
||||||
})
|
})
|
||||||
window.pageAgent = pageAgent
|
win.pageAgent = pageAgent
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await pageAgent.execute(task)
|
const result = await pageAgent.execute(task)
|
||||||
@@ -60,7 +70,7 @@ export default function HomePage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
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 />
|
<Header />
|
||||||
|
|
||||||
{/* Hero Section */}
|
{/* Hero Section */}
|
||||||
@@ -69,8 +79,8 @@ export default function HomePage() {
|
|||||||
<div className="max-w-7xl mx-auto text-center">
|
<div className="max-w-7xl mx-auto text-center">
|
||||||
{/* Background Pattern */}
|
{/* Background Pattern */}
|
||||||
<div className="absolute inset-0 opacity-30" aria-hidden="true">
|
<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-linear-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-l from-purple-400/20 to-blue-400/20 rounded-3xl transform -rotate-1"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="relative z-10">
|
<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"
|
className="w-2 h-2 bg-blue-500 rounded-full mr-2 animate-pulse"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
></span>
|
></span>
|
||||||
UI Agent in your webpage
|
{t('home:hero.badge')}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1
|
<h1
|
||||||
id="hero-heading"
|
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 />
|
<br />
|
||||||
拥有 AI 操作员
|
{t('home:hero.title_line2')}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p className="text-xl lg:text-2xl text-gray-600 dark:text-gray-300 mb-12 max-w-4xl mx-auto leading-relaxed">
|
<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">
|
<span className="bg-linear-to-r from-blue-500 to-purple-500 bg-clip-text text-transparent font-bold">
|
||||||
🪄 一行 CDN 引入
|
{t('home:hero.subtitle_emoji')}
|
||||||
</span>
|
</span>
|
||||||
,为你的网站添加智能 UI Agent。
|
{t('home:hero.subtitle_main')}
|
||||||
<br />
|
<br />
|
||||||
用户/答疑机器人给出文字指示,AI 帮你操作页面。
|
{t('home:hero.subtitle_detail')}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{/* Try It Now Section - Tab Card */}
|
{/* Try It Now Section - Tab Card */}
|
||||||
@@ -110,21 +120,21 @@ export default function HomePage() {
|
|||||||
onClick={() => setActiveTab('try')}
|
onClick={() => setActiveTab('try')}
|
||||||
className={`flex-1 px-4 py-4 text-lg font-medium transition-colors duration-200 ${
|
className={`flex-1 px-4 py-4 text-lg font-medium transition-colors duration-200 ${
|
||||||
activeTab === 'try'
|
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'
|
: '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>
|
||||||
<button
|
<button
|
||||||
onClick={() => setActiveTab('other')}
|
onClick={() => setActiveTab('other')}
|
||||||
className={`flex-1 px-4 py-4 text-lg font-medium transition-colors duration-200 ${
|
className={`flex-1 px-4 py-4 text-lg font-medium transition-colors duration-200 ${
|
||||||
activeTab === 'other'
|
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'
|
: '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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -136,7 +146,7 @@ export default function HomePage() {
|
|||||||
<input
|
<input
|
||||||
value={task}
|
value={task}
|
||||||
onChange={(e) => setTask(e.target.value)}
|
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"
|
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
|
data-page-agent-not-interactive
|
||||||
/>
|
/>
|
||||||
@@ -144,10 +154,10 @@ export default function HomePage() {
|
|||||||
onClick={handleExecute}
|
onClick={handleExecute}
|
||||||
// disabled
|
// disabled
|
||||||
// disabled={!task.trim()}
|
// 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
|
data-page-agent-not-interactive
|
||||||
>
|
>
|
||||||
执行
|
{t('home:hero.execute_button')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -160,7 +170,10 @@ export default function HomePage() {
|
|||||||
{/* Keyboard Shortcut Hint */}
|
{/* Keyboard Shortcut Hint */}
|
||||||
<div className="bg-blue-50 dark:bg-gray-700 p-4 rounded-lg">
|
<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">
|
<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>
|
</p>
|
||||||
<div className="flex items-center justify-center gap-2">
|
<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">
|
<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 */}
|
{/* Draggable Bookmarklet */}
|
||||||
<div className="bg-green-50 dark:bg-gray-700 p-4 rounded-lg">
|
<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">
|
<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>
|
</p>
|
||||||
<div
|
<div
|
||||||
className="flex items-center justify-center gap-2 text-gray-500 dark:text-gray-400"
|
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 */}
|
{/* Usage Instructions */}
|
||||||
<div className="bg-purple-50 dark:bg-gray-700 p-4 rounded-lg">
|
<div className="bg-purple-50 dark:bg-gray-700 p-4 rounded-lg">
|
||||||
<p className="text-gray-700 dark:text-gray-300 text-sm">
|
<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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -197,37 +214,37 @@ export default function HomePage() {
|
|||||||
{/* 右侧:注意事项 */}
|
{/* 右侧:注意事项 */}
|
||||||
<div className="bg-yellow-50 dark:bg-gray-700 p-4 rounded-lg">
|
<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">
|
<h4 className="font-semibold text-gray-900 dark:text-white mb-3 text-sm">
|
||||||
⚠️ 注意
|
{t('home:try_other.notice_title')}
|
||||||
</h4>
|
</h4>
|
||||||
<ul className="space-y-2 text-sm text-gray-700 dark:text-gray-300">
|
<ul className="space-y-2 text-sm text-gray-700 dark:text-gray-300">
|
||||||
<li className="flex items-start text-left">
|
<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>
|
||||||
<li className="flex items-start text-left">
|
<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>
|
||||||
使用 DeepSeek 模型,参考 DeepSeek 用户协议和隐私政策
|
{t('home:try_other.notice_items.item2')}
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-start text-left">
|
<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>
|
||||||
<li className="flex items-start text-left">
|
<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>
|
||||||
<li className="flex items-start text-left">
|
<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>
|
||||||
<li className="flex items-start text-left">
|
<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
|
<Link
|
||||||
href="/docs/introduction/limitations"
|
href="/docs/introduction/limitations"
|
||||||
className="text-blue-600 dark:text-blue-400 hover:underline"
|
className="text-blue-600 dark:text-blue-400 hover:underline"
|
||||||
>
|
>
|
||||||
《文档》
|
{t('home:try_other.notice_items.item6_link')}
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -248,28 +265,28 @@ export default function HomePage() {
|
|||||||
className="w-2 h-2 bg-green-500 rounded-full mr-2"
|
className="w-2 h-2 bg-green-500 rounded-full mr-2"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
></span>
|
></span>
|
||||||
无需后端
|
{t('home:benefits.no_backend')}
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-center">
|
<li className="flex items-center">
|
||||||
<span
|
<span
|
||||||
className="w-2 h-2 bg-green-500 rounded-full mr-2"
|
className="w-2 h-2 bg-green-500 rounded-full mr-2"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
></span>
|
></span>
|
||||||
支持私有模型
|
{t('home:benefits.private_model')}
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-center">
|
<li className="flex items-center">
|
||||||
<span
|
<span
|
||||||
className="w-2 h-2 bg-green-500 rounded-full mr-2"
|
className="w-2 h-2 bg-green-500 rounded-full mr-2"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
></span>
|
></span>
|
||||||
无痛脱敏
|
{t('home:benefits.data_masking')}
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-center">
|
<li className="flex items-center">
|
||||||
<span
|
<span
|
||||||
className="w-2 h-2 bg-green-500 rounded-full mr-2"
|
className="w-2 h-2 bg-green-500 rounded-full mr-2"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
></span>
|
></span>
|
||||||
DOM 智能操作
|
{t('home:benefits.open_source')}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@@ -285,75 +302,77 @@ export default function HomePage() {
|
|||||||
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-8" role="list">
|
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-8" role="list">
|
||||||
{/* Feature 1 */}
|
{/* Feature 1 */}
|
||||||
<article
|
<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"
|
role="listitem"
|
||||||
>
|
>
|
||||||
<div
|
<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"
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
<span className="text-white text-xl">🧠</span>
|
<span className="text-white text-xl">📦</span>
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-xl font-bold mb-4 text-gray-900 dark:text-white">
|
<h3 className="text-xl font-bold mb-4 text-gray-900 dark:text-white">
|
||||||
智能 DOM 理解
|
{t('home:features.in_page.title')}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-gray-600 dark:text-gray-300 leading-relaxed">
|
<p className="text-gray-600 dark:text-gray-300 leading-relaxed">
|
||||||
基于 DOM 分析,高强度脱水。无需视觉识别,纯文本实现精准操作。
|
{t('home:features.in_page.desc')}
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
{/* Feature 2 */}
|
{/* Feature 2 */}
|
||||||
<article
|
<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"
|
role="listitem"
|
||||||
>
|
>
|
||||||
<div
|
<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"
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
<span className="text-white text-xl">🔒</span>
|
<span className="text-white text-xl">⚡</span>
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-xl font-bold mb-4 text-gray-900 dark:text-white">
|
<h3 className="text-xl font-bold mb-4 text-gray-900 dark:text-white">
|
||||||
安全可控集成
|
{t('home:features.zero_backend.title')}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-gray-600 dark:text-gray-300 leading-relaxed">
|
<p className="text-gray-600 dark:text-gray-300 leading-relaxed">
|
||||||
支持操作黑白名单、数据脱敏保护。注入自定义知识库,让 AI 按你的规则工作。
|
{t('home:features.zero_backend.desc')}
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
{/* Feature 3 */}
|
{/* Feature 3 */}
|
||||||
<article
|
<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"
|
role="listitem"
|
||||||
>
|
>
|
||||||
<div
|
<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"
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
<span className="text-white text-xl">⚡</span>
|
<span className="text-white text-xl">🌈</span>
|
||||||
</div>
|
</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">
|
<p className="text-gray-600 dark:text-gray-300 leading-relaxed">
|
||||||
CDN 直接引入,自定义 LLM 接入点。从 OpenAI 到 qwen3,完全由你掌控。
|
{t('home:features.accessible.desc')}
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
{/* Feature 4 */}
|
{/* Feature 4 */}
|
||||||
<article
|
<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"
|
role="listitem"
|
||||||
>
|
>
|
||||||
<div
|
<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"
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
<span className="text-white text-xl">♿</span>
|
<span className="text-white text-xl">🔒</span>
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-xl font-bold mb-4 text-gray-900 dark:text-white">
|
<h3 className="text-xl font-bold mb-4 text-gray-900 dark:text-white">
|
||||||
普惠智能交互
|
{t('home:features.secure_integration.title')}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-gray-600 dark:text-gray-300 leading-relaxed">
|
<p className="text-gray-600 dark:text-gray-300 leading-relaxed">
|
||||||
为复杂 B端系统、管理后台提供自然语言入口。让每个用户都能轻松上手。
|
{t('home:features.secure_integration.desc')}
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
@@ -366,81 +385,80 @@ export default function HomePage() {
|
|||||||
<div className="text-center mb-16">
|
<div className="text-center mb-16">
|
||||||
<h2
|
<h2
|
||||||
id="use-cases-heading"
|
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>
|
</h2>
|
||||||
<p className="text-xl text-gray-600 dark:text-gray-300 max-w-3xl mx-auto">
|
<p className="text-xl text-gray-600 dark:text-gray-300 max-w-3xl mx-auto">
|
||||||
从简单的表单填写到复杂的业务流程,AI 都能理解并执行
|
{t('home:use_cases.section_subtitle')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid lg:grid-cols-2 gap-12" role="list">
|
<div className="grid lg:grid-cols-2 gap-12" role="list">
|
||||||
{/* Use Case 1 */}
|
{/* 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="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 h-20">
|
<div className="flex items-start space-x-4">
|
||||||
<div className="w-10 h-10 bg-blue-500 rounded-lg flex items-center justify-center flex-shrink-0">
|
<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>
|
<span className="text-white font-bold">1</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-xl font-bold mb-2 text-gray-900 dark:text-white">
|
<h3 className="text-xl font-bold mb-2 text-gray-900 dark:text-white">
|
||||||
对接答疑机器人
|
{t('home:use_cases.case1.title')}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-gray-600 dark:text-gray-300">
|
<p className="text-gray-600 dark:text-gray-300">
|
||||||
把你的答疑助手变成全能Agent。客服机器人不再只说"请先点击设置按钮然后点击...",而是直接帮用户现场操作。
|
{t('home:use_cases.case1.desc')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Use Case 2 */}
|
{/* 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="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 h-20">
|
<div className="flex items-start space-x-4">
|
||||||
<div className="w-10 h-10 bg-green-500 rounded-lg flex items-center justify-center flex-shrink-0">
|
<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>
|
<span className="text-white font-bold">2</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-xl font-bold mb-2 text-gray-900 dark:text-white">
|
<h3 className="text-xl font-bold mb-2 text-gray-900 dark:text-white">
|
||||||
交互升级/智能化改造
|
{t('home:use_cases.case2.title')}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-gray-600 dark:text-gray-300">
|
<p className="text-gray-600 dark:text-gray-300">
|
||||||
一行代码,老应用变身Agent,产品专家帮用户操作复杂 B
|
{t('home:use_cases.case2.desc')}
|
||||||
端软件。降低人工支持成本,提高用户满意度。
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Use Case 3 */}
|
{/* 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="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 h-20">
|
<div className="flex items-start space-x-4">
|
||||||
<div className="w-10 h-10 bg-purple-500 rounded-lg flex items-center justify-center flex-shrink-0">
|
<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>
|
<span className="text-white font-bold">3</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-xl font-bold mb-2 text-gray-900 dark:text-white">
|
<h3 className="text-xl font-bold mb-2 text-gray-900 dark:text-white">
|
||||||
产品教学
|
{t('home:use_cases.case3.title')}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-gray-600 dark:text-gray-300">
|
<p className="text-gray-600 dark:text-gray-300">
|
||||||
向用户演示交互过程,边做边教。例如让AI演示"如何提交报销申请"的完整操作流程。
|
{t('home:use_cases.case3.desc')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Use Case 4 */}
|
{/* 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="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 h-20">
|
<div className="flex items-start space-x-4">
|
||||||
<div className="w-10 h-10 bg-orange-500 rounded-lg flex items-center justify-center flex-shrink-0">
|
<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>
|
<span className="text-white font-bold">4</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-xl font-bold mb-2 text-gray-900 dark:text-white">
|
<h3 className="text-xl font-bold mb-2 text-gray-900 dark:text-white">
|
||||||
无障碍支持
|
{t('home:use_cases.case4.title')}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-gray-600 dark:text-gray-300">
|
<p className="text-gray-600 dark:text-gray-300">
|
||||||
为视障用户、老年用户提供自然语言交互,对接屏幕阅读器或语音助理,让软件人人可用。
|
{t('home:use_cases.case4.desc')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
+108
-58
@@ -19,19 +19,31 @@ interface WizardStep {
|
|||||||
export default function ComplexTestPage() {
|
export default function ComplexTestPage() {
|
||||||
const [currentStep, setCurrentStep] = useState(1)
|
const [currentStep, setCurrentStep] = useState(1)
|
||||||
const [cartItems, setCartItems] = useState<CartItem[]>([
|
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({
|
const [wizardData, setWizardData] = useState({
|
||||||
personalInfo: { name: '', email: '', phone: '' },
|
personalInfo: { name: '', email: '', phone: '' },
|
||||||
address: { street: '', city: '', zipCode: '' },
|
address: { street: '', city: '', zipCode: '' },
|
||||||
payment: { cardNumber: '', expiryDate: '', cvv: '' }
|
payment: { cardNumber: '', expiryDate: '', cvv: '' },
|
||||||
})
|
})
|
||||||
const [wizardSteps, setWizardSteps] = useState<WizardStep[]>([
|
const [wizardSteps, setWizardSteps] = useState<WizardStep[]>([
|
||||||
{ id: 1, title: '个人信息', description: '填写基本信息', completed: false },
|
{ id: 1, title: '个人信息', description: '填写基本信息', completed: false },
|
||||||
{ id: 2, title: '收货地址', description: '填写收货地址', completed: false },
|
{ id: 2, title: '收货地址', description: '填写收货地址', completed: false },
|
||||||
{ id: 3, 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 [showConfirmDialog, setShowConfirmDialog] = useState(false)
|
||||||
const [isProcessing, setIsProcessing] = useState(false)
|
const [isProcessing, setIsProcessing] = useState(false)
|
||||||
@@ -43,15 +55,13 @@ export default function ComplexTestPage() {
|
|||||||
removeItem(id)
|
removeItem(id)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setCartItems(prev =>
|
setCartItems((prev) =>
|
||||||
prev.map(item =>
|
prev.map((item) => (item.id === id ? { ...item, quantity: newQuantity } : item))
|
||||||
item.id === id ? { ...item, quantity: newQuantity } : item
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const removeItem = (id: number) => {
|
const removeItem = (id: number) => {
|
||||||
setCartItems(prev => prev.filter(item => item.id !== id))
|
setCartItems((prev) => prev.filter((item) => item.id !== id))
|
||||||
}
|
}
|
||||||
|
|
||||||
const addItem = () => {
|
const addItem = () => {
|
||||||
@@ -60,9 +70,9 @@ export default function ComplexTestPage() {
|
|||||||
name: `新产品 ${cartItems.length + 1}`,
|
name: `新产品 ${cartItems.length + 1}`,
|
||||||
price: Math.floor(Math.random() * 5000) + 1000,
|
price: Math.floor(Math.random() * 5000) + 1000,
|
||||||
quantity: 1,
|
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 = () => {
|
const getTotalPrice = () => {
|
||||||
@@ -73,11 +83,23 @@ export default function ComplexTestPage() {
|
|||||||
const validateStep = (step: number): boolean => {
|
const validateStep = (step: number): boolean => {
|
||||||
switch (step) {
|
switch (step) {
|
||||||
case 1:
|
case 1:
|
||||||
return !!(wizardData.personalInfo.name && wizardData.personalInfo.email && wizardData.personalInfo.phone)
|
return !!(
|
||||||
|
wizardData.personalInfo.name &&
|
||||||
|
wizardData.personalInfo.email &&
|
||||||
|
wizardData.personalInfo.phone
|
||||||
|
)
|
||||||
case 2:
|
case 2:
|
||||||
return !!(wizardData.address.street && wizardData.address.city && wizardData.address.zipCode)
|
return !!(
|
||||||
|
wizardData.address.street &&
|
||||||
|
wizardData.address.city &&
|
||||||
|
wizardData.address.zipCode
|
||||||
|
)
|
||||||
case 3:
|
case 3:
|
||||||
return !!(wizardData.payment.cardNumber && wizardData.payment.expiryDate && wizardData.payment.cvv)
|
return !!(
|
||||||
|
wizardData.payment.cardNumber &&
|
||||||
|
wizardData.payment.expiryDate &&
|
||||||
|
wizardData.payment.cvv
|
||||||
|
)
|
||||||
default:
|
default:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -92,10 +114,8 @@ export default function ComplexTestPage() {
|
|||||||
|
|
||||||
// 更新步骤完成状态
|
// 更新步骤完成状态
|
||||||
if (step > currentStep) {
|
if (step > currentStep) {
|
||||||
setWizardSteps(prev =>
|
setWizardSteps((prev) =>
|
||||||
prev.map(s =>
|
prev.map((s) => (s.id === currentStep ? { ...s, completed: true } : s))
|
||||||
s.id === currentStep ? { ...s, completed: true } : s
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,21 +123,21 @@ export default function ComplexTestPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleInputChange = (section: string, field: string, value: string) => {
|
const handleInputChange = (section: string, field: string, value: string) => {
|
||||||
setWizardData(prev => ({
|
setWizardData((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
[section]: {
|
[section]: {
|
||||||
...prev[section as keyof typeof prev],
|
...prev[section as keyof typeof prev],
|
||||||
[field]: value
|
[field]: value,
|
||||||
}
|
},
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSubmitOrder = async () => {
|
const handleSubmitOrder = async () => {
|
||||||
setIsProcessing(true)
|
setIsProcessing(true)
|
||||||
|
|
||||||
// 模拟处理时间
|
// 模拟处理时间
|
||||||
await new Promise(resolve => setTimeout(resolve, 3000))
|
await new Promise((resolve) => setTimeout(resolve, 3000))
|
||||||
|
|
||||||
// 模拟随机失败
|
// 模拟随机失败
|
||||||
if (Math.random() < 0.2) {
|
if (Math.random() < 0.2) {
|
||||||
setIsProcessing(false)
|
setIsProcessing(false)
|
||||||
@@ -135,9 +155,9 @@ export default function ComplexTestPage() {
|
|||||||
setWizardData({
|
setWizardData({
|
||||||
personalInfo: { name: '', email: '', phone: '' },
|
personalInfo: { name: '', email: '', phone: '' },
|
||||||
address: { street: '', city: '', zipCode: '' },
|
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)
|
setOrderComplete(false)
|
||||||
setShowConfirmDialog(false)
|
setShowConfirmDialog(false)
|
||||||
}
|
}
|
||||||
@@ -162,7 +182,10 @@ export default function ComplexTestPage() {
|
|||||||
>
|
>
|
||||||
重新开始
|
重新开始
|
||||||
</button>
|
</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>
|
</Link>
|
||||||
</div>
|
</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="min-h-screen bg-gray-50 dark:bg-gray-900 py-8">
|
||||||
<div className="max-w-6xl mx-auto px-4">
|
<div className="max-w-6xl mx-auto px-4">
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<h1 className="text-3xl font-bold text-gray-900 dark:text-white mb-2">
|
<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>
|
|
||||||
<p className="text-gray-600 dark:text-gray-300">
|
|
||||||
测试多步骤操作、状态管理和复杂用户交互
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
<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">
|
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-4">
|
||||||
购物车 ({cartItems.length})
|
购物车 ({cartItems.length})
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div className="space-y-4 mb-6">
|
<div className="space-y-4 mb-6">
|
||||||
{cartItems.map(item => (
|
{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">
|
<div
|
||||||
|
key={item.id}
|
||||||
|
className="flex items-center space-x-3 p-3 border border-gray-200 dark:border-gray-600 rounded-lg"
|
||||||
|
>
|
||||||
<img
|
<img
|
||||||
src={item.image}
|
src={item.image}
|
||||||
alt={item.name}
|
alt={item.name}
|
||||||
@@ -215,9 +237,7 @@ export default function ComplexTestPage() {
|
|||||||
>
|
>
|
||||||
-
|
-
|
||||||
</button>
|
</button>
|
||||||
<span className="text-sm font-medium w-8 text-center">
|
<span className="text-sm font-medium w-8 text-center">{item.quantity}</span>
|
||||||
{item.quantity}
|
|
||||||
</span>
|
|
||||||
<button
|
<button
|
||||||
onClick={() => updateQuantity(item.id, item.quantity + 1)}
|
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"
|
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
|
step.completed
|
||||||
? 'bg-green-500 text-white'
|
? 'bg-green-500 text-white'
|
||||||
: step.id === currentStep
|
: step.id === currentStep
|
||||||
? 'bg-blue-500 text-white'
|
? 'bg-blue-500 text-white'
|
||||||
: 'bg-gray-200 dark:bg-gray-600 text-gray-500 dark:text-gray-400'
|
: 'bg-gray-200 dark:bg-gray-600 text-gray-500 dark:text-gray-400'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{step.completed ? '✓' : step.id}
|
{step.completed ? '✓' : step.id}
|
||||||
</button>
|
</button>
|
||||||
{index < wizardSteps.length - 1 && (
|
{index < wizardSteps.length - 1 && (
|
||||||
<div className={`w-16 h-1 mx-2 ${
|
<div
|
||||||
step.completed ? 'bg-green-500' : 'bg-gray-200 dark:bg-gray-600'
|
className={`w-16 h-1 mx-2 ${
|
||||||
}`} />
|
step.completed ? 'bg-green-500' : 'bg-gray-200 dark:bg-gray-600'
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@@ -395,7 +417,9 @@ export default function ComplexTestPage() {
|
|||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={wizardData.payment.expiryDate}
|
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"
|
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"
|
placeholder="MM/YY"
|
||||||
/>
|
/>
|
||||||
@@ -419,25 +443,35 @@ export default function ComplexTestPage() {
|
|||||||
{currentStep === 4 && (
|
{currentStep === 4 && (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div>
|
<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="space-y-4">
|
||||||
<div className="bg-gray-50 dark:bg-gray-700 p-4 rounded-lg">
|
<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">
|
<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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-gray-50 dark:bg-gray-700 p-4 rounded-lg">
|
<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">
|
<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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-gray-50 dark:bg-gray-700 p-4 rounded-lg">
|
<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">
|
<p className="text-sm text-gray-600 dark:text-gray-300">
|
||||||
**** **** **** {wizardData.payment.cardNumber.slice(-4)}
|
**** **** **** {wizardData.payment.cardNumber.slice(-4)}
|
||||||
</p>
|
</p>
|
||||||
@@ -457,7 +491,7 @@ export default function ComplexTestPage() {
|
|||||||
>
|
>
|
||||||
上一步
|
上一步
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{currentStep < 4 ? (
|
{currentStep < 4 ? (
|
||||||
<button
|
<button
|
||||||
onClick={() => goToStep(currentStep + 1)}
|
onClick={() => goToStep(currentStep + 1)}
|
||||||
@@ -505,9 +539,25 @@ export default function ComplexTestPage() {
|
|||||||
>
|
>
|
||||||
{isProcessing ? (
|
{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">
|
<svg
|
||||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
className="animate-spin -ml-1 mr-3 h-5 w-5 text-white"
|
||||||
<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>
|
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>
|
</svg>
|
||||||
处理中...
|
处理中...
|
||||||
</>
|
</>
|
||||||
@@ -17,7 +17,7 @@ export default function ErrorTestPage() {
|
|||||||
username: '',
|
username: '',
|
||||||
password: '',
|
password: '',
|
||||||
email: '',
|
email: '',
|
||||||
file: null as File | null
|
file: null as File | null,
|
||||||
})
|
})
|
||||||
|
|
||||||
const errorScenarios: ErrorScenario[] = [
|
const errorScenarios: ErrorScenario[] = [
|
||||||
@@ -25,32 +25,32 @@ export default function ErrorTestPage() {
|
|||||||
id: 'network-error',
|
id: 'network-error',
|
||||||
title: '网络连接错误',
|
title: '网络连接错误',
|
||||||
description: '模拟网络连接失败,测试重试机制',
|
description: '模拟网络连接失败,测试重试机制',
|
||||||
type: 'network'
|
type: 'network',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'validation-error',
|
id: 'validation-error',
|
||||||
title: '表单验证错误',
|
title: '表单验证错误',
|
||||||
description: '模拟表单验证失败,测试错误提示',
|
description: '模拟表单验证失败,测试错误提示',
|
||||||
type: 'validation'
|
type: 'validation',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'permission-error',
|
id: 'permission-error',
|
||||||
title: '权限不足错误',
|
title: '权限不足错误',
|
||||||
description: '模拟权限验证失败,测试权限处理',
|
description: '模拟权限验证失败,测试权限处理',
|
||||||
type: 'permission'
|
type: 'permission',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'timeout-error',
|
id: 'timeout-error',
|
||||||
title: '请求超时错误',
|
title: '请求超时错误',
|
||||||
description: '模拟请求超时,测试超时处理',
|
description: '模拟请求超时,测试超时处理',
|
||||||
type: 'timeout'
|
type: 'timeout',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'server-error',
|
id: 'server-error',
|
||||||
title: '服务器内部错误',
|
title: '服务器内部错误',
|
||||||
description: '模拟服务器500错误,测试错误恢复',
|
description: '模拟服务器500错误,测试错误恢复',
|
||||||
type: 'server'
|
type: 'server',
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const simulateError = async (scenario: ErrorScenario): Promise<void> => {
|
const simulateError = async (scenario: ErrorScenario): Promise<void> => {
|
||||||
@@ -59,7 +59,7 @@ export default function ErrorTestPage() {
|
|||||||
setSuccess(null)
|
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) {
|
switch (scenario.type) {
|
||||||
case 'network':
|
case 'network':
|
||||||
@@ -91,7 +91,7 @@ export default function ErrorTestPage() {
|
|||||||
|
|
||||||
case 'timeout':
|
case 'timeout':
|
||||||
// 模拟超时
|
// 模拟超时
|
||||||
await new Promise(resolve => setTimeout(resolve, 8000))
|
await new Promise((resolve) => setTimeout(resolve, 8000))
|
||||||
throw new Error('请求超时:服务器响应时间过长,请稍后重试')
|
throw new Error('请求超时:服务器响应时间过长,请稍后重试')
|
||||||
|
|
||||||
case 'server':
|
case 'server':
|
||||||
@@ -117,7 +117,7 @@ export default function ErrorTestPage() {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
const errorMessage = err instanceof Error ? err.message : '未知错误'
|
const errorMessage = err instanceof Error ? err.message : '未知错误'
|
||||||
setError(errorMessage)
|
setError(errorMessage)
|
||||||
setRetryCount(prev => prev + 1)
|
setRetryCount((prev) => prev + 1)
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false)
|
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) {
|
if (Math.random() < 0.3) {
|
||||||
@@ -178,12 +178,18 @@ export default function ErrorTestPage() {
|
|||||||
|
|
||||||
const getErrorIcon = (type: string) => {
|
const getErrorIcon = (type: string) => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'network': return '🌐'
|
case 'network':
|
||||||
case 'validation': return '⚠️'
|
return '🌐'
|
||||||
case 'permission': return '🔒'
|
case 'validation':
|
||||||
case 'timeout': return '⏰'
|
return '⚠️'
|
||||||
case 'server': return '🔧'
|
case 'permission':
|
||||||
default: return '❌'
|
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="min-h-screen bg-gray-50 dark:bg-gray-900 py-8">
|
||||||
<div className="max-w-4xl mx-auto px-4">
|
<div className="max-w-4xl mx-auto px-4">
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<h1 className="text-3xl font-bold text-gray-900 dark:text-white mb-2">
|
<h1 className="text-3xl font-bold text-gray-900 dark:text-white mb-2">错误处理测试</h1>
|
||||||
错误处理测试
|
|
||||||
</h1>
|
|
||||||
<p className="text-gray-600 dark:text-gray-300">
|
<p className="text-gray-600 dark:text-gray-300">
|
||||||
测试各种错误场景和重试机制,验证 Agent 的错误处理能力
|
测试各种错误场景和重试机制,验证 Agent 的错误处理能力
|
||||||
</p>
|
</p>
|
||||||
@@ -205,18 +209,18 @@ export default function ErrorTestPage() {
|
|||||||
{error && (
|
{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="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 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">
|
<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>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-3 flex-1">
|
<div className="ml-3 flex-1">
|
||||||
<h3 className="text-sm font-medium text-red-800 dark:text-red-200">
|
<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>
|
|
||||||
<p className="mt-1 text-sm text-red-700 dark:text-red-300">
|
|
||||||
{error}
|
|
||||||
</p>
|
|
||||||
{retryCount > 0 && (
|
{retryCount > 0 && (
|
||||||
<p className="mt-2 text-xs text-red-600 dark:text-red-400">
|
<p className="mt-2 text-xs text-red-600 dark:text-red-400">
|
||||||
已重试 {retryCount} 次 {retryCount >= 3 && '(已达最大重试次数)'}
|
已重试 {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"
|
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">
|
<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>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -238,25 +246,31 @@ export default function ErrorTestPage() {
|
|||||||
{success && (
|
{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="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 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">
|
<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>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-3 flex-1">
|
<div className="ml-3 flex-1">
|
||||||
<h3 className="text-sm font-medium text-green-800 dark:text-green-200">
|
<h3 className="text-sm font-medium text-green-800 dark:text-green-200">
|
||||||
操作成功
|
操作成功
|
||||||
</h3>
|
</h3>
|
||||||
<p className="mt-1 text-sm text-green-700 dark:text-green-300">
|
<p className="mt-1 text-sm text-green-700 dark:text-green-300">{success}</p>
|
||||||
{success}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={clearMessages}
|
onClick={clearMessages}
|
||||||
className="ml-3 text-green-400 hover:text-green-600 dark:hover:text-green-300"
|
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">
|
<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>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -268,9 +282,7 @@ export default function ErrorTestPage() {
|
|||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
||||||
{/* 错误场景测试 */}
|
{/* 错误场景测试 */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
|
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">错误场景测试</h2>
|
||||||
错误场景测试
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
{errorScenarios.map((scenario) => (
|
{errorScenarios.map((scenario) => (
|
||||||
<div key={scenario.id} className="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
<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">
|
<div className="space-y-6">
|
||||||
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
|
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">表单验证测试</h2>
|
||||||
表单验证测试
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
<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">
|
<h3 className="text-lg font-medium text-gray-900 dark:text-white mb-4">
|
||||||
@@ -325,7 +335,7 @@ export default function ErrorTestPage() {
|
|||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={formData.username}
|
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"
|
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="请输入用户名"
|
placeholder="请输入用户名"
|
||||||
/>
|
/>
|
||||||
@@ -337,7 +347,7 @@ export default function ErrorTestPage() {
|
|||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
value={formData.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"
|
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="请输入密码"
|
placeholder="请输入密码"
|
||||||
/>
|
/>
|
||||||
@@ -349,13 +359,15 @@ export default function ErrorTestPage() {
|
|||||||
<input
|
<input
|
||||||
type="email"
|
type="email"
|
||||||
value={formData.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"
|
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="请输入邮箱地址"
|
placeholder="请输入邮箱地址"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => handleScenarioTest(errorScenarios.find(s => s.type === 'validation')!)}
|
onClick={() =>
|
||||||
|
handleScenarioTest(errorScenarios.find((s) => s.type === 'validation')!)
|
||||||
|
}
|
||||||
disabled={isLoading}
|
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"
|
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>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="file"
|
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"
|
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"
|
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>
|
</div>
|
||||||
{formData.file && (
|
{formData.file && (
|
||||||
<div className="text-sm text-gray-600 dark:text-gray-300">
|
<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>
|
</div>
|
||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
@@ -412,9 +427,25 @@ export default function ErrorTestPage() {
|
|||||||
{isLoading && (
|
{isLoading && (
|
||||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
<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">
|
<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">
|
<svg
|
||||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
className="animate-spin h-8 w-8 text-blue-600"
|
||||||
<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>
|
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>
|
</svg>
|
||||||
<span className="text-gray-900 dark:text-white">处理中,请稍候...</span>
|
<span className="text-gray-900 dark:text-white">处理中,请稍候...</span>
|
||||||
</div>
|
</div>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user