前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
原理篇
面经篇
AI 面试
自检篇
每日一题
  • 综合
    • 综合题型
    • 其他问题
    • 设计模式
    • 思维导图
    • 学习路线
  • 前端基础
    • HTTP
    • 浏览器
    • 计算机基础
  • 进阶学习
    • NPM工作流
    • Docker
    • Canvas
    • Node学习指南
    • 前端综合文章
  • 其他
    • Handbook
    • 职场话题
    • CSS可视化
小程序题库
公众号动态
博客动态
AI 热点
开发者导航
基础篇
进阶篇
高频篇
精选篇
手写篇
原理篇
面经篇
AI 面试
自检篇
每日一题
  • 综合
    • 综合题型
    • 其他问题
    • 设计模式
    • 思维导图
    • 学习路线
  • 前端基础
    • HTTP
    • 浏览器
    • 计算机基础
  • 进阶学习
    • NPM工作流
    • Docker
    • Canvas
    • Node学习指南
    • 前端综合文章
  • 其他
    • Handbook
    • 职场话题
    • CSS可视化
小程序题库
公众号动态
博客动态
AI 热点
开发者导航
返回 AI 情报前线
All News · 全部资讯3545
  • 语音 Agent 评估框架 EVA 发布
  • Claude Code 速查表与功能指南
  • Claude Code 实战提效工作流
  • Prompt 工程入门:问法决定质量
  • Cq:AI 编程 Agent 的专属问答社区
  • iPhone 17 成功运行 400B 大模型演示
  • 用 Claude 自动化移动应用 QA 测试
  • 现代 LLM 注意力机制可视化教程
  • OpenTelemetry 统一 LLM 追踪规范
  • OpenCode:开源 AI 编程 Agent 项目
  • 快速构建领域特定嵌入模型
  • 初次体验 Agent 技能开发
  • 一小时用 AI 快速原型应用
  • Gemini CLI 高级功能全解
  • 用四象限图判断 AI 技术价值
  • 企业级 MCP 应用案例分析
  • AI 术语速成手册
  • Meta AI 失控事件警示录
  • Google AI Studio 快速编程秘诀
  • 轻量级TTS模型库:25MB以内可直接嵌入
  • 用Markdown声明式构建生成式UI交互
  • OpenAI收购Astral:Python工具栈并购
  • OpenAI官宣收购Astral团队
  • Cook:编排Claude Code的轻量级CLI工具
  • Cursor Composer 2:前沿级AI编码能力升级
  • 无需训练!复制LLM参数层突破推理瓶颈
  • Claude Code 在工程领域的实战应用
  • AI 生成代码的保修和法律困境
  • Google Sashiko:Agent 式 AI 审查 Linux 内核
  • AI 生成代码引入的新型技术债
  • Colab MCP Server:AI Agent 的新连接方式
  • 自主运行的 Claude Code Agent:从工具到自主系统
  • Hugging Face 2026 春季开源生态报告
  • Antfly:用 Go 构建分布式多模态搜索系统
  • Holotron-12B:高吞吐量计算机自动化 Agent
  • 用 AI 自动生成技术规范文档
  • 用 MCP 为 AI 知识库接入 Notion
  • Claude Code Skills 官方实战指南:9 大类型与分发策略
  • Mistral 开源形式验证 Agent:提升代码可信性
  • 用 Claude Code Skills 完整开发 Godot 游戏
  • Apideck CLI:低消耗的 AI Agent 接口方案
  • 智能体工程进阶:从 Tab 补全到 Agent 团队的 8 个等级
  • LLM 架构可视化库:全景理解模型设计
  • Claude合作伙伴网络启动
  • Claude使用优惠活动
  • GitAgent:仓库变身AI代理的开放标准
  • Claude在3D创意工作中的实战技巧
  • Spine Swarm:可视化画布上的AI Agent编排
  • 大规模识别LLM的神经元交互机制
  • Claude Code与其他LLM的网关集成指南
  • VS Code官方:AI在自身开发流程中的应用
  • 已加载 51 / 3545
7.0
热点
AI SCORE
编程提效2026-03-19 21:42

用Markdown声明式构建生成式UI交互

Hacker News · fabian-kuebler.com#UI#Markdown#生成式
Editor brief · 编辑速览

开发者设计了Markdown→生成式UI协议系统,让前端开发者可用简洁语法动态生成和控制界面。

文章思维导图
Knowledge map
拖拽缩放
Full translation

完整中文译文

导言

