mirror of
https://github.com/Tencent/WeKnora.git
synced 2026-10-24 01:37:50 +08:00
Plugins can add pages to the app. Each is an HTML entry under ui/ in the
package:
| Point | Shown as | Default minRole |
| ------------------ | ----------------------------- | --------------- |
| `pages` | toolbox tabs | viewer |
| `settingsSections` | a section under Plugins in settings | admin |
| `kbTabs` | a tab on each knowledge base (gets knowledgeBaseId) | viewer |
Declarative plugins may have pages too.
Isolation:
- A page runs in `<iframe sandbox="allow-scripts allow-forms allow-popups
allow-popups-to-escape-sandbox">` without allow-same-origin, so it has an
opaque origin and cannot read the app's storage.
- Its files come from /api/v1/plugin-ui/assets/{id}/{version}/ui/...
(served without login, only files under ui/ of the loaded version).
- They carry a CSP with connect-src 'none', so the page cannot reach the
network.
Bridge:
- The page talks to the app only over postMessage: `api.request`,
`ui.toast`, `ui.confirm`, `ui.navigate`, `ui.resize`, `ui.close`.
- The app answers only its frame's window, rate-limited, and validates
paths and sizes.
- It sends init (plugin, mount, locale, role, theme tokens, context) and
theme/locale changes.
- api.request goes to POST /api/v1/plugins/:id/ui-request. The app checks
the workspace has the plugin on and the caller meets the page's minRole
(same rules as the route guards), then calls the plugin's new
/v1/ui/request endpoint with the caller's role.
SDKs:
- Go: `p.UI` and `UIJSON`.
- Python: `@plugin.ui` and `UIResponse`.
- Both list "ui" in their manifest; conformance checks the endpoint; the
OpenAPI spec documents it.
- packages/plugin-ui is the page side: a dependency-free ES module with
types that connects, applies the theme as --wk-* CSS variables and keeps
the frame sized.
Also:
- Fix the Python SDK reading Go timestamps with fewer than 6 fraction
digits on Python < 3.11. Key-value entries tripped on it.
- examples/plugins/links: a Python plugin with all three kinds of page,
storing links in the Host API key-value store.
@weknora/plugin-ui
The page side of the bridge between a WeKnora plugin's pages and the app. It has no dependencies and needs no build step.
WeKnora shows a plugin page in a sandboxed iframe:
- The page has an opaque origin, so it cannot read the app's storage.
- A CSP (
connect-src 'none') stops it reaching the network.
Everything else goes through this bridge.
import { connect } from './weknora-plugin-ui.js'
const wk = await connect()
wk.context.mount // "pages/links"
wk.context.context // what the mount passes, e.g. { knowledgeBaseId }
const { body } = await wk.get('/links') // the plugin's UI handler answers
await wk.put('/links', [...body, { url }])
await wk.toast('Saved', 'success')
if (await wk.confirm('Remove it?')) { /* … */ }
wk.navigate('/platform/knowledge-bases')
Ship index.js inside the plugin package, for example as
ui/weknora-plugin-ui.js, and load your page script with
<script type="module">.
What a page can do
| Call | Does |
|---|---|
request(method, path, body), get, post, put, delete |
Calls the plugin's own backend: the UI handler of the Go or Python SDK. Resolves {status, body}; non-2xx rejects unless { throwOnError: false }. |
toast(message, theme) |
A toast in the app: info, success, warning or error. |
confirm(message, title?) |
A confirmation dialog; resolves true or false. |
navigate(path) |
Opens a page of the app. |
resize(height) |
Sets the frame height. connect() does this automatically unless autoResize: false. |
close() |
Closes the page, where the mount allows it. |
on('theme' | 'locale' | 'init', fn) |
Theme or language changed, or the mount's context changed (another knowledge base). |
connect() applies the app's theme:
data-theme="light|dark"on<html>.- Design tokens as CSS variables, such as
--wk-brand-color,--wk-text-color-primary,--wk-bg-color-containerand--wk-component-border.
Where pages appear
Declare pages in plugin.yaml with an entry under ui/:
| Point | Shown | Default minRole |
|---|---|---|
pages |
A tab of the toolbox | viewer |
settingsSections |
A section of the settings dialog, under Plugins | admin |
kbTabs |
A tab on each knowledge base; context.knowledgeBaseId |
viewer |
WeKnora checks minRole again on each request a page makes. It also
passes the caller's role to the plugin, so the backend can refuse
anything finer-grained.
See examples/plugins/links for a plugin with all three.