feat: add AI workflow run management with detailed audit items and UI integration

This commit is contained in:
mlogclub
2026-06-24 09:52:00 +08:00
parent 02ae515bd6
commit 46bcc55658
12 changed files with 694 additions and 10 deletions
+29 -1
View File
@@ -89,10 +89,14 @@ func BuildAIWorkflowNodeSpecs(list []workflowregistry.NodeSpec) []response.AIWor
}
func BuildAIWorkflowRun(item *models.AIWorkflowRun) response.AIWorkflowRunResponse {
return BuildAIWorkflowRunWithContext(item, nil, nil, nil)
}
func BuildAIWorkflowRunWithContext(item *models.AIWorkflowRun, workflow *models.AIWorkflow, version *models.AIWorkflowVersion, agent *models.AIAgent) response.AIWorkflowRunResponse {
if item == nil {
return response.AIWorkflowRunResponse{}
}
return response.AIWorkflowRunResponse{
ret := response.AIWorkflowRunResponse{
ID: item.ID,
WorkflowID: item.WorkflowID,
WorkflowVersionID: item.WorkflowVersionID,
@@ -103,12 +107,23 @@ func BuildAIWorkflowRun(item *models.AIWorkflowRun) response.AIWorkflowRunRespon
StatusName: workflowRunStatusName(item.Status),
StartedAt: formatWorkflowTime(item.StartedAt),
EndedAt: formatWorkflowTimePtr(item.EndedAt),
DurationMS: workflowRunDurationMS(item.StartedAt, item.EndedAt),
InterruptType: item.InterruptType,
InterruptNodeID: item.InterruptNodeID,
ErrorMessage: item.ErrorMessage,
CreatedAt: formatWorkflowTime(item.CreatedAt),
UpdatedAt: formatWorkflowTime(item.UpdatedAt),
}
if workflow != nil {
ret.WorkflowName = workflow.Name
}
if version != nil {
ret.WorkflowVersion = version.Version
}
if agent != nil {
ret.AIAgentName = agent.Name
}
return ret
}
func BuildAIWorkflowRunDetail(item *models.AIWorkflowRun, nodes []models.AIWorkflowNodeRun) response.AIWorkflowRunResponse {
@@ -117,6 +132,12 @@ func BuildAIWorkflowRunDetail(item *models.AIWorkflowRun, nodes []models.AIWorkf
return ret
}
func BuildAIWorkflowRunDetailWithContext(item *models.AIWorkflowRun, nodes []models.AIWorkflowNodeRun, workflow *models.AIWorkflow, version *models.AIWorkflowVersion, agent *models.AIAgent) response.AIWorkflowRunResponse {
ret := BuildAIWorkflowRunWithContext(item, workflow, version, agent)
ret.Nodes = BuildAIWorkflowNodeRunList(nodes)
return ret
}
func BuildAIWorkflowRunList(list []models.AIWorkflowRun) []response.AIWorkflowRunResponse {
ret := make([]response.AIWorkflowRunResponse, 0, len(list))
for i := range list {
@@ -188,3 +209,10 @@ func formatWorkflowTimePtr(value *time.Time) string {
}
return formatWorkflowTime(*value)
}
func workflowRunDurationMS(startedAt time.Time, endedAt *time.Time) int64 {
if startedAt.IsZero() || endedAt == nil || endedAt.IsZero() {
return 0
}
return endedAt.Sub(startedAt).Milliseconds()
}
@@ -2,8 +2,10 @@ package builders
import (
"testing"
"time"
workflowregistry "agent-desk/internal/ai/workflow/registry"
"agent-desk/internal/models"
)
func TestBuildAIWorkflowNodeSpecsIncludesVariableContracts(t *testing.T) {
@@ -30,6 +32,39 @@ func TestBuildAIWorkflowNodeSpecsIncludesVariableContracts(t *testing.T) {
}
}
func TestBuildAIWorkflowRunIncludesAuditDisplayFields(t *testing.T) {
startedAt := time.Date(2026, 6, 23, 10, 0, 0, 0, time.UTC)
endedAt := startedAt.Add(1500 * time.Millisecond)
resp := BuildAIWorkflowRunWithContext(
&models.AIWorkflowRun{
ID: 9,
WorkflowID: 11,
WorkflowVersionID: 22,
AIAgentID: 33,
StartedAt: startedAt,
EndedAt: &endedAt,
Status: 1,
},
&models.AIWorkflow{Name: "售后会话流程"},
&models.AIWorkflowVersion{Version: 3},
&models.AIAgent{Name: "售后 Agent"},
)
if resp.WorkflowName != "售后会话流程" {
t.Fatalf("expected workflow name, got %q", resp.WorkflowName)
}
if resp.WorkflowVersion != 3 {
t.Fatalf("expected workflow version 3, got %d", resp.WorkflowVersion)
}
if resp.AIAgentName != "售后 Agent" {
t.Fatalf("expected agent name, got %q", resp.AIAgentName)
}
if resp.DurationMS != 1500 {
t.Fatalf("expected duration 1500ms, got %d", resp.DurationMS)
}
}
func hasResponseVariable(items []workflowregistry.VariableSpec, name string) bool {
for _, item := range items {
if item.Name == name {