package run import ( "encoding/json" "net/http" "git.iwork-ai.com/xdc/iqudo-top1/internal/auth" "git.iwork-ai.com/xdc/iqudo-top1/internal/model" "git.iwork-ai.com/xdc/iqudo-top1/internal/response" "github.com/gin-gonic/gin" "gorm.io/datatypes" ) type DetailHeader struct { model.SOPRun SOPName string `json:"sop_name"` ScenarioName string `json:"scenario_name"` OperatorName string `json:"operator_name"` } type DetailEvent struct { model.SOPRunEvent NodeTitle string `json:"node_title"` NodeType string `json:"node_type"` NodeContent string `json:"node_content"` NodeConfig datatypes.JSON `json:"-"` Outputs []KnowledgeGroup `json:"outputs,omitempty" gorm:"-"` } type DetailFeedback struct { model.SOPFeedback UserName string `json:"user_name"` } func (h *Handler) Detail(c *gin.Context) { principal, _ := auth.PrincipalFromContext(c) id, ok := runID(c) if !ok { return } var header DetailHeader err := h.db.Table("sop_runs r").Select( "r.*, s.name AS sop_name, sc.name AS scenario_name, u.display_name AS operator_name", ).Joins("JOIN sops s ON s.id = r.sop_id").Joins("JOIN scenarios sc ON sc.id = s.scenario_id").Joins("JOIN users u ON u.id = r.operator_id").Where("r.id = ? AND r.tenant_id = ?", id, principal.TenantID).Scan(&header).Error if err != nil || header.ID == 0 || !canViewRun(principal, header.SOPRun) { response.Error(c, http.StatusNotFound, "NOT_FOUND", "执行记录不存在") return } events := make([]DetailEvent, 0) if err := h.db.Table("sop_run_events e").Select("e.*, n.title AS node_title, n.type AS node_type, n.content AS node_content, n.config AS node_config").Joins("LEFT JOIN sop_nodes n ON n.sop_id = ? AND n.node_key = e.node_key", header.SOPID).Where("e.run_id = ? AND e.tenant_id = ?", id, principal.TenantID).Order("e.created_at, e.id").Scan(&events).Error; err != nil { response.Error(c, http.StatusInternalServerError, "QUERY_FAILED", "查询执行事件失败") return } answers := map[string]interface{}{} _ = json.Unmarshal(header.Answers, &answers) derived := map[string]interface{}{} _ = json.Unmarshal(header.Derived, &derived) input := map[string]interface{}{} _ = json.Unmarshal(header.Input, &input) context := runtimeContext(input, derived, answers) context["__knowledge_snapshot"] = json.RawMessage(header.KnowledgeSnapshot) for i := range events { if events[i].NodeType != "knowledge" { continue } var config knowledgeNodeConfig _ = json.Unmarshal(events[i].NodeConfig, &config) if config.KnowledgeSelector == nil { continue } var node model.SOPNode if err := h.db.Where("sop_id = ? AND node_key = ?", header.SOPID, events[i].NodeKey).First(&node).Error; err != nil { continue } outputs, loadErr := loadKnowledgeOutputs(h.db, node, principal.TenantID, context, *config.KnowledgeSelector) if loadErr != nil { response.Error(c, http.StatusInternalServerError, "KNOWLEDGE_NOT_FOUND", "执行记录知识快照不可用") return } events[i].Outputs = outputs } feedback := make([]DetailFeedback, 0) if err := h.db.Table("sop_feedback f").Select("f.*, u.display_name AS user_name").Joins("JOIN users u ON u.id = f.user_id").Where("f.run_id = ? AND f.tenant_id = ?", id, principal.TenantID).Order("f.created_at").Scan(&feedback).Error; err != nil { response.Error(c, http.StatusInternalServerError, "QUERY_FAILED", "查询执行反馈失败") return } fields := make([]model.ScenarioField, 0) if err := h.db.Table("scenario_fields sf").Joins("JOIN sops s ON s.scenario_id = sf.scenario_id").Where("s.id = ? AND sf.tenant_id = ?", header.SOPID, principal.TenantID).Order("sf.sort_order, sf.id").Find(&fields).Error; err != nil { response.Error(c, http.StatusInternalServerError, "QUERY_FAILED", "查询场景字段失败") return } if header.Answers == nil { header.Answers = datatypes.JSON([]byte(`{}`)) } response.OK(c, gin.H{"run": header, "events": events, "feedback": feedback, "fields": fields}) }