refactor: normalize website structure

This commit is contained in:
Simon
2026-01-11 01:16:57 +08:00
parent 398c6d0869
commit d9faca5d01
16 changed files with 30 additions and 29 deletions

View File

@@ -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/custom-instructions' },
{ 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>
)
}

View File

@@ -0,0 +1,135 @@
import { useTranslation } from 'react-i18next'
import CodeEditor from '@/components/CodeEditor'
export default function Instructions() {
const { i18n } = useTranslation()
const isZh = i18n.language === 'zh-CN'
return (
<div>
<h1 className="text-4xl font-bold mb-6">{isZh ? '知识注入' : 'Instructions'}</h1>
<p className="text-xl text-gray-600 dark:text-gray-300 mb-8 leading-relaxed">
{isZh
? '通过 instructions 配置,为 AI 注入系统级指导和页面级上下文,让它更好地理解你的业务场景。'
: 'Use the instructions config to inject system-level directives and page-specific context, helping the AI better understand your application.'}
</p>
{/* System Instructions */}
<section className="mb-12">
<h2 className="text-3xl font-bold mb-6">
{isZh ? '系统级指导 (System Instructions)' : 'System Instructions'}
</h2>
<p className="text-gray-600 dark:text-gray-300 mb-6">
{isZh
? '全局提示词,应用于所有任务。定义 AI 的角色、工作风格和行为边界。'
: "Global directives applied to all tasks. Define the AI's role, working style, and behavioral boundaries."}
</p>
<CodeEditor
className="mb-6"
code={`const agent = new PageAgent({
// ...other config
instructions: {
system: \`
You are a professional e-commerce assistant.
Guidelines:
- Always confirm before submitting orders
- Double-check prices and quantities
- Report errors immediately instead of retrying blindly
\`
}
})`}
/>
</section>
{/* Page Instructions */}
<section className="mb-12">
<h2 className="text-3xl font-bold mb-6">
{isZh ? '页面级指导 (Page Instructions)' : 'Page Instructions'}
</h2>
<p className="text-gray-600 dark:text-gray-300 mb-6">
{isZh
? '动态回调函数,在每个 step 执行前调用,根据当前页面 URL 返回特定提示词。适用于为不同页面提供针对性的操作引导。'
: 'A dynamic callback invoked before each step. Returns page-specific instructions based on the current URL. Useful for providing targeted guidance on different pages.'}
</p>
<CodeEditor
className="mb-6"
code={`const agent = new PageAgent({
// ...other config
instructions: {
system: 'You are an order management assistant.',
getPageInstructions: (url) => {
if (url.includes('/checkout')) {
return \`
This is the checkout page.
- Verify shipping address before proceeding
- Check if any discounts are applied
- Confirm the total amount with the user
\`
}
if (url.includes('/products')) {
return \`
This is the product listing page.
- Use filters to narrow down search results
- Check stock availability before adding to cart
\`
}
return undefined // No special instructions for other pages
}
}
})`}
/>
</section>
{/* How It Works */}
<section className="mb-12">
<h2 className="text-3xl font-bold mb-6">{isZh ? '工作原理' : 'How It Works'}</h2>
<p className="text-gray-600 dark:text-gray-300 mb-4">
{isZh
? '在每个执行步骤之前page-agent 会将 instructions 拼接到用户提示词中:'
: 'Before each execution step, page-agent prepends the instructions to the user prompt:'}
</p>
<CodeEditor
language="xml"
className="mb-6"
code={`<instructions>
<system_instructions>
You are a professional e-commerce assistant.
...
</system_instructions>
<page_instructions>
This is the checkout page.
...
</page_instructions>
</instructions>
<!-- followed by agent state, history, and browser state -->`}
/>
<ul className="list-disc list-inside space-y-2 text-gray-600 dark:text-gray-400">
<li>
{isZh
? '如果 system 为空,则不输出 <system_instructions> 标签'
: 'If system is empty, the <system_instructions> tag is omitted'}
</li>
<li>
{isZh
? '如果 getPageInstructions 返回空值,则不输出 <page_instructions> 标签'
: 'If getPageInstructions returns empty, the <page_instructions> tag is omitted'}
</li>
</ul>
</section>
</div>
)
}

View File

