Enhance Agent Documentation with Iframe Integration Details
- Added a new section in the README for Iframe Integration, providing an overview of iframe communication with CUI. - Expanded the pages documentation to include frontend scripting styles and examples for handling form submissions and backend API calls. - Introduced detailed examples for parent communication and receiving context in iframes, improving clarity on how to implement these features.
This commit is contained in:
parent
b7fc91a312
commit
e6778e83fd
3 changed files with 448 additions and 18 deletions
|
|
@ -184,6 +184,7 @@ ctx.Send({
|
|||
- [Models](docs/models.md) - Assistant-scoped data models
|
||||
- [Search](docs/search.md) - Web, knowledge base, and database search
|
||||
- [Pages](docs/pages.md) - Web UI for agents (SUI framework)
|
||||
- [Iframe Integration](docs/iframe.md) - Iframe communication with CUI
|
||||
- [Internationalization](docs/i18n.md) - Multi-language support
|
||||
- [Testing](docs/testing.md) - Agent testing framework
|
||||
|
||||
|
|
|
|||
300
agent/docs/iframe.md
Normal file
300
agent/docs/iframe.md
Normal file
|
|
@ -0,0 +1,300 @@
|
|||
# Iframe Integration
|
||||
|
||||
Agent Pages can be embedded in CUI via `/web/` routes. This document covers the iframe communication mechanism between embedded pages and the CUI host.
|
||||
|
||||
## Route Mapping
|
||||
|
||||
Pages are accessible via:
|
||||
|
||||
```
|
||||
/web/<assistant-id>/<page-path>
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
| Page File | URL |
|
||||
| -------------------------- | --------------------------------- |
|
||||
| `pages/index/index.html` | `/web/my-assistant/index` |
|
||||
| `pages/result/index.html` | `/web/my-assistant/result` |
|
||||
| `pages/report/detail.html` | `/web/my-assistant/report/detail` |
|
||||
|
||||
## URL Parameters
|
||||
|
||||
CUI automatically injects context via URL parameters:
|
||||
|
||||
| Parameter | Value | Description |
|
||||
| ---------- | ---------------------- | ------------- |
|
||||
| `__theme` | `light` / `dark` | Current theme |
|
||||
| `__locale` | `en-us`, `zh-cn`, etc. | User locale |
|
||||
|
||||
> **Note**: Authentication uses secure HTTP-only cookies, so `__token` parameter is not needed.
|
||||
|
||||
**Usage in page URL:**
|
||||
|
||||
```
|
||||
/web/my-assistant/result?theme=__theme&locale=__locale
|
||||
```
|
||||
|
||||
CUI replaces `__theme`, `__locale` with actual values before loading.
|
||||
|
||||
## Message Communication
|
||||
|
||||
### Receiving Setup Message
|
||||
|
||||
When the iframe loads, CUI sends a `setup` message:
|
||||
|
||||
```typescript
|
||||
// In your page script
|
||||
window.addEventListener("message", (e) => {
|
||||
if (e.data.type === "setup") {
|
||||
const { theme, locale, token } = e.data.message;
|
||||
// Apply theme, store token, set locale
|
||||
document.documentElement.setAttribute("data-theme", theme);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Sending Actions to CUI
|
||||
|
||||
Pages can trigger CUI actions via `postMessage` using the unified Action system:
|
||||
|
||||
```typescript
|
||||
// Send action to parent CUI
|
||||
window.parent.postMessage(
|
||||
{
|
||||
type: "action",
|
||||
message: {
|
||||
name: "notify.success",
|
||||
payload: { message: "Operation completed" },
|
||||
},
|
||||
},
|
||||
window.location.origin
|
||||
);
|
||||
```
|
||||
|
||||
### Action Types
|
||||
|
||||
#### Navigate
|
||||
|
||||
| Action | Description | Payload |
|
||||
| --------------- | ------------------------------- | ------------------------------------------- |
|
||||
| `navigate` | Open page in sidebar or new tab | `{ route, title?, icon?, query?, target? }` |
|
||||
| `navigate.back` | Navigate back in history | - |
|
||||
|
||||
**Navigate Payload:**
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
| -------- | ------------------------ | -------- | ----------------------------------------------- |
|
||||
| `route` | `string` | ✅ | Target route (`$dashboard/xxx`, `/xxx`, or URL) |
|
||||
| `title` | `string` | - | Page title (shows title bar with back button) |
|
||||
| `icon` | `string` | - | Tab icon (e.g., `material-folder`) |
|
||||
| `query` | `Record<string, string>` | - | Query parameters |
|
||||
| `target` | `'_self'` \| `'_blank'` | - | `_self` (sidebar) or `_blank` (new window) |
|
||||
|
||||
#### Notify
|
||||
|
||||
| Action | Description | Payload |
|
||||
| ---------------- | ------------------------- | ------------------------------------------ |
|
||||
| `notify.success` | Show success notification | `{ message, duration?, icon?, closable? }` |
|
||||
| `notify.error` | Show error notification | `{ message, duration?, icon?, closable? }` |
|
||||
| `notify.warning` | Show warning notification | `{ message, duration?, icon?, closable? }` |
|
||||
| `notify.info` | Show info notification | `{ message, duration?, icon?, closable? }` |
|
||||
|
||||
#### App
|
||||
|
||||
| Action | Description |
|
||||
| ----------------- | ------------------------ |
|
||||
| `app.menu.reload` | Refresh application menu |
|
||||
|
||||
#### Modal
|
||||
|
||||
| Action | Description |
|
||||
| ------------- | ----------------- |
|
||||
| `modal.open` | Open modal dialog |
|
||||
| `modal.close` | Close modal |
|
||||
|
||||
#### Table
|
||||
|
||||
| Action | Description |
|
||||
| --------------- | -------------------- |
|
||||
| `table.search` | Trigger table search |
|
||||
| `table.refresh` | Refresh table data |
|
||||
| `table.save` | Save table row |
|
||||
| `table.delete` | Delete table row(s) |
|
||||
|
||||
#### Form
|
||||
|
||||
| Action | Description |
|
||||
| ----------------- | --------------------- |
|
||||
| `form.find` | Load form data by ID |
|
||||
| `form.submit` | Submit form |
|
||||
| `form.reset` | Reset form |
|
||||
| `form.setFields` | Set form field values |
|
||||
| `form.fullscreen` | Toggle fullscreen |
|
||||
|
||||
#### MCP (Client-side)
|
||||
|
||||
| Action | Description |
|
||||
| ------------------- | ------------------ |
|
||||
| `mcp.tool.call` | Execute MCP tool |
|
||||
| `mcp.resource.read` | Read MCP resource |
|
||||
| `mcp.resource.list` | List MCP resources |
|
||||
| `mcp.prompt.get` | Get MCP prompt |
|
||||
| `mcp.prompt.list` | List MCP prompts |
|
||||
|
||||
#### Event
|
||||
|
||||
| Action | Description |
|
||||
| ------------ | ----------------- |
|
||||
| `event.emit` | Emit custom event |
|
||||
|
||||
#### Confirm
|
||||
|
||||
| Action | Description |
|
||||
| --------- | ------------------------ |
|
||||
| `confirm` | Show confirmation dialog |
|
||||
|
||||
### Receiving Events from CUI
|
||||
|
||||
CUI can send messages to iframe via `web/sendMessage` event:
|
||||
|
||||
```typescript
|
||||
// In your page script
|
||||
window.addEventListener("message", (e) => {
|
||||
const { type, message } = e.data;
|
||||
|
||||
switch (type) {
|
||||
case "setup":
|
||||
// Initial setup with theme, locale, token
|
||||
break;
|
||||
case "refresh":
|
||||
// CUI requests page refresh
|
||||
location.reload();
|
||||
break;
|
||||
case "data":
|
||||
// CUI sends data update
|
||||
handleDataUpdate(message);
|
||||
break;
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Complete Example
|
||||
|
||||
### Page HTML (pages/result/index.html)
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Result Page</title>
|
||||
<script src="@assets/js/result.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### Page Script (pages/result/result.ts)
|
||||
|
||||
```typescript
|
||||
import { $Backend, Component, EventData } from "@yao/sui";
|
||||
|
||||
const self = this as Component;
|
||||
|
||||
// Helper: Send action to CUI parent
|
||||
const sendAction = (name: string, payload?: any) => {
|
||||
try {
|
||||
window.parent.postMessage(
|
||||
{ type: "action", message: { name, payload } },
|
||||
window.location.origin
|
||||
);
|
||||
} catch (err) {
|
||||
console.error("Failed to send action to parent:", err);
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize message listener
|
||||
function init() {
|
||||
window.addEventListener("message", (e) => {
|
||||
if (e.origin !== window.location.origin) return;
|
||||
|
||||
const { type, message } = e.data;
|
||||
switch (type) {
|
||||
case "setup":
|
||||
// Apply theme, locale from CUI
|
||||
document.documentElement.setAttribute("data-theme", message.theme);
|
||||
break;
|
||||
case "update":
|
||||
// Handle data updates from CUI
|
||||
console.log("Received update:", message);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
// Make helper available globally
|
||||
(window as any).sendAction = sendAction;
|
||||
}
|
||||
|
||||
init();
|
||||
|
||||
// Event handler: Show success notification
|
||||
self.HandleSuccess = (event: Event, data: EventData) => {
|
||||
sendAction("notify.success", { message: data.message || "Success!" });
|
||||
};
|
||||
|
||||
// Event handler: Navigate to page
|
||||
self.HandleNavigate = (event: Event, data: EventData) => {
|
||||
sendAction("navigate", {
|
||||
route: data.path,
|
||||
title: data.title,
|
||||
});
|
||||
};
|
||||
|
||||
// Event handler: Close sidebar
|
||||
self.HandleClose = () => {
|
||||
sendAction("event.emit", { key: "app/closeSidebar", value: {} });
|
||||
};
|
||||
|
||||
// Event handler: Call backend and display result
|
||||
self.HandleQuery = async (event: Event, data: EventData) => {
|
||||
try {
|
||||
const result = await $Backend().Call("Query", data.id);
|
||||
console.log(result);
|
||||
} catch (error: any) {
|
||||
sendAction("notify.error", { message: error.message });
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## Triggering from Hooks
|
||||
|
||||
Open page in sidebar from agent hooks:
|
||||
|
||||
```typescript
|
||||
function Next(ctx: agent.Context, payload: agent.Payload): agent.Next {
|
||||
// Open result page in sidebar
|
||||
ctx.Send({
|
||||
type: "action",
|
||||
props: {
|
||||
name: "navigate",
|
||||
payload: {
|
||||
route: `/agents/my-assistant/result`,
|
||||
title: "Results",
|
||||
query: { id: resultId },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
||||
```
|
||||
|
||||
See [Pages](pages.md) for more details on triggering pages from hooks.
|
||||
|
||||
## Security Notes
|
||||
|
||||
1. **Same-origin only**: Messages are only processed from same-origin iframes
|
||||
2. **Secure cookies**: Authentication uses HTTP-only cookies, no token in URL
|
||||
3. **Validate messages**: Always validate message structure before processing
|
||||
|
|
@ -148,16 +148,65 @@ function ApiGetData(request: Request): any {
|
|||
|
||||
**`/assistants/my-assistant/pages/index/index.ts`**:
|
||||
|
||||
```typescript
|
||||
function index(component: HTMLElement) {
|
||||
this.root = component;
|
||||
this.store = new __sui_store(component);
|
||||
Frontend scripts can be written in two styles:
|
||||
|
||||
this.handleClick = async (event: Event) => {
|
||||
const data = await this.backend.ApiGetData({ id: 1 });
|
||||
console.log(data);
|
||||
};
|
||||
}
|
||||
**Style 1: Direct Code (Simple Pages)**
|
||||
|
||||
```typescript
|
||||
// Runs immediately when script loads
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const form = document.querySelector("#myForm") as HTMLFormElement;
|
||||
|
||||
form.addEventListener("submit", async (e) => {
|
||||
e.preventDefault();
|
||||
// Handle form submission
|
||||
});
|
||||
});
|
||||
|
||||
// Smooth scrolling for navigation
|
||||
document.querySelectorAll('a[href^="#"]').forEach((anchor) => {
|
||||
anchor.addEventListener("click", function (e) {
|
||||
e.preventDefault();
|
||||
const target = document.querySelector(this.getAttribute("href"));
|
||||
target?.scrollIntoView({ behavior: "smooth" });
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
**Style 2: Component Pattern (Interactive Pages)**
|
||||
|
||||
```typescript
|
||||
import { $Backend, Component, EventData } from "@yao/sui";
|
||||
|
||||
const self = this as Component;
|
||||
|
||||
// Event handler bound to s:on-click="HandleClick"
|
||||
self.HandleClick = async (event: Event, data: EventData) => {
|
||||
const result = await $Backend().Call("ApiGetData", data.id);
|
||||
console.log(result);
|
||||
};
|
||||
|
||||
// Form submission handler
|
||||
self.HandleSubmit = async (event: Event) => {
|
||||
event.preventDefault();
|
||||
const form = event.target as HTMLFormElement;
|
||||
const formData = new FormData(form);
|
||||
await $Backend().Call("ApiSubmit", Object.fromEntries(formData));
|
||||
};
|
||||
```
|
||||
|
||||
**Using Backend API:**
|
||||
|
||||
```typescript
|
||||
import { $Backend, Yao } from "@yao/sui";
|
||||
|
||||
// Call backend method
|
||||
const data = await $Backend().Call("ApiMethodName", arg1, arg2);
|
||||
|
||||
// Direct API calls
|
||||
const yao = new Yao();
|
||||
const res = await yao.Get("/api/endpoint", { param: "value" });
|
||||
await yao.Post("/api/endpoint", { data: "value" });
|
||||
```
|
||||
|
||||
### 6. Build and Run
|
||||
|
|
@ -495,23 +544,103 @@ ctx.Send({
|
|||
|
||||
## Frontend API
|
||||
|
||||
The SUI frontend SDK provides:
|
||||
### Backend Calls
|
||||
|
||||
```typescript
|
||||
// Backend calls
|
||||
const data = await this.backend.ApiMethodName(payload);
|
||||
import { $Backend, Yao } from "@yao/sui";
|
||||
|
||||
// State management
|
||||
this.store.Set("key", value);
|
||||
const value = this.store.Get("key");
|
||||
// Call backend method defined in .backend.ts
|
||||
const data = await $Backend().Call("ApiMethodName", arg1, arg2);
|
||||
|
||||
// OpenAPI client (if using oauth guard)
|
||||
const response = await OpenAPI.Get("/api/endpoint");
|
||||
await OpenAPI.Post("/api/endpoint", data);
|
||||
// Direct API calls
|
||||
const yao = new Yao();
|
||||
const res = await yao.Get("/api/endpoint", { query: "value" });
|
||||
await yao.Post("/api/endpoint", { body: "data" });
|
||||
```
|
||||
|
||||
### State Management
|
||||
|
||||
```typescript
|
||||
import { Component } from "@yao/sui";
|
||||
|
||||
const self = this as Component;
|
||||
|
||||
// Store values (per component instance)
|
||||
self.store.Set("key", value);
|
||||
const value = self.store.Get("key");
|
||||
```
|
||||
|
||||
### Parent Communication (Iframe)
|
||||
|
||||
```typescript
|
||||
// Helper: Send action to CUI parent
|
||||
const sendAction = (name: string, payload?: any) => {
|
||||
window.parent.postMessage(
|
||||
{ type: "action", message: { name, payload } },
|
||||
window.location.origin
|
||||
);
|
||||
};
|
||||
|
||||
// Usage
|
||||
sendAction("notify.success", { message: "Done!" });
|
||||
sendAction("navigate", {
|
||||
route: "/agents/my-assistant/detail",
|
||||
title: "Details",
|
||||
});
|
||||
|
||||
// Receive messages from parent
|
||||
window.addEventListener("message", (e) => {
|
||||
if (e.origin !== window.location.origin) return;
|
||||
const { type, message } = e.data;
|
||||
if (type === "setup") {
|
||||
document.documentElement.setAttribute("data-theme", message.theme);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Iframe Communication
|
||||
|
||||
When pages are embedded in CUI via `/web/<assistant-id>/<page>`, they can communicate with the host:
|
||||
|
||||
### Receiving Context
|
||||
|
||||
```javascript
|
||||
window.addEventListener("message", (e) => {
|
||||
if (e.origin !== window.location.origin) return;
|
||||
if (e.data.type === "setup") {
|
||||
const { theme, locale } = e.data.message;
|
||||
// Apply theme, set locale
|
||||
document.documentElement.setAttribute("data-theme", theme);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Sending Actions
|
||||
|
||||
```javascript
|
||||
// Helper function
|
||||
const sendAction = (name, payload) => {
|
||||
window.parent.postMessage(
|
||||
{ type: "action", message: { name, payload } },
|
||||
window.location.origin
|
||||
);
|
||||
};
|
||||
|
||||
// Show notification
|
||||
sendAction("notify.success", { message: "Done!" });
|
||||
|
||||
// Navigate to page
|
||||
sendAction("navigate", {
|
||||
route: "/agents/my-assistant/detail",
|
||||
title: "Details",
|
||||
});
|
||||
```
|
||||
|
||||
See [Iframe Integration](iframe.md) for complete documentation.
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Iframe Integration](iframe.md) - CUI iframe communication
|
||||
- [SUI Template Syntax](../../sui/docs/template-syntax.md)
|
||||
- [SUI Data Binding](../../sui/docs/data-binding.md)
|
||||
- [SUI Components](../../sui/docs/components.md)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue