鸿蒙智能体开发实战:39.鸿蒙壁纸大师 - 前端卡片显示与交互优化

引言

在前端展示层面,鸿蒙壁纸大师充分利用了鸿蒙 A2A 协议的卡片能力 ,通过 DisplayFaCard 实现丰富的交互式 UI,而非简单的文本对话。整个壁纸创作流程中,用户看到的是可点击的选择列表生动的进度提示精美的壁纸展示卡片

本文将深入解析前端的卡片交互设计,涵盖 selectList 选择卡片、wallpaper 展示卡片、SSE 事件驱动的前端更新以及内容安全策略。

上图展示了鸿蒙壁纸大师中的两种核心卡片类型:selectList 选择卡片和 wallpaper 壁纸展示卡片,以及它们在交互流程中的切换关系

一、卡片体系总览

1.1 三种卡片类型

鸿蒙壁纸大师使用了三种卡片类型:

卡片类型 用途 展示阶段
selectList 交互式选择列表 风格选择、主题选择、场景选择
wallpaper 壁纸结果展示 壁纸生成完成
text 文本消息 闲聊、进度提示

核心概念DisplayFaCard 是鸿蒙 A2A 协议中卡片展示的统一入口,通过 cardName 字段区分不同卡片类型,前端根据 cardName 渲染对应的 UI 组件。

1.2 卡片 JSON 结构规范

所有卡片都通过 A2A 协议的 artifact-update 事件的 parts 字段传递:

json 复制代码
{
  "jsonrpc": "2.0",
  "id": "request_id",
  "result": {
    "taskId": "task_id",
    "kind": "artifact-update",
    "append": false,
    "lastChunk": true,
    "final": true,
    "artifact": {
      "artifactId": "task_id-options",
      "parts": [
        {
          "kind": "data",
          "data": {
            "cardsInfo": [
              {
                "cardName": "selectList",
                "cardData": { ... },
                "displayType": "DisplayFaCard"
              }
            ]
          }
        }
      ]
    }
  },
  "error": { "code": 0, "message": "success" }
}

二、selectList 选择卡片

2.1 卡片构建函数

python 复制代码
# agent_routes.py


def build_style_options_card(
    title: str,
    options: List[str],
    task_id: str,
    request_id: str,
    has_next_batch: bool = True,
    can_replay: bool = True,
) -> dict:
    """构建风格/主题选项的卡片 SSE 事件"""
    items = [{"text": opt, "value": opt} for opt in options]
    return {
        "jsonrpc": "2.0",
        "id": request_id,
        "result": {
            "taskId": task_id,
            "kind": "artifact-update",
            "append": False,
            "lastChunk": True,
            "final": True,
            "artifact": {
                "artifactId": f"{task_id}-options",
                "parts": [{
                    "kind": "data",
                    "data": {
                        "cardsInfo": [{
                            "cardName": "selectList",
                            "cardData": {
                                "title": title,
                                "items": items,
                                "canReplay": can_replay,
                            },
                            "displayType": "DisplayFaCard"
                        }]
                    }
                }]
            }
        },
        "error": {"code": 0, "message": "success"}
    }

2.2 三级选择流程的卡片展示

第一级:风格选择

json 复制代码
{
  "cardName": "selectList",
  "cardData": {
    "title": "你可以从下面挑选喜欢的风格,也可以直接告诉我你想要的其他风格。",
    "items": [
      {"text": "极简主义", "value": "极简主义"},
      {"text": "弥散风", "value": "弥散风"},
      {"text": "水墨国风", "value": "水墨国风"},
      {"text": "莫兰迪风", "value": "莫兰迪风"}
    ],
    "canReplay": true
  }
}

第二级:主题选择

json 复制代码
{
  "cardName": "selectList",
  "cardData": {
    "title": "选一个喜欢的主题,打造你的专属壁纸。",
    "items": [
      {"text": "青峦远山", "value": "青峦远山"},
      {"text": "寒梅疏枝", "value": "寒梅疏枝"},
      {"text": "江上孤舟", "value": "江上孤舟"},
      {"text": "竹林清风", "value": "竹林清风"}
    ],
    "canReplay": true
  }
}

第三级:场景选择

json 复制代码
{
  "cardName": "selectList",
  "cardData": {
    "title": "最后,选择你的壁纸尺寸和使用场景。",
    "items": [
      {"text": "竖屏 · 锁屏", "value": "竖屏 · 锁屏"},
      {"text": "竖屏 · 主屏", "value": "竖屏 · 主屏"},
      {"text": "横屏 · 锁屏", "value": "横屏 · 锁屏"},
      {"text": "横屏 · 主屏", "value": "横屏 · 主屏"}
    ],
    "canReplay": false
  }
}