@@ -0,0 +1,104 @@
import { useTranslation } from 'react-i18next'
import BetaNotice from '@/components/BetaNotice'
import CodeEditor from '@/components/CodeEditor'
export default function CustomTools() {
const { t } = useTranslation('docs')
return (
<div>
<h1 className="text-4xl font-bold mb-6">{t('custom_tools.title')}</h1>
<p className="text-xl text-gray-600 dark:text-gray-300 mb-8 leading-relaxed">
{t('custom_tools.subtitle')}
</p>
<div className="space-y-8">
<section>
<h2 className="text-2xl font-bold mb-4">{t('custom_tools.registration')}</h2>
<p className="text-gray-600 dark:text-gray-300 mb-4">
{t('custom_tools.registration_desc')}
</p>
<CodeEditor
code={`import zod from 'zod'
import { PageAgent, tool } from 'page-agent'
// override internal tool
const customTools = {
ask_user: tool({
description:
'Ask the user or parent model a question and wait for their answer. Use this if you need more information or clarification.',
inputSchema: zod.object({
question: zod.string(),
}),
execute: async function (this: PageAgent, input) {
const answer = await do_some_thing(input.question)
return "✅ Received user answer: " + answer
},
})
}
// remove internal tool
const customTools = {
ask_user: null // never ask user questions
}
const pageAgent = new PageAgent({customTools})
`}
language="javascript"
/>
</section>
<section>
<h2 className="text-2xl font-bold mb-4">{t('custom_tools.page_filter')}</h2>
<BetaNotice />
<p className="text-gray-600 dark:text-gray-300 mb-4">
{t('custom_tools.page_filter_desc')}
</p>
<CodeEditor
code={`pageAgent.registerTool({
name: 'approveOrder',
description: '审批订单',
input: z.object({
orderId: z.string(),
approved: z.boolean()
}),
execute: async (params) => {
// 审批逻辑
},
// 可选:页面过滤器
pageFilter: {
// 只在订单管理页面显示
include: ['/admin/orders', '/admin/orders/*'],
// 排除特定页面
exclude: ['/admin/orders/archived']
}
})`}
language="javascript"
/>
</section>
<section>
<h2 className="text-2xl font-bold mb-4">{t('custom_tools.best_practices')}</h2>
<div className="space-y-4">
<div className="p-4 bg-yellow-50 dark:bg-yellow-900/20 rounded-lg">
<h3 className="text-lg font-semibold text-yellow-900 dark:text-yellow-300 mb-2">
{t('custom_tools.bp_performance')}
</h3>
<ul className="text-gray-600 dark:text-gray-300 space-y-1 text-sm">
<li>{t('custom_tools.bp_1')}</li>
<li>{t('custom_tools.bp_2')}</li>
<li>{t('custom_tools.bp_3')}</li>
</ul>
</div>
</div>
</section>
</div>
</div>
)
}

View File

@@ -0,0 +1,73 @@
import { useTranslation } from 'react-i18next'
import CodeEditor from '@/components/CodeEditor'
export default function DataMasking() {
const { i18n } = useTranslation()
const isZh = i18n.language === 'zh-CN'
return (
<div>
<h1 className="text-4xl font-bold mb-6">{isZh ? '数据脱敏' : 'Data Masking'}</h1>
<p className="text-xl text-gray-600 dark:text-gray-300 mb-8 leading-relaxed">
{isZh
? '使用 transformPageContent 钩子在页面内容发送给 LLM 之前进行处理,可用于检查清洗效果、修改页面信息、隐藏敏感数据等。'
: 'Use the transformPageContent hook to process page content before sending to LLM. Useful for inspecting extraction results, modifying page info, and masking sensitive data.'}
</p>
<section className="mb-12">
<h2 className="text-3xl font-bold mb-6">{isZh ? '接口定义' : 'API Definition'}</h2>
<CodeEditor
className="mb-6"
code={`interface PageAgentConfig {
/**
* Transform page content before sending to LLM.
* Called after DOM extraction and simplification.
*/
transformPageContent?: (content: string) => Promise<string> | string
}`}
/>
</section>
<section className="mb-12">
<h2 className="text-3xl font-bold mb-6">
{isZh ? '常用脱敏规则' : 'Common Masking Patterns'}
</h2>
<p className="text-gray-600 dark:text-gray-300 mb-6">
{isZh
? '以下示例展示了如何脱敏常见的敏感信息:'
: 'The following example shows how to mask common sensitive data:'}
</p>
<CodeEditor
code={`const agent = new PageAgent({
transformPageContent: async (content) => {
// China phone number (11 digits starting with 1)
content = content.replace(/\\b(1[3-9]\\d)(\\d{4})(\\d{4})\\b/g, '$1****$3')
// Email address
content = content.replace(
/\\b([a-zA-Z0-9._%+-])[^@]*(@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,})\\b/g,
'$1***$2'
)
// China ID card number (18 digits)
content = content.replace(
/\\b(\\d{6})(19|20\\d{2})(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])(\\d{3}[\\dXx])\\b/g,
'$1********$5'
)
// Bank card number (16-19 digits)
content = content.replace(/\\b(\\d{4})\\d{8,11}(\\d{4})\\b/g, '$1********$2')
return content
}
})`}
/>
</section>
</div>
)
}

