docs: add Page component tutorial (zh + en)

Add comprehensive Page component documentation covering:
- Use cases (admin panels, data displays, interactive tools)
- Manifest file format
- Backend handler with PageRequest/PageResponse
- Frontend development with Page SDK (API calls, dark mode, i18n)
- Full FAQ Manager example
- Update add.mdx and docs.json navigation for both languages

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
This commit is contained in:
Junyan Qin
2026-04-25 21:59:17 +08:00
co-authored by Claude Opus 4.6
parent 2281e4aea0
commit 3d1d97ecca
5 changed files with 498 additions and 2 deletions
+4 -2
View File
@@ -321,7 +321,8 @@
"en/plugin/dev/components/command",
"en/plugin/dev/components/tool",
"en/plugin/dev/components/knowledge-engine",
"en/plugin/dev/components/parser"
"en/plugin/dev/components/parser",
"en/plugin/dev/components/page"
]
},
"en/plugin/dev/style",
@@ -528,7 +529,8 @@
"zh/plugin/dev/components/command",
"zh/plugin/dev/components/tool",
"zh/plugin/dev/components/knowledge-engine",
"zh/plugin/dev/components/parser"
"zh/plugin/dev/components/parser",
"zh/plugin/dev/components/page"
]
},
"zh/plugin/dev/style",
+2
View File
@@ -9,6 +9,7 @@ Plugins consist of one or more components that provide different functionalities
- Tools (Tool): Called by LLMs during execution of LangBot's built-in Local Agent.
- Knowledge Engines (KnowledgeEngine): Provide knowledge base indexing and retrieval capabilities for LangBot, managing document ingestion, retrieval, and deletion.
- Parsers (Parser): Parse binary files such as PDF, Word, etc. into structured text before document ingestion, for use by Knowledge Engines.
- Pages (Page): Register custom visual pages in the LangBot WebUI sidebar, providing admin panels, data displays, and interactive interfaces.
## Adding Components
@@ -74,3 +75,4 @@ For detailed usage of each component, please refer to:
- [Tool](./tool)
- [KnowledgeEngine](./knowledge-engine)
- [Parser](./parser)
- [Page](./page)
+245
View File
@@ -0,0 +1,245 @@
---
title: "Component: Page"
---
The Page component allows plugins to register custom visual pages in the LangBot WebUI sidebar. Pages run inside an iframe sandbox, communicate with the host via the Page SDK, and can call the plugin's backend API.
## Use Cases
- **Admin panels**: Provide visual configuration and data management interfaces (e.g., FAQ management, analytics dashboards)
- **Data displays**: Show runtime statistics, logs, charts, etc.
- **Interactive tools**: Provide forms, editors, and other interactive interfaces
## Adding a Page Component
Run the following command in the plugin directory:
```bash
lbp comp Page
```
After entering the page name, the CLI will generate page files under `components/pages/`:
```bash
├── components
│ └── pages
│ └── dashboard
│ ├── dashboard.yaml # Page manifest
│ ├── dashboard.py # Backend handler
│ ├── index.html # Page entry point
│ └── i18n # Translation files (optional)
│ ├── en_US.json
│ └── zh_Hans.json
```
The plugin's `manifest.yaml` will also be updated with the component discovery config:
```yaml
spec:
components:
Page:
fromDirs:
- path: components/pages/
maxDepth: 2
```
## Manifest File: Page
```yaml
apiVersion: v1 # Do not modify
kind: Page # Do not modify
metadata:
name: dashboard # Unique page ID within the plugin
label:
en_US: Dashboard # Display name shown in the WebUI sidebar, supports i18n
zh_Hans: 仪表盘
spec:
path: index.html # HTML entry file, relative to this YAML file's directory
execution:
python:
path: dashboard.py # Backend handler file
attr: DashboardPage # Handler class name
```
## Backend Handler
The Page component's backend handler extends the `Page` base class and implements `handle_api` to process API requests from the frontend page.
```python
from langbot_plugin.api.definition.components.page import Page, PageRequest, PageResponse
class DashboardPage(Page):
async def handle_api(self, request: PageRequest) -> PageResponse:
# request.endpoint: API endpoint path, e.g. '/stats'
# request.method: HTTP method (GET, POST, PUT, DELETE)
# request.body: Request body (parsed JSON, or None)
if request.endpoint == '/stats' and request.method == 'GET':
# Access shared plugin state via self.plugin
return PageResponse.ok({
'total': len(self.plugin.entries),
})
return PageResponse.fail(f'Unknown endpoint: {request.endpoint}')
```
### PageRequest Fields
| Field | Type | Description |
|-------|------|-------------|
| `endpoint` | `str` | API endpoint path (e.g. `'/entries'`) |
| `method` | `str` | HTTP method (`GET`, `POST`, `PUT`, `DELETE`) |
| `body` | `Any` | Request body (parsed JSON, or `None`) |
### PageResponse Construction
| Method | Description |
|--------|-------------|
| `PageResponse.ok(data)` | Success response. `data` can be any JSON-serializable value |
| `PageResponse.fail(error)` | Error response. `error` is a human-readable error string |
## Frontend Page Development
Include the Page SDK in your HTML file to communicate with the plugin backend:
```html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
body {
background: var(--langbot-bg, #ffffff);
color: var(--langbot-text, #0a0a0a);
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
padding: 24px;
}
</style>
</head>
<body>
<h1 data-i18n="title">Dashboard</h1>
<p id="stats"></p>
<script src="/api/v1/plugins/_sdk/page-sdk.js"></script>
<script>
langbot.onReady(async function(ctx) {
// Call the backend API
var data = await langbot.api('/stats', null, 'GET');
document.getElementById('stats').textContent = 'Total: ' + data.total;
});
</script>
</body>
</html>
```
### Page SDK API
| Method | Description |
|--------|-------------|
| `langbot.onReady(callback)` | Fires when SDK is ready. `callback` receives `ctx` with `theme` and `language` |
| `langbot.api(endpoint, body?, method?)` | Calls the plugin's `handle_api`. Returns a Promise |
| `langbot.t(key, fallback?)` | Gets a translated string |
| `langbot.onThemeChange(callback)` | Fires when the theme changes |
| `langbot.onLanguageChange(callback)` | Fires when the language changes |
| `langbot.applyI18n()` | Manually re-apply `data-i18n` translations |
### Dark Mode
The SDK automatically sets CSS custom properties on the page. Use them directly:
| CSS Variable | Purpose |
|-------------|---------|
| `--langbot-bg` | Page background |
| `--langbot-bg-card` | Card background |
| `--langbot-text` | Primary text color |
| `--langbot-text-muted` | Secondary text color |
| `--langbot-border` | Border color |
| `--langbot-accent` | Accent color |
### Page i18n
Create an `i18n/` directory inside your page directory with JSON translation files:
```
pages/dashboard/
├── index.html
└── i18n/
├── en_US.json
└── zh_Hans.json
```
Translation files are flat JSON key-value pairs:
```json
{
"title": "Dashboard",
"totalEntries": "Total Entries"
}
```
Add the `data-i18n` attribute to HTML elements for automatic translation:
```html
<h1 data-i18n="title">Dashboard</h1>
```
## Full Example: FAQ Manager
Here is a complete Page component example that implements CRUD operations for FAQ entries.
**Backend handler** (`components/pages/manager/manager.py`):
```python
from langbot_plugin.api.definition.components.page import Page, PageRequest, PageResponse
class ManagerPage(Page):
async def handle_api(self, request: PageRequest) -> PageResponse:
plugin = self.plugin
if request.endpoint == '/entries' and request.method == 'GET':
return PageResponse.ok({'entries': plugin.entries})
if request.endpoint == '/entries' and request.method == 'POST':
question = (request.body or {}).get('question', '').strip()
answer = (request.body or {}).get('answer', '').strip()
if not question or not answer:
return PageResponse.fail('question and answer are required')
entry = plugin.add_entry(question, answer)
await plugin.persist()
return PageResponse.ok({'entry': entry})
if request.endpoint == '/entries' and request.method == 'DELETE':
entry_id = (request.body or {}).get('id', '')
if plugin.delete_entry(entry_id):
await plugin.persist()
return PageResponse.ok({'deleted': entry_id})
return PageResponse.fail('entry not found')
return PageResponse.fail(f'Unknown: {request.method} {request.endpoint}')
```
**Frontend page** (`components/pages/manager/index.html`) calls the backend via `langbot.api()`:
```javascript
// Load entries
var data = await langbot.api('/entries', null, 'GET');
// Add entry
await langbot.api('/entries', { question: '...', answer: '...' }, 'POST');
// Delete entry
await langbot.api('/entries', { id: '...' }, 'DELETE');
```
Full example code is available at [FAQManager plugin](https://github.com/langbot-app/langbot-plugin-demo/tree/main/FAQManager).
## Notes
- Pages run inside a `sandbox="allow-scripts allow-forms"` iframe and cannot open popups or navigate the parent page
- Access shared plugin state via `self.plugin` — Page and Tool components can share data
- Use `PageResponse.ok()` and `PageResponse.fail()` to construct responses for consistent formatting
- The Page SDK `<script>` tag must be placed before any code that uses the `langbot` object
+2
View File
@@ -9,6 +9,7 @@ title: "添加组件"
- 工具(Tool):供 LangBot 内置的 Local Agent 在执行期间由 LLM 调用。
- RAG 引擎(KnowledgeEngine):为 LangBot 提供知识库的索引和检索能力,管理文档摄取、检索和删除。
- 解析器(Parser):在文档摄取前将 PDF、Word 等二进制文件解析为结构化文本,供 RAG 引擎使用。
- 页面(Page):在 LangBot WebUI 侧边栏中注册自定义可视化页面,提供管理面板、数据展示等交互式界面。
## 添加组件
@@ -75,3 +76,4 @@ components:
- [Tool](./tool)
- [KnowledgeEngine](./knowledge-engine)
- [Parser](./parser)
- [Page](./page)
+245
View File
@@ -0,0 +1,245 @@
---
title: "组件:页面"
---
Page 组件允许插件在 LangBot 的 WebUI 侧边栏中注册自定义的可视化页面。页面运行在 iframe 沙箱中,通过 Page SDK 与宿主通信,并可调用插件后端 API。
## 适用场景
- **管理面板**:为插件提供可视化的配置和数据管理界面(如 FAQ 管理、数据统计仪表盘)
- **数据展示**:展示插件运行时的统计数据、日志、图表等
- **交互式工具**:提供表单、编辑器等交互式界面
## 添加页面组件
在插件目录执行命令:
```bash
lbp comp Page
```
根据提示输入页面名称后,会在 `components/pages/` 目录下生成页面文件:
```bash
├── components
│ └── pages
│ └── dashboard
│ ├── dashboard.yaml # 页面清单
│ ├── dashboard.py # 页面后端处理程序
│ ├── index.html # 页面入口
│ └── i18n # 翻译文件(可选)
│ ├── en_US.json
│ └── zh_Hans.json
```
同时,`manifest.yaml` 中会添加组件发现配置:
```yaml
spec:
components:
Page:
fromDirs:
- path: components/pages/
maxDepth: 2
```
## 清单文件:页面
```yaml
apiVersion: v1 # 请勿修改
kind: Page # 请勿修改
metadata:
name: dashboard # 页面唯一 ID,同一插件内不可重复
label:
en_US: Dashboard # 页面显示名称,显示在 WebUI 侧边栏,支持多语言
zh_Hans: 仪表盘
spec:
path: index.html # HTML 入口文件,相对于该 YAML 所在目录的路径
execution:
python:
path: dashboard.py # 页面后端处理程序
attr: DashboardPage # 处理程序类名
```
## 后端处理
Page 组件的后端处理程序继承 `Page` 基类,实现 `handle_api` 方法来处理来自前端页面的 API 请求。
```python
from langbot_plugin.api.definition.components.page import Page, PageRequest, PageResponse
class DashboardPage(Page):
async def handle_api(self, request: PageRequest) -> PageResponse:
# request.endpoint: API 端点路径,如 '/stats'
# request.method: HTTP 方法(GET、POST、PUT、DELETE)
# request.body: 请求体(已解析的 JSON,或 None)
if request.endpoint == '/stats' and request.method == 'GET':
# 通过 self.plugin 访问插件实例的共享状态
return PageResponse.ok({
'total': len(self.plugin.entries),
})
return PageResponse.fail(f'Unknown endpoint: {request.endpoint}')
```
### PageRequest 字段
| 字段 | 类型 | 说明 |
|------|------|------|
| `endpoint` | `str` | API 端点路径(如 `'/entries'`) |
| `method` | `str` | HTTP 方法(`GET`、`POST`、`PUT`、`DELETE`) |
| `body` | `Any` | 请求体(已解析的 JSON,或 `None`) |
### PageResponse 构造
| 方法 | 说明 |
|------|------|
| `PageResponse.ok(data)` | 成功响应,`data` 为任意可 JSON 序列化的数据 |
| `PageResponse.fail(error)` | 失败响应,`error` 为错误信息字符串 |
## 前端页面开发
在 HTML 文件中引入 Page SDK,即可与插件后端通信:
```html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
body {
background: var(--langbot-bg, #ffffff);
color: var(--langbot-text, #0a0a0a);
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
padding: 24px;
}
</style>
</head>
<body>
<h1 data-i18n="title">Dashboard</h1>
<p id="stats"></p>
<script src="/api/v1/plugins/_sdk/page-sdk.js"></script>
<script>
langbot.onReady(async function(ctx) {
// 调用后端 API
var data = await langbot.api('/stats', null, 'GET');
document.getElementById('stats').textContent = 'Total: ' + data.total;
});
</script>
</body>
</html>
```
### Page SDK API
| 方法 | 说明 |
|------|------|
| `langbot.onReady(callback)` | SDK 就绪后触发回调,`callback` 接收 `ctx` 参数(包含 `theme` 和 `language`) |
| `langbot.api(endpoint, body?, method?)` | 调用插件后端 `handle_api`,返回 Promise |
| `langbot.t(key, fallback?)` | 获取翻译字符串 |
| `langbot.onThemeChange(callback)` | 主题变更回调 |
| `langbot.onLanguageChange(callback)` | 语言变更回调 |
| `langbot.applyI18n()` | 手动重新应用 `data-i18n` 翻译 |
### 暗色模式
SDK 会自动在页面上设置 CSS 自定义属性,直接使用即可:
| CSS 变量 | 用途 |
|------|------|
| `--langbot-bg` | 页面背景色 |
| `--langbot-bg-card` | 卡片背景色 |
| `--langbot-text` | 主文字色 |
| `--langbot-text-muted` | 次要文字色 |
| `--langbot-border` | 边框色 |
| `--langbot-accent` | 强调色 |
### 页面 i18n
在页面目录下创建 `i18n/` 目录,放入 JSON 翻译文件:
```
pages/dashboard/
├── index.html
└── i18n/
├── en_US.json
└── zh_Hans.json
```
翻译文件格式为扁平 JSON 键值对:
```json
{
"title": "仪表盘",
"totalEntries": "条目总数"
}
```
在 HTML 元素上添加 `data-i18n` 属性,SDK 会自动替换文本:
```html
<h1 data-i18n="title">Dashboard</h1>
```
## 完整示例:FAQ 管理器
以下是一个完整的 Page 组件示例,实现了 FAQ 条目的增删改查。
**后端处理程序**(`components/pages/manager/manager.py`):
```python
from langbot_plugin.api.definition.components.page import Page, PageRequest, PageResponse
class ManagerPage(Page):
async def handle_api(self, request: PageRequest) -> PageResponse:
plugin = self.plugin
if request.endpoint == '/entries' and request.method == 'GET':
return PageResponse.ok({'entries': plugin.entries})
if request.endpoint == '/entries' and request.method == 'POST':
question = (request.body or {}).get('question', '').strip()
answer = (request.body or {}).get('answer', '').strip()
if not question or not answer:
return PageResponse.fail('question and answer are required')
entry = plugin.add_entry(question, answer)
await plugin.persist()
return PageResponse.ok({'entry': entry})
if request.endpoint == '/entries' and request.method == 'DELETE':
entry_id = (request.body or {}).get('id', '')
if plugin.delete_entry(entry_id):
await plugin.persist()
return PageResponse.ok({'deleted': entry_id})
return PageResponse.fail('entry not found')
return PageResponse.fail(f'Unknown: {request.method} {request.endpoint}')
```
**前端页面**(`components/pages/manager/index.html`)中通过 `langbot.api()` 调用后端:
```javascript
// 加载条目
var data = await langbot.api('/entries', null, 'GET');
// 添加条目
await langbot.api('/entries', { question: '...', answer: '...' }, 'POST');
// 删除条目
await langbot.api('/entries', { id: '...' }, 'DELETE');
```
完整示例代码请参考 [FAQManager 插件](https://github.com/langbot-app/langbot-plugin-demo/tree/main/FAQManager)。
## 注意事项
- 页面运行在 `sandbox="allow-scripts allow-forms"` 的 iframe 中,不能弹窗或导航父页面
- 通过 `self.plugin` 访问插件实例的共享状态,Page 和 Tool 组件可以共享数据
- 使用 `PageResponse.ok()` 和 `PageResponse.fail()` 构造响应,确保格式一致
- Page SDK 的 `<script>` 标签必须放在使用 `langbot` 对象的代码之前