Interaction Components
Interaction Components
1. 目标 & 范围
目标:在 AI 访谈流程中,Agent 可通过 render_interaction tool call 按需渲染标准化交互组件;受访者完成交互后,结构化结果经独立 session submit API 回传并持久化,为后续抽取与 evidence 构建提供结构化数据源。
MVP 范围(P0):
- 4 种必做组件:
single_choice(单选)、multiple_choice(多选)、likert(量表)、ranking(排序)。 - Interaction Registry:
InteractionType → React 组件映射表。 - 统一提交行为:
InteractionToolResult格式、跳过(skip)、校验(min/max_items)。 - 组件状态:
disabled/loading/submitted。 - 无障碍(a11y)与移动端适配。
- Unsupported 兜底组件(
UnsupportedInteraction)。
后续范围(P1+,本 PRD 不展开):
nps / rating / matrix / text_input / modal_form / concept_card / comparison / consent / file_upload / image_annotation / task_confirmation。
非目标:
- 组件不直接调用 Agent API;提交统一由
InterviewShell代理。 - 组件不负责 evidence 抽取逻辑(见 06_extraction_evidence)。
- Study Builder 中的组件预览不在本模块范围(见 03_study_builder)。
2. 用户故事
作为受访者(participant),我希望在访谈对话流中看到内嵌的交互卡片,以便快速完成结构化选择而不必离开对话界面。
作为受访者(participant),我希望在不确定或不愿回答时可以跳过组件,以便访谈不因强制填写而中断。
作为受访者(participant),我希望提交后看到轻量摘要(如”你选择了:人工整理成本高”),以便确认自己的选择已被记录。
作为研究人员(researcher),我希望 Agent 渲染的每次交互结果都能持久化并与 transcript 关联,以便在结果页面追溯具体选择与对话上下文。
作为研究人员(researcher),我希望组件校验(如多选最多 3 项)由前端执行,以便减少无效数据进入抽取流程。
作为 Admin/Developer,我希望新增交互类型时只需在 registry 中注册一条映射,以便扩展成本可控。
3. 功能需求
3.1 Interaction Registry
FR-05-1 优先级:P0
前端维护一个 interactionRegistry 映射表,将 InteractionType 字符串映射到对应 React 组件。
- 验收要点:MVP 阶段注册
single_choice → RadioCardGroup、multiple_choice → CheckboxCardGroup、likert → LikertScale、ranking → RankingList四条映射,缺失时回退到UnsupportedInteraction。
FR-05-2 优先级:P0
当 InteractionToolCall.type 不在 registry 中时,渲染 UnsupportedInteraction 兜底组件,显示类型名与提示文案,不阻断对话流。
- 验收要点:传入未注册的
type(如nps)时,页面显示兜底卡片,控制台无未捕获异常,allow_skip为true时兜底组件亦展示跳过按钮。
3.2 MVP 组件:single_choice(单选)
FR-05-3 优先级:P0
RadioCardGroup 根据 InteractionToolCall 中的 options[] 渲染单选卡片列表,每项含 label、可选 description;用户点击一项后高亮选中状态,点击”提交”触发 onSubmit。
- 字段要求:
options[].id(必填)、options[].label(必填)、options[].description(可选)、options[].exclusive(标记互斥兜底,单选组件默认全部互斥,此字段保留用于 schema 一致性)。 - 验收要点:选中项
selected_option_id与selected_label写入value;required: true时未选择不可提交;allow_skip: true时展示跳过按钮;display.mode默认inline_card。
FR-05-4 优先级:P0
RadioCardGroup 提交后切换为 submitted 状态:所有选项变为只读,选中项保持高亮,提交按钮消失,展示轻量摘要文案(如”你选择了:配置复杂”)。
- 验收要点:
submitted状态下无法重新点选;页面上只读展示,不触发二次onSubmit。
3.3 MVP 组件:multiple_choice(多选)
FR-05-5 优先级:P0
CheckboxCardGroup 渲染多选卡片列表;支持 validation.min_items 与 validation.max_items 约束,违反时禁用提交按钮并展示行内错误提示。
- 字段要求:
options[].exclusive(为true时选中该项自动取消其他已选项,其他项选中时自动取消带exclusive的项)。 - 验收要点:超出
max_items时新选项不可勾选(或勾选时弹出提示);未达min_items时提交按钮disabled;exclusive项与普通项互斥逻辑正确。
FR-05-6 优先级:P0
提交时 value.selected_option_ids 为已选 id 数组,顺序按用户选中先后排列;submitted 后展示摘要(如”你选择了 3 项:多项目管理、证据链输出、自定义工具”)。
- 验收要点:
selected_option_ids非空数组,每个元素均为options[].id中的合法值;提交后组件只读。
3.4 MVP 组件:likert(量表)
FR-05-7 优先级:P0
LikertScale 根据 scale.min、scale.max、scale.min_label、scale.max_label 渲染水平分级选择器;MVP 支持 1–5 和 1–7 两种量程;用户点击分值后高亮,可点击”提交”。
- 字段要求:
scale.min(整数,通常为 1)、scale.max(整数,通常为 5 或 7)、scale.min_label(端点文案)、scale.max_label(端点文案)。 - 验收要点:渲染分值点数量 =
scale.max - scale.min + 1;端点标签在两端正确展示;required: true时未选不可提交。
FR-05-8 优先级:P0
提交时 value.score 为用户选中的整数分值;submitted 后展示分值摘要(如”你的评分:4 / 5”)并切换只读。
- 验收要点:
score在[scale.min, scale.max]范围内;组件submitted后不可重新点击。
3.5 MVP 组件:ranking(排序)
FR-05-9 优先级:P0
RankingList 渲染可拖拽排序列表,每项显示 options[].label;支持拖拽(桌面)和上移/下移按钮(移动端兜底)两种交互方式;初始顺序与 options[] 数组一致。
- 字段要求:
options[].id(必填)、options[].label(必填);无额外 ranking 专属校验字段(默认要求所有项都参与排序)。 - 验收要点:拖拽后顺序实时更新;移动端上移/下移按钮正常响应;提交时
ranked_option_ids包含全部options[].id,顺序即排名结果(第 0 位 = 第 1 名)。
FR-05-10 优先级:P0
提交时 value.ranked_option_ids 为排序后 id 数组,长度必须等于 options 数组长度;submitted 后展示序号化摘要(如”1. 结构化抽取 2. Agent Harness …”)并切换只读。
- 验收要点:
ranked_option_ids.length === options.length;没有重复id;submitted状态下拖拽禁用。
3.6 InteractionRenderer 与提交流程
FR-05-11 优先级:P0
InteractionRenderer 接收 InteractionToolCall 对象,查找 registry 获取目标组件并渲染;渲染时向子组件传递 onSubmit 和 onSkip 两个回调,由 InterviewShell 统一执行 API 调用。
- 验收要点:
onSubmit调用时构造完整InteractionToolResult(含interaction_id、type、status: "submitted"、value、submitted_at、client_context)传递给上层;onSkip调用时构造status: "skipped"、value: null的结果。
FR-05-12 优先级:P0
提交结果经 InterviewShell 调用独立 session submit API(POST /api/sessions/{sessionId}/interactions)回传,不走标准 AG-UI tool-result 通道;回传成功后组件切换 submitted 状态,回传失败时展示错误提示并允许重试。
- 验收要点:成功响应后
interaction_results表有对应行(session_id、tool_call_id、interaction_id、status、value、submitted_at均不为空);网络失败时组件可重试,不丢失用户已选值。见 04_interview_runtime 中的 API 调用链。
FR-05-13 优先级:P0
每次 render_interaction tool call 及其 result 均写入 tool_calls 表(tool_input = InteractionToolCall,tool_output = InteractionToolResult);同时写入 interaction_results 表。
- 验收要点:session 结束后
tool_calls中tool_name = "render_interaction"的行数 = 访谈中实际触发的交互次数;每行均有对应interaction_results记录(含跳过)。见 03_data_model。
3.7 组件状态管理
FR-05-14 优先级:P0
所有 MVP 组件须支持以下四种状态,且状态转换单向:idle → (loading →) submitted | skipped;disabled 作为外部属性可在任意状态叠加。
| 状态 | 含义 | UI 表现 |
|---|---|---|
idle | 等待用户操作 | 正常可交互 |
loading | 正在提交 | 按钮 spinner,选项不可点击 |
submitted | 已提交 | 只读,展示摘要 |
skipped | 已跳过 | 折叠或展示”已跳过”标记 |
disabled | 外部禁用(如 session 已结束) | 灰化,不可操作 |
- 验收要点:无法从
submitted退回idle;loading期间二次点击提交无效;disabled覆盖所有其他状态。
3.8 校验
FR-05-15 优先级:P0
前端执行 InteractionToolCall.validation 中的约束:min_items / max_items(多选组件);required(所有组件)。校验失败时禁用提交按钮并展示行内错误文案,不弹 toast。
- 验收要点:
required: true且无选择时,提交按钮disabled,错误文案在卡片内展示;max_items达到上限时后续选项勾选被阻止或显示超限提示;错误状态在用户满足条件后自动清除。
3.9 跳过(Skip)
FR-05-16 优先级:P0
当 allow_skip: true 时,组件展示”跳过”按钮(低优先级样式,区别于主提交按钮);点击后直接构造 status: "skipped" result,组件切换 skipped 状态,不执行任何字段校验。
- 验收要点:
allow_skip: false或字段缺失时,跳过按钮不渲染;跳过后interaction_results.status = "skipped",value为null;跳过 required 字段不阻断访谈,由 extraction 阶段标记needs_review。
3.10 无障碍(a11y)与移动端
FR-05-17 优先级:P0
所有 MVP 组件满足 WCAG 2.1 AA 基础要求:可完整键盘操作(Tab 聚焦、Space/Enter 选择、Arrow 切换选项);选项有语义化 aria-label 或 role;状态变更(已选、错误、只读)通过 aria-* 属性反映。
- 验收要点:使用键盘可完成
single_choice全流程(聚焦 → 选择 → 提交);屏幕阅读器可读出选项文案与当前选中状态;提交成功后焦点移至摘要区域。
FR-05-18 优先级:P0
所有 MVP 组件在 375 px 宽度(iPhone SE 基准)下无横向滚动、无遮挡;RankingList 在移动端以上移/下移按钮替代拖拽(拖拽保留但非主交互)。
- 验收要点:在 375 px viewport 下视觉测试通过;触摸点击区域 ≥ 44 × 44 px;移动端上移/下移按钮有效。
3.11 Timeline 集成
FR-05-19 优先级:P0
交互组件以 card 形式内嵌在 ChatTimeline 中,与 MessageBubble 混排,不弹独立全屏模态(ranking 可选 wide card 展示);submitted / skipped 后组件收缩为轻量摘要行,保持在 timeline 历史中可见。
- 验收要点:timeline 中出现 interaction card 后不阻断滚动;提交/跳过后 card 转为摘要,不影响后续 Agent 消息渲染;同一 timeline 内不同 interaction 相互独立。
4. 关键流程
4.1 主流程:Agent 触发 → 组件渲染 → 用户提交
1. Agent Harness(LangGraph)决定调用 render_interaction tool2. 构造 InteractionToolCall(含 type / options / validation / research_intent …)3. 经 AG-UI 协议以 tool call 事件推送至前端4. assistant-ui generative UI 捕获事件,InteractionRenderer 查 registry 获取组件5. 渲染对应组件(RadioCardGroup / CheckboxCardGroup / LikertScale / RankingList)6. 用户与组件交互(选择 / 排序)7. 前端执行 validation;通过后启用提交按钮8. 用户点击提交 → 组件切换 loading 状态9. InterviewShell 调用 POST /api/sessions/{sessionId}/interactions10. 后端写入 interaction_results + tool_calls(tool_output)11. 返回 200 → 前端切换 submitted 状态,展示摘要12. InterviewShell 将 InteractionToolResult 以 tool result 事件回传 Agent13. Agent 继续对话(follow-up 或下一步)4.2 跳过流程
用户点击"跳过" → 组件切换 loading → InterviewShell 提交 status: "skipped" → 写库 → 组件切换 skipped→ Agent 收到 skipped result → 继续访谈(视 required 级别决定是否追问)4.3 Unsupported 兜底流程
InteractionRenderer 查 registry → 未命中 → 渲染 UnsupportedInteraction→ 展示"当前版本不支持此题型({type}),请跳过"→ allow_skip: true 时自动展示跳过按钮→ 跳过后正常写 skipped result协议消费细节见 04_interview_runtime。
5. 数据
5.1 核心表
本模块写入以下两张表(完整 DDL 见 03_data_model):
interaction_results(主结果表)
| 字段 | 类型 | 说明 |
|---|---|---|
id | UUID PK | — |
session_id | UUID FK → sessions | 所属 session |
tool_call_id | UUID FK → tool_calls | 对应 render_interaction tool call |
interaction_id | TEXT | 与 InteractionToolCall.interaction_id 一致 |
interaction_type | TEXT | single_choice / multiple_choice 等 |
status | TEXT | submitted / skipped / cancelled / expired |
value | JSONB | 结构化结果(见各组件字段定义) |
client_context | JSONB | duration_ms / changed_count / device |
submitted_at | TIMESTAMPTZ | 客户端提交时间 |
tool_calls(tool call 记录,interaction 作为其子集)
tool_name = "render_interaction"tool_input= 完整InteractionToolCallJSONtool_output= 完整InteractionToolResultJSON(提交后回填)
5.2 各组件 value schema
| 组件 | value 字段 |
|---|---|
single_choice | { selected_option_id: string, selected_label: string } |
multiple_choice | { selected_option_ids: string[] } |
likert | { score: integer } |
ranking | { ranked_option_ids: string[] } |
| 跳过(任意类型) | null |
6. 接口
6.1 提交接口
POST /api/sessions/{sessionId}/interactions请求体为 InteractionToolResult(见 03_interactive_ui_protocol):
{ "interaction_id": "int_primary_pain_001", "type": "single_choice", "status": "submitted", "value": { "selected_option_id": "manual_analysis", "selected_label": "人工整理成本高" }, "submitted_at": "2026-06-22T10:30:00Z", "client_context": { "duration_ms": 5400, "device": "desktop" }}响应:200 OK(含写入后的 interaction_result.id);400(validation 失败);409(该 interaction_id 在本 session 已提交)。
本接口为独立 REST 接口,非 AG-UI tool-result 通道。AG-UI tool result 事件由后端在写库成功后构造并推送,前端不直接发送。详见 05_implementation/02_api_contracts。
6.2 AG-UI 事件关联
- Agent 触发交互:
ToolCallStart+ToolCallArgs事件(tool_name = "render_interaction")。 - 后端收到提交结果后推送:
ToolCallEnd事件(含InteractionToolResult)。 - 前端 assistant-ui 的 generative UI 监听
ToolCallArgs映射到对应组件渲染,见 04_interview_runtime。
7. 验收标准
AC-05-1:传入合法 single_choice InteractionToolCall,RadioCardGroup 正确渲染所有 options,选中一项后点击提交,interaction_results 表中出现 status = "submitted"、value.selected_option_id 正确的行。
AC-05-2:传入 multiple_choice 并设 validation.max_items = 3,用户尝试选第 4 项时被阻止(或显示超限提示),提交按钮保持 disabled;选 1–3 项后可正常提交,value.selected_option_ids 长度在 [min_items, max_items] 范围内。
AC-05-3:传入 likert(scale 1–5),用户点击分值 4,提交后 interaction_results.value.score = 4,组件切换只读展示”你的评分:4 / 5”。
AC-05-4:传入 ranking(5 个选项),用户拖拽调整顺序后提交,interaction_results.value.ranked_option_ids 为长度 5 的数组,包含全部 options[].id 且无重复,顺序与用户最终排列一致。
AC-05-5:传入 allow_skip: true 的任意组件,点击跳过,interaction_results.status = "skipped",value = null;传入 allow_skip: false(或字段缺失)时,跳过按钮不渲染。
AC-05-6:传入 required: true 且未做任何选择时,提交按钮为 disabled,行内展示校验提示文案;完成选择后按钮恢复可用。
AC-05-7:提交成功后组件进入 submitted 状态:所有选项只读,提交/跳过按钮消失,展示摘要文案;无法通过点击恢复到 idle 状态。
AC-05-8:提交 API 请求失败(模拟 500)时,组件退回 idle(非 submitted),展示错误提示,用户可重试提交,已选值保留。
AC-05-9:传入 registry 中不存在的 type(如 nps),页面渲染 UnsupportedInteraction 兜底卡片,allow_skip: true 时展示跳过按钮,控制台无未捕获异常。
AC-05-10:使用键盘(无鼠标)完成 single_choice 全流程:Tab 聚焦组件 → Arrow 切换选项 → Space 确认 → Tab 到提交按钮 → Enter 提交;屏幕阅读器可读出选项文案与当前选中状态。
AC-05-11:在 375 px 宽度视口下,所有 MVP 组件无横向滚动,触摸区域有效,ranking 上移/下移按钮正常响应。
AC-05-12:tool_calls 表中每次 render_interaction 记录在提交/跳过后 tool_output 不为空;interaction_results 表中每条记录均可通过 tool_call_id 关联到 tool_calls。
AC-05-13:同一 session 中多个 interaction 依次渲染,前一个 submitted 后不影响后续 interaction 的 idle 状态;timeline 中历史 submitted card 保持可见(只读摘要形式)。
8. 边界 & 非目标
- 不做:组件不直接发送 AG-UI tool result 事件;结果提交统一经 session submit API,后端负责构造并推送
ToolCallEnd事件。 - 不做:组件不持有 Agent 对话状态;
InterviewShell负责 session 生命周期管理(见 04_interview_runtime)。 - 不做:MVP 不实现组件级多语言(i18n);
title/label由 Agent 在 tool call 中直接传入目标语言文案。 - 不做:MVP 不做 Admin 端的交互组件预览(Study Builder 的 preview 功能属于 03_study_builder)。
- 不做:MVP 不实现
expired状态(session 超时后组件自动失效),留作 P1。 - 边界:
display.confirm_before_submit字段在协议中已定义,MVP 默认忽略(不弹二次确认弹窗),P1 实现。 - 边界:
display.mode: "modal"在协议中已定义,MVP 仅实现inline_card;ranking可用 wide card,不做独立 modal。
9. 依赖 & 风险
9.1 依赖
| 依赖 | 说明 | 关联模块 |
|---|---|---|
| AG-UI 协议 / assistant-ui | generative UI 驱动 InteractionRenderer;ToolCallArgs 事件须正确传递 InteractionToolCall | 04_interview_runtime |
| session submit API | POST /api/sessions/{sessionId}/interactions 须在 Interview Runtime 实现 | 04_interview_runtime,05_implementation/02_api_contracts |
interaction_results / tool_calls 表 | 数据持久化基础,DDL 已在数据模型中定义 | 03_architecture/03_data_model |
| extraction 阶段 | interaction_results 中 cite_selection evidence 由抽取模块消费 | 06_extraction_evidence |
InteractionToolCall schema(schema_version: "2026-06-01") | 前端渲染依赖协议字段稳定 | 03_interactive_ui_protocol |
9.2 风险
| 风险 | 可能性 | 影响 | 缓解措施 |
|---|---|---|---|
| AG-UI tool call 事件与组件渲染时序不稳定(streaming 中断) | 中 | 高(组件无法渲染) | InterviewShell 维护 pending tool calls 队列;streaming 断开时组件保持 idle 并提示重试 |
InteractionToolCall schema 字段变更导致渲染失败 | 低 | 中 | 锁定 schema_version;前端对未知字段做 graceful ignore;UnsupportedInteraction 兜底 |
移动端拖拽体验不佳导致 ranking 可用性下降 | 高 | 中 | MVP 提供上移/下移按钮作为主交互替代方案,拖拽为增强 |
| 提交 API 与 AG-UI 事件回传顺序不一致导致 Agent 重复追问 | 中 | 中 | 后端在写库成功后才推送 ToolCallEnd;前端 submitted 后不重发 |
| 组件类型扩展导致 registry 维护成本上升 | 低 | 低 | Registry 设计为声明式映射,新增类型只需一行注册,无需修改 InteractionRenderer |