Files
smartmate/docs/frontend/frontend-schedule-integration.md
Losita 66c06eed0a Version: 0.9.45.dev.260427
后端:
1. execute 主链路重构为“上下文工具域 + 主动优化候选闭环”——移除 order_guard,粗排后默认进入主动微调,先诊断再从后端候选中选择 move/swap,避免 LLM 自由全局乱搜
2. 工具体系升级为动态注入协议——新增 context_tools_add / remove、工具域与二级包映射、主动优化白名单;schedule / taskclass / web 工具按域按包暴露,msg0 规则包与 execute 上下文同步重写
3. analyze_health 升级为主动优化唯一裁判入口——补齐 rhythm / tightness / profile / feasibility 指标、候选扫描与复诊打分、停滞信号、forced imperfection 判定,并把连续优化状态写回运行态
4. 任务类能力并入新 Agent 执行链——新增 upsert_task_class 写工具与启动注入事务写入;任务类模型补充学科画像与整天屏蔽配置,粗排支持 excluded_days_of_week,steady 策略改为基于目标位置/单日负载/分散度/缓冲的候选打分
5. 运行态与路由补齐优化模式语义——新增 active tool domain/packs、pending context hook、active optimize only、taskclass 写入回盘快照;区分 first_full / global_reopt / local_adjust,并完善首次粗排后默认 refine 的判定

前端:
6. 助手时间线渲染细化——推理内容改为独立 reasoning block,支持与工具/状态/正文按时序交错展示,自动收口折叠,修正 confirm reject 恢复动作

仓库:
7. newAgent 文档整体迁入 docs/backend,补充主动优化执行规划与顺序约束拆解文档,删除旧调试日志文件

PS:这次科研了2天,总算是有些进展了——LLM永远只适合做选择题、判断题,不适合做开放创新题。
2026-04-27 01:09:37 +08:00

17 KiB
Raw Blame History

日程卡片前端集成文档

本文档描述前端实现"排程结果卡片 + 暂存/写库按钮"所需的全部接口、数据结构和交互流程。


一、整体交互流程

用户发消息 → SSE 流式返回 → 收到 schedule_completed 事件
                                    ↓
                      调用 schedule-preview 接口拉取排程数据
                                    ↓
                        渲染日程卡片(可拖拽调整位置)
                                    ↓
                  ┌─────────────────┴─────────────────┐
                  │                                   │
          "暂存 state"按钮                      "写库"按钮
          POST /agent/schedule-state         PUT /task-class/apply-batch-into-schedule
          (暂存到 Redis 快照)                (真正写入 MySQL 日程表)

二、SSE 事件格式

2.1 基础 SSE 壳

所有 SSE data 行都是 JSON格式遵循 OpenAI 兼容协议:

{
  "id": "request-id",
  "object": "chat.completion.chunk",
  "created": 1745036800,
  "model": "worker",
  "choices": [
    {
      "index": 0,
      "delta": {
        "role": "assistant",
        "content": "正文内容",
        "reasoning_content": "思考内容"
      },
      "finish_reason": null
    }
  ],
  "extra": { ... }
}
  • choices 可以为空数组(纯结构化事件时)
  • extra 为可选字段,旧事件不含 extra

2.2 心跳保活

: ping

SSE 标准注释行,每 5 秒一次。前端 JSON.parse 失败后丢弃即可。

2.3 流结束标记

data: [DONE]

2.4 错误事件

{
  "error": {
    "message": "错误描述",
    "type": "server_error",
    "code": "5xxxx"
  }
}

三、extra 结构化事件类型

前端通过 extra.kind 判断事件类型。

3.1 事件类型枚举

kind 含义 display_mode 说明
reasoning_text 思考文字 append 逐块追加
assistant_text 回复正文 append 逐块追加
status 阶段状态 card 如"正在排程"
tool_call 工具调用开始 card 如"正在查询任务"
tool_result 工具调用结果 card 如"找到 3 个任务"
confirm_request 待确认事件 card 需要用户确认
interrupt 中断/追问 card ask_user 追问
schedule_completed 排程完毕 card 前端拉取排程数据的信号
finish 流结束 replace 收尾

3.2 status 事件

{
  "extra": {
    "kind": "status",
    "block_id": "execute.status",
    "stage": "execute",
    "display_mode": "card",
    "status": {
      "code": "planning",
      "summary": "正在智能排程..."
    }
  }
}

3.3 tool_call 事件(工具调用开始)

