mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-25 00:23:53 +08:00
Merge branch 'Knowledge-Base' of https://github.com/xinnan-tech/xiaozhi-esp32-server into Knowledge-Base
This commit is contained in:
+23
-1
@@ -57,11 +57,19 @@ public class KnowledgeFilesController {
|
|||||||
public Result<PageData<KnowledgeFilesDTO>> getPageList(
|
public Result<PageData<KnowledgeFilesDTO>> getPageList(
|
||||||
@PathVariable("dataset_id") String datasetId,
|
@PathVariable("dataset_id") String datasetId,
|
||||||
@RequestParam(required = false) String name,
|
@RequestParam(required = false) String name,
|
||||||
|
@RequestParam(required = false) Integer status,
|
||||||
@RequestParam(required = false, defaultValue = "1") Integer page,
|
@RequestParam(required = false, defaultValue = "1") Integer page,
|
||||||
@RequestParam(required = false, defaultValue = "10") Integer page_size) {
|
@RequestParam(required = false, defaultValue = "10") Integer page_size) {
|
||||||
// 验证知识库权限
|
// 验证知识库权限
|
||||||
validateKnowledgeBasePermission(datasetId);
|
validateKnowledgeBasePermission(datasetId);
|
||||||
|
|
||||||
|
// 如果指定了状态参数,使用状态查询接口
|
||||||
|
if (status != null) {
|
||||||
|
PageData<KnowledgeFilesDTO> pageData = knowledgeFilesService.getPageListByStatus(datasetId, status, page, page_size);
|
||||||
|
return new Result<PageData<KnowledgeFilesDTO>>().ok(pageData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 否则使用通用查询接口
|
||||||
KnowledgeFilesDTO knowledgeFilesDTO = new KnowledgeFilesDTO();
|
KnowledgeFilesDTO knowledgeFilesDTO = new KnowledgeFilesDTO();
|
||||||
knowledgeFilesDTO.setDatasetId(datasetId);
|
knowledgeFilesDTO.setDatasetId(datasetId);
|
||||||
knowledgeFilesDTO.setName(name);
|
knowledgeFilesDTO.setName(name);
|
||||||
@@ -69,6 +77,21 @@ public class KnowledgeFilesController {
|
|||||||
return new Result<PageData<KnowledgeFilesDTO>>().ok(pageData);
|
return new Result<PageData<KnowledgeFilesDTO>>().ok(pageData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/documents/status/{status}")
|
||||||
|
@Operation(summary = "根据状态分页查询文档列表")
|
||||||
|
@RequiresPermissions("sys:role:normal")
|
||||||
|
public Result<PageData<KnowledgeFilesDTO>> getPageListByStatus(
|
||||||
|
@PathVariable("dataset_id") String datasetId,
|
||||||
|
@PathVariable("status") Integer status,
|
||||||
|
@RequestParam(required = false, defaultValue = "1") Integer page,
|
||||||
|
@RequestParam(required = false, defaultValue = "10") Integer page_size) {
|
||||||
|
// 验证知识库权限
|
||||||
|
validateKnowledgeBasePermission(datasetId);
|
||||||
|
|
||||||
|
PageData<KnowledgeFilesDTO> pageData = knowledgeFilesService.getPageListByStatus(datasetId, status, page, page_size);
|
||||||
|
return new Result<PageData<KnowledgeFilesDTO>>().ok(pageData);
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping("/documents")
|
@PostMapping("/documents")
|
||||||
@Operation(summary = "上传文档到知识库")
|
@Operation(summary = "上传文档到知识库")
|
||||||
@RequiresPermissions("sys:role:normal")
|
@RequiresPermissions("sys:role:normal")
|
||||||
@@ -186,7 +209,6 @@ public class KnowledgeFilesController {
|
|||||||
return new Result<Map<String, Object>>().error("召回测试失败: " + e.getMessage());
|
return new Result<Map<String, Object>>().error("召回测试失败: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 解析JSON字符串为Map对象
|
* 解析JSON字符串为Map对象
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -47,6 +47,9 @@ public class KnowledgeFilesDTO implements Serializable {
|
|||||||
@Schema(description = "状态")
|
@Schema(description = "状态")
|
||||||
private Integer status;
|
private Integer status;
|
||||||
|
|
||||||
|
@Schema(description = "文档解析状态")
|
||||||
|
private String run;
|
||||||
|
|
||||||
@Schema(description = "创建者")
|
@Schema(description = "创建者")
|
||||||
private Long creator;
|
private Long creator;
|
||||||
|
|
||||||
@@ -58,4 +61,36 @@ public class KnowledgeFilesDTO implements Serializable {
|
|||||||
|
|
||||||
@Schema(description = "更新时间")
|
@Schema(description = "更新时间")
|
||||||
private Date updatedAt;
|
private Date updatedAt;
|
||||||
|
|
||||||
|
// 文档解析状态常量定义
|
||||||
|
private static final Integer STATUS_UNSTART = 0;
|
||||||
|
private static final Integer STATUS_RUNNING = 1;
|
||||||
|
private static final Integer STATUS_CANCEL = 2;
|
||||||
|
private static final Integer STATUS_DONE = 3;
|
||||||
|
private static final Integer STATUS_FAIL = 4;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取文档解析状态码(基于run字段转换)
|
||||||
|
*/
|
||||||
|
public Integer getParseStatusCode() {
|
||||||
|
if (run == null) {
|
||||||
|
return STATUS_UNSTART;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据run字段的值直接映射到对应的状态码
|
||||||
|
switch (run.toUpperCase()) {
|
||||||
|
case "RUNNING":
|
||||||
|
return STATUS_RUNNING;
|
||||||
|
case "CANCEL":
|
||||||
|
return STATUS_CANCEL;
|
||||||
|
case "DONE":
|
||||||
|
return STATUS_DONE;
|
||||||
|
case "FAIL":
|
||||||
|
return STATUS_FAIL;
|
||||||
|
case "UNSTART":
|
||||||
|
default:
|
||||||
|
return STATUS_UNSTART;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+11
@@ -47,6 +47,17 @@ public interface KnowledgeFilesService {
|
|||||||
Map<String, Object> metaFields, String chunkMethod,
|
Map<String, Object> metaFields, String chunkMethod,
|
||||||
Map<String, Object> parserConfig);
|
Map<String, Object> parserConfig);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据状态分页查询文档列表
|
||||||
|
*
|
||||||
|
* @param datasetId 知识库ID
|
||||||
|
* @param status 文档解析状态(0-未开始,1-进行中,2-已取消,3-已完成,4-失败)
|
||||||
|
* @param page 页码
|
||||||
|
* @param limit 每页数量
|
||||||
|
* @return 分页数据
|
||||||
|
*/
|
||||||
|
PageData<KnowledgeFilesDTO> getPageListByStatus(String datasetId, Integer status, Integer page, Integer limit);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据文档ID和知识库ID删除文档
|
* 根据文档ID和知识库ID删除文档
|
||||||
*
|
*
|
||||||
|
|||||||
+211
@@ -9,6 +9,7 @@ import java.util.Date;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.core.io.AbstractResource;
|
import org.springframework.core.io.AbstractResource;
|
||||||
@@ -36,6 +37,7 @@ import xiaozhi.common.constant.Constant;
|
|||||||
import xiaozhi.common.exception.ErrorCode;
|
import xiaozhi.common.exception.ErrorCode;
|
||||||
import xiaozhi.common.exception.RenException;
|
import xiaozhi.common.exception.RenException;
|
||||||
import xiaozhi.common.page.PageData;
|
import xiaozhi.common.page.PageData;
|
||||||
|
import xiaozhi.common.redis.RedisUtils;
|
||||||
import xiaozhi.modules.knowledge.dto.KnowledgeFilesDTO;
|
import xiaozhi.modules.knowledge.dto.KnowledgeFilesDTO;
|
||||||
import xiaozhi.modules.knowledge.service.KnowledgeFilesService;
|
import xiaozhi.modules.knowledge.service.KnowledgeFilesService;
|
||||||
import xiaozhi.modules.model.dao.ModelConfigDao;
|
import xiaozhi.modules.model.dao.ModelConfigDao;
|
||||||
@@ -49,6 +51,7 @@ public class KnowledgeFilesServiceImpl implements KnowledgeFilesService {
|
|||||||
|
|
||||||
private final ModelConfigService modelConfigService;
|
private final ModelConfigService modelConfigService;
|
||||||
private final ModelConfigDao modelConfigDao;
|
private final ModelConfigDao modelConfigDao;
|
||||||
|
private final RedisUtils redisUtils;
|
||||||
private RestTemplate restTemplate = new RestTemplate();
|
private RestTemplate restTemplate = new RestTemplate();
|
||||||
private ObjectMapper objectMapper = new ObjectMapper();
|
private ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
|
||||||
@@ -197,6 +200,8 @@ public class KnowledgeFilesServiceImpl implements KnowledgeFilesService {
|
|||||||
for (Map<String, Object> docMap : documentList) {
|
for (Map<String, Object> docMap : documentList) {
|
||||||
KnowledgeFilesDTO dto = convertRAGDocumentToDTO(docMap);
|
KnowledgeFilesDTO dto = convertRAGDocumentToDTO(docMap);
|
||||||
if (dto != null) {
|
if (dto != null) {
|
||||||
|
// 在文档列表获取时也进行状态同步检查
|
||||||
|
syncDocumentStatusWithRAGFlow(dto);
|
||||||
documents.add(dto);
|
documents.add(dto);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -239,6 +244,88 @@ public class KnowledgeFilesServiceImpl implements KnowledgeFilesService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步文档状态与RAGFlow实际状态
|
||||||
|
* 优化状态同步逻辑,确保解析中状态能够正常显示
|
||||||
|
* 只有当文档有切片且解析时间超过30秒时,才更新为完成状态
|
||||||
|
*/
|
||||||
|
private void syncDocumentStatusWithRAGFlow(KnowledgeFilesDTO dto) {
|
||||||
|
if (dto == null || StringUtils.isBlank(dto.getDocumentId())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String documentId = dto.getDocumentId();
|
||||||
|
Integer currentStatus = dto.getStatus();
|
||||||
|
|
||||||
|
// 只有当状态明确为处理中(1)时,才进行状态同步检查
|
||||||
|
// 避免在状态不确定或已完成的文档上重复检查
|
||||||
|
if (currentStatus != null && currentStatus == 1) {
|
||||||
|
try {
|
||||||
|
long currentTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
// 调用RAGFlow API获取文档切片信息
|
||||||
|
Map<String, Object> ragConfig = getDefaultRAGConfig();
|
||||||
|
String baseUrl = (String) ragConfig.get("base_url");
|
||||||
|
String apiKey = (String) ragConfig.get("api_key");
|
||||||
|
|
||||||
|
String url = baseUrl + "/api/v1/documents/" + documentId + "/chunks";
|
||||||
|
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||||
|
headers.set("Authorization", "Bearer " + apiKey);
|
||||||
|
|
||||||
|
HttpEntity<String> requestEntity = new HttpEntity<>(headers);
|
||||||
|
|
||||||
|
log.debug("检查文档切片状态,documentId: {}", documentId);
|
||||||
|
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, requestEntity,
|
||||||
|
String.class);
|
||||||
|
|
||||||
|
if (response.getStatusCode().is2xxSuccessful()) {
|
||||||
|
String responseBody = response.getBody();
|
||||||
|
Map<String, Object> responseMap = objectMapper.readValue(responseBody, Map.class);
|
||||||
|
|
||||||
|
Integer code = (Integer) responseMap.get("code");
|
||||||
|
if (code != null && code == 0) {
|
||||||
|
Object dataObj = responseMap.get("data");
|
||||||
|
if (dataObj instanceof Map) {
|
||||||
|
Map<String, Object> dataMap = (Map<String, Object>) dataObj;
|
||||||
|
|
||||||
|
// 检查是否有切片数据
|
||||||
|
Object chunksObj = getValueFromMultipleKeys(dataMap, "chunks", "items", "list", "data");
|
||||||
|
if (chunksObj instanceof List) {
|
||||||
|
List<?> chunks = (List<?>) chunksObj;
|
||||||
|
|
||||||
|
// 如果有切片且数量大于0,说明解析已完成
|
||||||
|
if (!chunks.isEmpty()) {
|
||||||
|
// 检查文档创建时间,确保解析过程有足够的时间显示
|
||||||
|
Date createdAt = dto.getCreatedAt();
|
||||||
|
long parseDuration = currentTime
|
||||||
|
- (createdAt != null ? createdAt.getTime() : currentTime);
|
||||||
|
|
||||||
|
// 只有当解析时间超过30秒时,才更新为完成状态
|
||||||
|
// 这样可以确保解析中状态有足够的时间显示
|
||||||
|
if (parseDuration > 30000) {
|
||||||
|
log.info("状态同步:文档已有切片且解析时间超过30秒,更新为完成状态,documentId: {}, 切片数量: {}, 解析时长: {}ms",
|
||||||
|
documentId, chunks.size(), parseDuration);
|
||||||
|
|
||||||
|
// 更新状态为完成(3)
|
||||||
|
dto.setStatus(3);
|
||||||
|
} else {
|
||||||
|
log.debug("文档已有切片但解析时间不足30秒,保持解析中状态,documentId: {}, 解析时长: {}ms",
|
||||||
|
documentId, parseDuration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.debug("检查文档切片状态失败,documentId: {}, 错误: {}", documentId, e.getMessage());
|
||||||
|
// 忽略检查失败,保持原状态
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将RAGFlow文档数据转换为KnowledgeFilesDTO
|
* 将RAGFlow文档数据转换为KnowledgeFilesDTO
|
||||||
*/
|
*/
|
||||||
@@ -313,6 +400,21 @@ public class KnowledgeFilesServiceImpl implements KnowledgeFilesService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 设置文档解析状态信息 - 直接使用RAGFlow最新状态
|
||||||
|
String documentId = dto.getDocumentId();
|
||||||
|
if (StringUtils.isNotBlank(documentId)) {
|
||||||
|
// 获取RAGFlow的最新状态
|
||||||
|
Object runObj = getValueFromMultipleKeys(docMap, "run", "status", "parse_status");
|
||||||
|
Integer ragFlowStatus = null;
|
||||||
|
if (runObj != null) {
|
||||||
|
dto.setRun(runObj.toString());
|
||||||
|
ragFlowStatus = dto.getParseStatusCode();
|
||||||
|
log.debug("获取RAGFlow最新状态,documentId: {}, run: {}, status: {}",
|
||||||
|
documentId, runObj, ragFlowStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
return dto;
|
return dto;
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -433,6 +535,115 @@ public class KnowledgeFilesServiceImpl implements KnowledgeFilesService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PageData<KnowledgeFilesDTO> getPageListByStatus(String datasetId, Integer status, Integer page,
|
||||||
|
Integer limit) {
|
||||||
|
if (StringUtils.isBlank(datasetId)) {
|
||||||
|
throw new RenException(ErrorCode.PARAMS_GET_ERROR, "datasetId不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("=== 开始根据状态查询文档列表 ===");
|
||||||
|
log.info("datasetId: {}, status: {}, page: {}, limit: {}", datasetId, status, page, limit);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 获取RAG配置
|
||||||
|
Map<String, Object> ragConfig = getDefaultRAGConfig();
|
||||||
|
String baseUrl = (String) ragConfig.get("base_url");
|
||||||
|
String apiKey = (String) ragConfig.get("api_key");
|
||||||
|
|
||||||
|
// 构建请求URL - 获取文档列表
|
||||||
|
StringBuilder urlBuilder = new StringBuilder();
|
||||||
|
urlBuilder.append(baseUrl).append("/api/v1/datasets/").append(datasetId).append("/documents");
|
||||||
|
|
||||||
|
// 构建查询参数
|
||||||
|
List<String> params = new ArrayList<>();
|
||||||
|
if (page != null && page > 0) {
|
||||||
|
params.add("page=" + page);
|
||||||
|
}
|
||||||
|
if (limit != null && limit > 0) {
|
||||||
|
params.add("page_size=" + limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!params.isEmpty()) {
|
||||||
|
urlBuilder.append("?").append(String.join("&", params));
|
||||||
|
}
|
||||||
|
|
||||||
|
String url = urlBuilder.toString();
|
||||||
|
log.debug("请求URL: {}", url);
|
||||||
|
|
||||||
|
// 设置请求头
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||||
|
headers.set("Authorization", "Bearer " + apiKey);
|
||||||
|
|
||||||
|
HttpEntity<String> requestEntity = new HttpEntity<>(headers);
|
||||||
|
|
||||||
|
// 发送GET请求
|
||||||
|
log.info("发送GET请求到RAGFlow API获取文档列表...");
|
||||||
|
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, requestEntity, String.class);
|
||||||
|
|
||||||
|
log.info("RAGFlow API响应状态码: {}", response.getStatusCode());
|
||||||
|
|
||||||
|
if (!response.getStatusCode().is2xxSuccessful()) {
|
||||||
|
log.error("RAGFlow API调用失败,状态码: {}, 响应内容: {}", response.getStatusCode(), response.getBody());
|
||||||
|
throw new RenException(ErrorCode.RAG_API_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
String responseBody = response.getBody();
|
||||||
|
log.debug("RAGFlow API响应内容: {}", responseBody);
|
||||||
|
|
||||||
|
// 解析响应
|
||||||
|
Map<String, Object> responseMap = objectMapper.readValue(responseBody, Map.class);
|
||||||
|
Integer code = (Integer) responseMap.get("code");
|
||||||
|
|
||||||
|
if (code != null && code == 0) {
|
||||||
|
Object dataObj = responseMap.get("data");
|
||||||
|
|
||||||
|
// 解析文档列表并过滤状态
|
||||||
|
PageData<KnowledgeFilesDTO> pageData = parseDocumentListResponse(dataObj, page, limit);
|
||||||
|
|
||||||
|
if (status != null) {
|
||||||
|
// 根据状态过滤文档列表
|
||||||
|
List<KnowledgeFilesDTO> filteredDocuments = pageData.getList().stream()
|
||||||
|
.filter(doc -> status.equals(doc.getStatus()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
// 更新分页数据
|
||||||
|
pageData.setList(filteredDocuments);
|
||||||
|
pageData.setTotal(filteredDocuments.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("根据状态查询文档列表成功,datasetId: {}, 状态: {}, 文档数量: {}",
|
||||||
|
datasetId, status, pageData.getList().size());
|
||||||
|
return pageData;
|
||||||
|
} else {
|
||||||
|
log.error("RAGFlow API调用失败,响应码: {}, 响应内容: {}", code, responseBody);
|
||||||
|
throw new RenException(ErrorCode.RAG_API_ERROR, "RAGFlow API调用失败,响应码: " + code);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("解析RAGFlow API响应失败: {}", e.getMessage(), e);
|
||||||
|
throw new RenException(ErrorCode.RAG_API_ERROR,
|
||||||
|
"解析RAGFlow响应失败: " + e.getMessage());
|
||||||
|
} catch (HttpClientErrorException e) {
|
||||||
|
log.error("RAGFlow API调用失败 - HTTP错误: {}, 状态码: {}, 响应内容: {}",
|
||||||
|
e.getMessage(), e.getStatusCode(), e.getResponseBodyAsString());
|
||||||
|
throw new RenException(ErrorCode.RAG_API_ERROR, "获取RAGFlow文档列表失败: " + e.getMessage());
|
||||||
|
} catch (HttpServerErrorException e) {
|
||||||
|
log.error("RAGFlow API调用失败 - 服务器错误: {}, 状态码: {}, 响应内容: {}",
|
||||||
|
e.getMessage(), e.getStatusCode(), e.getResponseBodyAsString());
|
||||||
|
throw new RenException(ErrorCode.RAG_API_ERROR, "获取RAGFlow文档列表失败: " + e.getMessage());
|
||||||
|
} catch (ResourceAccessException e) {
|
||||||
|
log.error("RAGFlow API调用失败 - 网络连接错误: {}", e.getMessage(), e);
|
||||||
|
throw new RenException(ErrorCode.RAG_API_ERROR, "获取RAGFlow文档列表失败: 网络连接错误 - " + e.getMessage());
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("根据状态查询文档列表失败: {}", e.getMessage(), e);
|
||||||
|
throw new RenException(ErrorCode.RAG_API_ERROR, "查询文档列表失败: " + e.getMessage());
|
||||||
|
} finally {
|
||||||
|
log.info("=== 根据状态查询文档列表操作结束 ===");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public KnowledgeFilesDTO uploadDocument(String datasetId, MultipartFile file, String name,
|
public KnowledgeFilesDTO uploadDocument(String datasetId, MultipartFile file, String name,
|
||||||
Map<String, Object> metaFields, String chunkMethod,
|
Map<String, Object> metaFields, String chunkMethod,
|
||||||
|
|||||||
@@ -1211,9 +1211,10 @@ export default {
|
|||||||
'knowledgeFileUpload.cancel': 'Cancel',
|
'knowledgeFileUpload.cancel': 'Cancel',
|
||||||
'knowledgeFileUpload.confirm': 'Confirm',
|
'knowledgeFileUpload.confirm': 'Confirm',
|
||||||
'knowledgeFileUpload.knowledgeBaseName': 'Knowledge Base Name',
|
'knowledgeFileUpload.knowledgeBaseName': 'Knowledge Base Name',
|
||||||
'knowledgeFileUpload.statusSuccess': 'Parse Success',
|
'knowledgeFileUpload.statusNotStarted': 'Not Started',
|
||||||
'knowledgeFileUpload.statusPending': 'Pending Parse',
|
'knowledgeFileUpload.statusProcessing': 'Processing',
|
||||||
'knowledgeFileUpload.statusParsing': 'Parsing',
|
'knowledgeFileUpload.statusCancelled': 'Cancelled',
|
||||||
|
'knowledgeFileUpload.statusCompleted': 'Completed',
|
||||||
'knowledgeFileUpload.statusFailed': 'Parse Failed',
|
'knowledgeFileUpload.statusFailed': 'Parse Failed',
|
||||||
'knowledgeFileUpload.uploadSuccess': 'Document upload successful',
|
'knowledgeFileUpload.uploadSuccess': 'Document upload successful',
|
||||||
'knowledgeFileUpload.uploadFailed': 'Document upload failed',
|
'knowledgeFileUpload.uploadFailed': 'Document upload failed',
|
||||||
|
|||||||
@@ -1211,9 +1211,10 @@ export default {
|
|||||||
'knowledgeFileUpload.cancel': '取消',
|
'knowledgeFileUpload.cancel': '取消',
|
||||||
'knowledgeFileUpload.confirm': '确定',
|
'knowledgeFileUpload.confirm': '确定',
|
||||||
'knowledgeFileUpload.knowledgeBaseName': '知识库名称',
|
'knowledgeFileUpload.knowledgeBaseName': '知识库名称',
|
||||||
'knowledgeFileUpload.statusSuccess': '解析成功',
|
'knowledgeFileUpload.statusNotStarted': '未开始',
|
||||||
'knowledgeFileUpload.statusPending': '待解析',
|
'knowledgeFileUpload.statusProcessing': '处理中',
|
||||||
'knowledgeFileUpload.statusParsing': '解析中',
|
'knowledgeFileUpload.statusCancelled': '已取消',
|
||||||
|
'knowledgeFileUpload.statusCompleted': '已完成',
|
||||||
'knowledgeFileUpload.statusFailed': '解析失败',
|
'knowledgeFileUpload.statusFailed': '解析失败',
|
||||||
'knowledgeFileUpload.uploadSuccess': '文档上传成功',
|
'knowledgeFileUpload.uploadSuccess': '文档上传成功',
|
||||||
'knowledgeFileUpload.uploadFailed': '文档上传失败',
|
'knowledgeFileUpload.uploadFailed': '文档上传失败',
|
||||||
|
|||||||
@@ -1211,10 +1211,10 @@ export default {
|
|||||||
'knowledgeFileUpload.cancel': '取消',
|
'knowledgeFileUpload.cancel': '取消',
|
||||||
'knowledgeFileUpload.confirm': '確定',
|
'knowledgeFileUpload.confirm': '確定',
|
||||||
'knowledgeFileUpload.knowledgeBaseName': '知識庫名稱',
|
'knowledgeFileUpload.knowledgeBaseName': '知識庫名稱',
|
||||||
'knowledgeFileUpload.statusSuccess': '解析成功',
|
'knowledgeFileUpload.statusNotStarted': '未開始',
|
||||||
'knowledgeFileUpload.statusPending': '待解析',
|
'knowledgeFileUpload.statusProcessing': '處理中',
|
||||||
'knowledgeFileUpload.statusParsing': '解析中',
|
'knowledgeFileUpload.statusCancelled': '已取消',
|
||||||
'knowledgeFileUpload.statusFailed': '解析失敗',
|
'knowledgeFileUpload.statusCompleted': '已完成',
|
||||||
'knowledgeFileUpload.uploadSuccess': '文檔上傳成功',
|
'knowledgeFileUpload.uploadSuccess': '文檔上傳成功',
|
||||||
'knowledgeFileUpload.uploadFailed': '文檔上傳失敗',
|
'knowledgeFileUpload.uploadFailed': '文檔上傳失敗',
|
||||||
'knowledgeFileUpload.parseSuccess': '文檔解析成功',
|
'knowledgeFileUpload.parseSuccess': '文檔解析成功',
|
||||||
|
|||||||
@@ -37,6 +37,13 @@
|
|||||||
<span>{{ formatDate(scope.row.createdAt) }}</span>
|
<span>{{ formatDate(scope.row.createdAt) }}</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
<el-table-column :label="$t('knowledgeFileUpload.status')" align="center" width="120">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-tag :type="getParseStatusType(scope.row.parseStatusCode)" size="small">
|
||||||
|
{{ getParseStatusText(scope.row.parseStatusCode) }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column :label="$t('knowledgeFileUpload.sliceCount')" align="center" width="100">
|
<el-table-column :label="$t('knowledgeFileUpload.sliceCount')" align="center" width="100">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<span>{{ scope.row.sliceCount || 0 }}</span>
|
<span>{{ scope.row.sliceCount || 0 }}</span>
|
||||||
@@ -45,7 +52,7 @@
|
|||||||
<el-table-column :label="$t('knowledgeFileUpload.operation')" align="center" width="200">
|
<el-table-column :label="$t('knowledgeFileUpload.operation')" align="center" width="200">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<el-button size="mini" type="text" @click="handleParse(scope.row)"
|
<el-button size="mini" type="text" @click="handleParse(scope.row)"
|
||||||
:disabled="scope.row.status === 1" v-if="!scope.row.sliceCount || scope.row.sliceCount <= 0">
|
:disabled="scope.row.parseStatusCode === 1 || scope.row.parseStatusCode === 3 || scope.row.parseStatusCode === 4" v-if="!scope.row.sliceCount || scope.row.sliceCount <= 0">
|
||||||
{{ $t('knowledgeFileUpload.parse') }}
|
{{ $t('knowledgeFileUpload.parse') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button size="mini" type="text" @click="handleViewSlices(scope.row)"
|
<el-button size="mini" type="text" @click="handleViewSlices(scope.row)"
|
||||||
@@ -298,7 +305,13 @@ export default {
|
|||||||
question: ''
|
question: ''
|
||||||
},
|
},
|
||||||
retrievalTestResult: null,
|
retrievalTestResult: null,
|
||||||
retrievalTestLoading: false
|
retrievalTestLoading: false,
|
||||||
|
|
||||||
|
// 状态轮询相关数据
|
||||||
|
statusPollingTimer: null,
|
||||||
|
statusPollingInterval: 5000, // 5秒轮询一次
|
||||||
|
maxStatusPollingTime: 300000, // 最大轮询时间5分钟
|
||||||
|
statusPollingStartTime: null
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
@@ -307,6 +320,10 @@ export default {
|
|||||||
this.uploadUrl = `${Api.getServiceUrl()}/api/v1/documents/upload`;
|
this.uploadUrl = `${Api.getServiceUrl()}/api/v1/documents/upload`;
|
||||||
this.fetchFileList();
|
this.fetchFileList();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
beforeDestroy() {
|
||||||
|
this.stopStatusPolling();
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
pageCount() {
|
pageCount() {
|
||||||
return Math.ceil(this.total / this.pageSize);
|
return Math.ceil(this.total / this.pageSize);
|
||||||
@@ -373,6 +390,9 @@ export default {
|
|||||||
|
|
||||||
// 为每个文档获取切片数量
|
// 为每个文档获取切片数量
|
||||||
await this.fetchSliceCountsForDocuments();
|
await this.fetchSliceCountsForDocuments();
|
||||||
|
|
||||||
|
// 自动为处理中的文档启动状态检测
|
||||||
|
this.startStatusPolling();
|
||||||
} else {
|
} else {
|
||||||
this.$message.error(data?.msg || this.$t('knowledgeFileUpload.getListFailed'));
|
this.$message.error(data?.msg || this.$t('knowledgeFileUpload.getListFailed'));
|
||||||
this.fileList = [];
|
this.fileList = [];
|
||||||
@@ -388,6 +408,113 @@ export default {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 启动文档状态轮询
|
||||||
|
startStatusPolling: function () {
|
||||||
|
// 检查是否已经有轮询在进行
|
||||||
|
if (this.statusPollingTimer) {
|
||||||
|
console.log('状态轮询已在运行');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否有处理中的文档
|
||||||
|
const hasProcessingDocuments = this.fileList.some(document =>
|
||||||
|
document.parseStatusCode === 1
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!hasProcessingDocuments) {
|
||||||
|
console.log('没有处理中的文档,不启动状态轮询');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('启动文档状态轮询');
|
||||||
|
this.statusPollingStartTime = Date.now();
|
||||||
|
|
||||||
|
// 立即执行一次状态检查
|
||||||
|
this.pollDocumentStatus();
|
||||||
|
|
||||||
|
// 开始轮询
|
||||||
|
this.statusPollingTimer = setInterval(() => {
|
||||||
|
this.pollDocumentStatus();
|
||||||
|
}, this.statusPollingInterval);
|
||||||
|
},
|
||||||
|
|
||||||
|
// 停止文档状态轮询
|
||||||
|
stopStatusPolling: function () {
|
||||||
|
if (this.statusPollingTimer) {
|
||||||
|
clearInterval(this.statusPollingTimer);
|
||||||
|
this.statusPollingTimer = null;
|
||||||
|
console.log('停止文档状态轮询');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 轮询文档状态
|
||||||
|
pollDocumentStatus: async function () {
|
||||||
|
// 检查是否超过最大轮询时间
|
||||||
|
if (Date.now() - this.statusPollingStartTime > this.maxStatusPollingTime) {
|
||||||
|
console.log('达到最大轮询时间,停止状态轮询');
|
||||||
|
this.stopStatusPolling();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const params = {
|
||||||
|
page: this.currentPage,
|
||||||
|
page_size: this.pageSize,
|
||||||
|
name: this.searchName
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await new Promise((resolve, reject) => {
|
||||||
|
KnowledgeBaseAPI.getDocumentList(this.datasetId, params,
|
||||||
|
({ data }) => resolve(data),
|
||||||
|
(err) => reject(err)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response && response.code === 0) {
|
||||||
|
const updatedFileList = response.data.list;
|
||||||
|
|
||||||
|
// 更新文档状态
|
||||||
|
this.updateDocumentStatuses(updatedFileList);
|
||||||
|
|
||||||
|
// 检查是否还有处理中的文档
|
||||||
|
const hasProcessingDocuments = updatedFileList.some(document =>
|
||||||
|
document.parseStatusCode === 1
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!hasProcessingDocuments) {
|
||||||
|
console.log('所有文档处理完成,停止状态轮询');
|
||||||
|
this.stopStatusPolling();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('轮询文档状态失败:', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 更新文档状态
|
||||||
|
updateDocumentStatuses: function (updatedFileList) {
|
||||||
|
let hasChanges = false;
|
||||||
|
|
||||||
|
updatedFileList.forEach(updatedDoc => {
|
||||||
|
const existingDoc = this.fileList.find(doc => doc.id === updatedDoc.id);
|
||||||
|
if (existingDoc && existingDoc.parseStatusCode !== updatedDoc.parseStatusCode) {
|
||||||
|
// 状态发生变化,更新文档
|
||||||
|
Object.assign(existingDoc, updatedDoc);
|
||||||
|
hasChanges = true;
|
||||||
|
console.log(`文档 ${existingDoc.name} 状态已更新: ${existingDoc.parseStatusCode} -> ${updatedDoc.parseStatusCode}`);
|
||||||
|
|
||||||
|
// 如果状态变为完成,启动切片数量检测
|
||||||
|
if (updatedDoc.parseStatusCode === 3) {
|
||||||
|
this.fetchSliceCountForSingleDocument(updatedDoc.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (hasChanges) {
|
||||||
|
this.$forceUpdate();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// 为文档列表中的每个文档获取切片数量
|
// 为文档列表中的每个文档获取切片数量
|
||||||
fetchSliceCountsForDocuments: async function () {
|
fetchSliceCountsForDocuments: async function () {
|
||||||
@@ -395,55 +522,9 @@ export default {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 为每个文档创建获取切片数量的Promise
|
// 为每个文档获取切片数量
|
||||||
const sliceCountPromises = this.fileList.map(async (document) => {
|
for (const document of this.fileList) {
|
||||||
try {
|
this.fetchSliceCountForSingleDocument(document.id);
|
||||||
const params = {
|
|
||||||
page: 1,
|
|
||||||
page_size: 1 // 只需要获取总数,不需要实际数据
|
|
||||||
};
|
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
KnowledgeBaseAPI.listChunks(this.datasetId, document.id, params,
|
|
||||||
({ data }) => {
|
|
||||||
if (data && data.code === 0) {
|
|
||||||
// 获取切片总数
|
|
||||||
const sliceCount = data.data.total || 0;
|
|
||||||
resolve({ documentId: document.id, sliceCount });
|
|
||||||
} else {
|
|
||||||
console.warn(`获取文档 ${document.name} 切片数量失败:`, data?.msg);
|
|
||||||
resolve({ documentId: document.id, sliceCount: 0 });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
(err) => {
|
|
||||||
console.warn(`获取文档 ${document.name} 切片数量失败:`, err);
|
|
||||||
resolve({ documentId: document.id, sliceCount: 0 });
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.warn(`获取文档 ${document.name} 切片数量失败:`, error);
|
|
||||||
return { documentId: document.id, sliceCount: 0 };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 等待所有切片数量获取完成
|
|
||||||
const sliceCountResults = await Promise.all(sliceCountPromises);
|
|
||||||
|
|
||||||
// 更新文档列表中的切片数量
|
|
||||||
sliceCountResults.forEach(result => {
|
|
||||||
const document = this.fileList.find(doc => doc.id === result.documentId);
|
|
||||||
if (document) {
|
|
||||||
// 使用Vue.set确保响应式更新
|
|
||||||
this.$set(document, 'sliceCount', result.sliceCount);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 强制更新视图
|
|
||||||
this.$forceUpdate();
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取切片数量失败:', error);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -480,72 +561,17 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// 智能检测切片生成状态并自动刷新
|
// 智能检测切片生成状态并自动刷新
|
||||||
smartRefreshSliceCount: function (documentId, maxRetries = 10, interval = 2000) {
|
smartRefreshSliceCount: function (documentId) {
|
||||||
const document = this.fileList.find(doc => doc.id === documentId);
|
const document = this.fileList.find(doc => doc.id === documentId);
|
||||||
if (!document) {
|
if (!document) {
|
||||||
console.warn('未找到文档:', documentId);
|
console.warn('未找到文档:', documentId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let retryCount = 0;
|
// 延迟2秒后获取切片数量,给后端更多处理时间
|
||||||
let lastSliceCount = document.sliceCount || 0;
|
setTimeout(() => {
|
||||||
|
this.fetchSliceCountForSingleDocument(documentId);
|
||||||
const checkSliceStatus = () => {
|
}, 2000);
|
||||||
const params = {
|
|
||||||
page: 1,
|
|
||||||
page_size: 1
|
|
||||||
};
|
|
||||||
|
|
||||||
KnowledgeBaseAPI.listChunks(this.datasetId, documentId, params,
|
|
||||||
({ data }) => {
|
|
||||||
if (data && data.code === 0) {
|
|
||||||
const currentSliceCount = data.data.total || 0;
|
|
||||||
|
|
||||||
if (currentSliceCount > 0 && currentSliceCount !== lastSliceCount) {
|
|
||||||
// 切片数量有变化,更新显示
|
|
||||||
this.$set(document, 'sliceCount', currentSliceCount);
|
|
||||||
this.$forceUpdate();
|
|
||||||
console.log(`文档 ${document.name} 切片数量已自动更新为:`, currentSliceCount);
|
|
||||||
|
|
||||||
// 如果切片数量稳定且大于0,停止检测
|
|
||||||
if (currentSliceCount > 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lastSliceCount = currentSliceCount;
|
|
||||||
|
|
||||||
// 继续检测直到达到最大重试次数
|
|
||||||
if (retryCount < maxRetries) {
|
|
||||||
retryCount++;
|
|
||||||
setTimeout(checkSliceStatus, interval);
|
|
||||||
} else {
|
|
||||||
console.log(`文档 ${document.name} 切片检测已达到最大重试次数`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.warn(`获取文档 ${document.name} 切片数量失败:`, data?.msg);
|
|
||||||
|
|
||||||
// 失败时也继续重试
|
|
||||||
if (retryCount < maxRetries) {
|
|
||||||
retryCount++;
|
|
||||||
setTimeout(checkSliceStatus, interval);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
(err) => {
|
|
||||||
console.warn(`获取文档 ${document.name} 切片数量失败:`, err);
|
|
||||||
|
|
||||||
// 失败时也继续重试
|
|
||||||
if (retryCount < maxRetries) {
|
|
||||||
retryCount++;
|
|
||||||
setTimeout(checkSliceStatus, interval);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 开始检测
|
|
||||||
setTimeout(checkSliceStatus, 1000); // 1秒后开始第一次检测
|
|
||||||
},
|
},
|
||||||
handleSearch: function () {
|
handleSearch: function () {
|
||||||
this.currentPage = 1;
|
this.currentPage = 1;
|
||||||
@@ -707,6 +733,17 @@ export default {
|
|||||||
({ data }) => {
|
({ data }) => {
|
||||||
if (data && data.code === 0) {
|
if (data && data.code === 0) {
|
||||||
this.$message.success('请求已提交,解析中');
|
this.$message.success('请求已提交,解析中');
|
||||||
|
|
||||||
|
// 立即更新文档状态为处理中
|
||||||
|
const document = this.fileList.find(doc => doc.id === row.id);
|
||||||
|
if (document) {
|
||||||
|
document.parseStatusCode = 1; // 处理中状态
|
||||||
|
this.$forceUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 启动状态轮询
|
||||||
|
this.startStatusPolling();
|
||||||
|
|
||||||
// 使用智能检测自动刷新切片数量
|
// 使用智能检测自动刷新切片数量
|
||||||
this.smartRefreshSliceCount(row.id);
|
this.smartRefreshSliceCount(row.id);
|
||||||
} else {
|
} else {
|
||||||
@@ -812,28 +849,36 @@ export default {
|
|||||||
this.$message.info(this.$t('knowledgeFileUpload.deleteCancelled'));
|
this.$message.info(this.$t('knowledgeFileUpload.deleteCancelled'));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
getStatusType: function (status) {
|
getParseStatusType: function (parseStatusCode) {
|
||||||
switch (status) {
|
switch (parseStatusCode) {
|
||||||
case 0:
|
case 0:
|
||||||
return 'info'; // 灰色 - 待解析
|
return 'info'; // 灰色 - 未开始
|
||||||
case 1:
|
case 1:
|
||||||
return 'success'; // 绿色 - 解析成功
|
return 'primary'; // 蓝色 - 处理中
|
||||||
case 2:
|
case 2:
|
||||||
return 'primary'; // 蓝色 - 解析中
|
return 'warning'; // 黄色 - 已取消
|
||||||
|
case 3:
|
||||||
|
return 'success'; // 绿色 - 完成
|
||||||
|
case 4:
|
||||||
|
return 'danger'; // 红色 - 失败
|
||||||
default:
|
default:
|
||||||
return 'danger'; // 红色 - 解析失败
|
return 'info'; // 默认灰色
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getStatusText: function (status) {
|
getParseStatusText: function (parseStatusCode) {
|
||||||
switch (status) {
|
switch (parseStatusCode) {
|
||||||
case 0:
|
case 0:
|
||||||
return this.$t('knowledgeFileUpload.statusPending');
|
return this.$t('knowledgeFileUpload.statusNotStarted');
|
||||||
case 1:
|
case 1:
|
||||||
return this.$t('knowledgeFileUpload.statusSuccess');
|
return this.$t('knowledgeFileUpload.statusProcessing');
|
||||||
case 2:
|
case 2:
|
||||||
return this.$t('knowledgeFileUpload.statusParsing');
|
return this.$t('knowledgeFileUpload.statusCancelled');
|
||||||
default:
|
case 3:
|
||||||
|
return this.$t('knowledgeFileUpload.statusCompleted');
|
||||||
|
case 4:
|
||||||
return this.$t('knowledgeFileUpload.statusFailed');
|
return this.$t('knowledgeFileUpload.statusFailed');
|
||||||
|
default:
|
||||||
|
return this.$t('knowledgeFileUpload.statusNotStarted');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
goToPage: function (page) {
|
goToPage: function (page) {
|
||||||
|
|||||||
Reference in New Issue
Block a user