OpenClaw A2UI

ClawSkills 作者 suuuy v1.0.2

为 OpenClaw webchat 回复增加个性化富 UI 展示,基于 HTML 直出渲染。 【默认启用】凡是结构化内容(列表、数据、步骤、表格、摘要、状态、代码等)一律用 HTML 卡片回复。 纯文字聊天(简单问答、闲聊)不强制套卡片。 当用户要求安装/迁移 openclaw-a2ui、启用 HTML 渲染、配置 DOMPurify 白名单扩展、 安装 skill-ui-bridge plugin,或排查 webchat 中 HTML 不渲染的问题时,参考底部安装章节。

源码 ↗

安装 / 下载方式

TotalClaw CLI推荐
totalclaw install clawskills:suuuy~openclaw-a2ui
cURL直接下载,无需登录
curl -fsSL https://skills.taituai.com/api/skills/clawskills%3Asuuuy~openclaw-a2ui/file -o openclaw-a2ui.md
Git 仓库获取源码
git clone https://github.com/openclaw/skills/commit/dad0e1ad5e5954612e759c6863418f746e2c87a1
# openclaw-a2ui

> **本技能已验证(2026-03-18):style / div / grid / flex / table / img / svg 全部渲染正常。**
> **除非用户要求纯文字,否则所有结构化内容必须用 HTML 卡片输出。**

### Changelog
- **v1.0.2(2026-03-18)**:骨架屏优化
  - 骨架插入到 `.chat-text` 直接父节点(气泡本身),并设 `inline-block + min-width: 320px` 撑开气泡
  - `.sui-skeleton` 固定 `width: 320px; display: block`,解决 inline-block 气泡宽度塌陷问题
  - 骨架行结构调整:标题行 12px / 正文行 8px / 底部两段不等宽细线,比例更自然
  - 移除骨架时还原气泡 `display` 和 `minWidth`
- **v1.0.1(2026-03-09)**:流式骨架屏 + 淡入动画,findBubble 隐藏整体气泡
- **v1.0.0(待更新)**:**单卡片模式** - 默认所有结构化回复使用单一 CompositeCard 统一承载,减少多卡片的视觉碎片化。

---

## ⚠️ HTML 不渲染?先看这里

如果 webchat 中 HTML 被显示为纯文本、标签被转义、或样式无效,**不要尝试手动修改 DOMPurify 配置**。根本原因是 `skill-ui-bridge` 插件未安装,解决方式:

| 现象 | 原因 | 解决方式 |
|------|--    |------|
| HTML 标签显示为文本(如 `<div>`) | marked.js 未开启 rawHTML | 安装 skill-ui-bridge 插件(见底部安装章节) |
| 样式被剥离,只剩裸文字/标签 | DOMPurify 未 patch,style/div 等被过滤 | 安装 skill-ui-bridge 插件(见底部安装章节) |
| 部分标签渲染,部分不渲染 | 插件未加载成功 | 检查安装验证步骤 5、6 |

`skill-ui-bridge` 插件安装后会**自动** patch DOMPurify 和 marked.js,无需手动配置白名单。

---

## 触发规则(优先级由高到低)

| 场景 | 处理方式 |
|------|--    |
| 结构化内容(列表、步骤、数据、表格、统计、对比、状态) | ✅ **必须用单一卡片显示,无文本前后缀** |
| 有标题 + 正文的摘要/说明 | ✅ **用单一 CompositeCard 承载所有内容**(单卡片模式) |
| 代码 + 说明 | ✅ **优先用单一 CodeCard**,必要时在 Card内嵌入代码块 |
| 简单一句话回答 | ⚪ 纯文字即可 |
| 用户明确说"纯文字"/"不要卡片" | ❌ 不用卡片 |