2.3 卡片交互的用户体验

阶段 卡片标题 选项数 支持换一批 支持重选
选择风格 引导选择风格 4
选择主题 引导选择主题 4
选择场景 引导选择场景 4

交互流程

复制代码
用户看到 selectList 卡片
    ↓
点击某个选项(如"水墨国风")
    ↓
前端将选项文本作为用户消息发送
    ↓
服务端识别关键词,跳过 LLM 意图识别
    ↓
构建下一级 selectList 卡片并推送
    ↓
前端自动刷新卡片

2.4 快捷命令与卡片的联动

当用户输入"换一批"时,服务端重新生成当前级别的选项并重新推送卡片:

python 复制代码
# agent_routes.py - 换一批处理

if "换一批" in user_text:
    if flow_manager.current_step == FlowStep.SELECT_STYLE:
        flow_result = flow_manager.handle_style_batch_refresh()
    elif flow_manager.current_step == FlowStep.SELECT_THEME:
        flow_result = await flow_manager.handle_theme_batch_refresh()

    # 重新构建并推送 selectList 卡片
    batch_event = build_style_options_card(
        title=flow_result["guidance"],
        options=flow_result["options"],  # 新的随机选项
        task_id=task_id,
        request_id=request_id,
        has_next_batch=True,
        can_replay=True,
    )
    yield f"data: {json.dumps(batch_event, ensure_ascii=False)}\n\n"

三、wallpaper 展示卡片

3.1 壁纸结果卡片

壁纸生成完成后,服务端推送 wallpaper 卡片展示结果:

python 复制代码
# services/wallpaper_service.py

# 步骤 5: 发送完成状态(卡片格式)
text_event = {
    "jsonrpc": "2.0",
    "id": task_id,
    "result": {
        "taskId": task_id,
        "kind": "artifact-update",
        "append": False,
        "lastChunk": True,
        "final": True,
        "artifact": {
            "artifactId": f"{task_id}-complete",
            "parts": [{
                "kind": "data",
                "data": {
                    "cardsInfo": [{
                        "cardName": "wallpaper",
                        "cardData": {
                            "title": "壁纸生成完成",
                            "imageUrl": image_url,
                        },
                        "displayType": "DisplayFaCard"
                    }]
                }
            }]
        }
    },
    "error": {"code": 0, "message": "success"}
}

3.2 壁纸卡片的呈现

生成的 JSON 使前端呈现类似以下效果:

复制代码
┌─────────────────────────┐
│  ✨ 壁纸生成完成         │
│                         │
│    ┌───────────────┐    │
│    │               │    │
│    │   壁纸图片     │    │
│    │               │    │
│    └───────────────┘    │
│                         │
│  风格:水墨国风          │
│  主题:青峦远山          │
│                         │
│  [换一张]  [重新开始]    │
└─────────────────────────┘

3.3 与流程管理器的状态同步

python 复制代码
# wallpaper_flow_service.py

def handle_generate_complete(self, image_urls: List[str]) -> Dict[str, Any]:
    """处理生成完成"""
    self.current_step = FlowStep.RESULT

    # 记录生成的壁纸
    for url in image_urls:
        self.generated_wallpapers.append({
            "url": url,
            "style": self.selected_style,
            "theme": self.selected_theme,
        })

    # 扣除免费次数
    if self.free_credits_remaining > 0:
        self.free_credits_remaining -= 1

    return {
        "step": "result",
        "message": "你的专属艺术壁纸已生成完成!",
        "wallpapers": [
            {"url": url, "style": self.selected_style, "theme": self.selected_theme}
            for url in image_urls
        ],
        "credits_remaining": self.free_credits_remaining,
        "has_regenerate": self.free_credits_remaining > 0,
    }

四、SSE 事件驱动的前端更新

4.1 完整事件序列

从前端视角看,一次完整的壁纸生成过程如下:

复制代码
前端发送 message/stream 请求
    ↓(后端返回 SSE 流)
事件1: status-update   →  state: submitted    →  "正在理解您的需求..."
事件2: status-update   →  state: working      →  "正在优化描述..."
事件3: status-update   →  state: working      →  "正在生成图片..."
事件4: status-update   →  state: working      →  "正在构思画面构图..."
事件5: status-update   →  state: working      →  "正在勾勒轮廓..."
...
事件N: artifact-update →  final: true         →  wallpaper 卡片

4.2 前端处理逻辑(示意)

typescript 复制代码
// 前端 SSE 事件处理示意代码

const eventSource = new EventSource('/agent/message');