{
  "extra": {
    "kind": "tool_call",
    "block_id": "execute.tool.1",
    "stage": "execute",
    "display_mode": "card",
    "tool": {
      "name": "smart_planning",
      "status": "start",
      "summary": "正在为任务类智能排程",
      "arguments_preview": "任务类: [高数作业, 英语阅读]"
    }
  }
}

3.4 tool_result 事件(工具调用结果)

{
  "extra": {
    "kind": "tool_result",
    "block_id": "execute.tool.1",
    "stage": "execute",
    "display_mode": "card",
    "tool": {
      "name": "smart_planning",
      "status": "done",
      "summary": "成功生成排程方案",
      "arguments_preview": "已排 3 个任务"
    }
  }
}
  • tool.status 取值:start | done | blocked | failed

3.5 confirm_request 事件(用户确认)

{
  "choices": [{
    "index": 0,
    "delta": { "role": "assistant", "content": "请确认是否应用排程结果...\n" }
  }],
  "extra": {
    "kind": "confirm_request",
    "block_id": "execute.confirm.1",
    "stage": "execute",
    "display_mode": "card",
    "confirm": {
      "interaction_id": "confirm_abc123",
      "title": "确认应用排程结果",
      "summary": "是否将 3 个任务安排到日程中?"
    }
  }
}

前端需要:

  1. 保存 interaction_id
  2. 展示确认卡片title + summary + 确认/拒绝按钮)
  3. 用户点击后发送 resume 请求(见第五节)

3.6 schedule_completed 事件(排程完毕信号)

{
  "extra": {
    "kind": "schedule_completed",
    "block_id": "deliver.schedule",
    "stage": "deliver",
    "display_mode": "card"
  }
}

前端收到后: 用当前 conversation_id 调用 GET /agent/schedule-preview 拉取排程数据。

3.7 finish 事件

{
  "choices": [{
    "index": 0,
    "delta": {},
    "finish_reason": "stop"
  }],
  "extra": {
    "kind": "finish",
    "block_id": "deliver.finish",
    "stage": "deliver",
    "display_mode": "replace"
  }
}

四、排程预览接口

收到 schedule_completed 事件后调用此接口获取排程数据。

GET /api/v1/agent/schedule-preview

Query 参数:

参数 类型 必填 说明
conversation_id string 会话 ID

响应:

{
  "status": "10000",
  "info": "success",
  "data": {
    "conversation_id": "1655dd9b-...",
    "trace_id": "trace-...",
    "summary": "已为你安排了 3 个任务",
    "candidate_plans": [
      {
        "week": 1,
        "events": [
          {
            "id": 10,
            "order": 1,
            "day_of_week": 1,
            "name": "高等数学",
            "start_time": "08:00",
            "end_time": "09:30",
            "location": "教学楼A",
            "type": "course",
            "span": 2,
            "status": "normal",
            "embedded_task_info": {
              "id": 100,
              "name": "数学作业",
              "type": "task"
            }
          },
          {
            "id": 11,
            "order": 2,
            "day_of_week": 1,
            "name": "编程练习",
            "start_time": "10:00",
            "end_time": "11:30",
            "location": "",
            "type": "task",
            "span": 2,
            "status": "normal",
            "embedded_task_info": {}
          }
        ]
      },
      {
        "week": 2,
        "events": [...]
      }
    ],
    "hybrid_entries": [
      {
        "week": 1,
        "day_of_week": 1,
        "section_from": 3,
        "section_to": 4,
        "name": "英语阅读",
        "type": "task",
        "status": "suggested",
        "task_item_id": 101,
        "task_class_id": 5,
        "event_id": 0,
        "can_be_embedded": false,
        "block_for_suggested": true,
        "context_tag": "Memory"
      },
      {
        "week": 1,
        "day_of_week": 2,
        "section_from": 1,
        "section_to": 2,
        "name": "高等数学",
        "type": "course",
        "status": "existing",
        "task_item_id": 0,
        "task_class_id": 0,
        "event_id": 10,
        "can_be_embedded": true,
        "block_for_suggested": false,
        "context_tag": ""
      }
    ],
    "task_class_ids": [5, 6],
    "generated_at": "2026-04-19T10:00:00Z"
  }
}

数据结构说明

HybridScheduleEntry混合日程条目

前端渲染日程卡片的核心数据结构。课程和任务统一到同一个列表中。