> **💡 单卡片模式强制规则:**
>
> **所有结构化内容必须只用卡片显示,不要有文本 + 卡片的组合形式。**
>
> - ✅ **正确**:所有内容(标题、要点、数据)都在一个 CompositeCard 内展示
> - ❌ **错误**:先写一段文字说明,再跟一个卡片
> - ❌ **错误**:卡片后再追加额外解释性文本
>
> **目标:**每张回复只出现一张卡片(或无卡片),避免视觉碎片化和信息重复。
>
> 例如:
> ```markdown
> ❌ 错误写法(多元素组合):
> 这是关于 AI Agent 的最新趋势,请查看下方卡片。
>
> <div class="a2ui">...
>
> 以上数据截至 3 月 18 日。
>
> ---
>
> ✅ 正确写法(单卡片模式):
> <div class="a2ui">
>   <!-- 标题:AI Agent 最新趋势 -->
>   <!-- 数据内容 -->
>   <!-- 数据来源说明 -->
>   <!-- 截止时间戳 -->
> </div>
> ```

---

## 输出方式(唯一方式)

直接在回复正文中输出 HTML,**不要包裹在代码块里**,webchat 会直接渲染。

### ⚠️ 强制要求:每张卡片最外层必须带 `class="a2ui"`

skill-ui-bridge 通过检测 `class="a2ui"` 识别需要渲染的 HTML,**缺少此 class 的卡片不会被渲染**。

```html
<!-- ✅ 正确 -->
<div class="a2ui" style="...">
  <!-- 你的卡片内容 -->
</div>

<!-- ❌ 错误,不会被渲染 -->
<div style="...">卡片内容</div>
```

### ⚠️ 关键规则:HTML 与文字必须分段

marked.js 在解析时,如果 HTML 和普通文字混在同一段落,会把 HTML 标签转义成文本显示。

**❌ 错误写法(HTML 夹在文字中间):**
```
以下是热榜内容:<div class="a2ui" style="..."></div> 感兴趣可以点击查看。
```

**✅ 正确写法(HTML 单独成段,前后空行隔开):**
```
以下是热榜内容:

<div class="a2ui" style=".">
  <!-- 卡片内容 -->
</div>

感兴趣可以点击查看。
```

规则:
- HTML 卡片前后必须有**空行**(不能紧跟文字)
- 文字说明放在卡片**之前**或**之后**,单独成段
- **单卡片模式优先**:多卡片连续输出时,考虑合并为一个 CompositeCard

---

## 设计规范(所有卡片统一)

```
背景:    #ffffff
圆角:    12px(卡片)/ 8px(内部元素)/ 999px(badge/tag)
阴影:    0 2px 12px rgba(0,0,0,0.08)
主色:    #5865f2(蓝紫)
成功:    #22c55e
警告:    #f59e0b
错误:    #ef4444
信息:    #3b82f6
标题色:  #1a1a2e
正文色:  #374151
说明色:  #6b7280
分割线:  #f0f0f0
底色块:  #f8fafc
最大宽:  600px
外边距:  margin:8px 0
字体:    -apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif
```

---

## 卡片模板速查

### TextCard — 文字摘要

```html
<div class="a2ui" style="background:#fff;border-radius:12px;padding:20px;box-shadow:0 2px 12px rgba(0,0,0,0.08);max-width:600px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;margin:8px 0">
  <div style="display:flex;align-items:center;gap:8px;margin-bottom:12px">
    <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#5865f2" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
    <strong style="font-size:16px;color:#1a1a2e">【标题】</strong>
  </div>
  <hr style="border:none;border-top:1px solid #f0f0f0;margin:0 0 14px">
  <p style="color:#374151;line-height:1.7;margin:0;font-size:14px">【正文内容】</p>
</div>
```

### ListCard — 列表/要点

```html
<div class="a2ui" style="background:#fff;border-radius:12px;padding:20px;box-shadow:0 2px 12px rgba(0,0,0,0.08);max-width:600px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;margin:8px 0">
  <strong style="font-size:16px;color:#1a1a2e;display:block;margin-bottom:12px">【标题】</strong>
  <hr style="border:none;border-top:1px solid #f0f0f0;margin:0 0 14px">
  <ul style="list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:10px">
    <li style="display:flex;align-items:flex-start;gap:10px">
      <svg style="flex-shrink:0;margin-top:2px" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#22c55e" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
      <span style="color:#374151;font-size:14px;line-height:1.5">【列表项】</span>
    </li>
  </ul>
</div>
```

