news 2026/9/6 5:07:23

[特殊字符] ZorvAI 动态 UI 组件:让 AI 的回答「看得见、用得上」

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
[特殊字符] ZorvAI 动态 UI 组件:让 AI 的回答「看得见、用得上」

🌟 项目简介

ZorvAI 动态 UI(quro-ui)是一套基于Jetpack Compose原生构建的可交互界面渲染框架。它让 AI 不再局限于「文字 + 代码块」的输出形态,而是能够主动地生成卡片、表单、列表、播放器、浏览器、富媒体等完整的交互式界面——就像一位熟练的前端工程师,根据用户意图即时绘制出最合适的 UI。

🔗开源地址:https://github.com/Quor-a/ZorvAI

✨ 核心理念

理念内涵
原生即正义直接用 Compose 渲染,不依赖 WebView/HTML(除了白名单的 HTML 节点)
DSL = 结构用 JSON 描述界面,模型输出友好、解析稳定、可版本控制
稳定可重现每个节点生成稳定 ID,重渲染后状态不丢、回调不串
密度自适应以 360 dp 设计宽度为基线,按当前真实宽度动态缩放(手机/折叠屏/平板)
暗色优先16 阶灰度 + 语义色板,深色场景默认开启,亮色按需切换
必备输出v1.0.82 起,AI 把动态 UI 作为默认呈现方式,不再需要用户要求

🏛 架构总览

┌────────────────────────────────────────────────────────────────────────┐ │ QuroAssistant 主对话流水线 │ ├────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────┐ ┌────────────┐ ┌─────────┐ ┌─────────────────┐ │ │ │ 用户提问 │ → │ System │ → │ LLM │ → │ 文本/quro-ui │ │ │ │ + 上下文 │ │ Prompt │ │ 决策 │ │ JSON 混合输出 │ │ │ └─────────┘ │ 「动态 UI │ │ 工具调用│ └─────────────────┘ │ │ │ 必备输出」 │ └─────────┘ │ │ │ └────────────┘ ▼ │ │ ┌─────────────────┐ │ │ │ A2uiEnvelope │ │ │ │ (a2ui 协议信封) │ │ │ └─────────────────┘ │ │ │ │ │ ┌───────────────────────────────────────────────────────────┘ │ │ ▼ │ │ ┌────────────────┐ ┌──────────────┐ ┌─────────────┐ │ │ │ QuroUiDslParser│ → │ QuroUiCatalog│ → │ QuroUiNode │ │ │ │ 净化/解析/纠错 │ │ 调色板与图标 │ │ AST │ │ │ │ │ │ 字面量校验 │ │ │ │ │ └────────────────┘ └──────────────┘ └─────────────┘ │ │ │ │ │ ▼ │ │ ┌──────────────────┐ │ │ │ SurfaceHost │ │ │ │ 挂载 Compose 容器 │ │ │ └──────────────────┘ │ │ │ │ │ ▼ │ │ ┌──────────────────┐ │ │ │ QuroUiRenderer │ │ │ │ 17+ 原生组件渲染 │ │ │ └──────────────────┘ │ └────────────────────────────────────────────────────────────────────────┘

📦 模块拆解 · 10 个文件各司其职

core/ui/dynamicui/ ├── QuroUiNode.kt # AST 节点类型定义 ├── QuroUiDslParser.kt # quro-ui 字符串净化 + JSON 解析 ├── QuroUiCatalog.kt # 调色板 / 图标库 / 校验器 ├── QuroUiColor.kt # 16 阶灰度 + 语义色映射 ├── QuroUiIcons.kt # Lucide 图标库(camelCase → snake_case) ├── QuroUiPointer.kt # 路径解析 + 数据更新指针([a-z0-9_./-]+) ├── QuroUiRenderer.kt # ⭐ 核心:JSON AST → Compose 组件 ├── SurfaceHost.kt # 无限尺寸 / maxWidth 崩溃修复 + 渲染挂载 ├── A2uiEnvelope.kt # a2ui 信封协议(deepMerge / updateDataModel) ├── A2uiInterpreter.kt # 信封嗅探(lowercase 开头自动识别) └── QuroDynamicUiTool.kt # ⭐ ui_dsl_spec / ui_validate 工具