View File

@@ -0,0 +1,127 @@
import { useTranslation } from 'react-i18next'
import CodeEditor from '@/components/CodeEditor'
// Recommended models: lightweight with excellent tool call capabilities
const MODELS = {
recommended: ['gpt-4.1-mini', 'claude-haiku-4.5', 'gemini-3-flash', 'deepseek-3.2', 'gpt-5.2'],
verified: [
'qwen-3-max',
'gpt-4.1',
'gpt-5',
'gpt-5-mini',
'gpt-5.1',
'grok-4',
'grok-code-fast',
'claude-sonnet-3.5',
'claude-sonnet-4.5',
'claude-opus-4.5',
'gemini-2.5',
'gemini-3-pro',
],
}
export default function ModelIntegration() {
const { t } = useTranslation('docs')
const allModels = [...MODELS.recommended, ...MODELS.verified]
return (
<div className="max-w-4xl">
<h1 className="text-4xl font-bold mb-4">{t('model_integration.title')}</h1>
<p className="text-lg text-gray-600 dark:text-gray-400 mb-8">
{t('model_integration.subtitle')}
</p>
{/* Models Section */}
<section className="mb-10">
<h2 className="text-2xl font-semibold mb-3">{t('model_integration.available')}</h2>
<p className="text-sm text-gray-600 dark:text-gray-400 mb-4">
{t('model_integration.recommendation_logic')}
</p>
<div className="bg-linear-to-br from-emerald-50 to-cyan-50 dark:from-emerald-950/30 dark:to-cyan-950/30 rounded-xl p-6 border border-emerald-200/50 dark:border-emerald-800/50">
<div className="flex flex-wrap gap-2">
{allModels.map((model) => {
const isRecommended = MODELS.recommended.includes(model)
return (
<div
key={model}
className={`px-3 py-1.5 rounded-md text-sm font-medium font-mono transition-colors ${
isRecommended
? 'bg-emerald-500 text-white shadow-sm'
: 'bg-white/80 dark:bg-gray-800/80 text-gray-800 dark:text-gray-200 border border-gray-300 dark:border-gray-600'
}`}
>
{model}
{isRecommended && <span className="ml-1"></span>}
</div>
)
})}
</div>
<p className="text-xs text-gray-600 dark:text-gray-400 mt-5"> baseline models</p>
</div>
</section>
{/* Tips Section */}
<section className="mb-10">
<h2 className="text-2xl font-semibold mb-4">{t('model_integration.tips')}</h2>
<div className="p-4 bg-blue-50 dark:bg-blue-950/20 rounded-lg border border-blue-200 dark:border-blue-800">
<ul className="text-sm text-gray-700 dark:text-gray-300 space-y-2 list-disc pl-5">
<li>{t('model_integration.tip_2')}</li>
<li>{t('model_integration.tip_3')}</li>
</ul>
</div>
</section>
{/* Security Section */}
<section className="mb-10">
<h2 className="text-2xl font-semibold mb-4">{t('model_integration.security')}</h2>
<div className="bg-yellow-50 dark:bg-yellow-950/20 border-l-4 border-yellow-500 p-5 rounded-r-lg mb-4">
<p className="text-sm font-semibold text-yellow-900 dark:text-yellow-200">
{t('model_integration.security_warning')}
</p>
</div>
<div className="bg-gray-50 dark:bg-gray-900/30 rounded-lg p-5 border border-gray-200 dark:border-gray-800">
<h3 className="font-semibold text-gray-900 dark:text-gray-100 mb-2">
{t('model_integration.security_backend_proxy')}
</h3>
<p className="text-sm text-gray-600 dark:text-gray-400 mb-3">
{t('model_integration.security_backend_desc')}
</p>
<ul className="text-sm text-gray-600 dark:text-gray-400 space-y-1">
<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>
</section>
{/* Configuration Section */}
<section className="mb-10">
<h2 className="text-2xl font-semibold mb-4">{t('model_integration.configuration')}</h2>
<CodeEditor
code={`// OpenAI-compatible services (e.g., Alibaba Bailian)
const pageAgent = new PageAgent({
baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
apiKey: 'your-api-key',
model: 'qwen-plus'
});
// Self-hosted models (e.g., Ollama)
const pageAgent = new PageAgent({
baseURL: 'http://localhost:11434/v1',
apiKey: 'N/A', // Ollama typically accepts any value
model: 'qwen3:latest'
});
// Free testing endpoint
// Note: Rate-limited, content-filtered, subject to change. Replace with your own.
// Note: Uses official DeepSeek-chat (3.2). See DeepSeek website for terms & privacy.
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'
`}
/>
</section>
</div>
)
}

