SkillController.java 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. package com.agent.management.controller;
  2. import com.agent.management.common.Result;
  3. import com.agent.management.model.dto.FileCreateDTO;
  4. import com.agent.management.model.dto.FileNodeDTO;
  5. import com.agent.management.model.dto.IOField;
  6. import com.agent.management.model.dto.SkillDTO;
  7. import com.agent.management.model.dto.SkillUpdateDTO;
  8. import com.agent.management.model.vo.SkillDetailVO;
  9. import com.agent.management.model.vo.SkillVO;
  10. import com.agent.management.service.SkillService;
  11. import com.agent.management.service.TranslationService;
  12. import lombok.RequiredArgsConstructor;
  13. import lombok.extern.slf4j.Slf4j;
  14. import org.springframework.web.bind.annotation.*;
  15. import org.springframework.web.multipart.MultipartFile;
  16. import java.util.List;
  17. import java.util.Map;
  18. import java.util.stream.Collectors;
  19. @Slf4j
  20. @RestController
  21. @RequestMapping("/api/skills")
  22. @RequiredArgsConstructor
  23. public class SkillController {
  24. private final SkillService skillService;
  25. private final TranslationService translationService;
  26. /**
  27. * 获取所有 Skill 列表
  28. * 立即返回数据(优先带缓存翻译),未缓存的通过 SSE 异步推送
  29. */
  30. @GetMapping
  31. public Result<List<SkillVO>> listSkills() {
  32. List<SkillDTO> skills = skillService.listSkills();
  33. // 构建 VO:优先从缓存读取翻译(不触发 LLM)
  34. List<SkillVO> voList = skills.stream()
  35. .map(this::toVOWithCache)
  36. .collect(Collectors.toList());
  37. // 对需要翻译的 Skill 异步翻译 + SSE 推送
  38. for (SkillDTO dto : skills) {
  39. // 只有缓存未命中时才需要异步翻译
  40. String cachedName = translationService.getCachedTranslation(dto.getName());
  41. String cachedDesc = translationService.getCachedTranslation(dto.getDescription());
  42. if (cachedName == null || cachedDesc == null) {
  43. translationService.asyncTranslateAndPush(
  44. dto.getFolderName(), dto.getName(), dto.getDescription());
  45. }
  46. }
  47. return Result.success(voList);
  48. }
  49. @PostMapping("/upload")
  50. public Result<Void> uploadSkill(@RequestParam("file") MultipartFile file) {
  51. skillService.createSkill(file);
  52. return Result.success("上传成功", null);
  53. }
  54. @PostMapping("/{folderName}/edit")
  55. public Result<Void> editSkill(@PathVariable String folderName,
  56. @RequestBody SkillUpdateDTO dto) {
  57. skillService.updateSkill(folderName, dto);
  58. return Result.success("更新成功", null);
  59. }
  60. @PostMapping("/{folderName}/delete")
  61. public Result<Void> deleteSkill(@PathVariable String folderName) {
  62. skillService.deleteSkill(folderName);
  63. return Result.success("删除成功", null);
  64. }
  65. // ==================== 技能编辑相关 API ====================
  66. /**
  67. * 获取技能详情(含 SKILL.md 正文内容)
  68. */
  69. @GetMapping("/{folderName}/detail")
  70. public Result<SkillDetailVO> getSkillDetail(@PathVariable String folderName) {
  71. return Result.success(skillService.getSkillDetail(folderName));
  72. }
  73. /**
  74. * 保存 SKILL.md(智能合并元数据)
  75. */
  76. @PutMapping("/{folderName}/skill-md")
  77. public Result<Void> saveSkillMd(@PathVariable String folderName,
  78. @RequestBody Map<String, Object> body) {
  79. String name = (String) body.getOrDefault("name", "");
  80. String description = (String) body.getOrDefault("description", "");
  81. String bodyText = (String) body.getOrDefault("body", "");
  82. // 解析 inputs / outputs
  83. List<IOField> inputs = parseIOFields(body.get("inputs"));
  84. List<IOField> outputs = parseIOFields(body.get("outputs"));
  85. skillService.saveSkillMd(folderName, name, description, inputs, outputs, bodyText);
  86. return Result.success("保存成功", null);
  87. }
  88. @SuppressWarnings("unchecked")
  89. private List<IOField> parseIOFields(Object obj) {
  90. if (obj == null) return null;
  91. if (obj instanceof List) {
  92. return ((List<?>) obj).stream()
  93. .map(item -> {
  94. if (item instanceof java.util.Map) {
  95. java.util.Map<String, Object> m = (java.util.Map<String, Object>) item;
  96. IOField field = new IOField();
  97. field.setName((String) m.getOrDefault("name", ""));
  98. field.setType((String) m.getOrDefault("type", "string"));
  99. field.setDescription((String) m.getOrDefault("description", ""));
  100. field.setRequired(Boolean.TRUE.equals(m.get("required")));
  101. return field;
  102. }
  103. return null;
  104. })
  105. .filter(java.util.Objects::nonNull)
  106. .collect(Collectors.toList());
  107. }
  108. return null;
  109. }
  110. /**
  111. * 列出技能目录下的所有文件(树形结构)
  112. */
  113. @GetMapping("/{folderName}/files")
  114. public Result<List<FileNodeDTO>> listFiles(@PathVariable String folderName) {
  115. return Result.success(skillService.listSkillFiles(folderName));
  116. }
  117. /**
  118. * 读取文件内容
  119. */
  120. @GetMapping("/{folderName}/files/content")
  121. public Result<String> readFile(@PathVariable String folderName,
  122. @RequestParam String path) {
  123. return Result.success(skillService.readFile(folderName, path));
  124. }
  125. /**
  126. * 保存文件内容
  127. */
  128. @PutMapping("/{folderName}/files/content")
  129. public Result<Void> saveFile(@PathVariable String folderName,
  130. @RequestBody Map<String, String> body) {
  131. skillService.saveFile(folderName, body.get("path"), body.get("content"));
  132. return Result.success("保存成功", null);
  133. }
  134. /**
  135. * 创建文件或目录
  136. */
  137. @PostMapping("/{folderName}/files")
  138. public Result<Void> createFile(@PathVariable String folderName,
  139. @RequestBody FileCreateDTO dto) {
  140. skillService.createFileOrDir(folderName, dto);
  141. return Result.success("创建成功", null);
  142. }
  143. /**
  144. * 删除文件或目录
  145. */
  146. @DeleteMapping("/{folderName}/files")
  147. public Result<Void> deleteFile(@PathVariable String folderName,
  148. @RequestParam String path) {
  149. skillService.deleteFile(folderName, path);
  150. return Result.success("删除成功", null);
  151. }
  152. // ==================== 私有方法 ====================
  153. /**
  154. * 构建 VO:优先从缓存读取翻译(纯内存操作,不触发 LLM)
  155. */
  156. private SkillVO toVOWithCache(SkillDTO dto) {
  157. SkillVO vo = new SkillVO();
  158. vo.setFolderName(dto.getFolderName());
  159. vo.setName(dto.getName());
  160. vo.setDescription(dto.getDescription());
  161. vo.setTranslatedName(translationService.getCachedTranslation(dto.getName()));
  162. vo.setTranslatedDescription(translationService.getCachedTranslation(dto.getDescription()));
  163. vo.setInputs(dto.getInputs());
  164. vo.setOutputs(dto.getOutputs());
  165. return vo;
  166. }
  167. }