eventSource.onmessage = (event) => {
    const data = JSON.parse(event.data);
    const result = data.result;

    switch (result.kind) {
        case 'status-update':
            // 更新进度状态
            updateProgress(result.status.message.parts[0].text);
            break;

        case 'artifact-update':
            // 处理最终产物
            const cardsInfo = result.artifact.parts[0].data.cardsInfo;
            cardsInfo.forEach(card => {
                switch (card.cardName) {
                    case 'selectList':
                        renderSelectListCard(card.cardData);
                        break;
                    case 'wallpaper':
                        renderWallpaperCard(card.cardData);
                        break;
                }
            });
            break;
    }
};

4.3 卡片替换逻辑

每次推送新的 selectList 卡片时,前端需要替换上一张卡片:

  • append: false:替换上一张卡片(风格→主题→场景的步骤推进)

  • append: true:追加到已有内容(闲聊的逐 token 输出)

    风格选择卡片 (append: false)
    ↓ 用户选择风格
    主题选择卡片 (append: false) → 替换风格卡片
    ↓ 用户选择主题
    场景选择卡片 (append: false) → 替换主题卡片
    ↓ 用户选择场景
    进度文本 (append: false)
    ↓ 逐条追加
    进度文本 (append: true)
    ...

    壁纸卡片 (append: false) → 替换所有内容

五、内容安全策略

5.1 肖像检测

python 复制代码
# wallpaper_flow_service.py

def check_content_policy(user_text: str) -> Tuple[bool, Optional[str]]:
    """检查内容是否符合规范(肖像、文字等限制)"""
    # 肖像检测关键词
    portrait_keywords = [
        "我", "自己", "本人", "照片", "肖像", "人脸", "头像",
        "明星", "网红", "模特", "人物", "人像", "自拍",
    ]

    # 文字/水印检测关键词
    text_keywords = [
        "文字", "字体", "写字", "名字", "姓名", "签名",
        "水印", "logo", "LOGO", "标志", "品牌",
    ]

    # 检测肖像请求
    for keyword in portrait_keywords:
        if keyword in user_text:
            if any(phrase in user_text for phrase in ["像我", "我的照片", "加入", "添加", "包含"]):
                return False, "抱歉呀,为了版权和隐私安全,我暂时不能生成包含人物肖像的壁纸哦~"

    # 检测文字/水印请求
    for keyword in text_keywords:
        if keyword in user_text:
            if any(phrase in user_text for phrase in ["添加", "写上", "显示", "包含", "要有"]):
                return False, "抱歉,目前暂时无法在壁纸上添加文字、水印或 LOGO~"

    return True, None

5.2 无关话题检测

python 复制代码
# wallpaper_flow_service.py

def is_irrelevant_topic(user_text: str) -> bool:
    """判断是否是无关话题(闲聊、知识问答等)"""
    # 壁纸相关关键词
    wallpaper_keywords = [
        "壁纸", "背景", "桌面", "风格", "主题", "画面", "图片",
        "生成", "创作", "设计", "图案", "色彩", "色调",
        "锁屏", "主屏", "横屏", "竖屏", "尺寸", "分辨率",
    ]

    # 检查是否包含壁纸相关关键词
    for keyword in wallpaper_keywords:
        if keyword in user_text:
            return False

    # 常见的无关问题类型
    irrelevant_patterns = [
        "你是谁", "你能做什么", "天气", "新闻", "股票",
        "帮我", "写代码", "翻译", "计算",
    ]
    for pattern in irrelevant_patterns:
        if pattern in user_text:
            return True

    return False


def get_irrelevant_response() -> str:
    """获取无关问题的统一回应"""
    return "我是你的专属壁纸创作助手,只专注于为你生成精美鸿蒙壁纸哦~我们继续设计你的专属壁纸吧!"

5.3 安全策略在流程中的位置

python 复制代码
# agent_routes.py - generate_sse() 中的安全检查

# 1. 内容政策检查(肖像、文字等)
policy_passed, policy_reject_reason = check_content_policy(user_text)
if not policy_passed:
    # 返回拒绝卡片
    reject_event = { ... "text": policy_reject_reason }
    yield f"data: {json.dumps(reject_event)}\n\n"
    return

# 2. 无关话题检查(闲聊、知识问答等)
if is_irrelevant_topic(user_text):
    # 引导回壁纸主题
    irrelevant_event = { ... "text": get_irrelevant_response() }
    yield f"data: {json.dumps(irrelevant_event)}\n\n"
    return

# 3. 通过检查后,继续处理正常流程

5.4 内容安全的交互设计

