Skip to content

CH 22 · UI 插件:给 dsh 换皮、加面板、塞内容

全文字数2420 字预估耗时约 20 分钟前置CH 17(插件安装)难度可照做

本章目标

前面几章的插件都在"看不见"的地方干活:注册工具、拦截调用、提供依赖。这一章换到看得见的层面——插件能直接改变 Web UI 的样子和能力。这一章讲清楚 UI 插件能干什么、它是怎么运作的,并动手看一次真实的事件流。

你其实已经用上 UI 插件了

回顾 CH 17,我们装过三个插件,它们全是 UI 插件:

插件干了什么
dsh-theme给 Web UI 换肤(设置 → 外观里多出主题卡片)
dsh-oil-sticky-prompt滚动时把最近的用户消息钉在顶部
dsh-better-sidebar侧边栏工作台,文件面板直接看目录

你当时可能只是"装了个插件",现在明白了:它们都在改 UI。这就是 UI 插件的第一层认知——它负责所有你"看得到"的定制。

UI 插件能干什么

按能力大致分三类:

  • 换肤:改颜色、字体、布局风格。最轻的一类,dsh-theme 就是。
  • 改布局 / 加面板 / 加功能:动侧边栏、顶栏、输入区,也能直接往界面里加一个完整的功能模块或可视化面板。dsh-better-sidebar、dsh-oil-sticky-prompt 属于这类;你 CH 12 装过的 DSH Skill & MCP Panel 也是——它直接在设置里多出一个"MCP 管理"入口,每台服务器的状态、工具数一眼可见,相当于给 Web UI 加了一个可视化运维面板。
  • 给对话贡献内容:在会话里渲染自定义的行(比如把某类工具结果画成卡片、插入特殊节点)。这是最深的一类,官方叫"给 Web Client 贡献业务行"。

前两类很多是纯前端工作;第三类要真正"接进"对话渲染管线。

UI 插件怎么运作:一切从事件流开始

UI 插件不直接操作页面上的 DOM,而是订阅事件流。这是理解 UI 插件最关键的一点。

一条会话从头到尾,每一件事都会变成一条标准事件:用户发了消息、模型开始流式输出、某个 step 开始了、某次工具调用发生了……这些事件像流水一样流过 session/event 这条通道。内置界面靠它们渲染出对话气泡、轨迹、状态;UI 插件也一样,通过 ctx.on('session/event', ...) 订阅同一条流。

官方给的 UI 插件最小示例就是干这个的:

js
export const name = 'my-ui'
export const inject = ['agents']

export function apply(ctx) {
  ctx.on('session/event', (_session, event) => {
    if (event.type === 'assistant/chunk' && event.data.chunk.type === 'text-delta') {
      console.log(event.data.chunk.text)   // 模型流式输出的每一段
    }
  })
}

整个机制的骨架:

你输入、模型回复,会话控制器把它们统一翻译成事件流;UI 插件和内置界面订阅同一条流,各取所需。界面是"事件流"的消费方,不是 DOM 的直接操纵者——这也解释了为什么"一切皆插件"能覆盖 UI:连界面都是插件从事件流里"听"出来的。

动手:装个"事件观察器",亲眼看事件流

光看机制不过瘾,我们写一个极简插件,把每一条会话事件打印出来——看看一次对话背后到底发生了多少事。

第 1 步:建目录、写插件

在一个你想放插件的工作区里:

powershell
New-Item -ItemType Directory -Path "ui-demo\src" -Force

新建 ui-demo\src\event-watch.js

js
export const name = 'event-watch'

export function apply(ctx) {
  ctx.on('session/event', (_session, event) => {
    console.log(`[event] ${event.type}${event.data?.type ? ' / ' + event.data.type : ''}`)
  })
}

就是订阅 session/event,把每条事件的类型打出来。

第 2 步:声明插件

新建 ui-demo\cordis.yml(路径换成你自己的,空格记得 %20):

yaml
- insert:
    - id: event-watch
      name: 'file:///E:/你的工作区/ui-demo/src/event-watch.js'

第 3 步:headless 跑一次

powershell
cd 你的工作区目录
dsh --profile headless --patch "./ui-demo/cordis.yml" "只回答:hi"

我实际跑出来的终端输出:

就回答一个"hi",背后走了 22 条事件:会话初始化(permission、sandbox、approval)、turn/startstep/start、三次 user/message、请求头、assistant/chunk 一条条流式吐字、assistant/message 落定、step/endturn/end……

你在 Web UI 里看到的那个对话,就是这一串事件被消费后渲染出来的。事件观察器让我们第一次"看见"了 UI 背后的原料。

让 dsh 自己搭:开发一个看得见的 UI 插件

前面的事件观察器只是"看事件",还没真正改变界面。让 dsh 直接开发一个"能在设置页看到效果"的 UI 插件,它也能干。

直接在 Web UI 的输入框发这条提示词:

text
在我当前的工作区帮我开发一个能在 Web UI 里看到的简单 UI 插件:往设置页加一个自定义 tab(比如叫"我的插件",页面里放点占位内容就行,不需要复杂功能)。你先去 dsh 官方文档读清楚 UI 插件的机制和设置页怎么加 tab,按官方规范实现,告诉我装到哪个 profile、怎么在界面里看到它(需要重启 dsh 或改配置也一并告诉我)。

它会自己去读文档、自己判断技术栈(这类界面插件通常要 TypeScript 和前端构建,它自己会处理)、自己实现、告诉你验收方式。你照着它说的重启 dsh,就能在设置页看到新加的 tab。

这是我实际跑的一次——它从读文档、写插件、装进 profile,到告诉我怎么在界面里看到、怎么退出,一路自己做完,还标出了关键路径:

重启后进设置 → 插件,顶部就多了"我的插件"这个 tab:

一个细节点:这个插件纯 UI,没有 Host 端行为,所以不用改任何 settings.yaml / cordis.yml——一切通过 profile 的 bundles 装载。想回滚也很简单:把它从 bundles 数组里删掉即可。这正是"一切皆插件"的又一次体现:连界面上加一个 tab,都是"装一个插件"的事。

常见坑

问题怎么回事怎么处理
事件观察器不打印session/event 没订阅对,或插件没加载确认 cordis.yml 路径、ctx.on 写法
事件很多看不懂一次对话本来就有几十条事件先看 turn/startturn/end 之间,那是主干
想在对话里直接画内容那要 React renderer,属于进阶先用好事件流和设置项,进阶再看
Cannot find package插件用了外部依赖没装在插件目录 npm install 对应包

这一章你学到了什么

  • [ ] 能说出 UI 插件的三类能力:换肤 / 布局加面板 / 会话内容贡献
  • [ ] 理解"界面是事件流的消费方"这个核心机制
  • [ ] 会用 ctx.on('session/event', ...) 订阅会话事件
  • [ ] 能用事件观察器看到一次对话背后的事件流
  • [ ] 知道 UI 插件可以往设置页加自定义 tab

Open Source · MIT · Community Driven