PRD: Chatbot 消息操作栏与平台转发能力
1. 概述
1.1 背景
Chatbot H5 页面目前仅支持文字对话交互,用户无法对 AI 回复的消息进行二次操作(重试、语音播放、复制、转发)。参照微信小程序已实现的"长按消息→操作栏→转发到微信聊天"模式,本次将补齐 H5 端的消息操作能力,并实现对飞书平台的转发支持。
1.2 目标
- 为 Chatbot H5 所有 AI 回复消息增加消息操作栏
- 支持 4 个核心操作:重新生成、语音播放、复制、转发
- 转发能力适配飞书环境(自建应用内 H5),实现与微信小程序一致的"缩略卡片→点击进入"体验
- 输出跨平台(微信/飞书)转发的架构方案,便于后续扩展
1.3 用户故事
作为 Chatbot 用户
我想要 对 AI 回复的消息进行重新生成、语音播放、复制、转发
以便 把有用的信息分享给同事,或者重新获取更满意的回答
2. 功能需求
2.1 触发方式
| 操作 | 触发方式 | 说明 |
|---|---|---|
| 显示操作栏 | 长按 AI 消息文本(约 600ms) | 松手后操作栏在该消息底部淡入 |
| 隐藏操作栏 | 点击页面空白处 | 或点击其他消息的操作栏会切换显示 |
2.2 操作栏 UI
┌─────────────────────────────────────────────┐
│ AI 生成,仅供参考 🔄 🔊 📋 ↗️ │
└─────────────────────────────────────────────┘
↑ 左侧提示文字 ↑ 右侧 4 个功能图标
(轻量文案) 重新生成 语音 复制 转发- 操作栏宽度通栏,不局限于消息气泡宽度
- 左侧灰色小字:"AI 生成,仅供参考"
- 右侧 4 个图标平铺展示
2.3 功能按钮说明
| 按钮 | 图标 | 交互 | 备注 |
|---|---|---|---|
| 重新生成 | 🔄 | 删除当前 AI 回复,重新发送上一条用户指令 | 详见第 7 节 Dify 后端实现分析 |
| 语音播放 | 🔊 | 调用系统 TTS 朗读消息文本(中文语音) | 点击后播放,再次点击其他消息切换播放 |
| 复制 | 📋 | 复制消息纯文本到系统剪贴板 | 成功后弹出 toast "已复制到剪贴板" |
| 转发 | ↗️ | 详见第 3 节 | 核心能力 |
3. 转发功能设计
3.1 产品目标
一句话描述: 实现"参考微信小程序的消息转发能力,适配飞书平台",用户长按 AI 消息后,通过转发按钮将聊天内容(单条或多条)以飞书小程序卡片形式分享给飞书联系人或群聊。
3.2 核心交互流程
用户 A 在 Chatbot H5 页面
↓ 长按消息 → 点击转发按钮
↓
系统判断当前环境
├── 微信小程序环境 → wx.shareAppMessage()
├── 飞书自建应用环境 → tt.shareAppMessage()
└── 纯 H5 环境 → navigator.share() / 复制链接 兜底
↓
拉起平台原生联系人/群聊选择器
↓
发送 → 接收方看到一张缩略卡片
↓ 点击卡片
接收方进入对应小程序/应用,查看完整消息内容3.3 转发的内容模型
每条消息需携带类型元数据,转发时根据类型决定缩略卡片如何展示:
消息类型枚举:
- text → 纯文本对话
- room-card → 会议室预定卡片
- success-card → 操作成功卡片(预定成功等)
- device-card → 设备控制状态
- prep-card → 主动整备通知
- cite-card → 引用来源卡片
- multi → 多条消息聚合转发
缩略卡片展示规则(以飞书为例):
├── text → 标题:"AI 智能助手" 摘要:消息前 50 字
├── room-card → 标题:"会议室预定" 摘要:会议室名称+时间
├── success-card→ 标题:"操作成功" 摘要:核心结果文案
└── multi → 标题:"智能助手 · N 条消息" 摘要:各条摘要预览3.4 多消息转发
| 场景 | 行为 |
|---|---|
| 单条转发 | 按上表规则渲染缩略卡片 |
| 多条消息组合转发 | 打包为一张"聚合卡片",标题显示"智能助手 · N 条消息",摘要展示各条消息的简短预览 |
| 消息+图片混合 | 缩略卡片以文字摘要为主,图片在"进入应用后"完整展示 |
3.5 平台差异与适配原则
| 场景 | 微信 | 飞书 |
|---|---|---|
| 环境 | 微信小程序 | 飞书自建应用内 H5(使用 tt.xxx API) |
| 转发 API | wx.shareAppMessage() | tt.shareAppMessage() |
| 缩略卡片 | 小程序卡片(封面+标题+路径) | 小程序卡片(封面+标题+路径) |
| 点击跳转 | 打开小程序指定页面 | 打开自建应用指定页面 |
| 体验一致性 | ✅ 基准体验 | ✅ 和微信完全一致 |
核心结论: 微信和飞书在"缩略卡片→点击进入"的体验上高度一致。开发时可用适配器模式封装,对外暴露统一
.forward(),内部分派到不同平台的 API。
4. 实现建议(给研发参考)
4.1 消息数据结构化
现有 appendBotMessage(htmlContent) 改为携带类型和结构化数据:
每条消息 DOM 节点附加 data 属性:
data-type → 消息类型枚举
data-payload → 结构化数据 JSON(用于转发时重新渲染缩略卡片摘要)4.2 跨平台适配层
ForwardManager
├── detect() // 检测运行环境
├── setAdapter(type) // 注入平台适配器
└── forward(msg) // 对外统一接口
适配器接口:
WeChatAdapter.forward(msg) → wx.shareAppMessage({...})
FeishuAdapter.forward(msg) → tt.shareAppMessage({...})
FallbackAdapter.forward(msg) → navigator.share() / 复制链接5. 附录:飞书转发实现形式参考资料
A. 小程序卡片转发(推荐方案)
通过 tt.shareAppMessage() 实现,体验与微信小程序卡片完全一致:
- 接收方看到一张缩略卡片(封面图 + 标题 + 摘要)
- 点击后进入飞书自建应用,加载对应页面
- 开发成本最低,与微信 API 一一对应
B. 消息卡片转发(未来可考虑升级)
通过飞书消息卡片(Message Card)实现,内容直接在飞书聊天中展开:
- 接收方直接在聊天中看到完整内容,无需点击跳转
- 可交互:卡片上可嵌入按钮(确认/取消等)
- 多条消息可拼成一张聚合卡片
- 缺点:需将 H5 组件翻译成飞书 Card JSON 格式,开发成本较高
- 建议:可作为 2.0 体验升级项
C. 截图兜底
若平台不支持小程序卡片或消息卡片,可截取消息区域为图片发送:
- 依赖
html2canvas等截图库 - 适用范围最广(任何平台可用)
- 缺点:不可交互,图片质量受限
7. "重新生成"的 Dify 后端实现分析
7.1 背景说明
后端基于 Dify 平台搭建,LLM 调用经由 Agent 路由层分发到多个 Sub-Agent(如会议室预定 Agent、设备控制 Agent、闲聊 Agent 等)完成回答。每条消息的生成涉及 Dify 的对话记忆(Memory)机制。
7.2 Dify 现状约束
Dify 的 POST /chat-messages API 具有以下特点:
| 能力 | 支持情况 |
|---|---|
| 发送消息到已有会话 | ✅ — 传入 conversation_id 自动追加对话历史 |
| 获取会话历史 | ✅ — GET /messages |
| 删除整个会话 | ✅ — DELETE /conversations/:id |
| 删除单条消息 | ❌ — 不支持 |
| 重新生成 / 忽略上轮回答 | ❌ — 无原生支持 |
| 自定义传入对话历史 | ❌ — 不支持(Memory 由 Dify 内部管理) |
这意味着:Dify 的对话记忆是黑盒的,一旦消息写入会话,无法从中途"弹出"某一条。
7.3 方案对比
由于架构中存在多 Sub-Agent 路由,选择"重新生成"的实现方案时需要特别考虑对路由层和各 Agent 的影响。
方案 A:同会话重发 ❌(不推荐)
点击重新生成 → 相同 conversation_id 重新发 query → Dify 带上全部历史(含上轮回答)发 LLM| 考量 | 评价 |
|---|---|
| 实现成本 | ★ 低 — 前端改几行代码即可 |
| Sub-Agent 影响 | ★ 极差 — 所有 Sub-Agent 的 system prompt 都要增加 "用户可能触发重新生成,请忽略自己之前的回答" 相关指令 |
| 稳定性 | ★★ — LLM 仍可能受上轮回答影响,产生"我刚才已经回答过了"等异常回复 |
| 上下文完整性 | ★ — 累计的 RAG 引用、工具调用结果都会重入上下文,可能干扰新回答 |
结论: 因 Sub-Agent 数量多且每个 Agent 的 prompt 设计独立,方案 A 需要在每个 Agent 中增加一致性约束,维护成本高且不可靠。不推荐。
方案 B:删除会话 + 历史回放 ✅(推荐)
1. GET /messages → 拉取该会话全部消息历史
2. DELETE /conversations/:id → 删除原会话
3. 逐条回放历史消息(跳过最后一轮 user+assistant)
4. 重发用户当前 query| 考量 | 评价 |
|---|---|
| 实现成本 | ★★ 中 — 需在 Dify 上层封装一个回放逻辑 |
| Sub-Agent 影响 | ★ 无影响 — Sub-Agent 完全无感知,和首次提问一样 |
| 稳定性 | ★★★★ — 上下文完全干净,LLM 不会看到旧回答 |
| 上下文完整性 | ★★★★ — 保留重新生成前的所有历史上下文,仅去掉最后一轮 |
| 性能影响 | 回放 N 轮需要 N 次 Dify API 调用,但 N 通常较小(5-10 轮) |
| Token 额外消耗 | 回放不产生回答 token,仅消耗 prompt token(约 20-50 条系统 prompt) |
回放实现示例(伪代码):
function regenerate(conversationId, userId):
// 1. 获取全部历史
history = GET /messages?conversation_id=conversationId
// 2. 去掉最后一轮 user+assistant
truncated = history[0..-2] // 移除最后一条 user query 和 assistant answer
// 3. 删除旧会话
DELETE /conversations/conversationId
// 4. 回放历史(重建新会话)
newConversationId = null
for each msg in truncated:
result = POST /chat-messages {
query: msg.query,
conversation_id: newConversationId,
...
}
newConversationId = result.conversation_id
// 5. 发送重新生成的 query(使用新会话 ID)
return POST /chat-messages {
query: lastUserQuery,
conversation_id: newConversationId,
...
}方案 C:前端管理上下文(备用方案)
- 关闭 Dify 内置的 Memory
- 对话历史由前端/业务层维护,通过 Dify Workflow 的
inputs传入 - 重新生成时,前端直接裁剪历史后发送
| 考量 | 评价 |
|---|---|
| 实现成本 | ★★★ 高 — 需改造 Dify 应用为 Workflow 模式,关闭 Memory |
| Sub-Agent 影响 | ★★★ — 需按新架构重新设计 Agent 路由 |
| 灵活性 | ★★★★★ — 前后端完全控制上下文 |
| 迁移风险 | ★★★ — 与当前 Dify 基础对话模式不兼容 |
7.4 推荐结论
生产阶段推荐方案 B(删除会话 + 历史回放)。 理由是:
- Sub-Agent 完全无感知,不需要修改任何 Agent 的 system prompt
- 上下文干净,回答质量和首次提问一致
- 实现相对简单,只需在 Dify Service 层封装一个
regenerate方法 - 额外 token 消耗在可接受范围内(回放 N 轮约等于 N 次无回答的请求)
| 阶段 | 方案 |
|---|---|
| 原型验证(当前) | 方案 A(同会话重发,快速示意) |
| 正式开发 | 方案 B(删除回放) |
| 长期演进 | 可评估方案 C(如需更精细的上下文控制) |
