package com.agent.management.controller; import com.agent.management.common.Result; import com.agent.management.model.entity.Workflow; import com.agent.management.model.entity.WorkflowRun; import com.agent.management.model.entity.WorkflowRunNode; import com.agent.management.model.vo.TagBriefVO; import com.agent.management.repository.WorkflowRepository; import com.agent.management.repository.WorkflowRunNodeRepository; import com.agent.management.repository.WorkflowRunRepository; import com.agent.management.service.TagService; import lombok.Data; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; import java.util.*; import java.util.stream.Collectors; /** * 运行记录查询 API */ @RestController @RequestMapping("/api/run-history") @RequiredArgsConstructor public class RunHistoryController { private final WorkflowRunRepository runRepo; private final WorkflowRunNodeRepository runNodeRepo; private final WorkflowRepository workflowRepo; private final TagService tagService; /** * 获取所有工作流的运行记录(按工作流分组) */ @GetMapping("/workflows") public Result> listWorkflowsWithRuns() { List allRuns = runRepo.findAllByOrderByStartedAtDesc(); // 按 workflowId 分组 Map> grouped = allRuns.stream() .collect(Collectors.groupingBy(WorkflowRun::getWorkflowId, LinkedHashMap::new, Collectors.toList())); List result = new ArrayList<>(); for (Map.Entry> entry : grouped.entrySet()) { Optional wfOpt = workflowRepo.findById(entry.getKey()); if (wfOpt.isEmpty()) continue; Workflow wf = wfOpt.get(); WorkflowWithRunsVO vo = new WorkflowWithRunsVO(); vo.setWorkflowId(wf.getId()); vo.setWorkflowName(wf.getName()); vo.setDisplayName(wf.getDisplayName()); vo.setTags(tagService.getEntityTags("workflow", String.valueOf(wf.getId()))); vo.setRuns(entry.getValue().stream().map(this::toRunVO).collect(Collectors.toList())); result.add(vo); } return Result.success(result); } /** * 获取某次运行的详情(含节点结果) */ @GetMapping("/runs/{runId}") public Result getRunDetail(@PathVariable Long runId) { Optional runOpt = runRepo.findById(runId); if (runOpt.isEmpty()) return Result.error("运行记录不存在"); WorkflowRun run = runOpt.get(); List nodes = runNodeRepo.findByRunIdOrderBySortOrderAsc(runId); RunDetailVO detail = new RunDetailVO(); detail.setRun(toRunVO(run)); detail.setNodes(nodes); return Result.success(detail); } /** * 删除某次运行记录 */ @PostMapping("/runs/{runId}/delete") public Result deleteRun(@PathVariable Long runId) { runNodeRepo.deleteByRunId(runId); runRepo.deleteById(runId); return Result.success(null); } private RunVO toRunVO(WorkflowRun run) { RunVO vo = new RunVO(); vo.setId(run.getId()); vo.setWorkflowId(run.getWorkflowId()); vo.setRunId(run.getRunId()); vo.setStatus(run.getStatus()); vo.setInputs(run.getInputs()); vo.setOutputs(run.getOutputs()); vo.setWorkingDirFiles(run.getWorkingDirFiles()); vo.setError(run.getError()); vo.setStartedAt(run.getStartedAt()); vo.setCompletedAt(run.getCompletedAt()); return vo; } @Data public static class WorkflowWithRunsVO { private Long workflowId; /** kebab-case 唯一标识(URL 用) */ private String workflowName; /** 中文显示名 */ private String displayName; private List tags; private List runs; } @Data public static class RunVO { private Long id; private Long workflowId; private String runId; private String status; private String inputs; private String outputs; /** 工作空间文件列表 JSON 数组字符串(应用 .agentignore 过滤后的相对路径) */ private String workingDirFiles; private String error; private Object startedAt; private Object completedAt; } @Data public static class RunDetailVO { private RunVO run; private List nodes; } }