View File

@@ -0,0 +1,81 @@
import BetaNotice from '@/components/BetaNotice'
export default function SecurityPermissions() {
return (
<div>
<h1 className="text-4xl font-bold mb-6"></h1>
<BetaNotice />
<p className="text-xl text-gray-600 dark:text-gray-300 mb-8 leading-relaxed">
page-agent AI
</p>
<div className="space-y-6">
<section>
<h2 className="text-2xl font-bold mb-3"></h2>
<div className="space-y-3">
<div className="p-4 bg-red-50 dark:bg-red-900/20 rounded-lg">
<h3 className="text-lg font-semibold text-red-900 dark:text-red-300">
🚫
</h3>
<p className="text-gray-600 dark:text-gray-300">
AI
</p>
</div>
<div className="p-4 bg-green-50 dark:bg-green-900/20 rounded-lg">
<h3 className="text-lg font-semibold text-green-900 dark:text-green-300">
</h3>
<p className="text-gray-600 dark:text-gray-300"> AI </p>
</div>
</div>
</section>
<section>
<h2 className="text-2xl font-bold mb-3">URL </h2>
<div className="space-y-3">
<div className="p-4 bg-red-50 dark:bg-red-900/20 rounded-lg">
<h3 className="text-lg font-semibold text-red-900 dark:text-red-300">
🚫 URL
</h3>
<p className="text-gray-600 dark:text-gray-300"> AI 访</p>
</div>
<div className="p-4 bg-green-50 dark:bg-green-900/20 rounded-lg">
<h3 className="text-lg font-semibold text-green-900 dark:text-green-300">
URL
</h3>
<p className="text-gray-600 dark:text-gray-300"> AI 访</p>
</div>
</div>
</section>
<section>
<h2 className="text-2xl font-bold mb-3">Instruction </h2>
<div className="p-4 bg-yellow-50 dark:bg-yellow-900/20 rounded-lg">
<h3 className="text-lg font-semibold mb-2 text-yellow-900 dark:text-yellow-300">
</h3>
<p className="text-gray-600 dark:text-gray-300 mb-3">
AI
</p>
<div className="space-y-2">
<div className="pl-3 border-l-2 border-red-400">
<p className="font-medium text-red-700 dark:text-red-300"></p>
<p className="text-sm text-gray-500 dark:text-gray-400">
</p>
</div>
<div className="pl-3 border-l-2 border-orange-400">
<p className="font-medium text-orange-700 dark:text-orange-300"></p>
<p className="text-sm text-gray-500 dark:text-gray-400">
</p>
</div>
</div>
</div>
</section>
</div>
</div>
)
}

View File