### DataCard — 键值对 / 参数

```html
<div class="a2ui" style="background:#fff;border-radius:12px;padding:20px;box-shadow:0 2px 12px rgba(0,0,0,0.08);max-width:600px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;margin:8px 0">
  <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px">
    <strong style="font-size:16px;color:#1a1a2e">【标题】</strong>
    <span style="background:#dcfce7;color:#16a34a;font-size:12px;padding:3px 10px;border-radius:999px;font-weight:600">【状态 badge,可选】</span>
  </div>
  <hr style="border:none;border-top:1px solid #f0f0f0;margin:0 0 14px">
  <div style="display:flex;flex-direction:column;gap:0">
    <div style="display:flex;justify-content:space-between;align-items:center;padding:9px 0;border-bottom:1px solid #f8fafc">
      <span style="color:#6b7280;font-size:14px">【字段名】</span>
      <span style="color:#1a1a2e;font-weight:500;font-size:14px">【字段值】</span>
    </div>
  </div>
</div>
```

### StatsCard — 统计数字

```html
<div class="a2ui" style="background:#fff;border-radius:12px;padding:20px;box-shadow:0 2px 12px rgba(0,0,0,0.08);max-width:600px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;margin:8px 0">
  <strong style="font-size:16px;color:#1a1a2e;display:block;margin-bottom:14px">【标题】</strong>
  <div style="display:grid;grid-template-columns:repeat(3,1fr);gap:12px">
    <div style="text-align:center;padding:14px 8px;background:#f8fafc;border-radius:10px">
      <div style="font-size:26px;font-weight:800;color:#5865f2;line-height:1">【数字】</div>
      <div style="font-size:11px;color:#6b7280;margin-top:4px">【说明】</div>
    </div>
  </div>
</div>
```

### AlertCard — 提示/警告/成功/错误

```html
<!-- info -->
<div class="a2ui" style="background:#eff6ff;border-left:4px solid #3b82f6;border-radius:0 8px 8px 0;padding:12px 16px;display:inline-flex;gap:10px;align-items:flex-start;font-family:-apple-system,sans-serif;margin:8px 0">
  <span style="font-size:16px;flex-shrink:0">ℹ️</span>
  <div><div style="font-size:13px;font-weight:600;color:#1e40af;margin-bottom:2px">【标题】</div><div style="font-size:13px;color:#1e3a8a">【内容】</div></div>
</div>
<!-- success: bg=#f0fdf4 border=#22c55e title-color=#15803d text-color=#14532d emoji=✅ -->
<!-- warning: bg=#fffbeb border=#f59e0b title-color=#b45309 text-color=#92400e emoji=⚠️ -->
<!-- error:   bg=#fef2f2 border=#ef4444 title-color=#b91c1c text-color=#991b1b emoji=❌ -->
```

### StepsCard — 步骤流程

```html
<div class="a2ui" style="background:#fff;border-radius:12px;padding:20px;box-shadow:0 2px 12px rgba(0,0,0,0.08);max-width:600px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;margin:8px 0">
  <strong style="font-size:16px;color:#1a1a2e;display:block;margin-bottom:16px">【标题】</strong>
  <div style="display:flex;flex-direction:column;gap:0">
    <!-- 非最后一步(有连接线) -->
    <div style="display:flex;gap:14px">
      <div style="display:flex;flex-direction:column;align-items:center">
        <div style="width:28px;height:28px;border-radius:50%;background:#5865f2;color:#fff;font-size:13px;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0">1</div>
        <div style="width:2px;background:#e5e7eb;flex:1;margin:4px 0"></div>
      </div>
      <div style="padding-bottom:16px">
        <div style="font-size:14px;font-weight:600;color:#1a1a2e;margin-bottom:4px">【步骤标题】</div>
        <div style="font-size:13px;