安全策略被触发时,用户看到的是友好的引导信息而非冷冰冰的错误提示:

违规类型 用户输入示例 系统回复
肖像请求 "加入我的照片" "为了版权和隐私安全,不能生成包含人物肖像的壁纸哦~"
文字请求 "写上'我爱你'" "暂时无法在壁纸上添加文字、水印或 LOGO~"
无关话题 "帮我写代码" "只专注于为你生成精美鸿蒙壁纸哦~"

安全设计原则:内容安全策略应始终返回友好的引导信息而非冷冰冰的错误码,让用户感受到智能体的温度。同时,所有安全检查必须在进入主流程之前完成,避免浪费 API 调用资源。

六、卡片的扩展设计

6.1 卡片数据的可扩展性

当前 cardData 结构预留了扩展字段:

python 复制代码
cardData = {
    "title": "标题文本",
    "items": [{"text": "选项", "value": "value"}],  # selectList
    "canReplay": True,                # 支持重新选择
    # 可扩展字段
    "imageUrl": "...",                # 壁纸展示卡片
    "style": "水墨国风",              # 壁纸元信息
    "theme": "青峦远山",
    "credits_remaining": 5,           # 剩余免费次数
    "has_regenerate": True,           # 是否支持重新生成
}

6.2 未来可扩展的卡片类型

卡片类型 用途 状态
imageGrid 多张壁纸网格展示 未实现
colorPicker 颜色选择器 未实现
progressBar 自定义进度条 未实现
shareCard 壁纸分享卡片 未实现

七、常见问题与解决方案

在前端卡片开发中,常见问题及解决方案如下:

问题 原因 解决方案
卡片不显示 cardNamedisplayType 字段错误 检查 JSON 结构,确保使用 DisplayFaCard
卡片重叠 append 设置为 true 多级选择流程中使用 false
选项点击无响应 前端未正确处理点击事件 确保将选项文本作为用户消息发送
SSE 连接断开 网络超时或服务端异常 前端实现自动重连机制
图片加载失败 imageUrl 无效或过期 检查图片 URL 的有效期

八、最佳实践总结

在鸿蒙壁纸大师的前端卡片交互开发中,以下最佳实践值得参考:

最佳实践 说明 收益
三级选择流程 风格→主题→场景的渐进式引导 降低用户决策成本
卡片替换机制 使用 append: false 替换上一张卡片 保持界面简洁
SSE 事件驱动 实时推送进度和卡片更新 提升用户体验
内容安全前置 在主流程前完成安全检查 避免 API 资源浪费
友好引导设计 违规请求返回引导提示 提升用户满意度
卡片可扩展性 预留字段支持未来扩展 降低维护成本

开发建议 :卡片交互设计应以用户为中心,每一步的选择都应有明确的引导和反馈。同时,合理使用 append 字段控制卡片的替换与追加,避免界面混乱。

九、总结

本文深入解析了鸿蒙壁纸大师的前端卡片交互体系,核心要点:

  1. 三种卡片类型selectList(选择)、wallpaper(展示)、text(文本)
  2. 三级选择流程:风格→主题→场景,每级通过卡片替换推进
  3. SSE 事件驱动:status-update 推送进度,artifact-update 推送卡片
  4. 内容安全策略:肖像检测、文字检测、无关话题过滤,确保合规
  5. 友好的交互设计:违规请求返回引导提示而非错误信息
  6. 卡片可扩展性:预留字段支持未来更多交互类型

如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!

相关资源

相关推荐
顾昂_2 小时前
内存泄漏排查和 Chrome DevTools 使用教程
前端·javascript·面试
IT_陈寒3 小时前
Java线程池这个坑我算是踩明白了
前端·人工智能·后端
31535669133 小时前
Superpowers:GPT-5.6 时代下的流程毒瘤
前端·后端
tyqtyq223 小时前
药品说明书解读 —— AI 安全用药助手,鸿蒙原生应用深度解析
人工智能·学习·华为·生活·harmonyos
清秋4 小时前
全网最全 ECMAScript 攻略( 更新至 ES2026)
前端·javascript·编程语言
风华圆舞4 小时前
鸿蒙HarmonyOS滚字组件实战 —— 逐字 Slot Roll,从浏览器到原生 ArkUI
华为·harmonyos·arkui·slottext·measureutils·共享时间轴·滚字
Cao_Ron4 小时前
用 SVG image symbol 画 ECharts graph 圆形节点
前端
hxy06014 小时前
React-Call让你的React组件像异步函数一样调用
前端·react.js
Hilaku4 小时前
为什么 AI 写前端时,总是优先选择 React,而不是 Vue?
前端·javascript·程序员