@@ -0,0 +1,66 @@
import BetaNotice from '@/components/BetaNotice'
import CodeEditor from '@/components/CodeEditor'
export default function BestPractices() {
return (
<div>
<h1 className="text-4xl font-bold mb-6"></h1>
<BetaNotice />
<p className="text-xl text-gray-600 dark:text-gray-300 mb-6 leading-relaxed">
使 page-agent
</p>
<h2 className="text-2xl font-bold mb-3"></h2>
<div className="space-y-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">
API
</h3>
<p className="text-gray-600 dark:text-gray-300 mb-3">
AI
</p>
<CodeEditor
code={`// 推荐:合并操作
await pageAgent.execute('填写表单姓名张三邮箱test@example.com然后提交');
// 不推荐:分别操作
await pageAgent.execute('填写姓名张三');
await pageAgent.execute('填写邮箱test@example.com');
await pageAgent.execute('点击提交按钮');`}
/>
</div>
<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>
<p className="text-gray-600 dark:text-gray-300">
使
</p>
</div>
</div>
<h2 className="text-2xl font-bold mb-3"></h2>
<div className="space-y-3 mb-6">
<div className="p-3 bg-red-50 dark:bg-red-900/20 rounded-lg border-l-4 border-red-500">
<h3 className="font-semibold mb-1 text-red-900 dark:text-red-300"></h3>
<p className="text-gray-600 dark:text-gray-300"></p>
</div>
<div className="p-3 bg-yellow-50 dark:bg-yellow-900/20 rounded-lg border-l-4 border-yellow-500">
<h3 className="font-semibold mb-1 text-yellow-900 dark:text-yellow-300"></h3>
<p className="text-gray-600 dark:text-gray-300"></p>
</div>
</div>
<h2 className="text-2xl font-bold mb-3"></h2>
<CodeEditor code={`// TODO`} />
</div>
)
}

View File

@@ -0,0 +1,44 @@
import BetaNotice from '@/components/BetaNotice'
import CodeEditor from '@/components/CodeEditor'
import { CDN_CN_URL, CDN_URL } from '@/constants'
export default function CdnSetup() {
return (
<div>
<h1 className="text-4xl font-bold mb-6">CDN </h1>
<p className="text-xl text-gray-600 dark:text-gray-300 mb-6 leading-relaxed">
CDN page-agent
</p>
<h2 className="text-2xl font-bold mb-3"></h2>
<CodeEditor
className="mb-8"
code={`
// CDN: \t${CDN_URL}
// Mirror: \t${CDN_CN_URL}
<script
src="${CDN_URL}"
crossorigin="true"
type="text/javascript"
></script>
<script>
// window.pageAgent
</script>`}
/>
<div className="bg-yellow-50 dark:bg-yellow-900/20 p-4 rounded-lg">
<h3 className="text-lg font-semibold mb-2 text-yellow-900 dark:text-yellow-300">
</h3>
<ul className="text-gray-600 dark:text-gray-300 space-y-1">
<li> 使</li>
<li> HTTPS 使</li>
<li> CSP </li>
</ul>
</div>
</div>
)
}

View File

@@ -0,0 +1,98 @@
import { useTranslation } from 'react-i18next'
import CodeEditor from '@/components/CodeEditor'
export default function Configuration() {
const { i18n } = useTranslation()
const isZh = i18n.language === 'zh-CN'
return (
<div>
<h1 className="text-4xl font-bold mb-6">{isZh ? '配置选项' : 'Configuration'}</h1>
<p className="text-xl text-gray-600 dark:text-gray-300 mb-8 leading-relaxed">
{isZh
? 'PageAgent 的完整配置接口定义。'
: 'Complete configuration interface for PageAgent.'}
</p>
<CodeEditor
className="mb-8"
language="typescript"
code={`type PageAgentConfig = LLMConfig & AgentConfig & PageControllerConfig
// ============ LLM Configuration ============
interface LLMConfig {
baseURL?: string
apiKey?: string
model?: string
temperature?: number
maxRetries?: number
/**
* Custom fetch function for LLM API requests.
* Use this to customize headers, credentials, proxy, etc.
*/
customFetch?: typeof globalThis.fetch
}
// ============ Agent Configuration ============
interface AgentConfig {
language?: 'en-US' | 'zh-CN'
/** Custom tools to extend or override built-in tools */
customTools?: Record<string, PageAgentTool | null>
/** Instructions to guide the agent's behavior */
instructions?: {
/** Global system-level instructions, applied to all tasks */
system?: string
/** Dynamic page-level instructions callback */
getPageInstructions?: (url: string) => string | undefined | null
}
// Lifecycle hooks
onBeforeStep?: (stepCnt: number) => Promise<void> | void
onAfterStep?: (stepCnt: number, history: AgentHistory[]) => Promise<void> | void
onBeforeTask?: () => Promise<void> | void
onAfterTask?: (result: ExecutionResult) => Promise<void> | void
onDispose?: (reason?: string) => void
/**
* Transform page content before sending to LLM.
* Use cases: inspect extraction results, modify page info, mask sensitive data.
*/
transformPageContent?: (content: string) => Promise<string> | string
/** @experimental Enable JavaScript execution tool */
experimentalScriptExecutionTool?: boolean
}
// ============ PageController Configuration ============
interface PageControllerConfig {
/** Elements to exclude from interaction */
interactiveBlacklist?: (Element | (() => Element))[]
/** Elements to force include for interaction */
interactiveWhitelist?: (Element | (() => Element))[]
/** Additional attributes to include in DOM extraction */
include_attributes?: string[]
/** Highlight overlay opacity (0-1) */
highlightOpacity?: number
/** Highlight label opacity (0-1) */
highlightLabelOpacity?: number
/** Viewport expansion in pixels (-1 for full page) */
viewportExpansion?: number
}`}
/>
</div>
)
}

