Files
smartmate/backend/dao/agent_timeline.go
Losita 0f749e9f5a Version: 0.9.32.dev.260419
后端:
1. 会话历史接口切换为统一时间线读取,并兼容 extra.resume 恢复协议
  - api/agent.go:新增 resume->confirm_action 映射(approve/reject/cancel),恢复请求缺 conversation_id 时拦截;GetConversationHistory 改为 GetConversationTimeline
  - routers/routers.go:路由从 GET /conversation-history 切换为 GET /conversation-timeline
  - model/agent.go:删除 GetConversationHistoryItem 旧 DTO
2. 新增会话时间线持久化链路(MySQL + Redis)
  - 新增 model/agent_timeline.go:定义 timeline kind、AgentTimelineEvent、持久化/返回结构
  - 新增 dao/agent_timeline.go:写入事件、按 seq 查询、查询 max seq
  - inits/mysql.go:AutoMigrate 增加 AgentTimelineEvent
  - dao/cache.go:新增 timeline list/seq key,支持 incr/set seq、append/list、全量回填与删除
  - 新增 service/agentsvc/agent_timeline.go:时间线读写编排(Redis 优先、DB 回源、seq 分配与冲突重试、extra 事件映射)
3. 聊天主链路改为写入 timeline,旧 history 服务下线
  - service/agentsvc/agent.go:普通聊天用户/助手消息改为 appendConversationTimelineEvent
  - service/agentsvc/agent_newagent.go:透传 resume_interaction_id;注入 emitter extra hook 持久化卡片事件;正文写入 timeline
  - 删除 service/agentsvc/agent_history.go:下线 conversation-history 旧缓存编排
4. newAgent 恢复与确认防串单增强
  - newAgent/model/graph_run_state.go:AgentGraphRequest 新增 ResumeInteractionID
  - newAgent/node/agent_nodes.go:透传 ResumeInteractionID
  - newAgent/node/chat.go:增加 stale_resume 校验;accept/reject 兼容 approve/cancel;非法动作返回 invalid_confirm_action
  - newAgent/stream/emitter.go:新增 extraEventHook / SetExtraEventHook,在 extra-only 与 confirm 事件触发
5. 日程暂存后同步刷新预览缓存,避免读到拖拽前旧数据
  - service/agentsvc/agent_schedule_state.go:Save 后重建并覆盖 preview 缓存,保留 trace/candidate 等字段
6. 缓存失效策略调整到 timeline 口径
  - middleware/cache_deleter.go:移除 conversation-history 失效逻辑;ChatHistory/AgentChat/AgentTimelineEvent 加入忽略集合

前端:
7. 新增时间线接口与类型定义
  - frontend/src/api/schedule_agent.ts:新增 TimelineEvent/TimelineToolPayload/TimelineConfirmPayload 与 getConversationTimeline
8. AssistantPanel 全面对接 timeline 重建消息与卡片
  - frontend/src/components/dashboard/AssistantPanel.vue:移除旧 history merge/normalize,新增 rebuildStateFromTimeline;支持 execution mode(always_execute);支持 resume-only 发送;修复 confirm 弹层手动关闭后重复弹出;会话标题显示放宽;流式中隐藏 action bar
9. 精排弹窗健壮性与交互动效优化
  - frontend/src/components/assistant/ScheduleFineTuneModal.vue:previewData 支持 nullable,新增 visible 控制与 watch 初始化,补齐空值保护并调整弹窗动画

仓库:
10. 新增前端时间线接入说明文档
  - docs/frontend/newagent_timeline_对接说明.md:接口、kind、payload、刷新重建与迁移建议
2026-04-19 19:03:41 +08:00

87 lines
2.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package dao
import (
"context"
"strings"
"time"
"github.com/LoveLosita/smartflow/backend/model"
)
// SaveConversationTimelineEvent 持久化单条会话时间线事件到 MySQL。
//
// 职责边界:
// 1. 只做单条写入,不负责 seq 分配;
// 2. 只保证字段标准化(去空格、空值置 nil不做业务语义修正
// 3. 返回 error 让上层决定是否中断当前链路。
func (a *AgentDAO) SaveConversationTimelineEvent(ctx context.Context, payload model.ChatTimelinePersistPayload) (int64, *time.Time, error) {
normalizedChatID := strings.TrimSpace(payload.ConversationID)
normalizedKind := strings.TrimSpace(payload.Kind)
normalizedRole := strings.TrimSpace(payload.Role)
normalizedContent := strings.TrimSpace(payload.Content)
normalizedPayloadJSON := strings.TrimSpace(payload.PayloadJSON)
var rolePtr *string
if normalizedRole != "" {
rolePtr = &normalizedRole
}
var contentPtr *string
if normalizedContent != "" {
contentPtr = &normalizedContent
}
var payloadPtr *string
if normalizedPayloadJSON != "" {
payloadPtr = &normalizedPayloadJSON
}
event := model.AgentTimelineEvent{
UserID: payload.UserID,
ChatID: normalizedChatID,
Seq: payload.Seq,
Kind: normalizedKind,
Role: rolePtr,
Content: contentPtr,
Payload: payloadPtr,
TokensConsumed: payload.TokensConsumed,
}
if err := a.db.WithContext(ctx).Create(&event).Error; err != nil {
return 0, nil, err
}
return event.ID, event.CreatedAt, nil
}
// ListConversationTimelineEvents 查询会话时间线,按 seq 正序返回。
func (a *AgentDAO) ListConversationTimelineEvents(ctx context.Context, userID int, chatID string) ([]model.AgentTimelineEvent, error) {
normalizedChatID := strings.TrimSpace(chatID)
var events []model.AgentTimelineEvent
err := a.db.WithContext(ctx).
Where("user_id = ? AND chat_id = ?", userID, normalizedChatID).
Order("seq ASC").
Order("id ASC").
Find(&events).Error
if err != nil {
return nil, err
}
return events, nil
}
// GetConversationTimelineMaxSeq 返回会话时间线当前最大 seq。
//
// 说明:
// 1. 该方法主要用于 Redis 顺序号不可用时的 DB 兜底;
// 2. 无记录时返回 0不视为错误
// 3. 上层需要自行 +1 后再写入新事件。
func (a *AgentDAO) GetConversationTimelineMaxSeq(ctx context.Context, userID int, chatID string) (int64, error) {
normalizedChatID := strings.TrimSpace(chatID)
var maxSeq int64
err := a.db.WithContext(ctx).
Model(&model.AgentTimelineEvent{}).
Where("user_id = ? AND chat_id = ?", userID, normalizedChatID).
Select("COALESCE(MAX(seq), 0)").
Scan(&maxSeq).Error
if err != nil {
return 0, err
}
return maxSeq, nil
}