"用户界面在很大程度上会消亡," Eric Schmidt 预测道。Agent 将在需要时动态生成你所需的任何 UI。我构建了一个原型来探索这个前提。

这是一个 Agent 化 AI 助手,从零开始生成 React UI,数据在客户端、服务器和 LLM 之间流动。该原型基于三个想法:

Markdown 作为协议 —— 一条流携带文本、可执行代码和数据。LLM 已经知道如何编写它。

流式执行 —— Agent 编写并执行代码。每个语句完成后立即执行 —— 无需等待完整响应。

mount() 原语 —— 一个函数,让 Agent 创建响应式 UI,具有客户端-服务器-LLM 通信的数据流模式。

查看这里的仓库。

为什么选择 Markdown?

如何将代码执行与文本和数据相结合?全部流式传输并以任意顺序交错?在一个协议中?

我一直回到 markdown。LLM 对 markdown 了如指掌 —— 格式化、代码围栏,所有这些。为什么要教它们新东西?

所以我确定了三种块类型:

这可能看起来像这样:

Hey! I am the assistant. This text is streamed to the user token by token.
But I can also run code...

```tsx agent.run
const messages = await fetchMessages()

I can mount UIs

const fakeMovieData = new StreamedData("fake-movies");

const form = mount({
  streamedData: fakeMovieData,
  ui: ({ streamedData }) => <Movies movies={streamedData} />
})

I can stream data into these UIs [data appears one by one...]

[
  { "name": "Blade Runner", "rating": 4.5 },
  { "name": "Dune", "rating": 4.2 }
]

All within the same response...


文本、代码和数据——交错,以任意顺序,任意次数。解析器在令牌到达时逐步处理它。

语法自然可扩展。需要新的块类型?只需添加新的围栏头。`tsx agent.run` 和 `json agent.data` 只是前两个。

## 反馈循环

反馈循环很简单:`console.log` 是 Agent 与自己沟通的方式。它的工作方式如下:

- LLM 生成带有代码块的 markdown
- 文本流式传输给用户,代码在服务器上逐步执行
- `console.*` 输出和异常作为新的转折反馈给 LLM
- 如果没有输出或异常 —— 完成,等待新的用户查询

这意味着 Agent 可以对自己的执行做出反应:

How many messages did I get?


```tsx agent.run
const messages = await fetchMessages();
console.log('messagesCount:', messages.length);
[runtime transcript]
messagesCount: 4
You have four new messages.

或者它可以暂停并等待用户输入:

const form = mount({ /* ... */ });
const answer = await form.result;  // Blocks until user submits
console.log("user:responded", answer);

流式执行

我希望语句在 LLM 生成它们时执行,无需等待代码围栏关闭。结果将是更响应的用户体验——API 调用启动、UI 渲染、错误浮出,所有这些都在 LLM 仍在发送令牌时发生。

问题:流式执行还不是标准原语。没有运行时让你输入令牌并在语句完成时执行它们,具有共享上下文和顶级 await。

我最终构建了 bun-streaming-exec 来处理这个问题,使用 vm.Script 和一些"创意性"的包装。如果你想深入了解,我写了一篇关于这个方法的专门文章。

它很奇怪吗?是的。有效吗?大部分时间。

构建反应式 UI

将文本、代码和数据放在一个流中,你拥有 Agent 化 UI 的大部分构建块。缺失的部分是一种将代码转变为实时界面的方式。对于 UI,React 是显而易见的选择。LLM 已经看到了数百万个 React 组件。它们知道 JSX。

核心原语是 mount():

mount({
  ui: () => <Card>Hello from the agent!</Card>
});

LLM 生成代码,服务器执行它。mount() 序列化 React 组件并将其发送到客户端。客户端在聊天中呈现它。

真正的力量来自数据流。

数据可以移动的四种方式

构建这个,我最终得到了四种不同的模式来在服务器、客户端和 LLM 之间移动数据:

1. 客户端 → 服务器(表单)

Agent 可以等待用户输入:

const form = mount({
  outputSchema: z.object({ name: z.string().min(1) }),
  ui: ({ output }) => (
    <Box>
      <TextField {...output.name} label="Your name" />
      <Button type="submit" {...output}>Submit</Button>
    </Box>
  )
});
const { name } = await form.result;  // Blocks until submit
console.log("user:responded", name);

{...output.name} 连接字段。await form.result 暂停执行,直到用户提交。结果通过 console.log 反馈给 LLM。

2. 服务器 → 客户端(实时更新)