View File

@@ -0,0 +1,115 @@
import CodeEditor from '@/components/CodeEditor'
export default function ThirdPartyAgentPage() {
return (
<div>
<h1 className="text-4xl font-bold mb-6"> Agent</h1>
<p className="mb-6 leading-relaxed text-gray-600 dark:text-gray-300">
pageAgent Agent Agent
</p>
<div className="bg-blue-50 dark:bg-blue-900/20 rounded-lg p-4 mb-6">
<h3 className="text-lg font-semibold mb-2 text-blue-900 dark:text-blue-300">💡 </h3>
<p className="text-blue-800 dark:text-blue-200">
"嘴巴""眼睛""手"
</p>
</div>
<h2 className="text-2xl font-bold mb-4"></h2>
<div className="space-y-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">
1. Function Calling
</h3>
<CodeEditor
code={`// 定义工具
const pageAgentTool = {
name: "page_agent",
description: "执行网页操作",
parameters: {
type: "object",
properties: {
instruction: { type: "string", description: "操作指令" }
},
required: ["instruction"]
},
execute: async (params) => {
const result = await pageAgent.execute(params.instruction)
return { success: result.success, message: result.message }
}
}
// 注册到你的 agent 中`}
language="javascript"
/>
</div>
</div>
<h2 className="text-2xl font-bold mb-4"></h2>
<div className="grid md:grid-cols-2 gap-4 mb-6">
<div className="bg-linear-to-br from-blue-50 to-purple-50 dark:from-gray-800 dark:to-gray-700 p-4 rounded-lg">
<h4 className="font-semibold mb-2 text-gray-900 dark:text-white">🤖 </h4>
<p className="text-sm text-gray-600 dark:text-gray-300">
"帮我提交工单"
</p>
</div>
<div className="bg-linear-to-br from-green-50 to-blue-50 dark:from-gray-800 dark:to-gray-700 p-4 rounded-lg">
<h4 className="font-semibold mb-2 text-gray-900 dark:text-white">📋 </h4>
<p className="text-sm text-gray-600 dark:text-gray-300">
"完成客户入职"
</p>
</div>
<div className="bg-linear-to-br from-purple-50 to-pink-50 dark:from-gray-800 dark:to-gray-700 p-4 rounded-lg">
<h4 className="font-semibold mb-2 text-gray-900 dark:text-white">🎯 </h4>
<p className="text-sm text-gray-600 dark:text-gray-300">
"预订会议室"
</p>
</div>
<div className="bg-linear-to-br from-orange-50 to-red-50 dark:from-gray-800 dark:to-gray-700 p-4 rounded-lg">
<h4 className="font-semibold mb-2 text-gray-900 dark:text-white">🔧 </h4>
<p className="text-sm text-gray-600 dark:text-gray-300">
"重启服务器"
</p>
</div>
</div>
<h2 className="text-2xl font-bold mb-4"></h2>
<div className="space-y-4 mb-6">
<div className="p-4 bg-gray-50 dark:bg-gray-800 rounded-lg">
<h3 className="text-lg font-semibold mb-2 text-gray-900 dark:text-white"></h3>
<CodeEditor code={`// @TODO`} language="javascript" />
</div>
<div className="p-4 bg-gray-50 dark:bg-gray-800 rounded-lg">
<h3 className="text-lg font-semibold mb-2 text-gray-900 dark:text-white"></h3>
<CodeEditor code={`// @TODO`} language="javascript" />
</div>
</div>
<div className="bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg p-4 mb-6">
<h3 className="text-lg font-semibold text-yellow-900 dark:text-yellow-100 mb-2">
</h3>
<ul className="text-yellow-800 dark:text-yellow-200 space-y-1 text-sm">
<li> </li>
<li> </li>
<li> </li>
</ul>
</div>
<div className="bg-linear-to-r from-green-50 to-blue-50 dark:from-green-900/20 dark:to-blue-900/20 p-4 rounded-lg">
<h3 className="text-lg font-semibold mb-2 text-gray-900 dark:text-white">🎉 </h3>
<p className="mb-3 text-gray-700 dark:text-gray-300">
Agent
</p>
<a
href="/docs/integration/configuration"
className="inline-flex items-center px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors duration-200"
>
</a>
</div>
</div>
)
}

