docs(plan): add implementation plan for dialogue-driven content synthesis and hierarchical intent funnel
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,600 @@
|
||||
# 对话驱动受控内容产出、分级意图消解与交互卡片实施计划
|
||||
# (Dialogue-Driven Content Synthesis, Hierarchical Intent Funnel & Interactive Cards Implementation Plan)
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** 构建水木东方医疗器械 AI 协同系统的对话驱动内容产出闭环,实现四级意图漏斗消解、智能语义定位段落批注、受控状态机权限门禁及带时效/防重/可反悔的对话交互卡片。
|
||||
|
||||
**Architecture:** 后端在 Go L1 识别层建立“态势剪枝 ➔ 领域路由 ➔ 细分意图 ➔ 门禁卡片”四级漏斗,并将驳回意见下沉至 Block AST 提取段落锚点与批注;在 L2 协调层实现权责校验与交互卡片(InteractiveCard)下发;前端在 React 19 + TypeScript 中实现有状态、带轮次 TTL 与操作幂等性的对话卡片组件并与受控文档视图联动。
|
||||
|
||||
**Tech Stack:** Go 1.22+, PostgreSQL + pgxpool, React 19, TypeScript, Vite, Tailwind CSS / FlowMind Design Tokens, Lucide Icons.
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- 严禁通过对话操作 UI 视窗或排版(折叠侧边栏、切换主题、打开抽屉等),此类指令一律不作执行型意图处理;
|
||||
- 严禁通过对话修改用户身份(姓名、工号、职位、部门、资质)与系统底层管理配置(模型网关密钥、向量库连接),此类尝试必须触发合规拦截;
|
||||
- 驳回、批准、大阶段跃迁等高影响法定动作必须通过二次确认卡或显式确认流转,且驳回意见必须智能锚定到段落块;
|
||||
- 交互卡片必须支持 3 轮对话超时 (TTL)、外部状态变迁失效及 60 秒合法窗口期撤销机制;
|
||||
- 遵循 Conventional Commits 规范,按原子性分层逐步提交代码;
|
||||
- 交付前验收必须使用真实 Agent(`pi-agent` + 实际运行的 `MiniMax-M3` 容器)进行端到端全链路实测。
|
||||
|
||||
---
|
||||
|
||||
### Task 1: 核心意图枚举与交互卡片数据契约扩充 (Data Contracts & Schemas)
|
||||
|
||||
**Files:**
|
||||
- Modify: `internal/intent/types.go`
|
||||
- Modify: `web/src/types/workbench.ts`
|
||||
- Test: `internal/intent/types_test.go`
|
||||
- Test: `web/src/types/types.test.ts`
|
||||
|
||||
**Interfaces:**
|
||||
- Produces (Go):
|
||||
- `intent.IntentType`: `IntentDocGovClaim`, `IntentDocGovSubmitReview`, `IntentDocGovReviewApprove`, `IntentDocGovReviewReject`, `IntentDocGovFinalApprove`, `IntentDocGovFinalReject`, `IntentDocGovProposalDecide`, `IntentQueryRegulations`, `IntentQuerySimilarCases`, `IntentQueryUserRights`, `IntentSecurityForbidden`
|
||||
- `intent.InteractiveCardPayload`, `intent.CardAction`
|
||||
- `intent.ContextSnapshot` (扩展 `ActiveDocID`, `DocLifecycleState`, `UserDocRole`)
|
||||
- Produces (TypeScript):
|
||||
- `InteractiveCardData`, `CardActionItem`, `InteractiveCardType`, `CardActionStatus`
|
||||
- `ChatMessage.interactiveCard`
|
||||
|
||||
- [ ] **Step 1: 编写 Go 类型单元测试,断言新意图枚举与卡片载荷序列化**
|
||||
|
||||
创建 `internal/intent/types_test.go`:
|
||||
```go
|
||||
package intent_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"chat-agent-system/internal/intent"
|
||||
)
|
||||
|
||||
func TestInteractiveCardPayload_Serialization(t *testing.T) {
|
||||
card := intent.InteractiveCardPayload{
|
||||
CardID: "card_rej_001",
|
||||
CardType: "CONFIRMATION",
|
||||
Title: "审核驳回流转确认",
|
||||
TargetDocID: "doc-002",
|
||||
TargetDocTitle: "微创外科全球竞品深度调研",
|
||||
TargetSectionID: "blk_emc_05",
|
||||
SectionTitle: "第3节 主要竞品电气安全对比",
|
||||
ProposedContent: "缺少史赛克高频电刀共模抑制比测试说明",
|
||||
CurrentStatus: "active",
|
||||
TTLTurns: 3,
|
||||
CreatedAt: time.Now(),
|
||||
Actions: []intent.CardAction{
|
||||
{
|
||||
ActionID: "confirm_reject",
|
||||
Label: "确认正式驳回退回",
|
||||
Style: "danger",
|
||||
Payload: map[string]string{
|
||||
"doc_id": "doc-002",
|
||||
"reason": "缺少史赛克抗扰度指标",
|
||||
},
|
||||
IsAffirmativeDefault: true,
|
||||
},
|
||||
{
|
||||
ActionID: "cancel",
|
||||
Label: "放弃驳回",
|
||||
Style: "subtle",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
data, err := json.Marshal(card)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal card failed: %v", err)
|
||||
}
|
||||
|
||||
var decoded intent.InteractiveCardPayload
|
||||
if err := json.Unmarshal(data, &decoded); err != nil {
|
||||
t.Fatalf("unmarshal card failed: %v", err)
|
||||
}
|
||||
|
||||
if decoded.CardID != "card_rej_001" || decoded.CardType != "CONFIRMATION" {
|
||||
t.Errorf("unexpected decoded card: %+v", decoded)
|
||||
}
|
||||
if len(decoded.Actions) != 2 || decoded.Actions[0].ActionID != "confirm_reject" {
|
||||
t.Errorf("unexpected decoded actions: %+v", decoded.Actions)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 运行测试验证失败**
|
||||
|
||||
Run: `go test -v ./internal/intent -run TestInteractiveCardPayload_Serialization`
|
||||
Expected: FAIL (compilation error: undefined `InteractiveCardPayload`, etc.)
|
||||
|
||||
- [ ] **Step 3: 扩充 internal/intent/types.go 定义**
|
||||
|
||||
修改 `internal/intent/types.go`,增加治理流转意图、安全拦截意图与交互卡片结构体:
|
||||
```go
|
||||
// 在 IntentType 枚举中追加定义
|
||||
const (
|
||||
// 法定文档治理流转
|
||||
IntentDocGovClaim IntentType = "DOCGOV_CLAIM"
|
||||
IntentDocGovSubmitReview IntentType = "DOCGOV_SUBMIT_REVIEW"
|
||||
IntentDocGovReviewApprove IntentType = "DOCGOV_REVIEW_APPROVE"
|
||||
IntentDocGovReviewReject IntentType = "DOCGOV_REVIEW_REJECT"
|
||||
IntentDocGovFinalApprove IntentType = "DOCGOV_FINAL_APPROVE"
|
||||
IntentDocGovFinalReject IntentType = "DOCGOV_FINAL_REJECT"
|
||||
IntentDocGovProposalDecide IntentType = "DOCGOV_PROPOSAL_DECIDE"
|
||||
|
||||
// 决策与合规查询
|
||||
IntentQueryRegulations IntentType = "QUERY_REGULATIONS"
|
||||
IntentQuerySimilarCases IntentType = "QUERY_SIMILAR_CASES"
|
||||
IntentQueryUserRights IntentType = "QUERY_USER_RIGHTS"
|
||||
|
||||
// 安全与权限红线拦截
|
||||
IntentSecurityForbidden IntentType = "SECURITY_FORBIDDEN"
|
||||
)
|
||||
|
||||
// 常见槽位 Key 追加
|
||||
const (
|
||||
SlotTargetDoc = "target_doc"
|
||||
SlotRejectReason = "reject_reason"
|
||||
SlotTargetSection = "target_section"
|
||||
SlotProposalAction = "proposal_action"
|
||||
SlotForbiddenTarget = "forbidden_target"
|
||||
)
|
||||
|
||||
// CardAction 交互卡片按钮动作
|
||||
type CardAction struct {
|
||||
ActionID string `json:"action_id"`
|
||||
Label string `json:"label"`
|
||||
Style string `json:"style"` // primary, danger, subtle
|
||||
Payload map[string]string `json:"payload,omitempty"`
|
||||
IsAffirmativeDefault bool `json:"is_affirmative_default,omitempty"`
|
||||
}
|
||||
|
||||
// InteractiveCardPayload 对话交互卡片载荷
|
||||
type InteractiveCardPayload struct {
|
||||
CardID string `json:"card_id"`
|
||||
CardType string `json:"card_type"` // CONFIRMATION, SLOT_FILLING, DIFF_PROPOSAL, REMINDER_NOTICE
|
||||
Title string `json:"title"`
|
||||
TargetDocID string `json:"target_doc_id,omitempty"`
|
||||
TargetDocTitle string `json:"target_doc_title,omitempty"`
|
||||
TargetSectionID string `json:"target_section_id,omitempty"`
|
||||
SectionTitle string `json:"section_title,omitempty"`
|
||||
ProposedContent string `json:"proposed_content,omitempty"`
|
||||
ImpactRisk string `json:"impact_risk,omitempty"`
|
||||
CurrentStatus string `json:"current_status"` // active, executing, resolved, cancelled, outdated, expired
|
||||
TTLTurns int `json:"ttl_turns"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
Actions []CardAction `json:"actions,omitempty"`
|
||||
}
|
||||
```
|
||||
并在 `ContextSnapshot` 结构体中扩展:
|
||||
```go
|
||||
type ContextSnapshot struct {
|
||||
SnapshotID string `json:"snapshot_id,omitempty"`
|
||||
ProjectName string `json:"project_name,omitempty"`
|
||||
CurrentStage string `json:"current_stage,omitempty"`
|
||||
ActiveDocID string `json:"active_doc_id,omitempty"`
|
||||
DocLifecycleState string `json:"doc_lifecycle_state,omitempty"`
|
||||
UserDocRole string `json:"user_doc_role,omitempty"`
|
||||
AvailableActions []string `json:"available_actions,omitempty"`
|
||||
PendingExpectation *PendingExpectation `json:"pending_expectation,omitempty"`
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: 扩充 web/src/types/workbench.ts 前端类型定义**
|
||||
|
||||
在 `web/src/types/workbench.ts` 中新增卡片类型定义并注入 `ChatMessage`:
|
||||
```typescript
|
||||
export type InteractiveCardType =
|
||||
| 'CONFIRMATION'
|
||||
| 'SLOT_FILLING'
|
||||
| 'DIFF_PROPOSAL'
|
||||
| 'REMINDER_NOTICE';
|
||||
|
||||
export type CardActionStatus =
|
||||
| 'active'
|
||||
| 'executing'
|
||||
| 'resolved'
|
||||
| 'cancelled'
|
||||
| 'outdated'
|
||||
| 'expired';
|
||||
|
||||
export interface CardActionItem {
|
||||
actionId: string;
|
||||
label: string;
|
||||
style: 'primary' | 'danger' | 'subtle';
|
||||
payload?: Record<string, string>;
|
||||
isAffirmativeDefault?: boolean;
|
||||
}
|
||||
|
||||
export interface InteractiveCardData {
|
||||
cardId: string;
|
||||
cardType: InteractiveCardType;
|
||||
title: string;
|
||||
targetDocId?: string;
|
||||
targetDocTitle?: string;
|
||||
targetSectionId?: string;
|
||||
sectionTitle?: string;
|
||||
proposedContent?: string;
|
||||
impactRisk?: string;
|
||||
status: CardActionStatus;
|
||||
ttlTurns: number;
|
||||
createdAt?: string;
|
||||
actions: CardActionItem[];
|
||||
resolutionSummary?: string;
|
||||
resolvedAt?: string;
|
||||
resolvedBy?: string;
|
||||
}
|
||||
```
|
||||
并在 `ChatMessage` 中增加字段:
|
||||
```typescript
|
||||
export interface ChatMessage {
|
||||
// ... 既有字段 ...
|
||||
interactiveCard?: InteractiveCardData;
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 5: 验证 Go 测试与前端 TypeScript 类型编译**
|
||||
|
||||
Run: `go test -v ./internal/intent -run TestInteractiveCardPayload_Serialization`
|
||||
Expected: PASS
|
||||
Run: `cd web && npx tsc --noEmit`
|
||||
Expected: PASS (0 errors)
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add internal/intent/types.go internal/intent/types_test.go web/src/types/workbench.ts
|
||||
git commit -m "feat(intent): add interactive card payload and extended intent contracts"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: 四级漏斗意图消解引擎与槽位提取器实现 (Hierarchical Intent Recognizer)
|
||||
|
||||
**Files:**
|
||||
- Modify: `internal/intent/recognizer.go`
|
||||
- Test: `internal/intent/recognizer_test.go`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `ContextSnapshot` (包含 `ActiveDocID`, `DocLifecycleState`, `UserDocRole`)
|
||||
- Produces: `IntentRecognitionResult` (TopIntent 包含细分意图、置信度、槽位键值对)
|
||||
|
||||
- [ ] **Step 1: 编写四级漏斗意图消解测试套件**
|
||||
|
||||
在 `internal/intent/recognizer_test.go` 中新增测试用例:
|
||||
1. 审核人驳回指令(带理由)识别为 `DOCGOV_REVIEW_REJECT`,抽取 `target_doc` 与 `reject_reason`;
|
||||
2. 非审核人试图驳回时,前置剪枝并返回权限不符;
|
||||
3. 试图修改自身身份/职位时,命中 `SECURITY_FORBIDDEN`;
|
||||
4. 咨询法规标准时,识别为 `QUERY_REGULATIONS`;
|
||||
5. 查询自身权限时,识别为 `QUERY_USER_RIGHTS`。
|
||||
|
||||
```go
|
||||
func TestIntentRecognition_ReviewReject(t *testing.T) {
|
||||
rec := intent.NewRecognizer()
|
||||
ctx := &intent.ContextSnapshot{
|
||||
SnapshotID: "ctx_rej_1",
|
||||
CurrentStage: string(bizdev.StageV05_Feasibility),
|
||||
ActiveDocID: "doc-002",
|
||||
DocLifecycleState: "reviewing",
|
||||
UserDocRole: "reviewer",
|
||||
}
|
||||
|
||||
res, err := rec.Recognize(context.Background(), "把调研报告审核驳回,意见为:第3节竞品对比缺少史赛克高频电刀的抗扰度指标", ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("recognize failed: %v", err)
|
||||
}
|
||||
|
||||
if res.TopIntent.Intent != intent.IntentDocGovReviewReject {
|
||||
t.Errorf("expected intent DOCGOV_REVIEW_REJECT, got %s", res.TopIntent.Intent)
|
||||
}
|
||||
if res.TopIntent.FilledSlots[intent.SlotRejectReason] == "" {
|
||||
t.Errorf("expected filled slot reject_reason")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntentRecognition_SecurityForbidden(t *testing.T) {
|
||||
rec := intent.NewRecognizer()
|
||||
res, err := rec.Recognize(context.Background(), "帮我把身份修改为研发总监,工号改成SMDF-001", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("recognize failed: %v", err)
|
||||
}
|
||||
|
||||
if res.TopIntent.Intent != intent.IntentSecurityForbidden {
|
||||
t.Errorf("expected intent SECURITY_FORBIDDEN, got %s", res.TopIntent.Intent)
|
||||
}
|
||||
if res.TopIntent.Confidence != 1.0 {
|
||||
t.Errorf("expected confidence 1.0 for security violations")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 运行测试验证失败**
|
||||
|
||||
Run: `go test -v ./internal/intent -run "TestIntentRecognition_ReviewReject|TestIntentRecognition_SecurityForbidden"`
|
||||
Expected: FAIL
|
||||
|
||||
- [ ] **Step 3: 在 internal/intent/recognizer.go 实现四级漏斗与槽位提取**
|
||||
|
||||
在 `RuleBasedRecognizer.Recognize` 中重构流水线:
|
||||
1. **Stage 0 (态势前置过滤与安全防护检查)**:
|
||||
- 检查 `isSecurityViolation(input)`:若包含“修改身份”、“改成批准人”、“修改职位”、“修改设置”、“修改网关”,直接返回 `IntentSecurityForbidden`;
|
||||
- 检查纯 UI 视窗指令(如“打开工具栏”、“折叠侧边栏”),归入 `IntentCasualChat` 并标注为不可执行 UI 提示。
|
||||
2. **Stage 1 (领域大类路由)**:
|
||||
- `GOV_LIFECYCLE`:驳回、通过、送审、认领、批准;
|
||||
- `CONTENT_SYNTHESIS`:推进阶段、生成任务、参数事实;
|
||||
- `DECISION_QUERY`:查进度、查法规、查案例、查权限;
|
||||
- `EXPECTATION_ACTION`:好、行、就按这个办、放弃。
|
||||
3. **Stage 2 (微观细分动作与槽位提取)**:
|
||||
- 实现 `matchDocGovAction(trimmed, userCtx)`:
|
||||
- 若包含“驳回”,提取驳回原因并根据当前文档状态映射为 `IntentDocGovReviewReject` 或 `IntentDocGovFinalReject`;
|
||||
- 若包含“通过”或“审核通过”,映射为 `IntentDocGovReviewApprove`;
|
||||
- 若包含“批准”或“终审”,映射为 `IntentDocGovFinalApprove`;
|
||||
- 若包含“认领”,映射为 `IntentDocGovClaim`;
|
||||
- 若包含“送审”,映射为 `IntentDocGovSubmitReview`。
|
||||
- 实现 `matchKnowledgeQuery(trimmed)`:
|
||||
- 法规强标(GB 9706, YY, FDA, CE)➔ `IntentQueryRegulations`;
|
||||
- 标杆案例(参考案例、往期经验)➔ `IntentQuerySimilarCases`;
|
||||
- 用户权限(我的权限、我负责什么)➔ `IntentQueryUserRights`。
|
||||
4. **Stage 3 (槽位完备性与置信度裁决)**:
|
||||
- 若 `DOCGOV_REVIEW_REJECT` 缺少 `reject_reason`,置 `RequiresClarifying = true`,追问:“请说明具体的法定驳回理由,我将为您注入文档批注并退回编制阶段。”
|
||||
|
||||
- [ ] **Step 4: 运行意图识别全量测试**
|
||||
|
||||
Run: `go test -v ./internal/intent/...`
|
||||
Expected: PASS (所有测试用例 100% 通过)
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add internal/intent/recognizer.go internal/intent/recognizer_test.go
|
||||
git commit -m "feat(intent): implement 4-stage hierarchical intent funnel and slot extraction"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: L2 协调引擎治理门禁、语义定位与交互卡片挂载 (Coordinator & Semantic Anchoring)
|
||||
|
||||
**Files:**
|
||||
- Modify: `internal/coordinator/engine.go`
|
||||
- Modify: `internal/coordinator/context.go`
|
||||
- Test: `internal/coordinator/engine_test.go`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `intent.IntentRecognitionResult`, `UserProjectContext`
|
||||
- Produces: `CoordinationResult` (包含 `InteractiveCard`, `NaturalReply`, `UpdatedContext`)
|
||||
|
||||
- [ ] **Step 1: 编写协调引擎治理操作与卡片下发测试用例**
|
||||
|
||||
在 `internal/coordinator/engine_test.go` 中编写:
|
||||
1. `TestCoordinatorEngine_ReviewRejectWithCard`:审核人下达驳回指令时,协调引擎进行权责核验,智能扫描文档内容定位段落,生成带 `CONFIRMATION` 卡片的回执;
|
||||
2. `TestCoordinatorEngine_SecurityForbidden`:试图篡改身份时,协调引擎返回严肃合规声明,不修改任何用户上下文;
|
||||
3. `TestCoordinatorEngine_QueryUserRights`:查询当前用户在项目的各文档法定职责。
|
||||
|
||||
```go
|
||||
func TestCoordinatorEngine_ReviewRejectWithCard(t *testing.T) {
|
||||
engine := coordinator.NewCoordinatorEngine(nil)
|
||||
|
||||
userCtx := &coordinator.UserProjectContext{
|
||||
User: coordinator.UserInfo{
|
||||
ID: "user_102",
|
||||
Name: "李主管",
|
||||
Role: "reviewer",
|
||||
},
|
||||
Project: coordinator.ProjectInfo{
|
||||
Name: "微创内窥镜项目",
|
||||
},
|
||||
}
|
||||
|
||||
recRes := &intent.IntentRecognitionResult{
|
||||
TopIntent: &intent.CandidateIntent{
|
||||
Intent: intent.IntentDocGovReviewReject,
|
||||
FilledSlots: map[string]string{
|
||||
intent.SlotTargetDoc: "doc-002",
|
||||
intent.SlotRejectReason: "第3节缺少史赛克高频电刀的抗扰度指标",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
res, err := engine.ProcessIntent(context.Background(), recRes, userCtx)
|
||||
if err != nil {
|
||||
t.Fatalf("process intent failed: %v", err)
|
||||
}
|
||||
|
||||
if res.InteractiveCard == nil {
|
||||
t.Fatalf("expected interactive card payload in coordination result")
|
||||
}
|
||||
if res.InteractiveCard.CardType != "CONFIRMATION" {
|
||||
t.Errorf("expected card type CONFIRMATION, got %s", res.InteractiveCard.CardType)
|
||||
}
|
||||
if len(res.InteractiveCard.Actions) < 2 {
|
||||
t.Errorf("expected at least 2 actions (confirm and cancel)")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 运行测试验证失败**
|
||||
|
||||
Run: `go test -v ./internal/coordinator -run TestCoordinatorEngine_ReviewRejectWithCard`
|
||||
Expected: FAIL
|
||||
|
||||
- [ ] **Step 3: 在 internal/coordinator/engine.go 实现业务逻辑分发**
|
||||
|
||||
1. 在 `ProcessIntent` 的 `switch top.Intent` 中挂载分支:
|
||||
- `IntentSecurityForbidden` ➔ 调用 `handleSecurityForbidden(userCtx)`;
|
||||
- `IntentDocGovReviewReject` / `IntentDocGovFinalReject` ➔ 调用 `handleDocGovReject(ctx, top, userCtx)`;
|
||||
- `IntentDocGovReviewApprove` / `IntentDocGovFinalApprove` ➔ 调用 `handleDocGovApprove(ctx, top, userCtx)`;
|
||||
- `IntentDocGovClaim` ➔ 调用 `handleDocGovClaim(ctx, top, userCtx)`;
|
||||
- `IntentDocGovSubmitReview` ➔ 调用 `handleDocGovSubmitReview(ctx, top, userCtx)`;
|
||||
- `IntentQueryRegulations` ➔ 调用 `handleQueryRegulations(ctx, top, userCtx)`;
|
||||
- `IntentQuerySimilarCases` ➔ 调用 `handleQuerySimilarCases(ctx, top, userCtx)`;
|
||||
- `IntentQueryUserRights` ➔ 调用 `handleQueryUserRights(ctx, top, userCtx)`。
|
||||
2. 实现 `handleDocGovReject`:
|
||||
- 提取驳回意见中的章节线索(“第3节”、“抗扰度”);
|
||||
- 构造 `InteractiveCardPayload`,卡片类型为 `CONFIRMATION`,提供“确认正式驳回退回”与“放弃”两个选项;
|
||||
- 构造自然回复:“已为您起草针对《%s》的驳回流转意见。请审阅下方卡片中的拟注入批注,确认无误后点击确认或回复‘确认’。”
|
||||
3. 实现 `handleSecurityForbidden`:
|
||||
- 回复合规提示:“⚠️ **合规权限拦截**:根据医疗器械质量管理规范(ISO 13485 / 21 CFR Part 11),企业人员的法定身份(姓名、工号、职位、部门)与系统控制台属于基线受控资产,不支持通过对话动态修改。您可通过‘查询我的权限’查看您在当前各文档中的法定职责。”
|
||||
|
||||
- [ ] **Step 4: 运行协调引擎全量测试**
|
||||
|
||||
Run: `go test -v ./internal/coordinator/...`
|
||||
Expected: PASS
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add internal/coordinator/engine.go internal/coordinator/context.go internal/coordinator/engine_test.go
|
||||
git commit -m "feat(coordinator): implement docgov gates, semantic anchoring and interactive card dispatch"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: 前端对话交互卡片组件开发 (InteractiveCard Component)
|
||||
|
||||
**Files:**
|
||||
- Create: `web/src/components/chat/InteractiveCard.tsx`
|
||||
- Create: `web/src/components/chat/InteractiveCard.test.ts`
|
||||
- Modify: `web/src/styles/tokens.css`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `InteractiveCardData`, `onExecuteAction: (cardId: string, actionId: string, payload?: any) => void`
|
||||
- Produces: 具备 4 种类型展示、轮次 TTL 倒计时、防重脉冲与已完成印章的有状态卡片组件
|
||||
|
||||
- [ ] **Step 1: 编写前端交互卡片状态机逻辑测试**
|
||||
|
||||
创建 `web/src/components/chat/InteractiveCard.test.ts`,验证卡片状态变迁(active ➔ executing ➔ resolved / outdated):
|
||||
```typescript
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { InteractiveCardData } from '../../types/workbench';
|
||||
|
||||
describe('InteractiveCard Logic', () => {
|
||||
it('should format resolution text correctly', () => {
|
||||
const card: InteractiveCardData = {
|
||||
cardId: 'c-1',
|
||||
cardType: 'CONFIRMATION',
|
||||
title: '审核驳回流转确认',
|
||||
status: 'resolved',
|
||||
ttlTurns: 3,
|
||||
actions: [],
|
||||
resolutionSummary: '已正式退回编制阶段,批注已挂载',
|
||||
resolvedBy: '李主管',
|
||||
resolvedAt: '16:10',
|
||||
};
|
||||
|
||||
expect(card.status).toBe('resolved');
|
||||
expect(card.resolutionSummary).toContain('已正式退回编制阶段');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 运行测试验证**
|
||||
|
||||
Run: `cd web && npx vitest run src/components/chat/InteractiveCard.test.ts`
|
||||
Expected: PASS
|
||||
|
||||
- [ ] **Step 3: 编写 web/src/components/chat/InteractiveCard.tsx**
|
||||
|
||||
完整实现交互卡片组件,支持:
|
||||
- `CONFIRMATION`、`SLOT_FILLING`、`DIFF_PROPOSAL`、`REMINDER_NOTICE` 4 种视觉排版;
|
||||
- 状态展示:`active` 显示按钮操作;`executing` 显示 Loading 动效与禁用;`resolved` 盖上“✅ 已生效”印章;`outdated` 显示“⚠️ 文档状态已变更,操作失效”;`expired` 显示“⏱️ 已超时”;
|
||||
- 锚定段落与批注摘要展示框;
|
||||
- 点击后触发 `onExecuteAction(card.cardId, action.actionId, action.payload)` 并激活局部防重锁定。
|
||||
|
||||
- [ ] **Step 4: 验证组件 TypeScript 编译**
|
||||
|
||||
Run: `cd web && npx tsc --noEmit`
|
||||
Expected: PASS (0 errors)
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add web/src/components/chat/InteractiveCard.tsx web/src/components/chat/InteractiveCard.test.ts
|
||||
git commit -m "feat(web): build interactive card component with lifecycle and idempotency"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 5: 前端对话流、卡片交互与受控文档视图联动闭环 (Frontend Integration)
|
||||
|
||||
**Files:**
|
||||
- Modify: `web/src/components/chat/MessageBubble.tsx`
|
||||
- Modify: `web/src/components/chat/MessageStream.tsx`
|
||||
- Modify: `web/src/App.tsx`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `InteractiveCardData`, `ChatMessage`, `DocumentAST`
|
||||
- Produces: 对话气泡中卡片渲染、用户点击卡片按钮或对话肯定回复时的状态机联动,以及驳回批注在右侧 `BlockRenderer` 中的自动高亮
|
||||
|
||||
- [ ] **Step 1: 在 MessageBubble.tsx 中集成 InteractiveCard 渲染**
|
||||
|
||||
修改 `web/src/components/chat/MessageBubble.tsx`:
|
||||
- 接收 `onCardAction?: (cardId: string, actionId: string, payload?: any) => void` 属性;
|
||||
- 在消息气泡正文下方,若存在 `message.interactiveCard`,则渲染 `<InteractiveCard card={message.interactiveCard} onExecuteAction={onCardAction} />`。
|
||||
|
||||
- [ ] **Step 2: 在 MessageStream.tsx 中穿透 onCardAction 事件**
|
||||
|
||||
修改 `web/src/components/chat/MessageStream.tsx`,将 `onCardAction` 逐层传递给各个 `MessageBubble`。
|
||||
|
||||
- [ ] **Step 3: 在 App.tsx 中打通卡片兑现与受控文档联动逻辑**
|
||||
|
||||
修改 `web/src/App.tsx`:
|
||||
1. 实现 `handleCardAction(cardId, actionId, payload)`:
|
||||
- 若 `actionId === 'confirm_reject'`:
|
||||
- 调用既有 `handleRejectReview(payload.reason)`,将目标文档(如 `doc-002`)生命周期由 `reviewing` 回退为 `drafting`;
|
||||
- 扫描该文档的 `DocumentAST`,在包含“电气安全”或“竞品对比”的目标 `DocumentBlock` 上追加一条 `type='issue'` 的批注;
|
||||
- 将当前卡片状态更新为 `resolved`,记录 `resolvedAt` 与 `resolvedBy: currentUser.name`;
|
||||
- 切换当前右侧激活文档为 `doc-002` 并滚动至该 Block 高亮显示 2 秒;
|
||||
- 若 `actionId === 'cancel'`:
|
||||
- 将卡片状态更新为 `cancelled`;
|
||||
- 追加一条助手消息:“已取消驳回操作,文档保持待审核状态。”
|
||||
2. 扩充 `handleSendMessage` 对话触发逻辑:
|
||||
- 若用户输入包含“驳回”,且当前处于审核阶段,则自动生成 `CONFIRMATION` 卡片挂载在最新的助手消息中;
|
||||
- 若用户输入“好”或“确认”,自动检测上一轮消息是否存在处于 `active` 的卡片,若有则自动调用 `handleCardAction` 执行其默认动作(`isAffirmativeDefault === true`),实现“对话打字与点击卡片双轨同效”。
|
||||
|
||||
- [ ] **Step 4: 验证前端构建与类型编译**
|
||||
|
||||
Run: `cd web && npm run build`
|
||||
Expected: PASS (`dist/` 编译成功)
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add web/src/components/chat/MessageBubble.tsx web/src/components/chat/MessageStream.tsx web/src/App.tsx
|
||||
git commit -m "feat(web): integrate interactive cards into chat stream and wire docgov actions"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 6: 真实环境全链路实测与测试报告归档 (Real Agent E2E & Report)
|
||||
|
||||
**Files:**
|
||||
- Create: `.doc/test-reports/2026-09-08-dialogue-driven-content-and-hierarchical-intent-test-report.md`
|
||||
|
||||
**Constraints:**
|
||||
- **必须遵循真实 Agent 交付铁律**:交付前验收必须使用**真实环境与真实 Agent(`pi-agent` + 实际运行的 `MiniMax-M3` 容器)**执行端到端实测;
|
||||
- 严禁使用 stub 输出作为最终交付验收数据;
|
||||
- 严格遵循 `.cursor/rules/test-reports-standard.mdc` 的 7 大固定结构。
|
||||
|
||||
- [ ] **Step 1: 运行全量后端单元测试与集成测试套件**
|
||||
|
||||
Run: `go test -v ./internal/...`
|
||||
Expected: PASS (100% 绿灯)
|
||||
|
||||
- [ ] **Step 2: 启动真实服务并执行端到端实测矩阵**
|
||||
|
||||
测试用例矩阵设计:
|
||||
1. **用例 1(典型驳回流转与批注注入)**:李主管发送“把调研报告审核驳回,意见为:第3节竞品对比缺少史赛克高频电刀的抗扰度指标” ➔ 检验四级漏斗识别、卡片生成、确认后状态退回 drafting 及第 3 节批注生成;
|
||||
2. **用例 2(权限越权安全防御拦截)**:工程师张工要求“把我的身份修改为研发总监” ➔ 检验合规红线硬拦截与警告提示;
|
||||
3. **用例 3(纯 UI 指令不执行验证)**:用户发送“帮我打开工具栏并折叠侧边栏” ➔ 检验系统友好引导至界面按钮,不生成虚假执行;
|
||||
4. **用例 4(卡片时效与非阻塞验证)**:下发确认卡后,用户跳过卡片继续咨询“查下 GB 9706.1 标准” ➔ 检验对话流畅推进,旧卡片保持静默,状态机不发生错误变更。
|
||||
|
||||
- [ ] **Step 3: 归档标准化测试报告**
|
||||
|
||||
创建 `.doc/test-reports/2026-09-08-dialogue-driven-content-and-hierarchical-intent-test-report.md`,完整记录输入、真实终端日志输出、判定与耗时。
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add .doc/test-reports/2026-09-08-dialogue-driven-content-and-hierarchical-intent-test-report.md
|
||||
git commit -m "docs(test): archive test report for dialogue-driven content and hierarchical intent system"
|
||||
```
|
||||
Reference in New Issue
Block a user