🔧 各文件职责一览

文件行数(估)关键能力
QuroUiNode.kt~350QuroUiRootNode/QuroUiContainerNode/QuroUiLeafNode三层 AST;QuroListNode支持{{item.field}}QuroHtmlNode透明 WebView
QuroUiDslParser.kt~280sanitizeJson()抹平误置闭合符;fixOutsideStrings()修复字符串外的脏括号;normalizeQuotes()跟踪inSgl/inDbl状态
QuroUiCatalog.kt~220颜色通过QuroUiColor.parse()校验;图标白名单 + 未知图标回退默认;LeafNode 合法性守卫
QuroUiColor.kt~120gray.0-gray.15灰度;primary/secondary/success/warning/danger语义色;自动判别 light/dark
QuroUiIcons.kt~200Lucide 图标集;camelCase → snake_case → 小写归一;缺失自动回退circle_help
QuroUiPointer.kt~150路径字段名正则[\w\-./];解决 JSON 路径解析时的中划线/下划线混用
QuroUiRenderer.kt~1400核心:RenderColumn/RenderRow/RenderBox/RenderCard/RenderList/RenderTabs/RenderSlider/RenderText/RenderImage/RenderIcon/RenderBadge/RenderProgress/RenderButton/RenderTextInput/RenderSelect/RenderMarkdown/RenderHtml/RenderVideo/RenderAudio/RenderBrowser/RenderCode/RenderDivider/RenderSpacer 等
SurfaceHost.kt~180修复Infinity触发的 Compose 崩溃;包一层BoxWithConstraints提供真实可用宽度
A2uiEnvelope.kt~200{ "version": ..., "a2ui": ... }信封;deepMerge()支持增量数据模型合并
A2uiInterpreter.kt~120嗅探 lowercase 键名头({"kind":"a2ui", ...})自动剥信封;纯 quro-ui JSON 不受影响
QuroDynamicUiTool.kt~300ui_dsl_spec拉取动态 UI 规格(提示词);ui_validate模型自检输出可解析性

🧬 DSL 解析管线

quro-ui 的输入是模型输出在 fenced code block 里的 JSON。我们永远不假设模型一定写出干净 JSON,所以解析管线有四层防护:

🛡 1️⃣sanitizeJson(raw: String)

目标:抹平「误置闭合符」(最常见的 AI 病)。