View File

@@ -0,0 +1,144 @@
import { useTranslation } from 'react-i18next'
export default function LimitationsPage() {
const { t } = useTranslation('docs')
return (
<div className="max-w-4xl mx-auto">
<div className="mb-8">
<h1 className="text-4xl font-bold mb-4 text-gray-900 dark:text-white">
{t('limitations.title')}
</h1>
<p className="text-xl text-gray-600 dark:text-gray-300">{t('limitations.subtitle')}</p>
</div>
<div className="prose prose-lg dark:prose-invert max-w-none">
<h2 className="text-2xl font-bold mb-3">{t('limitations.page_support')}</h2>
<div className="bg-blue-50 dark:bg-blue-900/20 border-l-4 border-blue-400 p-4 mb-6">
<h3 className="font-semibold text-blue-800 dark:text-blue-200 mb-2">
{t('limitations.spa_limit_title')}
</h3>
<ul className="text-blue-700 dark:text-blue-300 space-y-2">
<li>{t('limitations.spa_limit_1')}</li>
<li>{t('limitations.spa_limit_2')}</li>
<li>{t('limitations.spa_limit_3')}</li>
</ul>
</div>
<h2 className="text-2xl font-bold mb-3">{t('limitations.interaction_limits')}</h2>
<div className="bg-gray-50 dark:bg-gray-800 rounded-lg p-6 mb-6">
<h3 className="font-semibold mb-4">{t('limitations.supported_ops')}</h3>
<div className="grid md:grid-cols-2 gap-4 mb-6">
<div className="space-y-2">
<div className="flex items-center text-green-600 dark:text-green-400">
<span className="mr-2"></span>
<span>{t('limitations.op_click')}</span>
</div>
<div className="flex items-center text-green-600 dark:text-green-400">
<span className="mr-2"></span>
<span>{t('limitations.op_input')}</span>
</div>
<div className="flex items-center text-green-600 dark:text-green-400">
<span className="mr-2"></span>
<span>{t('limitations.op_scroll')}</span>
</div>
<div className="flex items-center text-green-600 dark:text-green-400">
<span className="mr-2"></span>
<span>{t('limitations.op_submit')}</span>
</div>
</div>
<div className="space-y-2">
<div className="flex items-center text-green-600 dark:text-green-400">
<span className="mr-2"></span>
<span>{t('limitations.op_select')}</span>
</div>
<div className="flex items-center text-green-600 dark:text-green-400">
<span className="mr-2"></span>
<span>{t('limitations.op_focus')}</span>
</div>
</div>
</div>
<h3 className="font-semibold mb-4">{t('limitations.unsupported_ops')}</h3>
<div className="grid md:grid-cols-2 gap-4">
<div className="space-y-2">
<div className="flex items-center text-red-600 dark:text-red-400">
<span className="mr-2"></span>
<span>{t('limitations.op_hover')}</span>
</div>
<div className="flex items-center text-red-600 dark:text-red-400">
<span className="mr-2"></span>
<span>{t('limitations.op_drag')}</span>
</div>
<div className="flex items-center text-red-600 dark:text-red-400">
<span className="mr-2"></span>
<span>{t('limitations.op_context')}</span>
</div>
</div>
<div className="space-y-2">
<div className="flex items-center text-red-600 dark:text-red-400">
<span className="mr-2"></span>
<span>{t('limitations.op_draw')}</span>
</div>
<div className="flex items-center text-red-600 dark:text-red-400">
<span className="mr-2"></span>
<span>{t('limitations.op_keyboard')}</span>
</div>
<div className="flex items-center text-red-600 dark:text-red-400">
<span className="mr-2"></span>
<span>{t('limitations.op_position')}</span>
</div>
</div>
</div>
</div>
<h2 className="text-2xl font-bold mb-3">{t('limitations.understanding_limits')}</h2>
<div className="bg-red-50 dark:bg-red-900/20 border-l-4 border-red-400 p-4 mb-6">
<h3 className="font-semibold text-red-800 dark:text-red-200 mb-2">
{t('limitations.no_vision_title')}
</h3>
<p className="text-red-700 dark:text-red-300 mb-3">{t('limitations.no_vision_desc')}</p>
<ul className="text-red-700 dark:text-red-300 space-y-1">
<li>{t('limitations.no_vision_1')}</li>
<li>{t('limitations.no_vision_2')}</li>
<li>{t('limitations.no_vision_3')}</li>
<li>{t('limitations.no_vision_4')}</li>
</ul>
</div>
<h2 className="text-2xl font-bold mb-3">{t('limitations.website_requirements')}</h2>
<div className="bg-gray-50 dark:bg-gray-800 rounded-lg p-6 mb-6">
<div className="space-y-4">
<div>
<h3 className="font-semibold mb-2">{t('limitations.req_semantic_title')}</h3>
<p className="text-gray-600 dark:text-gray-300">
{t('limitations.req_semantic_desc')}
</p>
</div>
<div>
<h3 className="font-semibold mb-2">{t('limitations.req_ux_title')}</h3>
<p className="text-gray-600 dark:text-gray-300">{t('limitations.req_ux_desc')}</p>
</div>
<div>
<h3 className="font-semibold mb-2">{t('limitations.req_env_title')}</h3>
<p className="text-gray-600 dark:text-gray-300">{t('limitations.req_env_desc')}</p>
</div>
</div>
</div>
<h2>{t('limitations.future')}</h2>
<div className="bg-green-50 dark:bg-green-900/20 border-l-4 border-green-400 p-4">
<h3 className="font-semibold text-green-800 dark:text-green-200 mb-2">
{t('limitations.future_title')}
</h3>
<ul className="text-green-700 dark:text-green-300 space-y-1">
<li>{t('limitations.future_1')}</li>
<li>{t('limitations.future_2')}</li>
<li>{t('limitations.future_3')}</li>
<li>{t('limitations.future_4')}</li>
</ul>
</div>
</div>
</div>
)
}

