鼎享会 | OpenClaw Control UI 前端架构全解析:自研 UI 对接 Server 实操指南

0 阅读17分钟

作者栏 -候晓飞网页.jpg 这份文档的目标,是把当前 OpenClaw Control UI 的前端结构讲清楚,并结合鼎道智联 DingVerse 服务的实践场景,回答核心问题:

DingOS 部分产品页面已尝试基于该架构思路开发,并应用于 DingVerse 服务中。在此背景下,若为 DingVerse 服务开发自研 UI 页面、后端仍对接 OpenClaw Server,前端应从哪里接入、哪些层可复用、哪些层需适配改造。

这份文档重点解决以下问题:

  • 当前 OpenClaw 前端主架构是什么?
  • 用户操作如何从页面流转至后端?
  • WebSocket 客户端如何与页面状态联动?
  • 鼎道智联 DingVerse 服务自研 UI 对接 OpenClaw Server,需完成哪些适配与开发工作?

架构图

当前前端可以先粗略理解成下面这张图:

浏览器入口
  -> ui/src/main.ts
  -> ui/src/ui/app.ts (根组件 / 全局状态容器)
  -> ui/src/ui/app-render.ts (把状态装配成各个 view 的 props)
  -> ui/src/ui/views/*.ts (页面/视图)
       -> 调用 props 回调
  -> ui/src/ui/app-*.ts (功能逻辑层)
       -> 编排状态、队列、连接、生命周期行为
  -> ui/src/ui/controllers/*.ts (请求/响应动作层)
       -> 调用 client.request(...)
  -> ui/src/ui/gateway.ts (GatewayBrowserClient)
       -> WebSocket
       -> OpenClaw Server

如果想把它看成更清晰的分层关系,可以用下面这张图理解:

flowchart TD
    A["ui/src/main.ts\n前端入口"] --> B["ui/src/ui/app.ts\n根组件 / 全局状态容器"]
    B --> C["ui/src/ui/app-render.ts\n渲染装配层"]
    C --> D["ui/src/ui/views/*.ts\n页面 / 视图层"]
    C --> E["ui/src/ui/components/*.ts\n可复用组件层"]
    B --> F["ui/src/ui/app-*.ts\n功能逻辑层"]
    F --> G["ui/src/ui/controllers/*.ts\n请求 / 数据动作层"]
    G --> H["ui/src/ui/gateway.ts\nGatewayBrowserClient"]
    H --> I["OpenClaw Server"]

再换一种方式,按“谁负责什么”来看:

view
  负责:展示 UI、绑定点击事件、触发 props 回调

app-render
  负责:把根状态装配成视图 props

app-*
  负责:功能逻辑、队列、连接、状态编排

controllers
  负责:调用后端方法、组织请求参数

gateway client
  负责:WebSocket、握手、req/res/event 协议

架构分析

1. 入口层

  • ui/src/main.ts

这个文件非常薄,只做两件事:

  • 引入全局样式
  • 引入根组件 openclaw-app

这说明真正的应用不是从 main.ts 开始写逻辑的,而是从根组件开始。 在 DingVerse 服务中的部分页面和交互已沿用该轻量入口设计,降低初始包体积,适配产品迭代期的快速调整需求。

2. 根组件层

  • ui/src/ui/app.ts

这是整个前端最核心的对象。

它的职责是:

  • 持有大量 @state() 状态
  • 暴露应用级方法,比如 handleSendChat()connect()handleAbortChat()
  • 在生命周期里做初始化、连接、同步
  • 把自身状态交给渲染层

重要特点:

  • 它不是只负责“渲染”
  • 它更像“全局应用实例 + 状态容器 + 方法门面”

在这个项目里,很多地方的:

  • this
  • host
  • state

本质上都经常指向这个根组件实例,只是不同文件里的命名不同。

3. 渲染装配层

  • ui/src/ui/app-render.ts

这个文件的职责是把根状态翻译成 UI。

它主要做:

  • 判断当前 tab 渲染什么页面
  • 给各个页面组装 props
  • 把 app.ts 暴露的方法作为回调传给 view
  • 做页面级懒加载,减少初始包体积

它不是某一个页面,而是整个页面结构的装配中心。

比如聊天页的 onSendonAbortdraftqueuemessages 等,都是在这里传给 chat view 的。

4. 视图层

  • ui/src/ui/views/chat.ts
  • ui/src/ui/views/overview.ts
  • ui/src/ui/views/config.ts
  • ui/src/ui/views/agents.ts
  • 以及 ui/src/ui/views/ 下的其他文件

这些文件主要负责:

  • 视图结构
  • 页面布局
  • 按钮、输入框、列表等 UI 细节
  • 调用 props 回调

它们通常不直接承载复杂业务逻辑。

例如聊天发送按钮只会调用 props.onSend(),它不会自己判断:

  • 当前是否 busy
  • 该直接发送还是入队
  • 是否是 slash command
  • 发送失败后是否恢复草稿

这些都不是 view 层该做的。

5. 功能逻辑层

  • ui/src/ui/app-chat.ts
  • ui/src/ui/app-gateway.ts
  • ui/src/ui/app-settings.ts
  • ui/src/ui/app-scroll.ts
  • ui/src/ui/app-tool-stream.ts

这层是当前 UI 很关键的一层。

可以把它理解成:

  • 某个功能域的应用逻辑
  • 对根状态对象的读写编排
  • 视图层和 controller 层之间的“中间大脑”

举例:

  • app-chat.ts 负责聊天发送、排队、出队、abort、slash command
  • app-gateway.ts 负责连接 Gateway、处理 hello / event / close 回调,并把事件同步回 app 状态
  • app-settings.ts 负责 settings、tab 切换和相关加载

所以 app-* 不是“页面本身”,而是“页面的功能逻辑”。

6. Controller 层

  • ui/src/ui/controllers/chat.ts
  • ui/src/ui/controllers/config.ts
  • ui/src/ui/controllers/agents.ts
  • ui/src/ui/controllers/channels.ts
  • 等等

这层主要负责:

  • 和后端通信
  • 组织请求参数
  • 调用 client.request(...)
  • 将返回结果映射到前端状态变化

举例:

  • 聊天真正发给后端是在 controllers/chat.ts
  • 配置的读取和保存是在 controllers/config.ts

所以它更接近“数据动作层”。

7. WS 封装层

  • ui/src/ui/gateway.ts

这个文件里有当前前端最重要的 WebSocket 客户端封装:

  • GatewayBrowserClient

它主要负责:

  • 建立 WebSocket 连接
  • 处理 open/message/close/error
  • connect 握手
  • req/res/event 帧收发
  • pending 请求 promise 管理
  • 重连和回退

这是当前前端和 Gateway 协议绑定最深的地方之一,同时 WS Client 可参考该封装逻辑,预留协议扩展空间,适配产品后续优化需求。

用 Chat 发送流程举例

聊天发送是理解整个架构最好的例子。

先看一张完整时序图:

sequenceDiagram
    participant U as 用户
    participant V as views/chat.ts
    participant R as app-render.ts
    participant A as app.ts
    participant AC as app-chat.ts
    participant C as controllers/chat.ts
    participant G as GatewayBrowserClient
    participant S as OpenClaw Server

    U->>V: 点击发送按钮
    V->>R: 调用 props.onSend()
    R->>A: state.handleSendChat()
    A->>AC: handleSendChatInternal(...)
    AC->>AC: 判断 busy / slash / stop / queue
    alt 当前 busy
        AC->>AC: enqueueChatMessage()
    else 直接发送
        AC->>C: sendChatMessage()
        C->>G: client.request("chat.send", ...)
        G->>S: 发送 WS req 帧
        S-->>G: 返回 res / event
        G-->>AC: 终态事件最终触发 flushChatQueue
    end

第一步:根组件开始渲染

ui/src/ui/app.ts 里:

  • render() 调用 renderApp(this as AppViewState)

这里把根组件当前状态交给渲染层。

第二步:渲染层把回调传给聊天视图

ui/src/ui/app-render.ts 里,渲染 chat view 时会传:

  • onSend: () => state.handleSendChat()

这里的 state 实际就是 OpenClawApp 实例。

所以聊天页拿到的 props.onSend,本质上就是根组件方法。

第三步:聊天视图点击按钮

ui/src/ui/views/chat.ts 里,发送按钮点击时会调用:

  • props.onSend()

这一步只是“触发动作”,不是“处理发送逻辑”。

也就是说:

  • 点击按钮时,view 调的是 prop
  • 但这个 prop 是上层传下来的

第四步:进入根组件方法

ui/src/ui/app.ts 里:

  • handleSendChat(...)

它内部再调用:

  • handleSendChatInternal(...)

这个 handleSendChatInternal 实际来自 ui/src/ui/app-chat.ts

所以 app.ts 在这里扮演的是方法门面角色。

第五步:进入 app-chat.ts

ui/src/ui/app-chat.ts 是聊天功能真正的应用逻辑层。

这里会决定:

  • 是否已连接
  • 是否为空消息
  • 是否是 stop
  • 是否是 slash command
  • 当前是否 busy
  • 是直接发送还是排队
  • 发送完成后是否继续 flush 队列

因此更准确地说:

  • view 负责“点发送”
  • app-chat.ts 负责“怎么发送”

第六步:如果 busy,则先入队

app-chat.ts 里有:

  • isChatBusy(host)
  • enqueueChatMessage(...)

如果当前有正在执行的 run,发送逻辑不会直接继续发,而是把消息写进:

  • host.chatQueue

这个队列是前端内存状态,不是本地持久化存储。

第七步:如果不 busy,则立即发送

app-chat.ts 会走:

  • sendChatMessageNow(...)

这个函数会先处理一些发送前动作,比如:

  • reset tool stream
  • reset scroll
  • 清理/恢复草稿与附件的状态逻辑

然后调用:

  • sendChatMessage(...)

第八步:controller 真正发请求

ui/src/ui/controllers/chat.ts 里,真正会调用:

  • state.client.request("chat.send", ...)

这说明:

  • app-chat.ts 负责发送决策和流程编排
  • controllers/chat.ts 负责真正发给后端

同时 controller 会更新前端状态,例如:

  • 立即插入一条 user message
  • 设定 chatSending = true
  • 生成并写入 chatRunId
  • 初始化 chatStream

所以用户在 UI 上会立刻看到“我发出去了”,即使后端结果还没回来。

第九步:后端事件通过 WS 回流

后端返回的运行中事件、完成事件、错误事件,不是通过按钮链路回来的,而是通过 WebSocket 回流到前端。

这些事件会先经过:

  • GatewayBrowserClient.handleMessage(...)

再通过回调交给:

  • app-gateway.ts

第十步:终态事件触发队列继续发送

当一个 run 到达终态后,app-gateway.ts 会:

  • 清理该 run 的 pending queue items
  • 调用 flushChatQueue(...)

这个函数在 app-chat.ts 中,会把队列里的下一条拿出来继续发送。

这样队列机制才能闭环。

这一段也可以单独看成“回流图”:

flowchart LR
    A["OpenClaw Server\nevent / res"] --> B["gateway.ts\nhandleMessage()"]
    B --> C["this.opts.onEvent?.(...)"]
    C --> D["app-gateway.ts\n事件协调层"]
    D --> E["更新根状态\nchatRunId / messages / queue / errors"]
    E --> F["app-render.ts"]
    F --> G["views/chat.ts\n界面重新渲染"]

hoststatethis 到底是什么

这是这个前端特别容易绕的一个点。

在很多地方:

  • this 指向 OpenClawApp 根组件实例
  • host 是把这个根组件实例作为参数传给 app-* 模块时的命名
  • state 是渲染层或 controller 层里对这个状态对象的命名

所以从工程上讲,它们常常是“同一个根对象”的不同叫法。

这意味着当前前端没有单独再做一个 Redux 风格的 store。

它的“共享状态容器”就是根组件实例本身。

WebSocket 客户端在哪里封装

WebSocket 客户端封装在:

  • ui/src/ui/gateway.ts

核心类是:

  • GatewayBrowserClient

它内部维护:

  • ws
  • pending 请求映射
  • connect 握手状态
  • seq 状态
  • 重连状态

它对外最关键的方法是:

  • start()
  • stop()
  • request<T>(method, params)

其中:

  • request(...) 会发送 type: "req"
  • handleMessage(...) 会解析 eventres

handleMessage() 怎么和 app-gateway.ts 关联

这不是直接 import 关联,而是通过“构造时注入回调”关联的。

流程是:

  1. app-gateway.ts 创建客户端:
    • new GatewayBrowserClient({ onHello, onEvent, onClose, onGap, ... })
  2. gateway.ts 构造函数把这些回调保存到 this.opts
  3. handleMessage() 收到消息后,根据帧类型执行:
    • this.opts.onEvent?.(evt)
    • this.opts.onHello?.(hello)
    • this.opts.onClose?.(...)
    • this.opts.onGap?.(...)

所以关系不是:

  • gateway.ts 知道 app-gateway.ts

而是:

  • app-gateway.ts 在创建 client 时把回调注入进去
  • gateway.ts 在合适的时机调用这些回调

这是一种典型的“底层传输层回调上浮到应用层”的设计。

如果只看这部分关系,可以把它简化成:

flowchart TD
    A["app-gateway.ts\nnew GatewayBrowserClient({ onEvent, onHello, onClose, onGap })"]
    A --> B["gateway.ts\nconstructor(private opts)"]
    B --> C["handleMessage()"]
    C --> D["this.opts.onEvent?.(evt)"]
    D --> E["app-gateway.ts\n处理 event -> 改状态"]

client 是怎么注入到全局状态里的

这里的“全局状态”并不是单独的 store,而是根组件实例。

app.ts 里,this.connect() 会调用:

  • connectGatewayInternal(this)

也就是说,整个 OpenClawApp 实例被作为 host 传给了 app-gateway.ts

然后 app-gateway.ts 里会:

  1. new GatewayBrowserClient(...)
  2. 把它挂回 host.client
  3. 用这个实例继续维护连接和事件

所以 controller 后面才能直接使用:

  • state.client.request(...)

因为这个 client 已经被绑定到根 app 状态对象上了。

为什么要这样分层

从工程角度,这样拆有几个好处。

1. view 层变轻

如果 view 直接做聊天发送逻辑,那 views/chat.ts 就必须自己处理:

  • busy 判断
  • stop 逻辑
  • slash command
  • queue
  • 草稿恢复
  • 网络失败恢复

这样页面会非常重,也很难维护。

2. 功能逻辑集中

把聊天行为放在 app-chat.ts,就能让“聊天这个功能”的逻辑集中在一个地方。

这比散落在:

  • view
  • app.ts
  • controller

里更容易维护。

3. 传输层可替换

把 WS 封装在 gateway.ts,可以让应用层不直接依赖原生 WebSocket

虽然当前实现仍然与 OpenClaw Gateway 协议强耦合,但至少耦合点集中在少数文件中。

4. 根对象作为共享状态容器,开发速度快

虽然这种方式没有独立 store 那么严格,但对当前这类工具型控制台前端来说,开发和改动会更直接。

如果我们做自己的 UI,但仍然接 OpenClaw Server,需要做什么

这里的前提要说清楚:

  • 页面是我们自己的
  • 后端仍然是 OpenClaw Server
  • 前端不复用 OpenClaw 现有 UI 代码
  • 我们会自己写 client

在这个前提下,这一节的重点就不是“复用哪些前端文件”,而是“我们自己需要补齐哪些能力,才能和 OpenClaw Server 正常通信”。

换句话说,现有前端代码在这里更多是参考实现,而不是直接复用对象。

如果把“我们自己的实现”画成结构图,大概应该长这样:

flowchart TD
    A["我们自己的页面 / 组件"] --> B["我们自己的状态层"]
    B --> C["我们自己的事件协调层"]
    C --> D["我们自己的 OpenClaw WS Client"]
    D --> E["OpenClaw Server"]

    B --> F["我们自己的动作封装层\nsendMessage / abort / loadSessions / loadConfig"]
    F --> D

1. 首先要明确:我们要对接的是 OpenClaw Gateway 协议

既然后端还是 OpenClaw Server,那么我们自己的前端无论页面长什么样,本质上还是要接它当前暴露出来的通信协议。

这意味着我们自己必须实现或理解这些能力:

  • WebSocket 建连
  • connect 握手
  • req / res / event 帧模型
  • 认证信息的组织方式
  • 请求与响应的对应关系
  • 服务端事件的分发
  • 断线重连和异常处理

也就是说,虽然我们“不复用代码”,但不能“不理解协议”。

2. 我们自己的前端,至少要自己实现哪些模块

如果不复用 OpenClaw 现有前端代码,建议把自己的前端拆成下面几块。

2.1 自己的 WS client

这是最核心的一层。

当前 OpenClaw 前端的 GatewayBrowserClient 给了一个很清楚的参考:一个浏览器端的 WS client 至少要支持:

  • start()
  • stop()
  • request(method, params)
  • 收到服务端 event 后触发回调
  • 收到 response 后 resolve/reject 对应 promise
  • close 后做错误处理和重连

即使我们完全自己写,也建议保留类似抽象。

自己写这个 client 时,至少要实现:

  1. 连接状态管理
  2. 请求 ID 生成
  3. pending request 映射表
  4. req 帧发送
  5. res 帧解析
  6. event 帧解析
  7. connect 握手
  8. close / error / reconnect 策略

2.2 自己的应用状态层

即使页面是我们自己写,也仍然需要一个“状态中枢”。

不一定要像 OpenClaw 这样用一个根组件实例承载所有状态,但你至少要有一层统一状态来管理:

  • 当前是否已连接
  • 当前 sessionKey
  • 当前消息列表
  • 当前 stream 内容
  • 当前 runId
  • 当前错误信息
  • 当前等待中的请求状态

如果没有这一层,view 很快就会和 WS 处理逻辑缠在一起。

2.3 自己的事件协调层

当前 OpenClaw 的 app-gateway.ts 本质上是一个“连接和事件协调层”。

我们自己写 UI 时,也最好单独做这一层,而不要让 view 直接吃 WS event。

这一层建议负责:

  • 初始化 client
  • 注册 onHello / onEvent / onClose / onGap 之类的回调
  • 把 event 转换成页面状态变化
  • 做 run 生命周期处理
  • 做重连恢复

简单说:

  • client 负责“收到消息”
  • 事件协调层负责“这条消息对页面意味着什么”

2.4 自己的 action / controller 层

如果你的页面上有“发送消息”“停止生成”“加载配置”“加载 agent 列表”这些动作,建议仍然保留一个类似 controller 的层。

原因是:

  • view 不应该知道具体方法名
  • view 不应该自己拼请求参数
  • view 不应该直接知道 WS 帧结构

这一层建议做的事:

  • 对 OpenClaw Server 方法做封装
  • 例如封装 chat.sendchat.abortsessions.reset
  • 把业务参数转成 OpenClaw 要的协议参数
  • 处理错误映射

3. 如果只是做一个最小可用页面,需要先打通哪些能力

如果目标是“先做一个最小可用 UI”,建议不要一开始就做完整控制台,而是先打通一个最小闭环。

这个最小闭环通常是聊天链路:

  1. WS 建连成功
  2. 完成 OpenClaw 的 connect 握手
  3. 可以发 chat.send
  4. 可以接收聊天事件
  5. 可以在终态时收尾
  6. 可以发 chat.abort

因为一旦这条链路通了,说明下面几层都基本通了:

  • 连接
  • 协议
  • 请求
  • 响应
  • 事件
  • 页面状态回流

4. 我们自己写 client 时,需要重点参考现有前端的哪些设计

虽然不复用代码,但下面这些设计思想非常值得保留。

4.1 请求-响应映射

当前前端通过请求 ID 和 pending map 来把 res 帧回填到对应 promise。

自己写 client 时,这个机制几乎一定要有。

否则你无法优雅地写:

  • await request("chat.send", ...)

4.2 event 和 response 分流

当前协议是明显双轨的:

  • res 负责某个 request 的结果
  • event 负责服务端主动推送

自己写的时候,不要把这两类消息混在一起处理。

4.3 运行状态与页面状态解耦

当前实现里,真正决定“队列是否继续”“run 是否结束”的,不是 view,而是 app 层和事件协调层。

这个思路建议保留。

否则页面会很快出现:

  • 一个按钮管太多事情
  • 一个组件维护太多隐式状态

4.4 transport 和 UI 分离

即使你是自己写页面,也不要让 UI 组件直接 new WebSocket

更好的做法仍然是:

  • transport 层单独封装
  • 状态层单独封装
  • 页面只通过回调和状态工作

5. 针对 OpenClaw Server,自研前端需要完成的实际工作清单

下面这份清单更接近真正要做的事。

5.1 协议理解

需要先确认并验证:

  • 建连 URL
  • 握手流程
  • connect challenge 是否存在
  • 请求帧格式
  • 响应帧格式
  • 事件帧格式
  • 认证参数需要哪些字段

5.2 client 实现

需要自己完成:

  • WS 封装
  • 请求 ID 管理
  • pending promise 管理
  • 消息解析
  • 重连策略
  • 错误处理策略

5.3 页面状态模型设计

需要定义自己的状态结构,例如:

  • connectionState
  • currentSessionKey
  • messages
  • streamText
  • activeRunId
  • lastError
  • queuedMessages

5.4 事件到状态的映射

需要自己规定:

  • 收到 hello 后怎么更新状态
  • 收到 chat 相关 event 后怎么更新消息列表
  • 收到 final/error/aborted 后怎么清理 run 状态
  • 断线时页面显示什么
  • 重连后是否恢复某些状态

5.5 动作封装

至少要实现你页面需要的动作封装,例如:

  • sendMessage()
  • abortMessage()
  • loadSessions()
  • resetSession()

这些方法内部再去调 OpenClaw Server 对应的方法。

5.6 页面实现

最后才是页面本身:

  • 聊天输入框
  • 消息列表
  • 发送按钮
  • 停止按钮
  • 连接状态 UI
  • 错误提示 UI

也就是说,UI 最后做,但不是最先做。

6. 推荐实施顺序

在“自己写 client、自己写 UI、后端仍然是 OpenClaw Server”的前提下,建议按这个顺序推进:

  1. 先读懂并验证 OpenClaw Server 的 WS 协议
  2. 先写最小 client,能连、能发 request、能收 response/event
  3. 再写最小状态层
  4. 再打通 chat.send / chat.abort / chat event 的闭环
  5. 再补会话、配置、agents 等功能
  6. 最后再做完整页面整理和 UI 体验优化

原因很简单:

  • 协议没打通,页面做得再好也只是壳
  • 状态没设计好,事件一多 UI 就会乱
  • chat 链路打通后,再扩展别的功能会稳很多

用路线图表示会更直观:

flowchart LR
    A["1. 读懂协议"] --> B["2. 写最小 WS client"]
    B --> C["3. 写最小状态层"]
    C --> D["4. 打通 chat.send / chat.abort / chat event"]
    D --> E["5. 扩展 sessions / config / agents"]
    E --> F["6. 完整 UI 和交互优化"]

当前架构的强耦合点

如果要接自己的后端,这几个地方耦合最深,要重点注意。

1. Gateway 协议帧格式

GatewayBrowserClient 假定了固定的协议结构:

  • type: "req"
  • type: "res"
  • type: "event"

如果你的服务不是这个格式,gateway.ts 一定要改。

2. connect 握手逻辑

当前客户端不是普通一连就发消息,它有 challenge、device identity、token、role、scopes 等流程。

这部分如果你的后端没有,就需要简化。

3. 聊天 run 生命周期

聊天队列依赖:

  • chatRunId
  • run 终态事件
  • 最终事件后 flush 队列

如果你的后端没有这一套,不能直接照搬当前 chat queue 逻辑。

4. controller 的方法名约定

目前 controller 强依赖方法名,例如:

  • chat.send
  • chat.abort
  • sessions.reset

如果你的后端方法名和参数结构不同,controller 必须改。

总结

当前 OpenClaw 前端的核心流转逻辑为:

  • view 负责交互触发
  • app.ts 暴露方法
  • app-* 负责功能逻辑
  • controllers/* 负责后端调用
  • gateway.ts 负责 WS 封装
  • app-gateway.ts 负责把 WS 事件回流到 UI 状态

在DingOS的自研产品体系中,如果我们的目标是“自己做 UI,但接 OpenClaw Server,而且前端代码自己写”,最重要的结论是:

  • 现有前端代码最重要的价值是参考架构和协议实现思路
  • 我们真正要自己补的是 client、状态层、事件协调层、动作封装层
  • 不应该从 view 开始做,而应该先从协议和 client 开始打通
  • chat 是最适合做第一阶段验证的最小闭环

从实施角度看,最小可行路径通常是:

  1. 先写自己的 WS client
  2. 先接通 OpenClaw Server 协议
  3. 再写自己的状态层和事件协调层
  4. 再做聊天最小页面
  5. 最后再扩展更多管理能力

如果你已经理解下面这些文件,基本就理解了这个前端的主干:

  • ui/src/ui/app.ts
  • ui/src/ui/app-render.ts
  • ui/src/ui/app-chat.ts
  • ui/src/ui/app-gateway.ts
  • ui/src/ui/controllers/chat.ts
  • ui/src/ui/gateway.ts

后面无论是:

  • 复用当前 UI 改成自己的 WS
  • 还是自己重做一个页面参考这套结构

这几个文件都是最值得先读懂的部分。

综上,OpenClaw 前端架构的核心价值在于分层解耦的设计思路,这也是鼎道智联 DingVerse 服务自研 UI 对接 OpenClaw Server 的关键参考。针对仍在优化、暂未上线的 DingVerse 产品,自研 UI 无需拘泥于复用现有代码,优先打通 WS 协议与 chat 链路的最小闭环,再逐步扩展功能更高效。

若你在对接过程中遇到协议理解、client 开发、状态层设计等问题,或是有适配 DingVerse 场景的优化思路想要交流,欢迎在评论区留言,我们一起探讨解决方案,助力产品迭代优化与上线。