前端:单一 React UI 与实时会话渲染
30 秒导读: OpenWork 只有一个 React 19 + Vite 前端。桌面 Electron 外壳加载它、 普通浏览器直接跑它、云端也是同一份。它的核心工作是:订阅后端一条 SSE 事件流,把一个 编码 agent(OpenCode)边想边做的过程——文字、工具调用、待办计划、要不要放行某个危险 操作——增量地、一帧一帧地渲染成一个连不懂代码的人也能看懂并能审批的控制面。
本章是 OpenWork 系列的第五章。它只讲前端这一层。相邻章节请看: 桌面外壳与启动流程、主机运行时 orchestrator、 openwork-server 鉴权代理、可扩展性、 远程与云。本章会引用它们但不重复其内容。
1. 这是什么(零基础也能懂)
一句话定义: 这是 OpenWork 唯一的用户界面——一个用 React 19 写的单页应用,负责把后台 那个「会自己读文件、跑命令、改代码」的 AI agent 的一举一动,实时画到屏幕上。
它要解决的问题: 一个自主 agent 在后台干活,过程是一串机器事件(「我开始生成第 3 个字」
「我要执行 rm -rf」「我更新了待办清单」)。非技术用户看不懂这些事件。前端的职责就是当
一个「翻译 + 值班台」:把事件翻译成人话(「正在读 config.ts」),把危险动作拦下来问一句
「要放行吗?」,并且这一切要边发生边显示,不能等 agent 干完再刷新。
同一份代码,三种部署。 这是 OpenWork 前端最关键的一个约定——桌面、web、云跑的是同一个 React 应用,区别只在它「连到哪个后端」:
| 部署形态 | 谁加载这份 UI | UI 连的后端 |
|---|---|---|
| 桌面 | Electron 外壳(见第 1 章) | 本机 openwork-server 挂载的 /opencode |
| web | 普通浏览器 | 同源的 OpenCode 反向代理 |
| 云 | 浏览器 | 托管 worker(见第 6 章) |
它靠一个「运行时探测叶子模块」判断自己身在何处:
// app/lib/runtime-env.ts:4 —— 真实源码(叶子模块,故意不 import 任何东西)
export function isElectronRuntime() {
return typeof window !== "undefined" && (window as Window).__OPENWORK_ELECTRON__ != null;
}
桌面外壳启动时往 window 上挂一个 __OPENWORK_ELECTRON__ 标记,前端据此切换「用系统级
desktopFetch 还是普通 fetch」等行为。一份 UI 之所以能三处通吃,靠的就是把「我在哪」
收敛成这一个探测点,而不是编译三份。
一句话直觉: 把前端想成体育比赛的实时字幕组。后台是赛场(agent 在打),后端发来的 是一条源源不断的赛况电报(SSE 流),前端要在观众(用户)面前把电报逐字变成看得懂的 解说、比分板(待办进度)和「是否允许换人」的裁判提示框。