View File

@@ -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/i3/O1CN014xLeic1gnF2iWdzjJ_!!6000000004186-49-tps-1280-353.webp"
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>
)
}

View File

@@ -0,0 +1,84 @@
import { useTranslation } from 'react-i18next'
import BetaNotice from '@/components/BetaNotice'
import CodeEditor from '@/components/CodeEditor'
import { CDN_CN_URL, CDN_URL } from '@/constants'
export default function QuickStart() {
const { t } = useTranslation('docs')
return (
<div>
<h1 className="text-4xl font-bold mb-6">{t('quick_start.title')}</h1>
<p className=" mb-6 leading-relaxed">{t('quick_start.subtitle')}</p>
<h2 className="text-2xl font-bold mb-3">{t('quick_start.installation')}</h2>
<div className="space-y-4 mb-6">
<div className="p-4 bg-blue-50 dark:bg-blue-900/20 rounded-lg">
<h3 className="text-lg font-semibold mb-2 text-blue-900 dark:text-blue-300">
{t('quick_start.step1_title')}
</h3>
<div className="space-y-3">
<div>
<p className="text-sm font-medium mb-2">{t('quick_start.step1_cdn')}</p>
<CodeEditor
code={`// CDN: \t${CDN_URL}
// Mirror: \t${CDN_CN_URL}
<script src="${CDN_URL}" crossorigin="true" type="text/javascript"></script>`}
language="html"
/>
</div>
<div>
<p className="text-sm font-medium mb-2">{t('quick_start.step1_npm')}</p>
<CodeEditor
code={`// npm install page-agent
import PageAgent from 'page-agent'`}
language="bash"
/>
</div>
</div>
</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('quick_start.step2_title')}
</h3>
<CodeEditor
code={`// 仅供测试使用,生产环境需要配置 LLM 接入点,本工具不提供 LLM 服务
// 测试接口
// @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'
const agent = new PageAgent({
model: DEMO_MODEL,
baseURL: DEMO_BASE_URL,
apiKey: DEMO_API_KEY,
language: 'zh-CN'
})`}
language="javascript"
/>
</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('quick_start.step3_title')}
</h3>
<CodeEditor
code={`// 程序化执行自然语言指令
await pageAgent.execute('点击提交按钮,然后填写用户名为张三');
// 或者显示对话框让用户输入指令
pageAgent.panel.show()
`}
language="javascript"
/>
</div>
</div>
</div>
)
}