字段 类型 说明
week int 学期周数
day_of_week int 星期几1=周一7=周日)
section_from int 起始节次1-based
section_to int 结束节次1-based
name string 名称
type string "course"(课程)或 "task"(任务)
status string "existing"(已确定)或 "suggested"(建议)
task_item_id int 任务项 ID仅 type=task 且 status=suggested 时有值)
task_class_id int 任务类 ID仅 type=task 且 status=suggested 时有值,对应写库接口的 task_class_id
event_id int 日程事件 ID仅 existing 时有值)
can_be_embedded bool 课程是否允许嵌入任务
block_for_suggested bool 是否阻塞建议任务占位
context_tag string 认知类型标签:"High-Logic" / "Memory" / "Review" / "General"

渲染建议:

  • status=existing → 已有课程/日程,渲染为固定色块(不可拖拽)
  • status=suggested → AI 建议的任务,渲染为可拖拽色块
  • can_be_embedded=true 的课程 → 任务可嵌入到其时段内

UserWeekSchedule按周视图的已有日程

字段 类型 说明
week int 周数
events WeeklyEventBrief[] 该周事件列表

WeeklyEventBrief

字段 类型 说明
id int ScheduleEvent.ID
order int 天内显示顺序
day_of_week int 星期几
name string 名称
start_time string 开始时间(如 "08:00"
end_time string 结束时间
location string 地点
type string "course" / "task"
span int 跨越节数(渲染高度)
status string "normal" / "interrupted"
embedded_task_info TaskBrief 嵌入的任务信息(可选)

TaskBrief

字段 类型 说明
id int 关联 ID
name string 任务名称
type string "task"

五、用户确认流程confirm / resume

5.1 流程说明

当后端需要用户确认时:

  1. SSE 推送 kind=confirm_request 事件
  2. 前端展示确认卡片
  3. 用户点击"确认"或"拒绝"
  4. 前端发送 resume 请求回同一聊天接口

5.2 请求格式

POST /api/v1/agent/chat(复用聊天入口)

{
  "conversation_id": "1655dd9b-2c4c-4b56-a712-f34c11b2634d",
  "message": "",
  "extra": {
    "resume": {
      "interaction_id": "confirm_abc123",
      "type": "confirm",
      "action": "approve"
    }
  }
}

5.3 resume 字段说明

字段 类型 必填 说明
interaction_id string 从 confirm_request 事件中获取
type string 默认为 "confirm",也可为 "ask_user""connection_recover"
action string 见下表

confirm 类型的 action

action 含义
approve 用户同意,继续执行
reject 用户拒绝
cancel 用户取消

ask_user 类型的 action

action 含义
reply 用户回答追问(回答内容放在顶层 message 字段)
cancel 用户取消

六、"暂存 state"按钮

将用户在卡片上拖拽调整后的任务位置暂存到 Redis 快照。不写 MySQL不触发 LLM。

POST /api/v1/agent/schedule-state

请求体:

{
  "conversation_id": "1655dd9b-2c4c-4b56-a712-f34c11b2634d",
  "items": [
    {
      "task_item_id": 101,
      "week": 1,
      "day_of_week": 1,
      "start_section": 3,
      "end_section": 4
    },
    {
      "task_item_id": 102,
      "week": 2,
      "day_of_week": 3,
      "start_section": 5,
      "end_section": 6,
      "embed_course_event_id": 20
    }
  ]
}

SaveScheduleStatePlacedItem 字段说明

字段 类型 必填 说明
task_item_id int 任务项 ID来自 HybridScheduleEntry 的 task_item_id
week int 学期周数≥1
day_of_week int 星期几1-7
start_section int 起始节次≥1
end_section int 结束节次≥1须 ≥ start_section
embed_course_event_id int 嵌入目标课程的 event_id来自 HybridScheduleEntry 的 event_id

安全保证: 只修改 type=task 的建议任务,课程数据永远不变。不在 items 中的任务保持原样。

成功响应

{
  "status": "10000",
  "info": "success",
  "data": null
}

错误码

错误码 status 含义
40004 缺少 conversation_id
40005 请求体格式错误
40058 排程快照不存在或已过期(需重新对话)
40059 week/day_of_week 坐标超出排程窗口范围
40060 task_item_id 在快照中不存在
40061 embed_course_event_id 在快照课程中不存在
40062 请求中包含重复的 task_item_id

七、"写库"按钮

将任务真正写入 MySQL 日程表。需要按任务类task_class分组调用。

PUT /api/v1/task-class/apply-batch-into-schedule

注意: 该接口需要幂等性 KeyIdempotency-Key header防止重复点击。

请求体:

{
  "task_class_id": 123,
  "items": [
    {
      "task_item_id": 101,
      "week": 1,
      "day_of_week": 1,
      "start_section": 3,
      "end_section": 4
    },
    {
      "task_item_id": 102,
      "week": 2,
      "day_of_week": 3,
      "start_section": 5,
      "end_section": 6,
      "embed_course_event_id": 20
    }
  ]
}

请求字段说明

字段 类型 必填 说明
task_class_id int 任务类 ID来自 HybridScheduleEntry 关联的任务类)
items SingleTaskClassItem[] 放置项列表

