package com.agent.management.controller; import com.agent.management.common.Result; import com.agent.management.model.dto.FileCreateDTO; import com.agent.management.model.dto.FileNodeDTO; import com.agent.management.model.dto.IOField; import com.agent.management.model.dto.SkillDTO; import com.agent.management.model.dto.SkillUpdateDTO; import com.agent.management.model.vo.SkillDetailVO; import com.agent.management.model.vo.SkillVO; import com.agent.management.service.SkillService; import com.agent.management.service.TranslationService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @Slf4j @RestController @RequestMapping("/api/skills") @RequiredArgsConstructor public class SkillController { private final SkillService skillService; private final TranslationService translationService; /** * 获取所有 Skill 列表 * 立即返回数据(优先带缓存翻译),未缓存的通过 SSE 异步推送 */ @GetMapping public Result> listSkills() { List skills = skillService.listSkills(); // 构建 VO:优先从缓存读取翻译(不触发 LLM) List voList = skills.stream() .map(this::toVOWithCache) .collect(Collectors.toList()); // 对需要翻译的 Skill 异步翻译 + SSE 推送 for (SkillDTO dto : skills) { // 只有缓存未命中时才需要异步翻译 String cachedName = translationService.getCachedTranslation(dto.getName()); String cachedDesc = translationService.getCachedTranslation(dto.getDescription()); if (cachedName == null || cachedDesc == null) { translationService.asyncTranslateAndPush( dto.getFolderName(), dto.getName(), dto.getDescription()); } } return Result.success(voList); } @PostMapping("/upload") public Result uploadSkill(@RequestParam("file") MultipartFile file) { skillService.createSkill(file); return Result.success("上传成功", null); } @PostMapping("/{folderName}/edit") public Result editSkill(@PathVariable String folderName, @RequestBody SkillUpdateDTO dto) { skillService.updateSkill(folderName, dto); return Result.success("更新成功", null); } @PostMapping("/{folderName}/delete") public Result deleteSkill(@PathVariable String folderName) { skillService.deleteSkill(folderName); return Result.success("删除成功", null); } // ==================== 技能编辑相关 API ==================== /** * 获取技能详情(含 SKILL.md 正文内容) */ @GetMapping("/{folderName}/detail") public Result getSkillDetail(@PathVariable String folderName) { return Result.success(skillService.getSkillDetail(folderName)); } /** * 保存 SKILL.md(智能合并元数据) */ @PutMapping("/{folderName}/skill-md") public Result saveSkillMd(@PathVariable String folderName, @RequestBody Map body) { String name = (String) body.getOrDefault("name", ""); String description = (String) body.getOrDefault("description", ""); String bodyText = (String) body.getOrDefault("body", ""); // 解析 inputs / outputs List inputs = parseIOFields(body.get("inputs")); List outputs = parseIOFields(body.get("outputs")); skillService.saveSkillMd(folderName, name, description, inputs, outputs, bodyText); return Result.success("保存成功", null); } @SuppressWarnings("unchecked") private List parseIOFields(Object obj) { if (obj == null) return null; if (obj instanceof List) { return ((List) obj).stream() .map(item -> { if (item instanceof java.util.Map) { java.util.Map m = (java.util.Map) item; IOField field = new IOField(); field.setName((String) m.getOrDefault("name", "")); field.setType((String) m.getOrDefault("type", "string")); field.setDescription((String) m.getOrDefault("description", "")); field.setRequired(Boolean.TRUE.equals(m.get("required"))); return field; } return null; }) .filter(java.util.Objects::nonNull) .collect(Collectors.toList()); } return null; } /** * 列出技能目录下的所有文件(树形结构) */ @GetMapping("/{folderName}/files") public Result> listFiles(@PathVariable String folderName) { return Result.success(skillService.listSkillFiles(folderName)); } /** * 读取文件内容 */ @GetMapping("/{folderName}/files/content") public Result readFile(@PathVariable String folderName, @RequestParam String path) { return Result.success(skillService.readFile(folderName, path)); } /** * 保存文件内容 */ @PutMapping("/{folderName}/files/content") public Result saveFile(@PathVariable String folderName, @RequestBody Map body) { skillService.saveFile(folderName, body.get("path"), body.get("content")); return Result.success("保存成功", null); } /** * 创建文件或目录 */ @PostMapping("/{folderName}/files") public Result createFile(@PathVariable String folderName, @RequestBody FileCreateDTO dto) { skillService.createFileOrDir(folderName, dto); return Result.success("创建成功", null); } /** * 删除文件或目录 */ @DeleteMapping("/{folderName}/files") public Result deleteFile(@PathVariable String folderName, @RequestParam String path) { skillService.deleteFile(folderName, path); return Result.success("删除成功", null); } // ==================== 私有方法 ==================== /** * 构建 VO:优先从缓存读取翻译(纯内存操作,不触发 LLM) */ private SkillVO toVOWithCache(SkillDTO dto) { SkillVO vo = new SkillVO(); vo.setFolderName(dto.getFolderName()); vo.setName(dto.getName()); vo.setDescription(dto.getDescription()); vo.setTranslatedName(translationService.getCachedTranslation(dto.getName())); vo.setTranslatedDescription(translationService.getCachedTranslation(dto.getDescription())); vo.setInputs(dto.getInputs()); vo.setOutputs(dto.getOutputs()); return vo; } }