| | |
| | | package cn.iocoder.yudao.module.ai.controller.admin.knowledge; |
| | | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.framework.common.util.collection.MapUtils; |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | | import cn.iocoder.yudao.module.ai.controller.admin.knowledge.vo.segment.*; |
| | | import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO; |
| | | import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO; |
| | | import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeDocumentService; |
| | | import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeSegmentService; |
| | | import cn.iocoder.yudao.module.ai.service.knowledge.bo.AiKnowledgeSegmentSearchReqBO; |
| | | import cn.iocoder.yudao.module.ai.service.knowledge.bo.AiKnowledgeSegmentSearchRespBO; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.Parameter; |
| | | import io.swagger.v3.oas.annotations.Parameters; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | | import jakarta.annotation.Resource; |
| | | import jakarta.validation.Valid; |
| | | import org.hibernate.validator.constraints.URL; |
| | | import org.springframework.security.access.prepost.PreAuthorize; |
| | | import org.springframework.validation.annotation.Validated; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import java.util.Collections; |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.stream.Collectors; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| | | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; |
| | | |
| | | @Tag(name = "管理后台 - AI 知识库段落") |
| | | @Tag(name = "管理后台 - AI 知识库分段") |
| | | @RestController |
| | | @RequestMapping("/ai/knowledge/segment") |
| | | @Validated |
| | | public class AiKnowledgeSegmentController { |
| | | |
| | | @Resource |
| | |
| | | @Resource |
| | | private AiKnowledgeDocumentService documentService; |
| | | |
| | | @GetMapping("/get") |
| | | @Operation(summary = "获取段落详情") |
| | | @Parameter(name = "id", description = "段落编号", required = true, example = "1024") |
| | | @GetMapping("/split") |
| | | @Operation(summary = "预览文档切分结果") |
| | | @PreAuthorize("@ss.hasPermission('ai:knowledge:query')") |
| | | public CommonResult<AiKnowledgeSegmentRespVO> getKnowledgeSegment(@RequestParam("id") Long id) { |
| | | AiKnowledgeSegmentDO segment = segmentService.getKnowledgeSegment(id); |
| | | return success(BeanUtils.toBean(segment, AiKnowledgeSegmentRespVO.class)); |
| | | public CommonResult<List<AiKnowledgeSegmentSplitRespVO>> previewSplit(@Valid AiKnowledgeSegmentSplitReqVO reqVO) { |
| | | String fileUrl = reqVO.getUrl(); |
| | | if (fileUrl == null || fileUrl.isEmpty()) { |
| | | throw new IllegalArgumentException("文档 URL 不能为空"); |
| | | } |
| | | // 从 URL 路径中提取文件名 |
| | | String name = fileUrl; |
| | | int queryIdx = name.indexOf('?'); |
| | | if (queryIdx > 0) name = name.substring(0, queryIdx); |
| | | int slashIdx = name.lastIndexOf('/'); |
| | | name = slashIdx >= 0 ? name.substring(slashIdx + 1) : name; |
| | | List<String> segments = documentService.previewSplit(fileUrl, name, reqVO.getSegmentMaxTokens()); |
| | | List<AiKnowledgeSegmentSplitRespVO> result = new ArrayList<>(); |
| | | for (String content : segments) { |
| | | result.add(new AiKnowledgeSegmentSplitRespVO() |
| | | .setContent(content) |
| | | .setContentLength(content.length()) |
| | | .setTokens(estimateTokens(content))); |
| | | } |
| | | return success(result); |
| | | } |
| | | |
| | | private int estimateTokens(String text) { |
| | | if (text == null || text.isEmpty()) return 0; |
| | | int chineseChars = 0, englishWords = 0; |
| | | for (char c : text.toCharArray()) { |
| | | if (c >= 0x4E00 && c <= 0x9FA5) chineseChars++; |
| | | } |
| | | for (String word : text.split("\\s+")) { |
| | | if (word.matches(".*[a-zA-Z].*")) englishWords++; |
| | | } |
| | | return chineseChars + (int) (englishWords * 1.3); |
| | | } |
| | | |
| | | @GetMapping("/get") |
| | | @Operation(summary = "获取分段") |
| | | @PreAuthorize("@ss.hasPermission('ai:knowledge:query')") |
| | | public CommonResult<AiKnowledgeSegmentRespVO> getSegment(@RequestParam("id") Long id) { |
| | | return success(BeanUtils.toBean(segmentService.getSegment(id), AiKnowledgeSegmentRespVO.class)); |
| | | } |
| | | |
| | | @GetMapping("/page") |
| | | @Operation(summary = "获取段落分页") |
| | | @Operation(summary = "获取分段分页") |
| | | @PreAuthorize("@ss.hasPermission('ai:knowledge:query')") |
| | | public CommonResult<PageResult<AiKnowledgeSegmentRespVO>> getKnowledgeSegmentPage( |
| | | @Valid AiKnowledgeSegmentPageReqVO pageReqVO) { |
| | | PageResult<AiKnowledgeSegmentDO> pageResult = segmentService.getKnowledgeSegmentPage(pageReqVO); |
| | | public CommonResult<PageResult<AiKnowledgeSegmentRespVO>> getSegmentPage(@Valid AiKnowledgeSegmentPageReqVO pageReqVO) { |
| | | PageResult<AiKnowledgeSegmentDO> pageResult = segmentService.getSegmentPage(pageReqVO); |
| | | return success(BeanUtils.toBean(pageResult, AiKnowledgeSegmentRespVO.class)); |
| | | } |
| | | |
| | | @PostMapping("/create") |
| | | @Operation(summary = "创建段落") |
| | | @Operation(summary = "创建分段") |
| | | @PreAuthorize("@ss.hasPermission('ai:knowledge:create')") |
| | | public CommonResult<Long> createKnowledgeSegment(@Valid @RequestBody AiKnowledgeSegmentSaveReqVO createReqVO) { |
| | | return success(segmentService.createKnowledgeSegment(createReqVO)); |
| | | public CommonResult<Long> createSegment(@Valid @RequestBody AiKnowledgeSegmentSaveReqVO saveReqVO) { |
| | | return success(segmentService.createSegment(saveReqVO)); |
| | | } |
| | | |
| | | @PutMapping("/update") |
| | | @Operation(summary = "更新段落内容") |
| | | @Operation(summary = "更新分段") |
| | | @PreAuthorize("@ss.hasPermission('ai:knowledge:update')") |
| | | public CommonResult<Boolean> updateKnowledgeSegment(@Valid @RequestBody AiKnowledgeSegmentSaveReqVO reqVO) { |
| | | segmentService.updateKnowledgeSegment(reqVO); |
| | | return success(true); |
| | | } |
| | | |
| | | @PutMapping("/update-status") |
| | | @Operation(summary = "启禁用段落内容") |
| | | @PreAuthorize("@ss.hasPermission('ai:knowledge:update')") |
| | | public CommonResult<Boolean> updateKnowledgeSegmentStatus( |
| | | @Valid @RequestBody AiKnowledgeSegmentUpdateStatusReqVO reqVO) { |
| | | segmentService.updateKnowledgeSegmentStatus(reqVO); |
| | | public CommonResult<Boolean> updateSegment(@Valid @RequestBody AiKnowledgeSegmentSaveReqVO saveReqVO) { |
| | | segmentService.updateSegment(saveReqVO); |
| | | return success(true); |
| | | } |
| | | |
| | | @DeleteMapping("/delete") |
| | | @Operation(summary = "删除段落") |
| | | @Parameter(name = "id", description = "段落编号", required = true, example = "1024") |
| | | @Operation(summary = "删除分段") |
| | | @PreAuthorize("@ss.hasPermission('ai:knowledge:delete')") |
| | | public CommonResult<Boolean> deleteKnowledgeSegment(@RequestParam("id") Long id) { |
| | | segmentService.deleteKnowledgeSegment(id); |
| | | public CommonResult<Boolean> deleteSegment(@RequestParam("id") Long id) { |
| | | segmentService.deleteSegment(id); |
| | | return success(true); |
| | | } |
| | | |
| | | @GetMapping("/split") |
| | | @Operation(summary = "切片内容") |
| | | @Parameters({ |
| | | @Parameter(name = "url", description = "文档 URL", required = true), |
| | | @Parameter(name = "segmentMaxTokens", description = "分段的最大 Token 数", required = true) |
| | | }) |
| | | @PreAuthorize("@ss.hasPermission('ai:knowledge:query')") |
| | | public CommonResult<List<AiKnowledgeSegmentRespVO>> splitContent( |
| | | @RequestParam("url") @URL String url, |
| | | @RequestParam(value = "segmentMaxTokens") Integer segmentMaxTokens) { |
| | | List<AiKnowledgeSegmentDO> segments = segmentService.splitContent(url, segmentMaxTokens); |
| | | return success(BeanUtils.toBean(segments, AiKnowledgeSegmentRespVO.class)); |
| | | @PutMapping("/update-status") |
| | | @Operation(summary = "更新分段状态") |
| | | @PreAuthorize("@ss.hasPermission('ai:knowledge:update')") |
| | | public CommonResult<Boolean> updateSegmentStatus(@Valid @RequestBody AiKnowledgeSegmentUpdateStatusReqVO updateStatusReqVO) { |
| | | segmentService.updateSegmentStatus(updateStatusReqVO); |
| | | return success(true); |
| | | } |
| | | |
| | | @GetMapping("/get-process-list") |
| | | @Operation(summary = "获取文档处理列表") |
| | | @Parameter(name = "documentIds", description = "文档编号列表", required = true, example = "1,2,3") |
| | | @Operation(summary = "获取文档处理进度") |
| | | @PreAuthorize("@ss.hasPermission('ai:knowledge:query')") |
| | | public CommonResult<List<AiKnowledgeSegmentProcessRespVO>> getKnowledgeSegmentProcessList( |
| | | @RequestParam("documentIds") List<Long> documentIds) { |
| | | List<AiKnowledgeSegmentProcessRespVO> list = segmentService.getKnowledgeSegmentProcessList(documentIds); |
| | | return success(list); |
| | | public CommonResult<List<AiKnowledgeSegmentProcessRespVO>> getProcessList(@RequestParam("documentIds") List<Long> documentIds) { |
| | | return success(documentService.getDocumentProcessingProgress(documentIds)); |
| | | } |
| | | |
| | | @GetMapping("/search") |
| | | @Operation(summary = "搜索段落内容") |
| | | @PostMapping("/search") |
| | | @Operation(summary = "检索分段(RAG 向量检索)") |
| | | @PreAuthorize("@ss.hasPermission('ai:knowledge:query')") |
| | | public CommonResult<List<AiKnowledgeSegmentSearchRespVO>> searchKnowledgeSegment( |
| | | @Valid AiKnowledgeSegmentSearchReqVO reqVO) { |
| | | // 1. 搜索段落 |
| | | List<AiKnowledgeSegmentSearchRespBO> segments = segmentService |
| | | .searchKnowledgeSegment(BeanUtils.toBean(reqVO, AiKnowledgeSegmentSearchReqBO.class)); |
| | | if (CollUtil.isEmpty(segments)) { |
| | | return success(Collections.emptyList()); |
| | | } |
| | | |
| | | // 2. 拼接 VO |
| | | Map<Long, AiKnowledgeDocumentDO> documentMap = documentService.getKnowledgeDocumentMap(convertSet( |
| | | segments, AiKnowledgeSegmentSearchRespBO::getDocumentId)); |
| | | return success(BeanUtils.toBean(segments, AiKnowledgeSegmentSearchRespVO.class, |
| | | segment -> MapUtils.findAndThen(documentMap, segment.getDocumentId(), |
| | | document -> segment.setDocumentName(document.getName())))); |
| | | public CommonResult<List<AiKnowledgeSegmentSearchRespVO>> searchSegments(@Valid @RequestBody AiKnowledgeSegmentSearchReqVO searchReqVO) { |
| | | AiKnowledgeSegmentSearchReqBO searchReqBO = BeanUtils.toBean(searchReqVO, AiKnowledgeSegmentSearchReqBO.class); |
| | | List<AiKnowledgeSegmentSearchRespBO> results = segmentService.searchSegments(searchReqBO); |
| | | return success(results.stream() |
| | | .map(bo -> BeanUtils.toBean(bo, AiKnowledgeSegmentSearchRespVO.class)) |
| | | .collect(Collectors.toList())); |
| | | } |
| | | |
| | | } |