funsanitizeJson(raw:String):String{// 1) 找到第一个 '[' 或 '{' 作为起点// 2) 跟踪 (字符串内/外) + (反斜杠转义) 状态机// 3) 在字符串外允许的成对字符 [ ] { } :// - 若遇到孤立的 ']' 或 '}',先看上层栈;不平衡则补一个同向(保守)补齐// 4) 丢弃顶层其余杂质(多余反引号、注释尾巴)}

🔧典型拯救

// 模型输出:[{"type":"text","text":"你好"}{"type":"button","label":"确定"}// ← 漏了 ,]// sanitizeJson 后:[{"type":"text","text":"你好"},{"type":"button","label":"确定"}]

🛡 2️⃣fixOutsideStrings(s: String)

目标:修字符串外的脏括号({ type: "foo}— 引号未关)。

funfixOutsideStrings(s:String):String{valout=StringBuilder()varinSgl=false;varinDbl=falsefor(cins){when{c=='\\'&&(inSgl||inDbl)->{out.append(c);/* 跳过下个 */}c=='"'&&!inSgl->inDbl=!inDbl c=='\''&&!inDbl->inSgl=!inSgl...}out.append(c)}}

🛡 3️⃣normalizeQuotes(s: String)

目标:统一单/双引号 → JSON 标准双引号。在字符串外为 inSgl = false 时安全替换。

🛡 4️⃣QuroUiCatalog + QuroUiColor.parse()校验

目标:颜色字面量必须是已知 token,否则归一为gray.7(中灰);图标名必须存在于白名单。


🎨 渲染管线

JSON AST (QuroUiNode) │ ▼ ┌─────────────────────────────┐ │ QuroUiRenderer.render(root) │ └─────────────────────────────┘ │ ├─ 容器节点 → RenderColumn/RenderRow/RenderBox/RenderCard │ │ │ └─ forEach child → 递归调用 renderChild() │ └─ 叶子节点 → RenderText/RenderImage/RenderButton/RenderHtml/... │ └─ stableId(prefix, json) → 用于 Compose Key

📐 密度自适应(360 dp 设计宽度)

@ComposablefunrememberDensityScale():Float{valconfig=LocalConfiguration.currentvaldesignWidthDp=360fvalactualWidthDp=config.screenWidthDp.toFloat()return(actualWidthDp/designWidthDp).coerceIn(0.85f,2.0f)}

文本、间距、内边距、圆角、卡片宽度都按densityScale缩放;图标按矢量自


🛠 实战:构建一个待办清单

理论讲完,来点能直接跑的东西。下面用quro-ui构建一个完整的「待办清单」:顶部一个输入框,中间是list渲染的待办项(每项带checkbox勾选),底部一个「清空已完成」按钮。

下面是你的待办清单,试试勾选或新增: ```quro-ui { "type": "column", "gap": 12, "padding": 14, "children": [ { "type": "text", "text": "📝 今日待办", "weight": "bold", "size": 18 }, { "type": "row", "gap": 8, "children": [ { "type": "text_input", "placeholder": "输入新任务,回车添加", "value": "{{input}}", "onSubmit": { "type": "callback", "name": "todo_add", "payload": { "text": "{{input}}" } } }, { "type": "button", "label": "添加", "variant": "primary", "action": { "type": "callback", "name": "todo_add", "payload": { "text": "{{input}}" } } } ] }, { "type": "list", "gap": 8, "data": [ { "id": "t1", "title": "写周报", "done": false }, { "id": "t2", "title": "回复邮件", "done": true }, { "id": "t3", "title": "预约会议室", "done": false } ], "template": { "type": "row", "gap": 10, "align": "spaceBetween", "children": [ { "type": "checkbox", "label": "{{item.title}}", "checked": "{{item.done}}", "onChange": { "type": "toggle", "stateKey": "todo.{{item.id}}.done" } }, { "type": "button", "label": "删除", "variant": "secondary", "action": { "type": "callback", "name": "todo_remove", "payload": { "id": "{{item.id}}" } } } ] } }, { "type": "button", "label": "🗑 清空已完成", "variant": "danger", "action": { "type": "callback", "name": "todo_clear_done", "payload": {} } } ] } ```

🧩 节点渲染效果拆解

节点渲染效果
column垂直容器,gap: 12让标题、输入行、列表、清空按钮之间保持 12 dp 间距
text顶部加粗标题「📝 今日待办」,size: 18突出层级
row水平排列「输入框 + 添加按钮」,gap: 8让两者紧贴不粘连
text_input占位提示「输入新任务,回车添加」,value绑定{{input}}保持受控
button「添加」用primary主色;「删除」用secondary次色;「清空」用danger红色
list遍历data数组,每行按template渲染,{{item.title}}取当前行标题
checkbox左侧勾选框 + 右侧标签,checked绑定{{item.done}}回显完成状态

⚡ 交互动作如何绑定

  • callback(新增 / 删除 / 清空):按钮或输入框的action/onSubmit里声明{ "type": "callback", "name": "todo_add", "payload": {...} }。点击后前端把name+payload回传给宿主,由业务层更新数据模型并重渲染。
  • toggle(勾选完成)checkboxonChange{ "type": "toggle", "stateKey": "todo.{{item.id}}.done" }。它不经过业务回调,直接翻转stateKey指向的布尔状态,实现「本地即时勾选」——配合{{item.done}}回显,勾选后整行状态立刻同步。
  • 占位符联动{{item.id}}/{{item.title}}/{{item.done}}list内逐行求值,让每个 checkbox 和删除按钮都拿到自己那一行的数据,互不串扰。

💡要点callback适合「需要宿主处理」的动作(增删、持久化),toggle适合「纯本地状态翻转」(勾选、开关)。两者组合,就能在纯 JSON 里搭出可交互的完整界面。

适应不缩放。


🧩 节点类型完整清单 · 17+ 组件

类型类别关键属性
column容器gap / padding / align / scroll
row容器gap / padding / align / wrap
box容器padding / align
card容器padding / radius / elevation / background
tabs容器tabs[]+activeIndex状态
list容器data+template占位符{{item}}/{{item.field}}/{{index}}
text叶子text / size / weight / color / align / maxLines
image叶子src / fit / radius / placeholder
icon叶子name (Lucide)/size / color
badge叶子text / variant (success/warning/danger/...)
progress叶子value / max / variant
divider叶子color / thickness
spacer叶子height / width
button叶子label / action / variant
text_input叶子placeholder / value / onSubmit
checkbox叶子label / checked / onChange
switch叶子label / checked / onChange
select叶子options[] / value / onChange
slider叶子min / max / value / onChange
markdown叶子content实时渲染 Markdown
html叶子content透明 WebView 容器(v1.0.82 深度修复)
video叶子src / controls / autoplay
audio叶子src / controls
browser叶子url / height / cookies / ua(内嵌 WebView 容器,v1.0.82 已稳定)
code叶子code / lang / theme

📝 实战示例:AI 输出

下面是配置服务器的一键操作清单: ```quro-ui { "type": "list", "padding": 12, "gap": 8, "data": [ { "emoji": "🛠", "title": "安装 Nginx", "desc": "通过 apt/yum 安装最新稳定版" }, { "emoji": "🔒", "title": "配置 HTTPS", "desc": "使用 Let's Encrypt 自动签发" }, { "emoji": "📦", "title": "部署静态站点", "desc": "/var/www/html 权限设置" } ], "template": { "type": "row", "gap": 12, "children": [ { "type": "text", "text": "{{item.emoji}}", "size": 20 }, { "type": "column", "children": [ { "type": "text", "text": "{{item.title}}", "weight": "bold" }, { "type": "text", "text": "{{item.desc}}", "size": 12, "color": "gray.10" } ] } ] } } ```

渲染效果:每行 = 表情 + 加粗标题 + 灰色描述,自适应宽度。


⚡ 动作类型 · 8+ 种交互

动作参数v1.0.82 增强
callback{ name, payload }
tool_call{ name, args }
skill{ name, args }
open_url{ url }支持深链zorvai://...
copy{ text }
open_app{ packageName }
toggle{ stateKey }
open_screen🆕{ screen, args }直达应用内屏(设置/插件/会话)
render_html🆕{ html }服务端/Skill 主动渲染 HTML 节点
render_vispro🆕{ spec }触发可视化处理管线(图表/流程图)
visual_popup🆕{ payload }系统级浮层提示
visual_ask🆕{ question, options[] }阻塞式可视化提问,等待用户选择

🔁 占位符与数据流

占位符适用场景示例
{{index}}list节点里返回当前序号第 {{index}} 项
{{item}}list节点里整行数据(字符串字段时)
{{item.field}}list节点里按字段取数据{{item.title}}/{{ite> **同源更新**:List 内部如嵌套tabs/card,子节点也能取到外层的{{item.xxx}}`,渲染时整树连坐求值。

🔧 工具支持 · 模型自检

🧰ui_dsl_spec

// 模型调用: { "tool": "ui_dsl_spec", "args": { "section": "all" } } // 返回:quro-ui 节点清单 + 示例 JSON + 注意事项

🧰ui_validate

// 模型自检:把刚才输出的 quro-ui JSON 喂回工具,立即返回可解析性评分 { "tool": "ui_validate", "args": { "dsl": "<JSON>" } } // 返回: { "ok": true, "warnings": [...], "fix_suggestions": [...] }

典型用法:模型自检一轮后再发出,比直接发送错误 JSON 被前端报错更稳健。


🎨 主题与样式

🌑 调色板(QuroUiColor)

Token 类示例说明
gray.0gray.15gray.0 = #FFFFFFgray.15 = #0A0A0A16 阶中性灰
primary主品牌色(v1.0.82:靛蓝 #5046E4)主操作
secondary次操作色次按钮
successwarningdangerinfo绿/橙/红/蓝状态徽章
surfaceonSurface卡片背景/前景自动暗色反转

✏️ 图标(QuroUiIcons)

  • 内置Lucide图标集(约 1000 个常用图标)
  • camelCase → snake_case → 小写归一
  • 未知图标回退circle_help,绝不渲染空白方块

🧠 v1.0.82 必备输出设计 · 系统提示词

这一节是让「动态 UI」真正成为默认行为的关键。

### 动态 UI(quro-ui 原生组件 · 必备输出) **何时用:** 始终默认使用。任何需要呈现「操作清单 / 选项 / 表单 / 播放器 / 浏览器 / 富媒体」的回答,都优先用 quro-ui 渲染,而不是 纯文本。即使只生成一张卡片也要用它。 **输出规范:** 1. 单条 quro-ui JSON 必须被 ```quro-ui … ```围栏包裹; 2. 复杂的可拆为多条 ```quro-ui 块; 3. 关键结论、解释、对话照常用正文;UI 只是更强的呈现通道。 **自检:** 发送前调用 ui_validate 工具。

🧭 在工具分类中的位置

🧠 ToolCapabilityDirectory.DYNAMIC_UI ├─ IntentMatcher: "原生交互界面 / 动态UI" │ └─ 命中工具: [ui_dsl_spec, ui_validate] ├─ IntentMatcher: "卡片 / 列表 / 表单 / 播放器 / 浏览器界面" │ └─ 命中工具: [ui_dsl_spec] └─ 优先级: 5(高于普通 text/image)

QuroToolRouter.categorize()已加入DYNAMIC_UI映射,早于ui_*规则,避免被通用 UI 工具误判。


🐞 8 轮 Bug 修复亮点

轮次模块症状修复
Round 2QuroUiRenderer占位符{{item.emoji}}显示原文不替换ListNode 取值模板改用 key 路径item.emoji,不再依赖整段item字符串化
Round 2QuroUiDslParser字符串内含未转义引号导致 parse 崩溃normalizeQuotes引入inSgl跟踪,未关闭时强制补双引号
Round 3SurfaceHost父容器传Infinity触发 Compose 测量崩溃外层裹BoxWithConstraints,把可用宽度收紧到maxWidth - padding
Round 4QuroUiCatalog颜色字面量大小写不一致(Primary/PRIMARYQuroUiColor.parse()单点入口,统一归一
Round 5QuroUiRenderertext_inputcard内只能点一次聚焦Modifier.focusRequesterremember(root)防止重渲染拿错引用
Round 6QuroUiRenderer暗色下文字看不清(用了浅色 token)渲染时根据当前isSystemInDarkTheme()二次反转
Round 7QuroUiNodeQuroHtmlNode透明背景露原生控件色WebViewsetBackgroundColor(Color.TRANSPARENT)+ 容器同步graphicsLayer = 0f
Round 8QuroUiRendererQuroUI 区块与普通消息块视觉混淆(无边框、间距过近)quro-ui段落加 12 dp 顶部间距 + 卡片化外框,淡化正文连续感

🎯 设计哲学

❓ 为什么选 Compose 原生而不是 WebView?

维度Compose 原生WebView + HTML
性能与系统同帧率,零额外进程独立进程,重绘制、内存抖动
暗色一致性跟随主题,零额外样式需要在 HTML 里镜像一套 token
滚动/手势LazyColumn、NestedScroll 原生开箱即用手势与宿主 Activity 冲突、需手写桥接
体积代码约 40 KB(解析+渲染)离线 HTML 模板 + 50 KB+ 运行时桥接
调试Layout Inspector / Preview 直接看远程 Chrome DevTools
AI 输出适配JSON 描述简单、字段扁平HTML/CSS 结构脆弱、标签嵌套深

结论:可枚举的非媒体场景一律 Compose 原生;只有真正需要浏览器内核的(如打开任意 URL)才走 WebView 容器节点browser

❓ 为什么 JSON DSL 而不是 JSON Schema 或 Protobuf?

  • JSON Schema太啰嗦,模型不爱输出;结构校验可以靠 catalog 完成
  • Protobuf / TypeScript模型往往拼错大小写或忘了枚举值;JSON 字面量最稳
  • YAML缩进依赖坑惨过模型
  • JSON是当下 LLM 输出文本的最稳定格式(token 训练量最大)

🚀 未来扩展方向

  • 🧩可视化处理(render_vispro):流程图、时序图、思维导图渲染器
  • 🎞Timeline / Carousel 节点:横向滑动 + 自动播放
  • 🪟visual_ask 增强:多选、可填空、附件上传
  • 🧠state.io 持久化:节点状态写入数据模型,跨消息保持
  • 🌐a2ui envelope 互通:与外部 a2ui 协议完全双向兼容
  • 📱桌面 / 折叠屏断点:除 360 dp 外,新增 ≥ 600 dp / ≥ 840 dp 的多断点布局

📚 参考示例 · 完整卡片输出

下面为你列出 3 款适合远程开发的笔记本,按性价比排序: ```quro-ui { "type": "card", "padding": 14, "gap": 10, "background": "surface", "radius": 14, "children": [ { "type": "row", "align": "spaceBetween", "children": [ { "type": "text", "text": "🏆 性价比首选", "weight": "bold", "size": 16 }, { "type": "badge", "text": "TOP1", "variant": "success" } ] }, { "type": "text", "text": "MacBook Air M2 · 16 GB / 512 GB", "size": 14 }, { "type": "row", "gap": 8, "children": [ { "type": "button", "label": "查看配置", "variant": "primary", "action": { "type": "open_url", "url": "https://example.com/mac-air" } }, { "type": "button", "label": "加入对比", "variant": "secondary", "action": { "type": "tool_call", "name": "add_to_compare", "args": { "id": "mac-air-m2" } } } ] } ] } ``` 如果你需要开发 Android 原生,建议再考虑内存升级到 24 GB 的型号。

🌟 动态 UI,让 AI 的回答「看得见、用得上」🌟

ZorvAI · v1.0.82 · 2026-09-05

如嵌套 `tabs`/`card`,子节点也能
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/6 5:06:15

PaddleOCR-VL 在 Intel Arc A770 上的部署与调优指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/6 4:59:05

【无标题】AI获客工具包:5套即用提示词,帮自由职业者快速写高转化文案

这个工具包把“获客文案”拆成 5 个可以直接替换使用的提示词模板&#xff1a;冷邮件、LinkedIn 私信、跟进序列、提案、异议处理。适合自由职业者、小型代理机构和独立创业者。你只需要把服务、目标客户、成果案例填进去&#xff0c;就能快速生成高转化沟通内容。适用 ChatGPT…

作者头像 李华
网站建设 2026/9/6 4:58:18

三款AI论文写作软件亲测:从大纲到降重怎么选才不踩坑?

写论文这事&#xff0c;最怕的不是写不出来&#xff0c;而是写得心里没底。 题目改了七八版还怕选重了&#xff0c;文献下载了两百篇越读越乱&#xff0c;参考文献格式调到崩溃&#xff0c;交稿前还得担心重复率和AIGC检测。今年开学季一到&#xff0c;又有一波人在搜“AI论文工…

作者头像 李华
网站建设 2026/9/6 4:56:37

基于SpringBoot的企业公文流转管理系统的设计与实现毕业设计项目源码

联系博主 温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;本人主页置顶文章(点我)开头有 …

作者头像 李华
网站建设 2026/9/6 4:52:43

物流运输行业承运合同管理难点、运费结算风控与数字化落地实战方案

物流货运、供应链运输、同城配送、干线物流行业具备运输车次量大、承运主体多、线路繁杂、月结结算为主、回单对账滞后、合同批次零散的核心特点。多数中小物流企业依托人工登记承运合同、Excel批量统计车次运费、线下整理回单凭证&#xff0c;长期面临合同批次混乱、运费核算偏…

作者头像 李华