SingleTaskClassItem 字段说明

字段 类型 必填 说明
task_item_id int 任务项 ID
week int 学期周数≥1
day_of_week int 星期几1-7
start_section int 起始节次≥1
end_section int 结束节次≥1须 ≥ start_section
embed_course_event_id int 嵌入目标课程的 ScheduleEvent.ID

成功响应

{
  "status": "10000",
  "info": "success"
}

错误码

错误码 status 含义
40005 请求体格式错误
40037 缺少 Idempotency-Key header
40038 请求正在处理中(幂等性防重)
40026 日程冲突
40025 课程已被其他任务块嵌入
40034 任务项已安排
40048 任务项不属于该任务类
40049 任务项时间超出学期范围

八、两个按钮的 items 格式完全一致

关键设计: "暂存 state"和"写库"两个按钮的 items 数据格式完全相同。

前端只需维护一份 items 数组:
  - 用户拖拽调整位置 → 更新 items
  - 点击"暂存" → POST /agent/schedule-state { items }
  - 点击"写库" → PUT /task-class/apply-batch-into-schedule { task_class_id, items }

区别:

  • "暂存"接口需要 conversation_id"写库"接口需要 task_class_id
  • task_class_id 来自 HybridScheduleEntry.task_class_id(每个 suggested 任务条目都有),也可从响应顶层 task_class_ids 数组获取
  • 写库时需按 task_class_id 分组:相同 task_class_id 的 items 放在同一个请求中
  • "暂存"写 Redis 快照(可恢复),"写库"写 MySQL 日程表(持久化)
  • "写库"需要 Idempotency-Key header 防重

九、前端实现建议

9.1 日程卡片渲染

  1. hybrid_entries 作为主数据源渲染周视图
  2. status=existing 的条目渲染为只读色块(灰色/蓝色课程块)
  3. status=suggested 的条目渲染为可拖拽色块(绿色/橙色任务块)
  4. 拖拽时校验:目标位置是否与 block_for_suggested=true 的条目冲突
  5. 嵌入:拖拽到 can_be_embedded=true 的课程块上时,设置 embed_course_event_id

9.2 items 数组维护

interface PlacedItem {
  task_item_id: number;
  week: number;
  day_of_week: number;
  start_section: number;
  end_section: number;
  embed_course_event_id?: number;
}

// 从 hybrid_entries 中筛选 suggested 任务,构建 items
function buildItemsFromEntries(entries: HybridScheduleEntry[]): PlacedItem[] {
  return entries
    .filter(e => e.status === 'suggested' && e.task_item_id)
    .map(e => ({
      task_item_id: e.task_item_id,
      week: e.week,
      day_of_week: e.day_of_week,
      start_section: e.section_from,
      end_section: e.section_to,
      embed_course_event_id: e.event_id || undefined,
    }));
}

9.3 SSE 连接管理

  • 使用 fetch + ReadableStream(非 EventSource,因为需要 POST body
  • 心跳 : ping 行不是 data: 开头,JSON.parse 会失败,直接忽略
  • 错误事件格式为 { "error": { ... } },注意与正常事件区分
  • 流结束标记为 data: [DONE]
  • X-Conversation-ID 响应头包含服务端分配的 conversation_id

9.4 完整交互时序

1. 用户发送消息 → POST /agent/chat
2. SSE 流返回 thinking + 工具事件 + 正文
3. 收到 extra.kind === "schedule_completed"
4. GET /agent/schedule-preview?conversation_id=xxx
5. 渲染日程卡片
6. 用户拖拽调整任务位置 → 更新本地 items 数组
7a. 点击"暂存" → POST /agent/schedule-state { conversation_id, items }
7b. 点击"写库" → PUT /task-class/apply-batch-into-schedule { task_class_id, items }