SA 空间预约:查询与取消功能设计 (Query & Cancel PRD)
版本: v1.0 日期: 2026-06-01 状态: 初始设计稿 目标: 补齐"查询我的会议预定"和"取消会议预定"两个操作,完成会议预定功能闭环。
1. 设计背景与前提
1.1 现状
当前 SA 空间预约仅支持正向会议预定流程(Slot Filling → Time Pipeline → Resource Pipeline → 预定确认),查询和取消两个能力完全缺失,用户无法在系统内查看已有的会议预定,也无法取消。
1.2 产品约束
| 约束项 | 说明 |
|---|---|
| 首个潜在落地客户 | 奇瑞(使用飞书作为办公平台) |
| 产品形态 | 飞书工作台轻应用(小程序/H5) |
| 能力边界 | 不做重、不复制飞书日历功能;编辑/修改通过 AppLink 跳转飞书原生日历处理 |
| 目标 | 轻量闭环:查得到、取消得掉,编辑走飞书 |
1.3 设计原则
- 轻量化:查询+取消仅做最简功能,无参会人管理、无会议群、无复杂编辑
- 飞书优先:编辑/修改→AppLink 跳飞书日历;取消→我们自己做(飞书无取消 API)
2. 功能定义
2.1 查询我的会议预定 (Query My Bookings)
用户故事:作为员工,我希望能快速查看我最近预定的会议室,以便掌握自己的日程安排。
查询范围:默认展示配置的默认查询范围的预定(当前默认未来 7 天)。用户可指定日期缩小范围。
筛选维度:
| 维度 | 说明 |
|---|---|
| 时间范围 | date_from + date_to,默认未来 7 天;支持指定单日或日期范围 |
展示上限:最多展示 20 条会议记录,超出截断不显示。组件采用固定高度 + 内部滚动(max-height: 320px),避免撑开聊天区域。房间名仅展示会议室名称,不带路径。
2.2 取消会议预定 (Cancel Booking)
用户故事:作为员工,我希望能在查看我的预定时直接取消不需要的会议,以便释放会议室资源。
交互方式:行内快捷取消(每行显示 [✕] 按钮)+ 二次确认 Sheet。
无需详情卡片,采用行内取消方案。如需编辑会议(如修改时间、添加参会人),点击蓝色会议名称通过 AppLink 跳转飞书日历处理。
取消时限:会议未结束时(当前时间 < 结束时间) 可取消,[✕] 可点击;已结束的会议 [✕] 置灰。按结束时间而非开始时间判断,允许会中提前散会释放房间资源。
主流日历产品(Google Calendar、飞书日历等)均不设取消时限,会议结束后仍可取消。我们的会议室预定场景额外增加了"已结束会议置灰"规则,避免无意义的操作入口。
3. 交互原型 (ASCII Prototype)
3.1 入口:对话触发
(Agent 响应查询)
┌──────────────────────────────────────────────┐
│ 📋 您的会议预定 (共3场) │
│ ┌──────────────────────────────────────┐ │
│ │ 今天 06/01 │ │
│ │ ──────────────────────────────── │ │
│ │ 09:00-10:00 火星厅 站立会① [✕] │ │ ← 未结束,可取消
│ │ 10:00-11:00 305 研发周会① [✕̲]│ │ ← 已结束,✕ 置灰
│ │ 14:00-16:00 总裁办 客户评审① [✕] │ │ ← 会中,可取消
│ └──────────────────────────────────────┘ │
│ ╱ 最大高度 320px,超出内部滚动 ╲ │
│ │
│ ① 蓝色可点击,点击拉起飞书日历编辑 AppLink │
│ [✕] 仅未结束的会议可取消(含会中),已结束置灰 │
└──────────────────────────────────────────────┘3.2 取消:二次确认 Sheet
用户点击 [✕] 后弹出底部 Sheet:
┌──────────────────────────────────────────────┐
│ ⚠️ 确认取消会议 │
│ │
│ ┌────────────────────────────────────────┐ │
│ │ │ │
│ │ 主题:研发周会 │ │
│ │ 时间:今天 10:00 - 11:00 │ │
│ │ 地点:火星厅 │ │
│ │ 预定人:强哥 │ │
│ │ │ │
│ │ 取消后无法恢复,请谨慎操作 │ │
│ │ │ │
│ └────────────────────────────────────────┘ │
│ │
│ [确认取消] [暂不取消] │
└──────────────────────────────────────────────┘3.3 取消:结果反馈
取消成功:
小程序原生 wx.showToast({ title: '已取消', icon: 'success' })
1.5s 后 Toast 自动消失,列表刷新(条目移除/变灰)
取消失败:
小程序原生 wx.showToast({ title: '取消失败', icon: 'error' })
Toast 消失后列表保持当前状态3.4 空结果
(AI 话术回复,无组件)
"当前查询范围内没有会议预定"4. Slot 定义与 Prompt 设计
4.1 查询槽位
{
"intent": "my_bookings",
"date_from": "yyyyMMdd | null",
"date_to": "yyyyMMdd | null"
}| 槽位 | 必填 | 说明 | 示例 |
|---|---|---|---|
intent | 是 | L2 意图标志,固定为 my_bookings | my_bookings |
date_from | 否 | 查询起始日期;null 默认为今天 | 20260601 |
date_to | 否 | 查询截止日期;null 默认为今天+7 天 | 20260607 |
4.2 Slot Filling Prompt(设计注意)
QUERY 与 BOOK 共用同一个 Slot Filling Prompt,由 intent 字段区分输出结构。技术实现上需注意:
- QUERY 场景不新增 LLM 节点,避免额外延时
- 同一 Prompt 内通过
intent: "my_bookings"区分查询场景,BOOK 字段全填 null - 下游 Dify 根据
intent字段分流:"my_bookings"→ Query Pipeline;其他值 → Time + Resource Pipeline
具体 Prompt 格式与字段映射由技术团队在 Dify 编排时确定,以现有 Prompt 模板为基础扩展即可。
4.3 多智能体路由(MAS)
查询与取消功能不单独拆分为新 Sub-Agent,而是在会议预定 Agent(Meeting Specialist) 内部新增 L2 意图分支:
Master Agent (L1 路由)
├── 会议预定 Agent (Meeting Specialist)
│ ├── L2: BOOK (已有)
│ └── L2: QUERY (新增) ← 取消是前端组件交互,LLM 不感知
├── 设备控制 Agent
└── 工单建单 AgentL1 路由关键词扩展:
| 触发关键词 | L1 路由 | L2 意图 | 处理函数 |
|---|---|---|---|
查一下/查看/看看/有什么会/我的预定/我的会议/我的会议预定 | → 会议预定 Agent | QUERY → my_bookings | renderBookingListWithCancel() |
注意:
查看等关键词需优先匹配 QUERY 意图,避免被其他 Agent 拦截。
5. 编排流程 (Dify Workflow)
5.1 整体编排图
5.2 Query Pipeline 逻辑
输入: { date_from, date_to }
1. 确定查询范围
- date_from = null → 默认为今天
- date_to = null → 默认为配置的默认查询天数(当前为今天+7天)
- 最终查询范围 = [date_from 00:00, date_to 23:59]
2. 调用业务系统 API
- GET /my_bookings?user_id={current_user}&from={start}&to={end}
3. 返回列表
- 列表为空 → 话术回复 "当前查询范围内没有会议预定"
- 列表非空 → 按日期分组,按时间排序 → 前端渲染(最多展示 20 条,超出截断并提示)5.3 取消操作逻辑(纯工程化,无 LLM 参与)
取消由前端组件处理,LLM 不感知、不参与。
触发条件: 用户在预定列表卡片中点击某条预定的 [✕]
1. 前端判断: 会议结束时间 > 当前时间 → [✕] 可点击(未结束);否则 [✕] 置灰(已结束)
2. 弹出确认 Sheet(展示会议详情: 主题/时间/地点)
3. 用户点击"确认取消"
- 调用 API: POST /cancel_booking
{ booking_id, user_id, event_id }
4. 结果处理
- 成功 → wx.showToast({ title: '已取消', icon: 'success' }) → 1.5s 后列表自动刷新
- 失败 → wx.showToast({ title: '取消失败', icon: 'error' }) → 列表保持不变6. 飞书 AppLink 集成
6.1 交互方案
会议主题(topic)显示为蓝色可点击文字,点击后通过飞书 AppLink 拉起原生日历编辑页。
┌──────────────────────────────────────────────┐
│ 10:00-11:00 火星厅 研发周会① [✕] │
│ ↑ │
│ 蓝色可点击,样式: color:#3370ff │
│ 点击 → 飞书日历编辑 AppLink │
└──────────────────────────────────────────────┘设计意图:无需额外详情页,编辑入口自然嵌入列表行。"取消"走我们自己的轻量确认 Sheet,"编辑"交给飞书原生能力,各司其职。
6.2 AppLink 格式
https://applink.feishu.cn/client/calendar/event/detail
?calendarId=primary
&key={event_id}
&originalTime=0
&startTime={timestamp}| 参数 | 来源 |
|---|---|
event_id | 查询 API 返回的 event_id 字段 |
timestamp | 会议开始时间戳(秒级),由 start_time 转换 |
6.3 多平台适配
| 平台 | 处理方式 |
|---|---|
| 飞书 | 直接使用上述 AppLink 拉起飞书日历编辑页 |
| 钉钉 | 暂不实现,降级为仅展示文案(钉钉 AppLink 格式待验证) |
| 自有系统 | 暂不实现,降级为仅展示文案(无移动端) |
7. API 接口契约
7.1 查询我的会议预定
GET /api/v1/bookings/my
?user_id={string}
&from={ISO8601}
&to={ISO8601}
&limit={int} // 可选,默认 20,最大 50
Response:
{
"bookings": [
{
"booking_id": "string",
"event_id": "string", // 飞书/钉钉 日程ID
"room_name": "string", // 房间展示名,已去除路径(如"火星厅"而非"F8-305火星厅")
"room_id": "string",
"topic": "string",
"start_time": "ISO8601",
"end_time": "ISO8601",
"status": "confirmed | cancelled"
}
]
}
room_name由后端清洗后返回,前端直接展示,不做二次处理。
7.2 取消会议预定
POST /api/v1/bookings/cancel
Request:
{
"booking_id": "string",
"user_id": "string" // 必须为预定本人,后端校验
}
Response:
{
"success": true,
"message": "string"
}后端必须校验
user_id为该预定的预定人(创建者),非本人取消返回 403。
8. 异常场景与话术
| 场景 | 触发条件 | Agent 话术/UI 表现 |
|---|---|---|
| 无会议 | 查询范围为空 | 话术回复 "当前查询范围内没有会议预定" |
| 会议已结束 | 当前时间 ≥ 结束时间 | [✕] 置灰不可点击,无 LLM 话术 |
| API 超时 | 查询/取消接口超时 | "系统繁忙,请稍后再试" |
9. 测试用例
9.1 查询用例
A. 泛化查询(不指定时间→默认查询范围)
| ID | 用户输入 | 预期 Slot | 预期结果 |
|---|---|---|---|
| Q-00 | "查一下我的会议预定" | {date_from:null, date_to:null} | 默认查询范围全部会议 |
| Q-01 | "我的会议" | {date_from:null, date_to:null} | 同上 |
| Q-02 | "我的会议预定" | {date_from:null, date_to:null} | 同上 |
| Q-03 | "查一下我的日程" | {date_from:null, date_to:null} | 同上(注:仅限会议预定范围,非全量日程) |
B. 按指定日期
| ID | 用户输入 | 预期 Slot | 预期结果 |
|---|---|---|---|
| Q-05 | "今天有什么会" | {date_from:today, date_to:today} | 展示今天会议 |
| Q-06 | "今天的会议安排" | {date_from:today, date_to:today} | 同上 |
| Q-07 | "我今天有哪些会" | {date_from:today, date_to:today} | 同上 |
| Q-08 | "明天有什么会" | {date_from:tomorrow, date_to:tomorrow} | 展示明天会议 |
| Q-09 | "看看明天的会" | {date_from:tomorrow, date_to:tomorrow} | 同上 |
| Q-10 | "查一下6月10号的会" | {date_from:20260610, date_to:20260610} | 展示该日会议 |
| Q-11 | "查一下2026年6月10号的会" | {date_from:20260610, date_to:20260610} | 同上 |
C. 按日期范围
| ID | 用户输入 | 预期 Slot | 预期结果 |
|---|---|---|---|
| Q-12 | "这周有什么会" | {date_from:this_monday, date_to:this_sunday} | 展示本周全部会议 |
| Q-13 | "下周的会议" | {date_from:next_monday, date_to:next_sunday} | 展示下周全部会议 |
| Q-14 | "下周一到周三有什么会" | {date_from:next_monday, date_to:next_wednesday} | 展示下周一至三会议 |
| Q-15 | "查一下6月10号到6月20号的会" | {date_from:20260610, date_to:20260620} | 展示该区间会议 |
| Q-16 | "这周五的会" | {date_from:this_friday, date_to:this_friday} | 展示本周五会议 |
D. 边界场景
| ID | 用户输入 | 条件 | 预期行为 |
|---|---|---|---|
| Q-17 | "查一下我的会议预定" | 查询范围无任何会议 | 话术回复"当前查询范围内没有会议预定" |
| Q-18 | "查一下我的会议预定" | 全部会议已取消/已过期 | 话术回复"当前查询范围内没有会议预定" |
| Q-19 | "上周的会" | 日期在过去 | 按上周日期查,过去会议照常展示 |
9.2 取消用例
| ID | 前置条件 | 操作路径 | 预期行为 |
|---|---|---|---|
| C-00 | 列表中有会议 | 查询列表 → 点击 [✕] → 确认取消 | 成功 → Toast "已取消:研发周会",列表自动刷新 |
| C-01 | 列表中有会议 | 查询列表 → 点击 [✕] → 暂不取消 | Sheet 关闭,列表保持不变 |
| C-02 | 列表中有会议 | 确认取消 → API 返回失败 | Toast "取消失败,请重试",列表保持不变 |
| C-03 | 列表中有会议 | 确认取消 → 网络超时 | Toast "网络异常,请稍后再试",列表保持不变 |
| C-04 | 会议已结束(当前 ≥ 结束时间) | 查看列表 | [✕] 置灰不可点击 |
| C-05 | 会议未结束(当前 < 结束时间) | 查看列表 | [✕] 可点击,含会中状态 |
10. 待决议题
| 议题 | 状态 | 决策 |
|---|---|---|
| 查询取消是否独立 Sub-Agent | ✅ 已定 | 不拆分,并入会议预定 Agent 内作为 L2 意图 |
| 行内取消 vs 详情页取消 | ✅ 已定 | 行内 [✕] + 确认 Sheet,不做详情页 |
| 查询默认范围 | ✅ 已定 | 默认查询范围(当前为今天+7天),可配置 |
| AppLink 是否 MVP 实现 | ✅ 已定 | 飞书场景直接实现,会议主题蓝色可点击跳转编辑 |
| 取消后是否通知参会人 | ❌ 不做 | 通知是飞书日历自身的能力,本功能不涉及 |
| 取消后是否回滚整备 | ❌ 不做 | 整备 Agent 有独立的日程扫描逻辑,无需联动处理 |