服务器端变化透明地更新 UI:

const data = new Data({ progress: 0 });
mount({
  data,
  ui: ({ data }) => <LinearProgress value={data.progress} />
});

data.progress = 40;  // UI updates immediately

在幕后,Data 对象是代理。突变被检测、序列化为补丁、通过 WebSocket 发送并在客户端应用。

3. LLM → 客户端(流式)

LLM 可以将 JSON 直接流式传输到 UI:

const movies = new StreamedData("movies-list");
mount({
  streamedData: movies,
  ui: ({ streamedData }) => (
    <Card>
      {streamedData?.map((movie, i) => (
        <Recommendation key={i} rating={movie.rating}>{movie.name}</Recommendation>
      )) ?? <Loading />}
    </Card>
  )
});
[
  { "name": "Blade Runner", "rating": 4.5 },
  { "name": "Dune", "rating": 4.2 }
]

JSON 逐令牌流式传输。客户端使用 jsonriver 逐步解析,随着数据到达而更新 UI。完成后,服务器也可以通过 StreamedData 对象访问它。

4. 客户端 → 服务器(回调)

对于 UI 内的实时交互:

const data = new Data({ messages, loading: false });
const onRefresh = async () => {
  data.loading = true;
  data.messages = await loadMessages();
  data.loading = false;
};

mount({
  data,
  callbacks: { onRefresh },
  ui: ({ data, callbacks }) => (
    <Card>
      <MessageList messages={data.messages} />
      <RefreshButton loading={data.loading} onClick={callbacks.onRefresh} />
    </Card>
  )
});

单击按钮调用服务器端函数。回调获取新数据、更新状态,UI 反映它 —— 全部在代码中,不会触发新的 LLM 轮次。

插槽机制

随着 UI 变得更加复杂,用户必须等待更长时间才能让 LLM 生成代码。对于更复杂的 UI,有一个插槽机制:Agent 可以先挂载骨架界面,然后稍后注入更重的部分。

结合流式执行,骨架在其 mount() 语句完成的瞬间出现。每个 mountSlot() 调用在 LLM 完成生成它时填充一个部分:

const shell = mount({
  data, callbacks: { onResolve },
  ui: () => (
    <Card>
      <Slot name="stats" fallback={<Skeleton variant="rectangular" height={120} />} />
      <Slot name="blockers" fallback={<Skeleton variant="rectangular" height={80} />} />
    </Card>
  ),
});

shell.mountSlot("stats", ({ data }) => <StatsRow data={data} />);
shell.mountSlot("blockers", ({ callbacks }) => <BlockerList onResolve={callbacks.onResolve} />);

插槽与其父元素共享相同的上下文:数据、回调、流式数据。这意味着插槽相互间保持反应性。一个插槽中的回调可以改变共享数据,而每个读取它的其他插槽都会自动更新。

安全性与生产就绪度

Claude Code 和 ChatGPT 的 Code Interpreter 已经大规模执行 LLM 生成的代码 —— 沙箱、基于能力的权限和静态分析正在整个行业积极开发中。困难的未解决问题是 prompt injection,这对所有 Agent 架构同样成立 —— 工具调用、MCP 和代码执行一样。该项目没有处理任何这些。它探索上面的层:一旦假设安全得到合理解决,你可以构建什么。我们还没有完全到达那里。

为什么有效

我构建这个原型是为了看看 markdown 是否真的可以作为 Agent 化 UI 的协议而不需要任何微调。当我第一次让它运行时,我很惊讶。模型立即接受了它。虽然不完美,但核心想法就是有效的。

那是因为这里的每个设计选择都优化了一件事:LLM 人体工学。

Markdown 与代码围栏一起使用,因为 LLM 已在数十亿文档上训练。TypeScript 因为它在 GitHub 上最常用的语言中连接服务器和客户端。React 因为它是它们最熟悉的 UI 框架。mount() 因为它的构建块 —— 可等待的结果、回调、Zod 架构 —— 是模型已经看到数百万次的模式。

该系统不会教模型任何新东西。它将模型已知的模式排列成一个真正运行的系统。

你可以从头开始为 Agent 化 UI 设计新协议。或者你可以将运行时与模型的训练数据相匹配:markdown。

查看这里的仓库。

Original source

本文由 AI 翻译整理自 Hacker News · fabian-kuebler.com,原文版权归原作者所有。

阅读英文原文
上一篇
轻量级TTS模型库:25MB以内可直接嵌入
下一篇
OpenAI收购Astral:Python工具栈并购