Files
inkreach-agent/docs/plugins/04-ui-slots.md
T

122 lines
5.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# L3 · UI 槽位:胶囊、工具栏、视图、面板、iframe 页
> 前置:[L0](./01-hello-plugin.md)。宿主开放 5 个数据驱动 UI 槽位 + 1 个 iframe 页形态。**数据驱动**意味着插件只提交数据,渲染与样式由宿主统一负责——这是 UI 一致性的第一道保障(自定义视觉见 [DESIGN.md](./DESIGN.md) 与 [L4](./05-renderer-components.md))。
## 1. 状态胶囊(悬浮胶囊:最小化药丸 ⇄ 展开面板)
所有插件**共用一个悬浮胶囊**,悬浮于 main 区域右上角(侧面板打开时自动左移让位;ZCode 复刻)。插件照常 `setStatusItem`,宿主记录每次写入时间,两种形态:
- **最小化药丸**:展示「最新的消息」(全部插件中 `updatedAt` 最新的一条:tone 点 + 插件图标 + 文本;多于一条时带总数角标),点击展开面板;
- **展开面板**:面板头就是插件名(第一个区域的右侧是收回药丸的 ⤡);多个插件时各占一个区域、区域头可收起/展开(记忆跨启动)。区域内 `detail` 的**分组是手风琴**——默认收起只显示「分组标题 + 行尾徽标」,点击展开 rows(ZCode 式「计划 ›」);无标题分组直接平铺。面板最大高度封顶(min(480px,58vh)),高度随内容自适应,溢出才滚动;点击面板外部 / Esc 收回药丸。
```js
// ① 纯展示(只出现在药丸/区域行)
api.ui.setStatusItem('conn', { text: '已连接 · 3 任务' })
// ② 点击深链
api.ui.setStatusItem('conn', {
text: '已连接 · 3 任务',
tone: 'accent', // default | accent | warn | danger
icon: '<svg .../>', // 可选,12px 线条风
openView: { view: 'my-plugin' } // 点击跳到插件整页视图
})
// ③ 携详情(分组 = 手风琴:收起显示 标题+trailing 徽标,点击展开 rows)
api.ui.setStatusItem('tasks', {
text: '任务 2/5',
tone: 'accent',
detail: {
title: '任务概览', // 面板中省略(区域头已是插件名)
sections: [
{
title: '进行中', // 分组标题可省略
rows: [
{ label: '构建索引', detail: '已完成 60%', trailing: '2/5', tone: 'accent' },
{ label: '清理临时文件' }
]
},
{
title: '操作',
rows: [{ label: '打开任务面板', openView: { view: 'my-plugin' } }] // 行点击深链
}
]
}
})
api.ui.removeStatusItem('tasks')
```
弹层由宿主统一渲染(分组标题 / 行 / 右侧徽标 / 深链),样式与宿主一致——插件只提交
数据;点击弹层外部或 Esc 关闭;dispose 时宿主自动清理本插件全部条目。
## 2. 工具栏按钮(输入区工具行)
```js
api.ui.registerToolbarButton({
id: 'open',
title: '打开我的面板',
icon: '<svg .../>', // 16-20px 线条风,currentColor
openView: { view: 'my-plugin' }
})
```
## 3. 整页视图 + 侧栏入口
manifest 声明 `icon` / `order` 后,插件自动出现在侧栏(有 UI 能力时);任何槽位用 `openView` 深链:
```js
api.ui.openView('my-plugin', { section: 'detail' }) // params 透传给插件 UI
api.ui.openView('chat') // 回到聊天
```
## 4. 面板 tab(右侧 SidePanel)
manifest 加 `"panel": true`,插件在右侧面板获得一个 tab。
## 5. iframe 插件页(entry.page)
需要**完全自定义的 Web UI** 且不想构建 React 时用。物理隔离(sandbox iframe),经桥通信:
```
<插件目录>/index.html + 页面 JS
manifest: "entry": { "page": "index.html" }
```
页面侧用 SDK 桥(`@aso/plugin-sdk/client`):
```html
<script type="module">
import { getHarnessClient, useHarnessTheme, useDeepLinkParams } from './sdk-client.mjs'
const harness = getHarnessClient('my-plugin')
harness.openView('chat') // 深链
harness.setStatus('state', { text: '就绪' })
harness.invoke('getState') // 白名单:getState / send / getSettings
</script>
```
**样式统一(强制)**:插件页 `<head>` 里必须引宿主 token 表,并用自己的 Tailwind 按同样 token 名编译——
```html
<link rel="stylesheet" href="peer://assets/tokens.css" />
```
主题(深浅色/accent)由宿主推送(`useHarnessTheme`),禁止自行探测。详细规则见 [DESIGN.md](./DESIGN.md#iframe-插件页)。
## 6. 安全边界速记
| 形态 | 代码位置 | 能力 |
|---|---|---|
| main.mjs | 主进程 | 全权(与 pi 扩展同信任级) |
| renderer.mjs(L4) | 渲染层 | 宿主 React + window.api 受控面 |
| iframe 页 | 沙箱 iframe | 仅白名单桥(getState/send/getSettings/setStatus/openView) |
## 7. 验证
1. 状态胶囊:`setStatusItem` 后 TitleBar 立即出现,`removeStatusItem` 消失
2. 面板 tab:`panel: true` + 任一 UI 形态 → 右侧面板出现 tab
3. iframe 页:改页面文件 → 插件页开关触发重载 → iframe 换 src 整页刷新
4. 全部 UI 状态在日志可查(`plugin activated` 之后你在 main.mjs 里的 `api.log` 输出)
---
**上一篇** [L2](./03-settings.md) · **下一篇**:[L4 React 组件](./05-renderer-components.md)——宿主内真 React 组件。