Merge branch 'refs/heads/main' into perf-llm-lazy-optimization

This commit is contained in:
DaGou12138
2026-03-13 17:03:28 +08:00
111 changed files with 11727 additions and 3557 deletions
@@ -0,0 +1,45 @@
# 知识库模块全量集成测试报告
## 1. 测试背景
针对 `KnowledgeBaseController``KnowledgeFilesController` 共 14 个接口进行了深度集成测试。主要解决了本地影子库与 RAGFlow 远程服务之间的状态对齐、数据反序列化兼容性以及批量操作逻辑安全性问题。
## 2. 修复的核心 Bug 清单 (Hotfixes)
| 模块 | 问题类型 | 修复方案 | 验证结果 |
| :--- | :--- | :--- | :--- |
| **DTO** | `positions` 反序列化失败 | 类型从 `List<Integer>` 提升为 `Object`,支持嵌套数组 | ✅ 已验证 |
| **DTO** | 日期格式不兼容 | 针对 RAGFlow 的 RFC 1123 格式,将 `Date` 改为 `String` 透传 | ✅ 已验证 |
| **请求** | 检索参数 `null` 拒绝 | 增加 `@JsonInclude(NON_NULL)`,跳过可选字段的空值序列化 | ✅ 已验证 |
| **同步** | 状态自愈死锁 | 增加 `CANCEL/FAIL` 状态的 60s 低频同步机制,防止逻辑错误锁定 | ✅ 已验证 |
| **逻辑** | 删除守卫逻辑错误 | 将拦截条件从 `status="1"` 修正为 `run="RUNNING"` | ✅ 已验证 |
## 3. 全量接口测试统计
### KnowledgeBaseController (7/7)
- [x] 分页查询 (`GET /datasets`)
- [x] 详情获取 (`GET /datasets/{id}`)
- [x] 创建知识库 (`POST /datasets`)
- [x] 修改配置 (`PUT /datasets/{id}`)
- [x] 物理删除 (`DELETE /datasets/{id}`)
- [x] 批量删除 (`DELETE /datasets/batch`)
- [x] 模型列表获取 (`GET /datasets/rag-models`)
### KnowledgeFilesController (7/7)
- [x] 文档列表与同步 (`GET /datasets/{id}/documents`)
- [x] 状态过滤查询 (`GET /datasets/{id}/documents/status/{s}`)
- [x] 文档上传 (`POST /datasets/{id}/documents`)
- [x] 触发解析 (`POST /datasets/{id}/chunks`)
- [x] 切片详情 (`GET /datasets/{id}/documents/{docId}/chunks`)
- [x] 召回测试 (`POST /datasets/{id}/retrieval-test`)
- [x] 批量删除文档 (`DELETE /datasets/{id}/documents`)
## 4. 自动化审计结论
通过执行 `comprehensive_audit.ps1` 自动化脚本,模拟了“创建->上传->解析->同步->检索->删除”的完整生产链路。
- **解析成功率**100%
- **数据准确性**:DTO 转换无异常,坐标及得分提取正常
- **系统安全性**:解析中拦截机制生效
- **结论****准生产就绪 (Production Ready)**
---
*报告生成时间:2026-02-13*
*审核:dora--1206563805@qq.com*
@@ -2,7 +2,9 @@ package xiaozhi.common.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.JdkClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import java.time.Duration;
/**
* RestTemplate配置
@@ -12,6 +14,8 @@ public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
JdkClientHttpRequestFactory factory = new JdkClientHttpRequestFactory();
factory.setReadTimeout(Duration.ofSeconds(30));
return new RestTemplate(factory);
}
}
@@ -79,6 +79,22 @@ public class SwaggerConfig {
.build();
}
@Bean
public GroupedOpenApi knowledgeApi() {
return GroupedOpenApi.builder()
.group("knowledge")
.pathsToMatch("/datasets/**")
.build();
}
@Bean
public GroupedOpenApi botApi() {
return GroupedOpenApi.builder()
.group("bot")
.pathsToMatch("/api/v1/**")
.build();
}
@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI().info(new Info()
@@ -250,4 +250,5 @@ public interface ErrorCode {
int AGENT_TAG_NAME_EMPTY = 10197; // 标签名称不能为空
int AGENT_TAG_NOT_EXIST = 10198; // 标签不存在
int RAG_DOCUMENT_PARSING_DELETE_ERROR = 10199; // 文档解析中,禁止删除
}
@@ -13,23 +13,25 @@ public class RenException extends RuntimeException {
private String msg;
public RenException(int code) {
super(MessageUtils.getMessage(code));
this.code = code;
this.msg = MessageUtils.getMessage(code);
}
public RenException(int code, String... params) {
super(MessageUtils.getMessage(code, params));
this.code = code;
this.msg = MessageUtils.getMessage(code, params);
}
public RenException(int code, Throwable e) {
super(e);
super(MessageUtils.getMessage(code), e);
this.code = code;
this.msg = MessageUtils.getMessage(code);
}
public RenException(int code, Throwable e, String... params) {
super(e);
super(MessageUtils.getMessage(code, params), e);
this.code = code;
this.msg = MessageUtils.getMessage(code, params);
}
@@ -12,28 +12,29 @@ import xiaozhi.modules.sys.service.SysParamsService;
* 封装了重复的SM2解密、验证码提取和验证逻辑
*/
public class Sm2DecryptUtil {
/**
* 验证码长度
*/
private static final int CAPTCHA_LENGTH = 5;
/**
* 解密SM2加密内容,提取验证码并验证
*
* @param encryptedPassword SM2加密的密码字符串
* @param captchaId 验证码ID
* @param captchaService 验证码服务
* @param sysParamsService 系统参数服务
* @param captchaId 验证码ID
* @param captchaService 验证码服务
* @param sysParamsService 系统参数服务
* @return 解密后的实际密码
*/
public static String decryptAndValidateCaptcha(String encryptedPassword, String captchaId,
CaptchaService captchaService, SysParamsService sysParamsService) {
public static String decryptAndValidateCaptcha(String encryptedPassword, String captchaId,
CaptchaService captchaService, SysParamsService sysParamsService) {
// 获取SM2私钥
String privateKeyStr = sysParamsService.getValue(Constant.SM2_PRIVATE_KEY, true);
if (StringUtils.isBlank(privateKeyStr)) {
throw new RenException(ErrorCode.SM2_KEY_NOT_CONFIGURED);
}
// 使用SM2私钥解密密码
String decryptedContent;
try {
@@ -41,19 +42,20 @@ public class Sm2DecryptUtil {
} catch (Exception e) {
throw new RenException(ErrorCode.SM2_DECRYPT_ERROR);
}
// 分离验证码和密码:前5位是验证码,后面是密码
if (decryptedContent.length() > CAPTCHA_LENGTH) {
String embeddedCaptcha = decryptedContent.substring(0, CAPTCHA_LENGTH);
String actualPassword = decryptedContent.substring(CAPTCHA_LENGTH);
// 验证嵌入的验证码是否正确
boolean embeddedCaptchaValid = captchaService.validate(captchaId, embeddedCaptcha, true);
if (!embeddedCaptchaValid) {
throw new RenException(ErrorCode.SMS_CAPTCHA_ERROR);
}
return actualPassword;
} else if (decryptedContent.length() > 0) {
throw new RenException(ErrorCode.SMS_CAPTCHA_ERROR);
} else {
throw new RenException(ErrorCode.SM2_DECRYPT_ERROR);
}
@@ -66,7 +66,7 @@ public class OtaServiceImpl extends BaseServiceImpl<OtaDao, OtaEntity> implement
// 同类固件只保留最新的一条
List<OtaEntity> otaList = baseDao.selectList(queryWrapper);
if (otaList != null && otaList.size() > 0) {
OtaEntity otaBefore = otaList.getFirst();
OtaEntity otaBefore = otaList.get(0);
entity.setId(otaBefore.getId());
baseDao.updateById(entity);
return true;
@@ -0,0 +1,13 @@
package xiaozhi.modules.knowledge.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
/**
* 知识库模块定时任务配置
* 启用 Spring Schedule 能力
*/
@Configuration
@EnableScheduling
public class RAGTaskConfig {
}
@@ -26,6 +26,7 @@ import xiaozhi.common.utils.Result;
import xiaozhi.common.utils.ToolUtil;
import xiaozhi.modules.knowledge.dto.KnowledgeBaseDTO;
import xiaozhi.modules.knowledge.service.KnowledgeBaseService;
import xiaozhi.modules.knowledge.service.KnowledgeManagerService;
import xiaozhi.modules.model.entity.ModelConfigEntity;
import xiaozhi.modules.security.user.SecurityUser;
@@ -36,6 +37,7 @@ import xiaozhi.modules.security.user.SecurityUser;
public class KnowledgeBaseController {
private final KnowledgeBaseService knowledgeBaseService;
private final KnowledgeManagerService knowledgeManagerService;
@GetMapping
@Operation(summary = "分页查询知识库列表")
@@ -96,6 +98,8 @@ public class KnowledgeBaseController {
throw new RenException(ErrorCode.NO_PERMISSION);
}
// [FIX] 注入 ID,防止 Service 层找不到记录
knowledgeBaseDTO.setId(existingKnowledgeBase.getId());
knowledgeBaseDTO.setDatasetId(datasetId);
KnowledgeBaseDTO resp = knowledgeBaseService.update(knowledgeBaseDTO);
return new Result<KnowledgeBaseDTO>().ok(resp);
@@ -117,7 +121,8 @@ public class KnowledgeBaseController {
throw new RenException(ErrorCode.NO_PERMISSION);
}
knowledgeBaseService.deleteByDatasetId(datasetId);
// [Architecture Fix] 通过编排层级联删除,防止孤儿数据并解决循环依赖
knowledgeManagerService.deleteDatasetWithFiles(datasetId);
return new Result<>();
}
@@ -133,15 +138,16 @@ public class KnowledgeBaseController {
// 获取当前登录用户ID
Long currentUserId = SecurityUser.getUserId();
List<String> idList = Arrays.asList(ids.split(","));
List<KnowledgeBaseDTO> knowledgeBaseDTOs = Optional.ofNullable(knowledgeBaseService.getByDatasetIdList(idList)).orElseGet(ArrayList::new);
List<KnowledgeBaseDTO> knowledgeBaseDTOs = Optional.ofNullable(knowledgeBaseService.getByDatasetIdList(idList))
.orElseGet(ArrayList::new);
if (ToolUtil.isNotEmpty(knowledgeBaseDTOs)) {
knowledgeBaseDTOs.forEach(item->{
knowledgeBaseDTOs.forEach(item -> {
// 检查权限:用户只能删除自己创建的知识库
if (item.getCreator() == null || !item.getCreator().equals(currentUserId)) {
throw new RenException(ErrorCode.NO_PERMISSION);
}
//删除
knowledgeBaseService.deleteByDatasetId(item.getDatasetId());
// [Architecture Fix] 通过编排层级联删除
knowledgeManagerService.deleteDatasetWithFiles(item.getDatasetId());
});
}
return new Result<>();
@@ -4,6 +4,7 @@ import java.util.List;
import java.util.Map;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springdoc.core.annotations.ParameterObject;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
@@ -11,7 +12,6 @@ import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import xiaozhi.common.exception.ErrorCode;
@@ -20,6 +20,9 @@ import xiaozhi.common.page.PageData;
import xiaozhi.common.utils.Result;
import xiaozhi.modules.knowledge.dto.KnowledgeBaseDTO;
import xiaozhi.modules.knowledge.dto.KnowledgeFilesDTO;
import xiaozhi.modules.knowledge.dto.document.ChunkDTO;
import xiaozhi.modules.knowledge.dto.document.DocumentDTO;
import xiaozhi.modules.knowledge.dto.document.RetrievalDTO;
import xiaozhi.modules.knowledge.service.KnowledgeBaseService;
import xiaozhi.modules.knowledge.service.KnowledgeFilesService;
import xiaozhi.modules.security.user.SecurityUser;
@@ -57,13 +60,13 @@ public class KnowledgeFilesController {
public Result<PageData<KnowledgeFilesDTO>> getPageList(
@PathVariable("dataset_id") String datasetId,
@RequestParam(required = false) String name,
@RequestParam(required = false) Integer status,
@RequestParam(required = false) String status,
@RequestParam(required = false, defaultValue = "1") Integer page,
@RequestParam(required = false, defaultValue = "10") Integer page_size) {
// 验证知识库权限
validateKnowledgeBasePermission(datasetId);
//组装参数
// 组装参数
KnowledgeFilesDTO knowledgeFilesDTO = new KnowledgeFilesDTO();
knowledgeFilesDTO.setDatasetId(datasetId);
knowledgeFilesDTO.setName(name);
@@ -77,12 +80,12 @@ public class KnowledgeFilesController {
@RequiresPermissions("sys:role:normal")
public Result<PageData<KnowledgeFilesDTO>> getPageListByStatus(
@PathVariable("dataset_id") String datasetId,
@PathVariable("status") Integer status,
@PathVariable("status") String status,
@RequestParam(required = false, defaultValue = "1") Integer page,
@RequestParam(required = false, defaultValue = "10") Integer page_size) {
// 验证知识库权限
validateKnowledgeBasePermission(datasetId);
//组装参数
// 组装参数
KnowledgeFilesDTO knowledgeFilesDTO = new KnowledgeFilesDTO();
knowledgeFilesDTO.setDatasetId(datasetId);
knowledgeFilesDTO.setStatus(status);
@@ -111,16 +114,29 @@ public class KnowledgeFilesController {
return new Result<KnowledgeFilesDTO>().ok(resp);
}
@DeleteMapping("/documents/{document_id}")
@Operation(summary = "删除单个文档")
@Parameter(name = "document_id", description = "文档ID", required = true)
@DeleteMapping("/documents")
@Operation(summary = "批量删除文档")
@RequiresPermissions("sys:role:normal")
public Result<Void> delete(@PathVariable("dataset_id") String datasetId,
@RequestBody DocumentDTO.BatchIdReq req) {
// 验证知识库权限
validateKnowledgeBasePermission(datasetId);
knowledgeFilesService.deleteDocuments(datasetId, req);
return new Result<>();
}
@DeleteMapping("/documents/{document_id}")
@Operation(summary = "删除单个文档")
@RequiresPermissions("sys:role:normal")
public Result<Void> deleteSingle(@PathVariable("dataset_id") String datasetId,
@PathVariable("document_id") String documentId) {
// 验证知识库权限
validateKnowledgeBasePermission(datasetId);
knowledgeFilesService.deleteByDocumentId(documentId, datasetId);
DocumentDTO.BatchIdReq req = new DocumentDTO.BatchIdReq();
req.setIds(java.util.Collections.singletonList(documentId));
knowledgeFilesService.deleteDocuments(datasetId, req);
return new Result<>();
}
@@ -148,64 +164,53 @@ public class KnowledgeFilesController {
@GetMapping("/documents/{document_id}/chunks")
@Operation(summary = "列出指定文档的切片")
@RequiresPermissions("sys:role:normal")
public Result<Map<String, Object>> listChunks(@PathVariable("dataset_id") String datasetId,
public Result<ChunkDTO.ListVO> listChunks(
@PathVariable("dataset_id") String datasetId,
@PathVariable("document_id") String documentId,
@RequestParam(required = false) String keywords,
@RequestParam(required = false, defaultValue = "1") Integer page,
@RequestParam(required = false, defaultValue = "1024") Integer page_size,
@RequestParam(required = false) String id) {
// 验证知识库权限
@ParameterObject ChunkDTO.ListReq req) {
// 验证权限 (内部已包含知识库存在性校验与归属权校验)
validateKnowledgeBasePermission(datasetId);
Map<String, Object> result = knowledgeFilesService.listChunks(datasetId, documentId, keywords, page, page_size, id);
return new Result<Map<String, Object>>().ok(result);
// 设置默认值
if (req.getPage() == null)
req.setPage(1);
if (req.getPageSize() == null)
req.setPageSize(50);
// 调用服务层获取强类型切片列表
ChunkDTO.ListVO result = knowledgeFilesService.listChunks(datasetId, documentId, req);
return new Result<ChunkDTO.ListVO>().ok(result);
}
/**
* 召回测试
*/
@PostMapping("/retrieval-test")
@Operation(summary = "召回测试")
@RequiresPermissions("sys:role:normal")
public Result<Map<String, Object>> retrievalTest(@PathVariable("dataset_id") String datasetId,
@RequestBody Map<String, Object> params) {
public Result<RetrievalDTO.ResultVO> retrievalTest(
@PathVariable("dataset_id") String datasetId,
@RequestBody RetrievalDTO.TestReq req) {
// 验证知识库权限
validateKnowledgeBasePermission(datasetId);
try {
// 提取参数
String question = (String) params.get("question");
if (question == null || question.trim().isEmpty()) {
return new Result<Map<String, Object>>().error("问题不能为空");
}
List<String> datasetIds = (List<String>) params.get("dataset_ids");
List<String> documentIds = (List<String>) params.get("document_ids");
Integer page = (Integer) params.get("page");
Integer pageSize = (Integer) params.get("page_size");
Float similarityThreshold = (Float) params.get("similarity_threshold");
Float vectorSimilarityWeight = (Float) params.get("vector_similarity_weight");
Integer topK = (Integer) params.get("top_k");
String rerankId = (String) params.get("rerank_id");
Boolean keyword = (Boolean) params.get("keyword");
Boolean highlight = (Boolean) params.get("highlight");
List<String> crossLanguages = (List<String>) params.get("cross_languages");
Map<String, Object> metadataCondition = (Map<String, Object>) params.get("metadata_condition");
// 如果未指定数据集ID,使用当前数据集
if (datasetIds == null || datasetIds.isEmpty()) {
datasetIds = java.util.Arrays.asList(datasetId);
}
Map<String, Object> result = knowledgeFilesService.retrievalTest(
question, datasetIds, documentIds, page, pageSize, similarityThreshold,
vectorSimilarityWeight, topK, rerankId, keyword, highlight, crossLanguages, metadataCondition);
return new Result<Map<String, Object>>().ok(result);
} catch (Exception e) {
return new Result<Map<String, Object>>().error("召回测试失败: " + e.getMessage());
// 业务下沉逻辑:如果未指定知识库ID,则设为当前路径中的 datasetId
if (req.getDatasetIds() == null || req.getDatasetIds().isEmpty()) {
req.setDatasetIds(java.util.Arrays.asList(datasetId));
}
// [Reinforce] 强管控分页参数,防止 RAGFlow 端出现 Negative Slicing 报错
if (req.getPage() == null || req.getPage() < 1) {
req.setPage(1);
}
if (req.getPageSize() == null || req.getPageSize() < 1) {
req.setPageSize(100);
}
// 调用检索服务,返回强类型聚合对象
RetrievalDTO.ResultVO result = knowledgeFilesService.retrievalTest(req);
return new Result<RetrievalDTO.ResultVO>().ok(result);
}
/**
* 解析JSON字符串为Map对象
*/
@@ -0,0 +1,12 @@
package xiaozhi.modules.knowledge.dao;
import org.apache.ibatis.annotations.Mapper;
import xiaozhi.common.dao.BaseDao;
import xiaozhi.modules.knowledge.entity.DocumentEntity;
/**
* 文档 DAO
*/
@Mapper
public interface DocumentDao extends BaseDao<DocumentEntity> {
}
@@ -19,4 +19,17 @@ public interface KnowledgeBaseDao extends BaseDao<KnowledgeBaseEntity> {
*/
void deletePluginMappingByKnowledgeBaseId(@Param("knowledgeBaseId") String knowledgeBaseId);
/**
* 通用维度原子更新知识库统计信息
*
* @param datasetId 数据集ID
* @param docDelta 文档数增量
* @param chunkDelta 分块数增量
* @param tokenDelta Token数增量
*/
void updateStatsAfterChange(@Param("datasetId") String datasetId,
@Param("docDelta") Integer docDelta,
@Param("chunkDelta") Long chunkDelta,
@Param("tokenDelta") Long tokenDelta);
}
@@ -0,0 +1,102 @@
# RAGFlow API Interface Classification
## 1. External APIs (三方接入体系)
**Path Prefix:** `/api/v1`
**Authentication:** API Key (`@token_required`)
**Primary Use:** External system integration, SDK usage.
| Interface Type | Python File Path | Class/Function Name | URL Pattern | Notes |
|---|---|---|---|---|
| **External** | `api/apps/sdk/session.py` | `agent_bot_completions` | `/api/v1/agentbots/<agent_id>/completions` | Agent Bot completion |
| **External** | `api/apps/sdk/session.py` | `begin_inputs` | `/api/v1/agentbots/<agent_id>/inputs` | Get Agent Bot inputs |
| **External** | `api/apps/sdk/agents.py` | `list_agents` | `/api/v1/agents` | List Agents |
| **External** | `api/apps/sdk/agents.py` | `create_agent` | `/api/v1/agents` | Create Agent |
| **External** | `api/apps/sdk/agents.py` | `update_agent` | `/api/v1/agents/<agent_id>` | Update Agent |
| **External** | `api/apps/sdk/agents.py` | `delete_agent` | `/api/v1/agents/<agent_id>` | Delete Agent |
| **External** | `api/apps/sdk/session.py` | `agent_completions` | `/api/v1/agents/<agent_id>/completions` | Agent completion |
| **External** | `api/apps/sdk/session.py` | `create_agent_session` | `/api/v1/agents/<agent_id>/sessions` | Create Agent Session |
| **External** | `api/apps/sdk/session.py` | `list_agent_session` | `/api/v1/agents/<agent_id>/sessions` | List Agent Sessions |
| **External** | `api/apps/sdk/session.py` | `delete_agent_session` | `/api/v1/agents/<agent_id>/sessions` | Delete Agent Session |
| **External** | `api/apps/sdk/session.py` | `agents_completion_openai_compatibility` | `/api/v1/agents_openai/<agent_id>/chat/completions` | OpenAI compatible Agent completion |
| **External** | `api/apps/sdk/session.py` | `chatbot_completions` | `/api/v1/chatbots/<dialog_id>/completions` | Chatbot completion |
| **External** | `api/apps/sdk/session.py` | `chatbots_inputs` | `/api/v1/chatbots/<dialog_id>/info` | Chatbot info |
| **External** | `api/apps/sdk/chat.py` | `create` | `/api/v1/chats` | Create Chat |
| **External** | `api/apps/sdk/chat.py` | `delete_chats` | `/api/v1/chats` | Delete Chat |
| **External** | `api/apps/sdk/chat.py` | `list_chat` | `/api/v1/chats` | List Chats |
| **External** | `api/apps/sdk/chat.py` | `update` | `/api/v1/chats/<chat_id>` | Update Chat |
| **External** | `api/apps/sdk/session.py` | `chat_completion` | `/api/v1/chats/<chat_id>/completions` | Chat completion |
| **External** | `api/apps/sdk/session.py` | `create` | `/api/v1/chats/<chat_id>/sessions` | Create Chat Session |
| **External** | `api/apps/sdk/session.py` | `list_session` | `/api/v1/chats/<chat_id>/sessions` | List Chat Sessions |
| **External** | `api/apps/sdk/session.py` | `delete` | `/api/v1/chats/<chat_id>/sessions` | Delete Chat Session |
| **External** | `api/apps/sdk/session.py` | `update` | `/api/v1/chats/<chat_id>/sessions/<session_id>` | Update Chat Session |
| **External** | `api/apps/sdk/session.py` | `chat_completion_openai_like` | `/api/v1/chats_openai/<chat_id>/chat/completions` | OpenAI compatible Chat completion |
| **External** | `api/apps/sdk/dataset.py` | `create` | `/api/v1/datasets` | Create Dataset |
| **External** | `api/apps/sdk/dataset.py` | `delete` | `/api/v1/datasets` | Delete Dataset |
| **External** | `api/apps/sdk/dataset.py` | `list_datasets` | `/api/v1/datasets` | List Datasets |
| **External** | `api/apps/sdk/dataset.py` | `update` | `/api/v1/datasets/<dataset_id>` | Update Dataset |
| **External** | `api/apps/sdk/doc.py` | `parse` | `/api/v1/datasets/<dataset_id>/chunks` | Parse Document Chunks |
| **External** | `api/apps/sdk/doc.py` | `stop_parsing` | `/api/v1/datasets/<dataset_id>/chunks` | Stop Parsing |
| **External** | `api/apps/sdk/doc.py` | `upload` | `/api/v1/datasets/<dataset_id>/documents` | Upload Document |
| **External** | `api/apps/sdk/doc.py` | `list_docs` | `/api/v1/datasets/<dataset_id>/documents` | List Documents |
| **External** | `api/apps/sdk/doc.py` | `delete` | `/api/v1/datasets/<dataset_id>/documents` | Delete Document |
| **External** | `api/apps/sdk/doc.py` | `update_doc` | `/api/v1/datasets/<dataset_id>/documents/<document_id>` | Update Document |
| **External** | `api/apps/sdk/doc.py` | `download` | `/api/v1/datasets/<dataset_id>/documents/<document_id>` | Download Document |
| **External** | `api/apps/sdk/doc.py` | `list_chunks` | `/api/v1/datasets/<dataset_id>/documents/<document_id>/chunks` | List Chunks |
| **External** | `api/apps/sdk/doc.py` | `add_chunk` | `/api/v1/datasets/<dataset_id>/documents/<document_id>/chunks` | Add Chunk |
| **External** | `api/apps/sdk/doc.py` | `update_chunk` | `/api/v1/datasets/<dataset_id>/documents/<document_id>/chunks/<chunk_id>` | Update Chunk |
| **External** | `api/apps/sdk/dataset.py` | `knowledge_graph` | `/api/v1/datasets/<dataset_id>/knowledge_graph` | Knowledge Graph |
| **External** | `api/apps/sdk/dataset.py` | `delete_knowledge_graph` | `/api/v1/datasets/<dataset_id>/knowledge_graph` | Delete Knowledge Graph |
| **External** | `api/apps/sdk/doc.py` | `metadata_summary` | `/api/v1/datasets/<dataset_id>/metadata/summary` | Metadata Summary |
| **External** | `api/apps/sdk/doc.py` | `metadata_batch_update` | `/api/v1/datasets/<dataset_id>/metadata/update` | Batch Update Metadata |
| **External** | `api/apps/sdk/dataset.py` | `run_graphrag` | `/api/v1/datasets/<dataset_id>/run_graphrag` | Run GraphRAG |
| **External** | `api/apps/sdk/dataset.py` | `run_raptor` | `/api/v1/datasets/<dataset_id>/run_raptor` | Run Raptor |
| **External** | `api/apps/sdk/dataset.py` | `trace_graphrag` | `/api/v1/datasets/<dataset_id>/trace_graphrag` | Trace GraphRAG |
| **External** | `api/apps/sdk/dataset.py` | `trace_raptor` | `/api/v1/datasets/<dataset_id>/trace_raptor` | Trace Raptor |
| **External** | `api/apps/sdk/dify_retrieval.py` | `retrieval` | `/api/v1/dify/retrieval` | Dify Retrieval |
| **External** | `api/apps/sdk/files.py` | `get_all_parent_folders` | `/api/v1/file/all_parent_folder` | Get All Parent Folders |
| **External** | `api/apps/sdk/files.py` | `convert` | `/api/v1/file/convert` | File Convert |
| **External** | `api/apps/sdk/files.py` | `create` | `/api/v1/file/create` | File Create |
| **External** | `api/apps/sdk/files.py` | `download_attachment` | `/api/v1/file/download/<attachment_id>` | Download Attachment |
| **External** | `api/apps/sdk/files.py` | `get` | `/api/v1/file/get/<file_id>` | Get File |
| **External** | `api/apps/sdk/files.py` | `list_files` | `/api/v1/file/list` | List Files |
| **External** | `api/apps/sdk/files.py` | `move` | `/api/v1/file/mv` | Move File |
| **External** | `api/apps/sdk/files.py` | `get_parent_folder` | `/api/v1/file/parent_folder` | Get Parent Folder |
| **External** | `api/apps/sdk/files.py` | `rename` | `/api/v1/file/rename` | Rename File |
| **External** | `api/apps/sdk/files.py` | `rm` | `/api/v1/file/rm` | Remove File |
| **External** | `api/apps/sdk/files.py` | `get_root_folder` | `/api/v1/file/root_folder` | Get Root Folder |
| **External** | `api/apps/sdk/files.py` | `upload` | `/api/v1/file/upload` | Upload File |
| **External** | `api/apps/sdk/doc.py` | `retrieval_test` | `/api/v1/retrieval` | Retrieval Test |
| **External** | `api/apps/sdk/session.py` | `ask_about_embedded` | `/api/v1/searchbots/ask` | Searchbot Ask |
| **External** | `api/apps/sdk/session.py` | `detail_share_embedded` | `/api/v1/searchbots/detail` | Searchbot Detail |
| **External** | `api/apps/sdk/session.py` | `mindmap` | `/api/v1/searchbots/mindmap` | Searchbot Mindmap |
| **External** | `api/apps/sdk/session.py` | `related_questions_embedded` | `/api/v1/searchbots/related_questions` | Searchbot Related Questions |
| **External** | `api/apps/sdk/session.py` | `retrieval_test_embedded` | `/api/v1/searchbots/retrieval_test` | Searchbot Retrieval Test |
| **External** | `api/apps/sdk/session.py` | `ask_about` | `/api/v1/sessions/ask` | Session Ask |
| **External** | `api/apps/sdk/session.py` | `related_questions` | `/api/v1/sessions/related_questions` | Session Related Questions |
| **External** | `api/apps/sdk/agents.py` | `webhook` | `/api/v1/webhook_test/<agent_id>` | Webhook Test |
| **External** | `api/apps/sdk/agents.py` | `webhook_trace` | `/api/v1/webhook_trace/<agent_id>` | Webhook Trace |
| **External** | `api/apps/sdk/doc.py` | `rm_chunk` | `/api/v1datasets/<dataset_id>/documents/<document_id>/chunks` | Remove Chunk |
## 2. Internal APIs (内部前端体系)
**Path Prefix:** `/v1/<app_name>` matches file `api/apps/<app_name>_app.py`
**Authentication:** Session/Cookie (`@login_required`)
**Primary Use:** RAGFlow Web Frontend.
**Selected Core Interfaces:**
| Interface Type | Python File Path | Class/Function Name | URL Pattern | Notes |
|---|---|---|---|---|
| Internal | `api/apps/user_app.py` | `login` | `/v1/user/login` | User Login (Frontend) |
| Internal | `api/apps/user_app.py` | `log_out` | `/v1/user/logout` | User Logout |
| Internal | `api/apps/user_app.py` | `user_add` | `/v1/user/register` | User Registration |
| Internal | `api/apps/user_app.py` | `user_profile` | `/v1/user/info` | User Profile Info |
| Internal | `api/apps/api_app.py` | `new_token` | `/v1/api/new_token` | Generate new API Token |
| Internal | `api/apps/conversation_app.py` | `set_conversation` | `/v1/conversation/set` | Create/Update Conversation |
| Internal | `api/apps/conversation_app.py` | `completion` | `/v1/conversation/completion` | Chat Conversation Completion |
| Internal | `api/apps/kb_app.py` | `list_kbs` | `/v1/kb/list` | List Knowledge Bases |
| Internal | `api/apps/kb_app.py` | `create` | `/v1/kb/create` | Create Knowledge Base |
| Internal | `api/apps/document_app.py` | `upload` | `/v1/document/upload` | Upload Document to KB |
| Internal | `api/apps/document_app.py` | `parse` | `/v1/document/parse` | Parse Document |
*(For a complete list of all 200+ internal APIs, please refer to the `api_endpoints.txt` file or the full scan results)*
@@ -0,0 +1,279 @@
# RAGFlow Agent 与 Dify 兼容接口详解 (Agent & Dify Compatibility)
## 1. Dify 兼容检索 - `retrieval`
**接口描述**: 模拟 Dify API 格式的知识库检索接口。此接口主要用于让现有的 Dify 客户端或系统能够方便地接入 RAGFlow 的知识库检索能力。它支持文本检索、混合检索以及通过元数据过滤文档。
**请求方法**: `POST`
**接口地址**: `/api/v1/dify/retrieval`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Body Parameters (JSON)
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| knowledge_id | string | 是 | - | **知识库 ID**。 |
| query | string | 是 | - | **查询文本**。用户输入的检索问题。 |
| use_kg | boolean | 否 | false | **使用知识图谱**。是否结合知识图谱进行检索。 |
| retrieval_setting | object | 否 | {} | **检索配置**。包含相似度阈值和 Top-K。 |
| metadata_condition | object | 否 | {} | **元数据过滤条件**。用于筛选特定文档。 |
#### 参数详情 (Detail Objects)
**retrieval_setting**:
```json
{
"score_threshold": 0.5, // 相似度阈值 (default: 0.0)
"top_k": 5 // 返回数量 (default: 1024)
}
```
**metadata_condition**:
```json
{
"logic": "and", // 逻辑关系 (and/or)
"conditions": [
{
"name": "author", // 字段名
"comparison_operator": "eq",// 运算符 (eq, ne, gt, lt 等)
"value": "Alice" // 字段值
}
]
}
```
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": {
"records": [
{
"content": "RAGFlow 是一个基于深度文档理解的检索增强生成引擎...",
"score": 0.92,
"title": "RAGFlow_Introduction.pdf",
"metadata": {
"doc_id": "doc_uuid_123",
"author": "Alice",
"publish_year": "2024"
}
},
{
"content": "DeepDOC 模型能够精准识别复杂的表格结构...",
"score": 0.88,
"title": "DeepDOC_Tech_Report.pdf",
"metadata": {
"doc_id": "doc_uuid_456",
"author": "Bob"
}
}
]
}
}
```
---
## 2. 创建 Agent 会话 - `create_agent_session`
**接口描述**: 创建一个新的 Agent 会话 (Session)。会话是用户与 Agent 交互的上下文容器,保存了历史对话记录和 DSL(领域特定语言)状态。
**请求方法**: `POST`
**接口地址**: `/api/v1/agents/<agent_id>/sessions`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Path Parameters
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| agent_id | string | 是 | **Agent ID**。 |
#### Query Parameters
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| user_id | string | 否 | **用户标识**。用于区分不同终端用户的会话。若不传,默认为当前 Tenant ID。 |
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": {
"id": "session_uuid_new_123",
"agent_id": "agent_uuid_abc",
"user_id": "user_123",
"source": "agent",
"dsl": { ... }, // 完整的 Agent DSL 定义
"messages": [
{
"role": "assistant",
"content": "你好!我是你的智能助手,有什么可以帮你的吗?" // Prologue (开场白)
}
]
}
}
```
---
## 3. 获取 Agent 会话列表 - `list_agent_session`
**接口描述**: 分页获取指定 Agent 下的会话列表。支持按 ID 或 User ID 过滤。
**请求方法**: `GET`
**接口地址**: `/api/v1/agents/<agent_id>/sessions`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Path Parameters
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| agent_id | string | 是 | **Agent ID**。 |
#### Query Parameters
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| page | int | 否 | 1 | **页码**。 |
| page_size | int | 否 | 30 | **每页数量**。 |
| orderby | string | 否 | "update_time" | **排序字段**。 |
| desc | boolean | 否 | true | **是否降序**。 |
| id | string | 否 | - | **会话 ID**。精确筛选。 |
| user_id | string | 否 | - | **用户标识**。筛选特定用户的会话。 |
| dsl | boolean | 否 | true | **包含 DSL**。是否在返回结果中包含完整的 DSL 结构 (数据量较大)。 |
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": [
{
"id": "session_uuid_123",
"agent_id": "agent_uuid_abc",
"user_id": "user_123",
"create_time": 1715000000000,
"update_time": 1715000050000,
"source": "agent",
"messages": [
{
"role": "assistant",
"content": "Hi there!"
},
{
"role": "user",
"content": "What is RAG?"
}
]
}
]
}
```
---
## 4. 删除 Agent 会话 - `delete_agent_session`
**接口描述**: 批量删除 Agent 会话。
**请求方法**: `DELETE`
**接口地址**: `/api/v1/agents/<agent_id>/sessions`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Path Parameters
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| agent_id | string | 是 | **Agent ID**。 |
#### Body Parameters (JSON)
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| ids | array<string> | 否 | **会话 ID 列表**。若不传该参数,将尝试删除(或清空)该 Agent 下的所有会话(需谨慎)。 |
**Request Example**:
```json
{
"ids": ["session_id_1", "session_id_2"]
}
```
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": {
"success_count": 2,
"errors": []
}
}
```
---
## 5. Agent 对话 (流式) - `agent_completions`
**接口描述**: 向 Agent 发送用户问题并获取回复。这是 Agent 交互的核心接口,支持 **Server-Sent Events (SSE)** 流式响应。Agent 会根据编排好的 DSL 流程执行(可能涉及多个节点、知识库检索、LLM 推理等),并实时推送执行过程和最终结果。
**请求方法**: `POST`
**接口地址**: `/api/v1/agents/<agent_id>/completions`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Path Parameters
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| agent_id | string | 是 | **Agent ID**。 |
#### Body Parameters (JSON)
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| session_id | string | 是 | - | **会话 ID**。必须是 `create_agent_session` 返回的 ID。 |
| question | string | 是 | - | **用户问题**。 |
| stream | boolean | 否 | true | **是否流式响应**。强烈建议设为 `true` 以获得更好的用户体验。 |
| return_trace | boolean | 否 | false | **返回执行轨迹**。如果为 `true`,流式响应中将包含各个节点的执行过程数据 (Trace)。 |
### 响应参数 (Stream Response)
**Content-Type**: `text/event-stream`
响应是一个 SSE 流,每一行以 `data:` 开头,包含一个 JSON 对象。
**Event Types**:
- `message`: 普通文本消息片段。
- `node_finished`: (当 `return_trace=true` 时) 节点执行完成事件,包含节点输出数据。
- `message_end`: 消息结束。
- `[DONE]`: 流结束标志。
#### Stream Chunk Examples:
**1. 文本生成片段 (message)**:
```text
data:{"code": 0, "message": "success", "data": {"content": "Hello", "reference": {}, "id": "msg_uuid_1"}, "event": "message"}
data:{"code": 0, "message": "success", "data": {"content": " world", "reference": {}, "id": "msg_uuid_1"}, "event": "message"}
```
**2. 节点执行轨迹 (node_finished, return_trace=true)**:
```text
data:{"code": 0, "message": "success", "data": {"component_id": "retrieval_node_1", "content": "...", "trace": [...]}, "event": "node_finished"}
```
**3. 最终结束 (DONE)**:
```text
data:[DONE]
```
#### Non-Stream Response (stream=false)
如果不使用流式响应,将等待 Agent 全流程执行完毕后一次性返回 JSON。
```json
{
"code": 0,
"message": "success",
"data": {
"content": "Hello world! This is the final answer.",
"reference": {
"chunk_id_1": { ... } // 引用来源
},
"trace": [ ... ] // 如果 return_trace=true
}
}
```
@@ -0,0 +1,233 @@
## 1. 获取 Agent 列表 - `list_agents`
**接口描述**: 分页查询当前租户下的所有 Agent 列表,支持按 ID 或标题筛选。
**请求方法**: `GET`
**接口地址**: `/api/v1/agents`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Path Parameters
#### Query Parameters
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| page | int | 否 | 1 | 页码 |
| page_size | int | 否 | 30 | 每页条数 |
| orderby | string | 否 | update_time | 排序字段 (create_time, update_time, title) |
| desc | boolean | 否 | True | 是否降序排列 (True: 降序, False: 升序) |
| id | string | 否 | - | 按 Agent ID 精确筛选 |
| title | string | 否 | - | 按 Agent 标题精确筛选 |
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": [
{
"id": "e0d34e2c-...",
"title": "My Assistant",
"description": "A helpful AI assistant",
"dsl": { ... }, // Agent 的 DSL 流程定义
"user_id": "tenant_123",
"avatar": "", // 头像 Base64 或 URL
"canvas_category": "Agent",
"create_time": 1715623400000,
"update_time": 1715624500000
}
]
}
```
---
## 2. 创建 Agent - `create_agent`
**接口描述**: 创建一个新的 Agent,必须包含标题和 DSL 定义。
**请求方法**: `POST`
**接口地址**: `/api/v1/agents`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Path Parameters
#### Body Parameters (JSON)
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| title | string | 是 | - | Agent 的名称 (必须唯一) |
| dsl | object | 是 | - | Agent 的流程定义 (节点、连线配置) |
| description | string | 否 | - | Agent 的功能描述 |
| avatar | string | 否 | - | Agent 头像 (Base64 字符串或 URL) |
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": true
}
```
---
## 3. 更新 Agent - `update_agent`
**接口描述**: 更新指定 Agent 的配置信息,支持增量更新(仅传递需要修改的字段)。
**请求方法**: `PUT`
**接口地址**: `/api/v1/agents/<agent_id>`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Path Parameters
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| agent_id | string | 是 | 要更新的 Agent ID |
#### Body Parameters (JSON)
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| title | string | 否 | - | 新的 Agent 名称 |
| dsl | object | 否 | - | 新的 DSL 流程定义 |
| description | string | 否 | - | 新的功能描述 |
| avatar | string | 否 | - | 新的头像 |
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": true
}
```
---
## 4. 删除 Agent - `delete_agent`
**接口描述**: 根据 ID 删除指定的 Agent。此操作不可恢复。
**请求方法**: `DELETE`
**接口地址**: `/api/v1/agents/<agent_id>`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Path Parameters
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| agent_id | string | 是 | 要删除的 Agent ID |
#### Body Parameters (JSON)
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": true
}
```
---
## 5. Webhook 测试触发 - `webhook`
**接口描述**: 用于测试 Agent 的 Webhook 触发功能。该接口模拟外部系统调用,触发 Agent 按照配置的 "Begin" 节点逻辑开始执行。支持同步等待结果或流式返回(取决于 Agent 配置)。
**请求方法**: `POST` (支持 GET/PUT/DELETE 等,取决于 Canvas 配置)
**接口地址**: `/api/v1/webhook_test/<agent_id>`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Path Parameters
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| agent_id | string | 是 | Agent 的唯一标识符 |
#### Query / Headers / Body Parameters
**说明**: 此接口的参数完全动态,取决于 Agent 画布中 **"Begin" (开始)** 节点的 **Webhook** 配置。
- 如果配置了 Query 参数验证,则需在 URL 中传递对应参数。
- 如果配置了 Header 验证,则需传递对应 Header。
- **Body**: 通常为 JSON 格式,包含 Agent 运行所需的变量(inputs)或上下文数据。
**Body Example (JSON)**:
```json
{
"inputs": {
"topic": "AI Trends",
"style": "professional"
},
"query": "Start generation"
}
```
### 响应参数 (Response)
**Content-Type**: `application/json` (或 `text/event-stream`)
**即时响应模式 (Immediately)**:
```json
{
"code": 0,
"data": {
"content": "生成的回答内容...",
"usage": { ... }
}
}
```
**流式响应模式 (SSE)**:
如果不使用 `webhook_test` 而是生产环境 `webhook` 且配置为 SSE,则返回流式数据。但在 `webhook_test` 接口中,通常配合 `webhook_trace` 进行异步调试。
---
## 6. Webhook 执行轨迹查询 - `webhook_trace`
**接口描述**: 轮询查询 Agent 在 Webhook 测试触发后的执行日志和中间状态。采用长轮询或游标机制,实时获取执行进度。
**请求方法**: `GET`
**接口地址**: `/api/v1/webhook_trace/<agent_id>`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Path Parameters
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| agent_id | string | 是 | Agent 的唯一标识符 |
#### Query Parameters
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| since_ts | float | 否 | 当前时间 | 起始时间戳。返回此时间之后的日志事件。首次调用可不传(获取当前时间作为游标)。 |
| webhook_id | string | 否 | - | Webhook 会话 ID。用于锁定特定的某次执行记录。首次轮询时不传,接口会返回新生成的 ID。 |
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": {
"webhook_id": "YWdlbnxxxx...", // 当前追踪的会话 ID (加密串)
"finished": false, // 执行是否已结束 (true/false)
"next_since_ts": 1715629999.5, // 下一次轮询应使用的 since_ts
"events": [ // 本次轮询获取到的新事件列表
{
"ts": 1715629998.1,
"event": "message", // 事件类型: message, start_to_think, finished, error 等
"data": {
"content": "思考中...",
"reference": []
}
}
]
}
}
```
### 💡 最佳实践 (调试流程)
1. **初始化**: 调用 `GET /webhook_trace/<id>` (不带参数),获取 `next_since_ts` (记为 `T0`)。
2. **触发**: 调用 `POST /webhook_test/<id>` 发送测试数据。
3. **首帧捕获**: 循环调用 `GET /webhook_trace/<id>?since_ts=T0`,直到返回 `webhook_id` (记为 `WID`) 和第一批 `events`
4. **持续追踪**: 使用 `WID` 和响应中的 `next_since_ts` 持续轮询,直到 `data.finished == true`
@@ -0,0 +1,164 @@
# RAGFlow 对话交互接口详解 (Chat Completion & OpenAI Compatibility)
## 5. 对话助手对话 (流式) - `chat_completion`
**接口描述**: 发送问题给对话助手 (Assistant/Chat) 并获取回复。这是 RAGFlow 最核心的原生对话接口,支持 **Server-Sent Events (SSE)** 流式响应。它会根据助手绑定的知识库进行 RAG 检索生成。
**请求方法**: `POST`
**接口地址**: `/api/v1/chats/<chat_id>/completions`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Path Parameters
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| chat_id | string | 是 | **助手 ID**。 |
#### Body Parameters (JSON)
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| session_id | string | 是 | - | **会话 ID**。从 `create_chat_session` 获取。 |
| question | string | 是 | - | **用户问题**。 |
| stream | boolean | 否 | true | **是否流式响应**。 |
| quote | boolean | 否 | false | **返回引用**。是否在响应中包含检索到的引用片段。 |
| doc_ids | string | 否 | - | **限定文档 ID**。多个 ID 用逗号分隔,仅检索指定文档。 |
| metadata_condition | object | 否 | {} | **元数据过滤**。用于限定检索范围。 |
### 响应参数 (Stream Response)
**Content-Type**: `text/event-stream`
每一行数据以 `data:` 开头,包含一个 JSON 对象。
**Event Example**:
```text
data:{"code": 0, "message": "success", "data": {"answer": "Hello", "reference": {}}}
data:{"code": 0, "message": "success", "data": {"answer": " world!", "reference": {}}}
data:{"code": 0, "message": "success", "data": {"answer": "", "reference": {"chunk_1": {...}}}} // 引用数据
```
### 响应参数 (Non-Stream Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": {
"answer": "Hello world! This is the generated response.",
"reference": {
"chunk_id_1": {
"content_with_weight": "Original text...",
"doc_name": "manual.pdf"
}
}
}
}
```
---
## 6. OpenAI 兼容对话 - `chat_completion_openai_like`
**接口描述**: 提供与 **OpenAI API (`/v1/chat/completions`)** 完全兼容的接口。允许开发者使用 LangChain、OpenAI Python SDK 或其他支持 OpenAI 协议的工具直接调用 RAGFlow,实现无缝迁移。
**请求方法**: `POST`
**接口地址**: `/api/v1/chats_openai/<chat_id>/chat/completions`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Path Parameters
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| chat_id | string | 是 | **助手 ID**。在此上下文中充当 "Base URL" 的一部分。 |
#### Body Parameters (JSON - OpenAI Standard)
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| messages | array | 是 | **消息列表**。包含 `role` (system/user/assistant) 和 `content`。 |
| model | string | 是 | **模型名称**。可以是任意非空字符串 (RAGFlow 会使用助手预设的模型)。 |
| stream | boolean | 否 | **是否流式**。默认为 `true`。 |
**Request Example**:
```json
{
"model": "ragflow_default",
"messages": [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Explain quantum physics."}
],
"stream": true
}
```
### 响应参数 (Stream Response - OpenAI Format)
**Content-Type**: `text/event-stream`
严格遵循 OpenAI Chunk 格式:
```text
data: {"id": "chatcmpl-123", "object": "chat.completion.chunk", "created": 1715000000, "model": "model", "choices": [{"index": 0, "delta": {"role": "assistant", "content": ""}, "finish_reason": null}]}
data: {"id": "chatcmpl-123", "object": "chat.completion.chunk", "created": 1715000001, "model": "model", "choices": [{"index": 0, "delta": {"content": "Quantum"}, "finish_reason": null}]}
data: {"id": "chatcmpl-123", "object": "chat.completion.chunk", "created": 1715000002, "model": "model", "choices": [{"index": 0, "delta": {"content": " physics"}, "finish_reason": null}]}
data: [DONE]
```
---
## 7. 嵌入式 Chatbot 对话 - `chatbot_completions`
**接口描述**: 专为 **嵌入式窗口 (Embed Window)** 设计的公开对话接口。它通常用于将 RAGFlow 助手作为客服窗口嵌入到第三方网站。与普通接口不同,它通过 `Authorization` Header 传递 **Beta Token** (即 API Key) 进行鉴权,且通常面向最终用户。
**请求方法**: `POST`
**接口地址**: `/api/v1/chatbots/<dialog_id>/completions`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Path Parameters
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| dialog_id | string | 是 | **助手 ID** (Dialog ID)。 |
#### Body Parameters (JSON)
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| question | string | 是 | - | **用户问题**。 |
| stream | boolean | 否 | true | **是否流式**。 |
| session_id | string | 否 | - | **会话 ID**。用于维持上下文。 |
| quote | boolean | 否 | false | **返回引用**。 |
### 响应参数 (Stream Response)
**Content-Type**: `text/event-stream`
`chat_completion` 类似,返回 RAGFlow 原生 SSE 格式。
```text
data:{"code": 0, "message": "success", "data": {"answer": "Here is the answer...", "reference": {}}}
```
---
## 8. Chatbot 初始化信息 - `chatbots_inputs`
**接口描述**: 获取嵌入式 Chatbot 的初始化配置信息。通常在前端组件加载时调用,用于展示助手的头像、名称、开场白 (Prologue) 等信息。
**请求方法**: `GET`
**接口地址**: `/api/v1/chatbots/<dialog_id>/info`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Path Parameters
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| dialog_id | string | 是 | **助手 ID**。 |
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": {
"title": "IT Support Bot", // 助手名称
"avatar": "http://...", // 头像 URL
"prologue": "Hi! How can I help?" // 开场白
}
}
```
@@ -0,0 +1,208 @@
# RAGFlow 聊天助手会话管理接口详解 (Chat Assistant Session Management)
## 1. 创建会话 - `create_chat_session`
**接口描述**: 为指定的聊天助手 (Chat/Assistant) 创建一个新的会话。系统会自动加载该助手的开场白 (Prologue) 作为第一条消息。
**请求方法**: `POST`
**接口地址**: `/api/v1/chats/<chat_id>/sessions`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Path Parameters
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| chat_id | string | 是 | **助手 ID** (Assistant/Dialog ID)。 |
#### Body Parameters (JSON)
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| name | string | 否 | "New session" | **会话名称**。 |
| user_id | string | 否 | - | **用户标识**。用于区分不同终端用户的会话。 |
**Request Example**:
```json
{
"name": "Consulting regarding RAG",
"user_id": "client_001"
}
```
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": {
"id": "session_uuid_123",
"chat_id": "chat_uuid_abc",
"name": "Consulting regarding RAG",
"user_id": "client_001",
"create_time": 1715000000000,
"create_date": "2024-05-01 10:00:00",
"update_time": 1715000000000,
"update_date": "2024-05-01 10:00:00",
"messages": [
{
"role": "assistant",
"content": "Hi! I am your AI assistant. How can I help you today?" // 自动加载的开场白
}
]
}
}
```
---
## 2. 获取会话列表 - `list_chat_session`
**接口描述**: 分页获取指定助手下的会话列表。支持按名称或用户 ID 过滤。
**请求方法**: `GET`
**接口地址**: `/api/v1/chats/<chat_id>/sessions`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Path Parameters
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| chat_id | string | 是 | **助手 ID**。 |
#### Query Parameters
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| page | int | 否 | 1 | **页码**。 |
| page_size | int | 否 | 30 | **每页数量**。 |
| orderby | string | 否 | "create_time" | **排序字段**。 |
| desc | boolean | 否 | true | **是否降序**。 |
| name | string | 否 | - | **会话名称搜索**。 |
| id | string | 否 | - | **会话 ID 精确筛选**。 |
| user_id | string | 否 | - | **用户标识筛选**。 |
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": [
{
"id": "session_uuid_123",
"chat_id": "chat_uuid_abc",
"name": "Consulting regarding RAG",
"user_id": "client_001",
"create_time": 1715000000000,
"create_date": "2024-05-01 10:00:00",
"update_time": 1715000050000,
"update_date": "2024-05-01 10:00:50",
"messages": [
{
"role": "assistant",
"content": "Hi! I am your AI assistant. How can I help you today?"
},
{
"role": "user",
"content": "What is RAGFlow?"
}
]
},
{
"id": "session_uuid_456",
"chat_id": "chat_uuid_abc",
"name": "New session",
"user_id": "client_002",
"create_time": 1714900000000,
"create_date": "2024-04-30 09:00:00",
"update_time": 1714900000000,
"update_date": "2024-04-30 09:00:00",
"messages": [ ... ]
}
]
}
```
---
## 3. 更新会话 - `update_chat_session`
**接口描述**: 更新会话信息。目前主要用于 **重命名** 会话。注意:不能通过此接口修改消息记录 (`messages`)。
**请求方法**: `PUT`
**接口地址**: `/api/v1/chats/<chat_id>/sessions/<session_id>`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Path Parameters
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| chat_id | string | 是 | **助手 ID**。 |
| session_id | string | 是 | **会话 ID**。 |
#### Body Parameters (JSON)
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| name | string | 否 | **新的会话名称**。不可为空字符串。 |
**Request Example**:
```json
{
"name": "RAG Technical Discussion"
}
```
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": null
}
```
---
## 4. 删除会话 - `delete_chat_session`
**接口描述**: 批量删除指定助手下的会话。
**请求方法**: `DELETE`
**接口地址**: `/api/v1/chats/<chat_id>/sessions`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Path Parameters
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| chat_id | string | 是 | **助手 ID**。 |
#### Body Parameters (JSON)
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| ids | array<string> | 否 | **待删除的会话 ID 列表**。若不传该参数,将尝试删除该助手下的**所有会话**(请极其谨慎使用)。 |
**Request Example**:
```json
{
"ids": ["session_uuid_123", "session_uuid_456"]
}
```
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": null // 若全部删除成功
}
```
**Response (部分成功时)**:
```json
{
"code": 0,
"message": "Partially deleted 1 sessions with 1 errors",
"data": {
"success_count": 1,
"errors": ["The chat doesn't own the session session_uuid_999"]
}
}
```
@@ -0,0 +1,213 @@
## 1. 创建助手应用 - `create`
**接口描述**: 创建一个新的对话助手(Chat Assistant)。支持配置关联知识库、LLM 模型参数、提示词(Prompt)以及开场白等高级设置。
**请求方法**: `POST`
**接口地址**: `/api/v1/chats`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Path Parameters
#### Body Parameters (JSON)
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| name | string | 是 | - | 助手应用名称 (租户内唯一) |
| avatar | string | 否 | - | 助手头像 (URL 或 Base64 字符串) |
| description | string | 否 | "A helpful Assistant" | 助手的功能描述 |
| dataset_ids | array | 否 | [] | 关联的知识库 ID 列表 (必须是当前租户有权限访问的知识库) |
| llm | object | 否 | - | LLM 模型生成配置 (如模型名称、温度等) |
| prompt | object | 否 | - | 提示词引擎与检索配置 (包含 System Prompt, Opener, Rerank 等) |
**`llm` 对象详细结构**:
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| model_name | string | 是 | - | 模型名称 (例如: `deepseek-chat`, `gpt-4`, `qwen-turbo`) |
| temperature | float | 否 | 0.1 | 温度系数 (0.0 ~ 1.0),越高越随机,越低越确定 |
| top_p | float | 否 | 0.3 | 核采样概率阈值 |
| max_tokens | int | 否 | 512 | 单次回答的最大 Token 数限制 |
| presence_penalty | float | 否 | 0.4 | 话题新鲜度惩罚 (-2.0 ~ 2.0),正值鼓励讨论新话题 |
| frequency_penalty | float | 否 | 0.7 | 频率惩罚 (-2.0 ~ 2.0),正值减少重复词汇 |
**`prompt` 对象详细结构**:
*注意:此对象包含“提示词配置”与“检索策略配置”两部分。*
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| prompt | string | 否 | (内置默认提示词) | **System Prompt (系统提示词)**。给大模型的角色指令,例如 "你是一个客服..."。可使用变量占位符 `{knowledge}`。 |
| opener | string | 否 | "Hi! I'm your assistant..." | **开场白**。用户进入对话窗口时,助手自动发送的第一条欢迎语。 |
| show_quote | boolean | 否 | true | **显示引用**。回答中是否标注来源文档 (e.g., [1])。 |
| variables | array | 否 | `[{"key": "knowledge", "optional": false}]` | **变量列表**。定义用于填充 System Prompt 的变量。`knowledge` 为保留变量,代表检索到的知识片段。 |
| rerank_model | string | 否 | - | **重排序模型 ID**。配置后会对检索结果进行二次精排 (如 `BAAI/bge-reranker-v2-m3`)。 |
| keywords_similarity_weight | float | 否 | 0.7 | **关键字权重** (0.0 ~ 1.0)。控制混合检索的比例。更接近 1.0 侧重关键字匹配,更接近 0.0 侧重向量语义匹配。 |
| similarity_threshold | float | 否 | 0.2 | **相似度阈值** (0.0 ~ 1.0)。低于此相似度的文档块将被过滤,不喂给大模型。 |
| top_n | int | 否 | 6 | **Top N**。最终截取并输入给大模型的文档块数量。 |
| empty_response | string | 否 | "Sorry! No relevant..." | **空结果回复**。当没有检索到相关知识库内容时的兜底回复。 |
| tts | boolean | 否 | false | **启用 TTS**。是否将助手的文本回答自动转为语音播放。 |
| refine_multiturn | boolean | 否 | true | **多轮对话优化**。是否根据历史上下文重写用户问题 (Query Rewrite) 以提高检索准确率。 |
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": {
"id": "e0d34e2c-1234-5678-9xxx-xxxxxxxxxxxx",
"name": "企业知识库助手",
"avatar": "http://example.com/avatar.png",
"description": "用于回答员工内部问题的 AI",
"dataset_ids": ["kb_123", "kb_456"],
"llm": {
"model_name": "deepseek-chat",
"temperature": 0.1,
"top_p": 0.3,
"max_tokens": 512,
"presence_penalty": 0.4,
"frequency_penalty": 0.7
},
"prompt": {
"prompt": "你是一个智能助手,请根据以下知识回答问题:\n{knowledge}",
"opener": "你好!有什么可以帮你的?",
"show_quote": true,
"variables": [
{ "key": "knowledge", "optional": false }
],
"rerank_model": "",
"keywords_similarity_weight": 0.7,
"similarity_threshold": 0.2,
"top_n": 8,
"empty_response": "抱歉,知识库中没有找到相关答案。",
"tts": false,
"refine_multiturn": true
},
"create_time": 1715623400000,
"update_time": 1715624500000
}
}
```
---
## 2. 获取助手列表 - `list_chat`
**接口描述**: 获取当前租户下的所有助手应用列表。支持分页、排序及按名称/ID筛选。
**请求方法**: `GET`
**接口地址**: `/api/v1/chats`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Path Parameters
#### Query Parameters
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| page | int | 否 | 1 | 页码 |
| page_size | int | 否 | 30 | 每页条数 |
| orderby | string | 否 | create_time | 排序字段 (`create_time`, `update_time`) |
| desc | boolean | 否 | true | 是否降序排列 (`true`: 降序, `false`: 升序) |
| name | string | 否 | - | 按名称模糊搜索 (支持 partial match) |
| id | string | 否 | - | 按 ID 精确筛选 |
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": [
{
"id": "e0d34e2c-...",
"name": "客服机器人",
"avatar": "http://...",
"datasets": [
{
"id": "kb_1",
"name": "产品手册",
"avatar": "",
"chunk_num": 100
}
],
"llm": { ... }, // (结构同 create 接口响应)
"prompt": { ... }, // (结构同 create 接口响应)
"create_time": 1715623400000
}
]
}
```
---
## 3. 更新助手配置 - `update`
**接口描述**: 更新指定助手应用的配置信息。支持全量或增量更新部分字段。
**请求方法**: `PUT`
**接口地址**: `/api/v1/chats/<chat_id>`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Path Parameters
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| chat_id | string | 是 | 助手应用 ID |
#### Body Parameters (JSON)
*(以下所有字段均为可选,仅传递需要修改的字段即可)*
| 参数名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| name | string | - | 新的助手名称 |
| avatar | string | - | 新的头像 URL 或 Base64 |
| dataset_ids | array | - | **全量替换**关联的知识库 ID 列表 |
| llm | object | - | 更新 LLM 配置。需包含 `model_name`,其他字段覆盖更新。 |
| prompt | object | - | 更新提示词配置。支持增量更新 (e.g. 只改 `opener`)。 |
| show_quotation | boolean | - | 是否显示引用来源 (此字段直接位于根对象下,对应 prompt.show_quote) |
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": null
}
```
---
## 4. 批量删除助手 - `delete_chats`
**接口描述**: 批量删除一个或多个助手应用。
**请求方法**: `DELETE`
**接口地址**: `/api/v1/chats`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Path Parameters
#### Body Parameters (JSON)
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| ids | array<string> | 是 | 要删除的助手应用 ID 列表。**⚠️ 注意:若列表为空或不传,虽然后端有全量删除逻辑,但在实际业务中应严谨传递 ID。** |
**Request Example**:
```json
{
"ids": ["chat_id_1001", "chat_id_1002"]
}
```
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": {
"success_count": 2, // 成功删除的数量
"errors": [] // 失败原因列表 (如 ID 不存在)
}
}
```
@@ -0,0 +1,420 @@
## 1. 创建知识库 - `create`
**接口描述**: 创建一个新的知识库(Dataset),用于存储和检索文档数据。支持配置嵌入模型(Embedding Model)、解析方法、权限范围等。
**请求方法**: `POST`
**接口地址**: `/api/v1/datasets`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Path Parameters
#### Body Parameters (JSON)
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| name | string | 是 | - | **知识库名称**。在同一个租户(Tenant)内必须唯一。 |
| avatar | string | 否 | "" | **知识库头像**。Base64 编码的图片字符串。 |
| description | string | 否 | "" | **描述信息**。用于说明知识库的用途或内容概要。 |
| embedding_model | string | 否 | (系统默认) | **嵌入模型名称** (例如 `BAAI/bge-large-zh-v1.5`)。若不传,则自动使用系统设置的默认 Embedding 模型。 |
| permission | string | 否 | "me" | **可见权限**`me`: 仅自己可见;`team`: 团队内所有成员可见。 |
| chunk_method | string | 否 | "naive" | **默认分块解析方法**。当上传文件未指定解析方式时使用。可选值: `naive` (通用), `manual` (手动), `qa` (Q&A拆分), `table` (表格), `paper` (论文), `book` (书籍), `laws` (法律), `presentation` (PPT), `picture` (图片), `one` (单文档), `email` (邮件)。 |
| parser_config | object | 否 | (见下文) | **解析器详细配置**。根据 `chunk_method` 的不同而变化。 |
**`parser_config` 默认配置参数 (Naive 通用模式)**:
| 参数名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| chunk_token_num | int | 512 | **切片最大 Token 数**。超过该长度会被截断到下一块。 |
| delimiter | string | "\\n" | **分段分隔符**。用于识别段落边界。 |
| layout_recognize | string | "DeepDOC" | **布局识别模型**。用于处理复杂文档结构 (如 `DeepDOC``Simple`)。 |
| html4excel | boolean | false | **Excel转HTML**。是否将 Excel 表格转为 HTML 格式进行解析。 |
| auto_keywords | int | 0 | **自动关键词抽取**。0 表示不抽取;N>0 表示为每个切片抽取 N 个关键词。 |
| auto_questions | int | 0 | **自动问题生成**。0 表示不生成;N>0 表示为每个切片生成 N 个相关问题。 |
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": {
"id": "kb_uuid_12345678",
"name": "企业产品手册",
"avatar": "",
"tenant_id": "tenant_001",
"description": "存放所有产品相关的说明文档",
"embedding_model": "BAAI/bge-large-zh-v1.5",
"permission": "me",
"chunk_method": "naive",
"parser_config": {
"chunk_token_num": 512,
"delimiter": "\n",
"layout_recognize": "DeepDOC",
"html4excel": false,
"auto_keywords": 0,
"auto_questions": 0
},
"chunk_count": 0,
"document_count": 0,
"create_time": 1715623400000,
"update_time": 1715624500000
}
}
```
---
## 2. 删除知识库 - `delete`
**接口描述**: 批量删除一个或多个知识库。删除知识库将连带删除其中的所有文档和索引数据,**不可恢复**。
**请求方法**: `DELETE`
**接口地址**: `/api/v1/datasets`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Path Parameters
#### Body Parameters (JSON)
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| ids | array<string> | 是 | **ID 列表**。指定要删除的知识库 ID。如果传递 `null`,则会**清空当前租户下所有**知识库(高危操作,请谨慎使用)。 |
**Request Example**:
```json
{
"ids": ["kb_id_101", "kb_id_102"]
}
```
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "Successfully deleted 2 datasets, 0 failed...",
"data": {
"success_count": 2, // 成功删除的数量
"errors": [] // 失败的 ID 及原因列表
}
}
```
---
## 3. 获取知识库列表 - `list_datasets`
**接口描述**: 获取当前用户(及团队)有权限访问的知识库列表。支持分页、排序和筛选。
**请求方法**: `GET`
**接口地址**: `/api/v1/datasets`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Path Parameters
#### Query Parameters
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| page | int | 否 | 1 | **页码**。从 1 开始。 |
| page_size | int | 否 | 30 | **每页条数**。 |
| orderby | string | 否 | "create_time" | **排序字段**。可选值: `create_time` (创建时间), `update_time` (更新时间), `document_count` (文档数)。 |
| desc | boolean | 否 | true | **是否降序**`true`: 降序 (最新的在前); `false`: 升序。 |
| name | string | 否 | - | **名称筛选**。支持模糊匹配。 |
| id | string | 否 | - | **ID 筛选**。精确匹配知识库 ID。 |
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": [
{
"id": "kb_uuid_123",
"name": "HR 政策库",
"document_count": 12, // 包含的文档数量
"token_num": 10240, // 总 Token 数
"chunk_count": 150, // 总切片数
"create_time": 1715623400000,
"permission": "team",
"embedding_model": "BAAI/bge-large-zh-v1.5"
}
],
"total": 1 // 匹配查询条件的总记录数 (用户分页计算)
}
```
---
## 4. 更新知识库配置 - `update`
**接口描述**: 更新指定知识库的配置信息。注意:如果知识库内已有解析过的切片,通常不允许修改嵌入模型 (`embedding_model`)。
**请求方法**: `PUT`
**接口地址**: `/api/v1/datasets/<dataset_id>`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Path Parameters
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| dataset_id | string | 是 | 知识库 ID |
#### Body Parameters (JSON)
*(以下所有字段均为可选,仅传递需要修改的字段即可)*
| 参数名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| name | string | - | **新名称**。需保持租户内唯一。 |
| avatar | string | - | **新头像**。Base64 字符串。 |
| description | string | - | **新描述**。 |
| permission | string | - | **新权限**`me``team`。 |
| embedding_model | string | - | **嵌入模型**。**注意**: 仅当知识库为空(chunk_count=0)时才允许修改。 |
| chunk_method | string | - | **默认解析方法**。修改后将应用于后续新上传的文件 (旧文件解析方式不变)。 |
| parser_config | object | - | **解析器配置**。全量覆盖旧配置 (结构参考 create 接口)。 |
| pagerank | int | 0 | **PageRank 权重**。仅在使用 Elasticsearch 引擎且需调整图谱权重时设置。 |
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": {
"id": "kb_uuid_...",
"name": "新名称",
"update_time": 1715629999000,
...
}
}
```
---
## 5. 获取知识图谱数据 - `knowledge_graph`
**接口描述**: 获取知识库构建的知识图谱数据,包含节点(Nodes)和边(Edges),用于前端可视化展示(如 ECharts 力导向图)。
**请求方法**: `GET`
**接口地址**: `/api/v1/datasets/<dataset_id>/knowledge_graph`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Path Parameters
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| dataset_id | string | 是 | 知识库 ID |
#### Query Parameters
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": {
"graph": {
"nodes": [
{
"id": "node_1",
"label": "人工智能", // 节点显示的文本
"pagerank": 0.05, // PageRank 权重 (决定节点大小)
"color": "#fcb", // 节点颜色
"img": "" // 节点图标 (如有)
},
{
"id": "node_2",
"label": "机器学习",
"pagerank": 0.03,
"color": "#e2b"
}
],
"edges": [
{
"source": "node_1", // 起始节点 ID
"target": "node_2", // 目标节点 ID
"weight": 0.8, // 边权重 (决定连线粗细)
"label": "includes" // 关系名称 (显示在连线上)
}
]
},
"mind_map": { // 思维导图结构的保留字段 (通常用于脑图展示)
"root": {
"id": "root_node",
"children": [...]
}
}
}
}
```
---
## 6. 清空知识图谱数据 - `delete_knowledge_graph`
**接口描述**: 删除指定知识库中已生成的知识图谱索引数据(包括所有实体节点和关系边)。
**注意**: 此操作**不会**删除原始文档或普通的向量索引,仅仅是重置图谱结构。如果需要重新生成图谱,请再次调用 `chunk` 相关接口或使用 `run_graphrag`
**请求方法**: `DELETE`
**接口地址**: `/api/v1/datasets/<dataset_id>/knowledge_graph`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Path Parameters
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| dataset_id | string | 是 | 知识库 ID |
#### Body Parameters
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": true
}
```
---
## 7. 运行/触发 GraphRAG 索引任务 - `run_graphrag`
**接口描述**: 触发后台异步任务,对知识库中的文档进行 GraphRAG 索引构建。此过程会使用 LLM 抽取实体(Entities)和关系(Relationships),并构建全局社区摘要。
**前提条件**: 知识库中必须包含已解析的文档。
**请求方法**: `POST`
**接口地址**: `/api/v1/datasets/<dataset_id>/run_graphrag`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Path Parameters
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| dataset_id | string | 是 | 知识库 ID |
#### Body Parameters (JSON)
*(Body 可为空 `{}`, 后续版本将扩展以下配置参数)*
| 参数名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| entity_types | array | ["organization", "person", "geo", "event"] | **(预留)** 指定要抽取的实体类型列表。 |
| method | string | "light" | **(预留)** 构建模式: `light` (轻量级), `general` (标准), `complex` (深度)。 |
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": {
"graphrag_task_id": "task_uuid_12345678" // 异步任务 ID,用于后续追踪进度
}
}
```
---
## 8. 运行/触发 RAPTOR 递归摘要任务 - `run_raptor`
**接口描述**: 触发后台异步任务,对知识库中的文档运行 RAPTOR (Recursive Abstractive Processing for Tree-Organized Retrieval) 算法。
**功能说明**: 该算法会递归地对文档块进行聚类和摘要,生成多层级的树状索引,显著提升对长文档和复杂问题的回答能力。
**请求方法**: `POST`
**接口地址**: `/api/v1/datasets/<dataset_id>/run_raptor`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Path Parameters
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| dataset_id | string | 是 | 知识库 ID |
#### Body Parameters (JSON)
*(Body 可为空 `{}`, 后续版本将扩展以下配置参数)*
| 参数名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| max_cluster | int | 64 | **(预留)** 最大聚类数。 |
| prompt | string | (内置摘要提示词) | **(预留)** 用于生成摘要的 Prompt。 |
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": {
"raptor_task_id": "task_uuid_87654321" // 异步任务 ID
}
}
```
---
## 9. 查询 GraphRAG 任务进度 - `trace_graphrag`
**接口描述**: 查询指定知识库当前 **GraphRAG** 索引构建任务的实时状态。支持长轮询机制监测进度。
**请求方法**: `GET`
**接口地址**: `/api/v1/datasets/<dataset_id>/trace_graphrag`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Path Parameters
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| dataset_id | string | 是 | 知识库 ID |
#### Query Parameters
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": {
"id": "task_uuid_12345678", // 任务 ID
"doc_id": "doc_uuid_...", // 当前正在处理的文档 ID (如果是多文档任务)
"from_page": 0, // 当前处理的起始页码
"to_page": 10, // 当前处理的结束页码
"progress": 0.45, // **总进度** (0.0 ~ 1.0)。0.0: 未开始/刚开始; 1.0: 完成; -1.0: 失败。
"progress_msg": "Extracting entities from chunk 25...", // **当前状态描述**。用于前端展示 Loading 提示。
"create_time": 1715623400000,
"update_time": 1715624500000
}
}
```
---
## 10. 查询 RAPTOR 任务进度 - `trace_raptor`
**接口描述**: 查询指定知识库当前 **RAPTOR** 递归摘要任务的实时状态。
**请求方法**: `GET`
**接口地址**: `/api/v1/datasets/<dataset_id>/trace_raptor`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Path Parameters
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| dataset_id | string | 是 | 知识库 ID |
#### Query Parameters
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": {
"id": "task_uuid_87654321",
"progress": 1.0, // 进度值。1.0 表示树构建完成。
"progress_msg": "Tree construction completed.", // 状态消息。
"create_time": 1715629000000
}
}
```
@@ -0,0 +1,757 @@
## 1. 上传文档 - `upload`
**接口描述**: 向指定的知识库上传一个或多个文档文件。上传后,文档将立即被存入文件系统/对象存储,并在数据库中创建记录。默认解析状态为 `UNSTART` (未开始),解析配置将继承自 KnowledgeBase 的默认设置。
**请求方法**: `POST`
**接口地址**: `/api/v1/datasets/<dataset_id>/documents`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
**Content-Type**: `multipart/form-data`
### 请求参数 (Request)
#### Path Parameters
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| dataset_id | string | 是 | **知识库 ID**。指定文档归属的知识库。 |
#### Form Data Parameters
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| file | file | 是 | **文件二进制流**。支持多文件上传 (Multiple Files)。<br>支持格式: PDF, DOCX, TXT, MD, CS, HTML, CSV, XLSX, PPTX 等。<br>单文件大小限制请参考系统配置 (默认通常为 10MB/100MB)。 |
| parent_path | string | 否 | **父级目录路径**。类似于文件系统的文件夹结构,默认为 `/`。如果指定 (如 `/docs/v1/`),文档将在该虚拟路径下列出。 |
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": [
{
"id": "e457f92e3c0411ef8d4c0242ac120003",
"thumbnail": null,
"dataset_id": "d1234567890abcdef1234567890abcde",
"chunk_method": "naive",
"pipeline_id": null,
"parser_config": {
"chunk_token_num": 512,
"delimiter": "\\n",
"layout_recognize": "DeepDOC",
"html4excel": false,
"auto_keywords": 0,
"auto_questions": 0,
"topn_tags": 3,
"raptor": {
"use_raptor": false
},
"graphrag": {
"use_graphrag": false
}
},
"source_type": "local",
"type": "pdf",
"created_by": "user_id_123",
"name": "UserGuide_v2.pdf",
"location": "UserGuide_v2.pdf",
"size": 102400,
"token_count": 0,
"chunk_count": 0,
"progress": 0.0,
"progress_msg": "",
"process_begin_at": null,
"process_duration": 0.0,
"meta_fields": {},
"suffix": "pdf",
"run": "UNSTART",
"status": "1",
"create_time": 1715623400123,
"create_date": "2024-05-13 10:03:20",
"update_time": 1715623400123,
"update_date": "2024-05-13 10:03:20"
}
]
}
```
---
## 2. 获取文档列表 - `list_docs`
**接口描述**: 查询知识库下的文档列表。支持分页检索、关键词搜索、状态筛选等功能。
**请求方法**: `GET`
**接口地址**: `/api/v1/datasets/<dataset_id>/documents`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Path Parameters
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| dataset_id | string | 是 | **知识库 ID**。 |
#### Query Parameters
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| page | int | 否 | 1 | **页码**。从 1 开始计数。 |
| page_size | int | 否 | 30 | **每页数量**。 |
| orderby | string | 否 | "create_time" | **排序字段**。支持 `create_time` (创建时间), `name` (文件名), `size` (大小) 等。 |
| desc | boolean | 否 | true | **是否降序**`true` (最新/最大在前), `false` (最旧/最小在前)。 |
| id | string | 否 | - | **精确筛选 ID**。仅返回指定 ID 的文档。 |
| name | string | 否 | - | **精确筛选文件名**。仅返回指定名称的文档。 |
| keywords | string | 否 | - | **模糊搜索**。匹配文档名称包含该关键词的记录。 |
| suffix | array | 否 | - | **文件后缀筛选** (如 `pdf`, `docx`)。 |
| run | array | 否 | - | **运行状态筛选**。可选值: `UNSTART`, `RUNNING`, `CANCEL`, `DONE`, `FAIL`。 |
| create_time_from | int | 否 | 0 | **起始时间戳** (毫秒)。查询在此时间之后创建的文档。 |
| create_time_to | int | 否 | 0 | **结束时间戳** (毫秒)。查询在此时间之前创建的文档。 |
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": {
"total": 128,
"docs": [
{
"id": "e457f92e3c0411ef8d4c0242ac120003",
"thumbnail": null,
"dataset_id": "d1234567890abcdef1234567890abcde",
"chunk_method": "naive",
"pipeline_id": null,
"parser_config": {
"chunk_token_num": 512,
"delimiter": "\\n",
"layout_recognize": "DeepDOC",
"html4excel": false,
"auto_keywords": 0,
"auto_questions": 0,
"topn_tags": 3,
"raptor": {
"use_raptor": false
},
"graphrag": {
"use_graphrag": false
}
},
"source_type": "local",
"type": "pdf",
"created_by": "user_id_123",
"name": "UserGuide_v2.pdf",
"location": "UserGuide_v2.pdf",
"size": 102400,
"token_count": 45000,
"chunk_count": 120,
"progress": 1.0,
"progress_msg": "Parsing finished",
"process_begin_at": "2024-05-13 10:05:00",
"process_duration": 45.2,
"meta_fields": {
"author": "RAGFlow Team",
"version": "2.0"
},
"suffix": "pdf",
"run": "DONE",
"status": "1",
"create_time": 1715623400123,
"create_date": "2024-05-13 10:03:20",
"update_time": 1715623450000,
"update_date": "2024-05-13 10:05:45"
}
]
}
}
```
---
## 3. 更新文档信息 - `update_doc`
**接口描述**: 更新文档的名称、状态或解析配置。
**特别注意**: 如果修改了 `chunk_method``parser_config`,后端会自动将 `run` 状态重置为 `UNSTART`,并清除已有的 chunk 数据,等待重新解析。
**请求方法**: `PUT`
**接口地址**: `/api/v1/datasets/<dataset_id>/documents/<document_id>`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Path Parameters
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| dataset_id | string | 是 | **知识库 ID**。 |
| document_id | string | 是 | **文档 ID**。 |
#### Body Parameters (JSON)
*(仅需传递要修改的字段)*
| 参数名 | 类型 | 说明 |
|---|---|---|
| name | string | **新文档名称**。需包含文件后缀且不能改变原始文件类型 (如从 `.pdf` 改为 `.txt` 会导致错误)。 |
| enabled | boolean | **启用/禁用**`true`: 启用 (DEFAULT, 对应 status="1"); `false`: 禁用 (对应 status="0")。禁用后该文档不参与检索。 |
| chunk_method | string | **解析方法**。可选值: `naive`, `manual`, `qa`, `table`, `paper`, `book`, `laws`, `presentation`, `picture`, `one`, `knowledge_graph`, `email`。 |
| parser_config | object | **解析器详细配置**。应与 `chunk_method` 匹配。以下列出 `naive` (通用) 方法的完整配置参数。 |
**parser_config (Naive 模式全量参数)**:
| 参数名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| chunk_token_num | int | 512 | **切片最大 Token 数**。 |
| delimiter | string | "\\n" | **分段符**。支持转义字符。 |
| layout_recognize | string | "DeepDOC" | **布局识别模型**。可选 `DeepDOC``Simple`。 |
| html4excel | boolean | false | **Excel转HTML**。是否将 Excel 解析为 HTML 表格。 |
| auto_keywords | int | 0 | **自动关键词数量**。0 表示不抽取。 |
| auto_questions | int | 0 | **自动问题数量**。0 表示不生成。 |
| topn_tags | int | 3 | **自动标签数量**。 |
| raptor | object | `{ "use_raptor": false }` | **RAPTOR 配置**。设置 `use_raptor: true` 可开启递归摘要索引。 |
| graphrag | object | `{ "use_graphrag": false }` | **GraphRAG 配置**。设置 `use_graphrag: true` 可开启图谱增强。 |
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": {
"id": "e457f92e3c0411ef8d4c0242ac120003",
"thumbnail": null,
"dataset_id": "d1234567890abcdef1234567890abcde",
"chunk_method": "naive",
"pipeline_id": null,
"parser_config": {
"chunk_token_num": 1024,
"delimiter": "\\n",
"layout_recognize": "DeepDOC",
"html4excel": false,
"auto_keywords": 0,
"auto_questions": 0,
"topn_tags": 3,
"raptor": {
"use_raptor": false
},
"graphrag": {
"use_graphrag": false
}
},
"source_type": "local",
"type": "pdf",
"created_by": "user_id_123",
"name": "Renamed_Guide.pdf",
"location": "UserGuide_v2.pdf",
"size": 102400,
"token_count": 45000,
"chunk_count": 0,
"progress": 0.0,
"progress_msg": "",
"process_begin_at": null,
"process_duration": 0.0,
"meta_fields": {},
"suffix": "pdf",
"run": "UNSTART",
"status": "0",
"create_time": 1715623400123,
"create_date": "2024-05-13 10:03:20",
"update_time": 1715629999000,
"update_date": "2024-05-13 12:00:00"
}
}
```
---
## 4. 删除文档 - `delete`
**接口描述**: 物理删除一个或多个文档。此操作不可恢复,将同时删除数据库记录、MinIO 中的源文件以及 Elasticsearch 中的所有相关切片索引。
**请求方法**: `DELETE`
**接口地址**: `/api/v1/datasets/<dataset_id>/documents`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Path Parameters
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| dataset_id | string | 是 | **知识库 ID**。 |
#### Body Parameters (JSON)
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| ids | array<string> | 是 | **文档 ID 列表**。必须指定要删除的文档 ID。 |
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": null
}
```
---
## 5. 下载/预览原始文件 - `download`
**接口描述**: 获取文档的原始二进制文件流。响应头将会包含 `Content-Disposition` 字段,指示浏览器以附件形式下载。
**请求方法**: `GET`
**接口地址**: `/api/v1/datasets/<dataset_id>/documents/<document_id>`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Path Parameters
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| dataset_id | string | 是 | **知识库 ID**。 |
| document_id | string | 是 | **文档 ID**。 |
### 响应参数 (Response)
**Content-Type**: `application/octet-stream`
**Content-Disposition**: `attachment; filename="UserGuide_v2.pdf"`
*(直接返回文件的二进制数据流)*
## 6. 触发/重试文档解析 - `parse`
**接口描述**: 手动触发文档的解析任务。通常在上传文件后、或修改了解析配置(如 `chunk_method`)后调用此接口。支持批量触发。
**请求方法**: `POST`
**接口地址**: `/api/v1/datasets/<dataset_id>/chunks`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Path Parameters
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| dataset_id | string | 是 | **知识库 ID**。 |
#### Body Parameters (JSON)
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| document_ids | array<string> | 是 | **文档 ID 列表**。指定需要(重新)解析的文档 ID。 |
**Request Example**:
```json
{
"document_ids": ["doc_id_1", "doc_id_2"]
}
```
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": null
}
```
---
## 7. 停止文档解析 - `stop_parsing`
**接口描述**: 停止当前正在进行的文档解析任务。
**请求方法**: `DELETE`
**接口地址**: `/api/v1/datasets/<dataset_id>/chunks`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Path Parameters
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| dataset_id | string | 是 | **知识库 ID**。 |
#### Body Parameters (JSON)
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| document_ids | array<string> | 是 | **文档 ID 列表**。指定要停止解析的任务。 |
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": null
}
```
---
## 8. 获取切片列表 - `list_chunks`
**接口描述**: 获取指定文档已解析出的切片(Chunk)列表。支持分页和关键词搜索。返回结果包含文档的详细元数据和具体的切片内容。
**请求方法**: `GET`
**接口地址**: `/api/v1/datasets/<dataset_id>/documents/<document_id>/chunks`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Path Parameters
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| dataset_id | string | 是 | **知识库 ID**。 |
| document_id | string | 是 | **文档 ID**。 |
#### Query Parameters
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| page | int | 否 | 1 | **页码**。 |
| page_size | int | 否 | 30 | **每页数量**。 |
| keywords | string | 否 | - | **搜索关键词**。在切片内容中进行全文检索。 |
| id | string | 否 | - | **精确切片 ID**。若指定,则只返回该 ID 对应的切片。 |
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": {
"total": 150,
"chunks": [
{
"id": "e457f92e3c0411ef8d4c0242ac120003_0",
"content": "RAGFlow 是一款基于深度文档理解的开源 RAG(检索增强生成)引擎。它旨在为各种规模的企业提供精简的 RAG 工作流。RAGFlow 结合了传统文档处理的稳健性与现代大语言模型(LLM)的生成能力,确保在处理复杂格式数据(如 PDF 表格、扫描件等)时依然能保持极高的召回率和准确性。",
"document_id": "doc_uuid_123",
"docnm_kwd": "RAGFlow_UserGuide_v2.pdf",
"important_keywords": ["RAGFlow", "开源", "深度文档理解", "LLM"],
"questions": ["什么是 RAGFlow?", "RAGFlow 的主要特点是什么?"],
"image_id": "",
"dataset_id": "kb_uuid_456",
"available": true,
"positions": [1]
},
{
"id": "e457f92e3c0411ef8d4c0242ac120003_1",
"content": "主要特性:\n1. **深度文档解析**:内置 DeepDOC 识别引擎,精准还原表格、段落结构。\n2. **多路召回**:支持关键词 + 向量的混合检索。\n3. **可视化编排**:提供基于 Graph 的工作流编排能力。",
"document_id": "doc_uuid_123",
"docnm_kwd": "RAGFlow_UserGuide_v2.pdf",
"important_keywords": ["DeepDOC", "混合检索", "可视化编排"],
"questions": [],
"image_id": "img_uuid_789",
"dataset_id": "kb_uuid_456",
"available": true,
"positions": [2]
}
],
"doc": {
"id": "doc_uuid_123",
"name": "RAGFlow_UserGuide_v2.pdf",
"chunk_count": 150,
"token_count": 45000,
"chunk_method": "naive",
"run": "DONE",
"status": "1",
"progress": 1.0,
"progress_msg": "Parsing finished",
"process_begin_at": "2024-05-13 10:05:00",
"process_duration": 45.2,
"meta_fields": {
"author": "RAGFlow Team",
"version": "2.0"
},
"create_time": 1715623400123,
"create_date": "2024-05-13 10:03:20",
"update_time": 1715623450000,
"update_date": "2024-05-13 10:05:45",
"dataset_id": "kb_uuid_456"
}
}
}
```
---
## 9. 手动新增切片 - `add_chunk`
**接口描述**: 向指定文档中手动添加一个新的切片。系统会自动计算该切片的向量嵌入 (Embedding)。
**请求方法**: `POST`
**接口地址**: `/api/v1/datasets/<dataset_id>/documents/<document_id>/chunks`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Path Parameters
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| dataset_id | string | 是 | **知识库 ID**。 |
| document_id | string | 是 | **文档 ID**。 |
#### Body Parameters (JSON)
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| content | string | 是 | **切片内容**。手动输入的文本内容。 |
| important_keywords | array<string> | 否 | **重要关键词**。用于关键词检索增强。 |
| questions | array<string> | 否 | **预设问题**。用于 Q&A 检索模式增强。 |
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": {
"chunk": {
"id": "new_chunk_uuid_999",
"content": "这是管理员手动添加的一条补充切片,用于修正文档中缺失的关键信息。",
"document_id": "doc_uuid_123",
"docnm_kwd": "RAGFlow_UserGuide_v2.pdf",
"important_keywords": ["手动添加", "补充信息"],
"questions": ["如何手动添加切片?"],
"image_id": "",
"dataset_id": "kb_uuid_456",
"available": true,
"positions": []
}
}
}
```
---
## 10. 修改切片信息 - `update_chunk`
**接口描述**: 修改已存在的切片内容、关键词、可用状态等。修改内容后,系统会自动重新计算向量。
**请求方法**: `PUT`
**接口地址**: `/api/v1/datasets/<dataset_id>/documents/<document_id>/chunks/<chunk_id>`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Path Parameters
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| dataset_id | string | 是 | **知识库 ID**。 |
| document_id | string | 是 | **文档 ID**。 |
| chunk_id | string | 是 | **切片 ID**。 |
#### Body Parameters (JSON)
*(以下字段均为可选,仅传递需修改的字段)*
| 参数名 | 类型 | 说明 |
|---|---|---|
| content | string | **新的切片内容**。 |
| important_keywords | array<string> | **更新关键词列表**。覆盖原有列表。 |
| available | boolean | **启用/禁用**`true`: 启用 (默认); `false`: 禁用 (检索时将忽略此切片)。 |
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": null
}
```
---
## 11. 删除切片 - `rm_chunk`
**接口描述**: 批量删除文档中的指定切片。
**请求方法**: `DELETE`
**接口地址**: `/api/v1/datasets/<dataset_id>/documents/<document_id>/chunks`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Path Parameters
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| dataset_id | string | 是 | **知识库 ID**。 |
| document_id | string | 是 | **文档 ID**。 |
#### Body Parameters (JSON)
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| chunk_ids | array<string> | 是 | **切片 ID 列表**。 |
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "deleted 2 chunks",
"data": null
}
```
## 12. 获取元数据摘要 - `metadata_summary`
**接口描述**: 获取知识库中所有文档的元数据摘要信息。通常用于前端展示知识库的数据分布概况,例如不同文件类型的数量统计、文件状态分布等。
**请求方法**: `GET`
**接口地址**: `/api/v1/datasets/<dataset_id>/metadata/summary`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Path Parameters
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| dataset_id | string | 是 | **知识库 ID**。 |
#### Query Parameters
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": {
"summary": {
"total_doc_count": 120,
"total_token_count": 500000,
"file_type_distribution": {
"pdf": 80,
"docx": 30,
"txt": 10
},
"status_distribution": {
"1": 118, // 正常启用
"0": 2 // 禁用
},
"custom_metadata": {
"author": {
"Alice": 50,
"Bob": 30
},
"department": {
"HR": 20,
"Engineering": 100
}
}
}
}
}
```
---
## 13. 批量更新元数据 - `metadata_batch_update`
**接口描述**: 对知识库中的文档进行批量元数据修改。支持基于复杂的条件筛选文档,然后执行批量更新或删除元数据字段的操作。
**请求方法**: `POST`
**接口地址**: `/api/v1/datasets/<dataset_id>/metadata/update`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Path Parameters
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| dataset_id | string | 是 | **知识库 ID**。 |
#### Body Parameters (JSON)
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| selector | object | 否 | **筛选器**。定义要更新哪些文档。如果不传,可能作用于全量文档(请谨慎)。 |
| updates | array | 否 | **更新操作列表**。包含 `key``value`。 |
| deletes | array | 否 | **删除操作列表**。包含 `key`。 |
**Request Example (复杂场景)**:
```json
{
"selector": {
"document_ids": ["doc_id_101", "doc_id_102"],
"metadata_condition": {
"logic": "and",
"conditions": [
{"key": "author", "value": "OldName", "operator": "eq"},
{"key": "status", "value": "draft", "operator": "eq"}
]
}
},
"updates": [
{"key": "author", "value": "Admin"},
{"key": "reviewed_by", "value": "ManagerA"}
],
"deletes": [
{"key": "temp_tag"},
{"key": "draft_flag"}
]
}
```
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": {
"updated": 2, // 实际更新成功的文档数量
"matched_docs": 2 // 匹配到的文档数量
}
}
```
---
## 14. 检索测试 (Hit Test) - `retrieval_test`
**接口描述**: 在指定的知识库中进行模拟检索测试。此接口用于验证分段(Chunk)质量、检索参数(相似度阈值、Top K)的效果,是调试 RAG 效果的核心工具。
**请求方法**: `POST`
**接口地址**: `/api/v1/retrieval`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
**注意**: 即使是简单的查询,由于包含较多配置参数,本接口也设计为 `POST` 请求。
### 请求参数 (Request)
#### Path Parameters
#### Body Parameters (JSON)
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| dataset_ids | array<string> | 是 | - | **目标知识库 ID 列表**。支持跨多个知识库检索。 |
| question | string | 是 | - | **用户查询问题**。 |
| similarity_threshold | float | 否 | 0.2 | **相似度阈值**。低于此分数的 Chunk 将被过滤。 |
| vector_similarity_weight | float | 否 | 0.3 | **向量权重**。混合检索时,向量检索结果的权重 (0~1)。剩余权重归于关键词检索。 |
| top_k | int | 否 | 1024 | **初筛数量**。向量检索返回的候选切片数量。 |
| rerank_id | string | 否 | - | **重排模型 ID**。若指定,将对检索结果进行 Rerank 二次排序。 |
| highlight | boolean | 否 | true | **高亮匹配**。是否在返回内容中高亮关键词。 |
| keyword | boolean | 否 | false | **关键词增强**。是否使用 LLM 提取问题关键词以增强检索。 |
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": {
"total": 15,
"chunks": [
{
"id": "e457f92e3c0411ef8d4c0242ac120003_12",
"content": "RAGFlow 支持多种文档解析模式,其中 DeepDOC 模式特别适合处理包含大量表格和扫描件的 PDF 文档。它使用深度学习模型识别文档布局,精准提取表格内容。",
"document_id": "doc_uuid_123",
"dataset_id": "kb_uuid_456",
"document_name": "RAGFlow_UserGuide_v2.pdf",
"document_keyword": "RAGFlow_UserGuide_v2.pdf",
"similarity": 0.88,
"vector_similarity": 0.85,
"term_similarity": 0.92,
"index": 12,
"highlight": "RAGFlow 支持多种<em>文档解析模式</em>,其中 <em>DeepDOC</em> 模式特别适合处理包含大量表格和扫描件的 PDF 文档。",
"important_keywords": ["DeepDOC", "PDF"],
"questions": ["DeepDOC 模式有什么用?"],
"image_id": "",
"positions": [12]
},
{
"id": "e457f92e3c0411ef8d4c0242ac120003_15",
"content": "如果文档主要由纯文本构成,建议使用 Naive 模式。该模式解析速度快,适合通用场景。",
"document_id": "doc_uuid_123",
"dataset_id": "kb_uuid_456",
"document_name": "RAGFlow_UserGuide_v2.pdf",
"document_keyword": "RAGFlow_UserGuide_v2.pdf",
"similarity": 0.45,
"vector_similarity": 0.40,
"term_similarity": 0.50,
"index": 15,
"highlight": "如果文档主要由纯文本构成,建议使用 <em>Naive</em> 模式。",
"important_keywords": ["Naive", "纯文本"],
"questions": [],
"image_id": "",
"positions": [15]
}
],
"doc_aggs": [
{
"doc_name": "RAGFlow_UserGuide_v2.pdf",
"doc_id": "doc_uuid_123",
"count": 2
}
]
}
}
```
@@ -0,0 +1,503 @@
# RAGFlow 文件管理接口详解 (File Management API)
## 1. 上传文件 - `upload`
**接口描述**: 上传一个或多个文件到指定文件夹。支持多文件上传 (Multipart)。上传成功后,文件将存储在 MinIO/S3 中,并返回文件元数据列表。
**请求方法**: `POST`
**接口地址**: `/api/v1/file/upload`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
**Content-Type**: `multipart/form-data`
### 请求参数 (Request)
#### Form Data Parameters
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| file | file | 是 | **文件二进制流**。支持多文件上传。 |
| parent_id | string | 否 | **父级目录 ID**。如果省略,默认上传到根目录 (root)。 |
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": [
{
"id": "e457f92e3c0411ef8d4c0242ac120003",
"parent_id": "root_folder_id_123",
"tenant_id": "tenant_uuid_456",
"created_by": "user_uuid_789",
"type": "pdf",
"name": "ProjectReport.pdf",
"location": "ProjectReport.pdf",
"size": 204800,
"source_type": "",
"create_time": 1715623400123,
"create_date": "2024-05-13 10:03:20",
"update_time": 1715623400123,
"update_date": "2024-05-13 10:03:20"
}
]
}
```
---
## 2. 新建文件夹 - `create`
**接口描述**: 在指定父目录下创建一个新的文件夹(逻辑目录)。
**请求方法**: `POST`
**接口地址**: `/api/v1/file/create`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Body Parameters (JSON)
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| name | string | 是 | **文件夹名称**。同一目录下不可重名。 |
| parent_id | string | 否 | **父级目录 ID**。省略则默认为根目录。 |
| type | string | 是 | **类型**。固定值为 `FOLDER` 创建文件夹。 |
**Request Example**:
```json
{
"name": "Year2024_Reports",
"parent_id": "root_folder_id_123",
"type": "FOLDER"
}
```
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": {
"id": "folder_uuid_abc",
"parent_id": "root_folder_id_123",
"tenant_id": "tenant_uuid_456",
"created_by": "user_uuid_789",
"name": "Year2024_Reports",
"location": "",
"size": 0,
"type": "folder",
"source_type": "",
"create_time": 1715623500000,
"create_date": "2024-05-13 10:05:00",
"update_time": 1715623500000,
"update_date": "2024-05-13 10:05:00"
}
}
```
---
## 3. 获取文件列表 - `list_files`
**接口描述**: 分页获取指定文件夹下的文件和子文件夹列表。支持按名称模糊搜索。
**请求方法**: `GET`
**接口地址**: `/api/v1/file/list`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Query Parameters
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| parent_id | string | 否 | (Root) | **父级目录 ID**。指定要查看的目录 ID。 |
| keywords | string | 否 | - | **搜索关键词**。按文件名模糊搜索。 |
| page | int | 否 | 1 | **页码**。 |
| page_size | int | 否 | 15 | **每页数量**。 |
| orderby | string | 否 | "create_time" | **排序字段**。 |
| desc | boolean | 否 | true | **是否降序**。 |
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": {
"total": 25,
"parent_folder": {
"id": "root_folder_id_123",
"parent_id": "",
"tenant_id": "tenant_uuid_456",
"created_by": "system",
"name": "ROOT",
"location": "",
"size": 0,
"type": "folder",
"source_type": "",
"create_time": 1710000000000,
"create_date": "2024-03-01 00:00:00",
"update_time": 1710000000000,
"update_date": "2024-03-01 00:00:00"
},
"files": [
{
"id": "folder_uuid_abc",
"parent_id": "root_folder_id_123",
"tenant_id": "tenant_uuid_456",
"created_by": "user_uuid_789",
"name": "Year2024_Reports",
"location": "",
"size": 0,
"type": "folder",
"source_type": "",
"create_time": 1715623500000,
"create_date": "2024-05-13 10:05:00",
"update_time": 1715623500000,
"update_date": "2024-05-13 10:05:00"
},
{
"id": "e457f92e3c0411ef8d4c0242ac120003",
"parent_id": "root_folder_id_123",
"tenant_id": "tenant_uuid_456",
"created_by": "user_uuid_789",
"name": "ProjectReport.pdf",
"location": "ProjectReport.pdf",
"size": 204800,
"type": "pdf",
"source_type": "",
"create_time": 1715623400123,
"create_date": "2024-05-13 10:03:20",
"update_time": 1715623400123,
"update_date": "2024-05-13 10:03:20"
}
]
}
}
```
---
## 4. 获取文件流 (下载) - `get`
**接口描述**: 通过文件 ID 下载文件内容。不同于获取元数据,该接口直接返回文件的二进制流(Octet-stream 或 Image 等)。
**请求方法**: `GET`
**接口地址**: `/api/v1/file/get/<file_id>`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Path Parameters
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| file_id | string | 是 | **文件 ID**。 |
### 响应参数 (Response)
**Content-Type**: `application/octet-stream` (或具体 MIME 类型如 `image/png`)
*(返回二进制文件流)*
---
## 5. 下载附件 - `download_attachment`
**接口描述**: 这是一个通用的附件下载接口,通常用于系统内部引用或特定路径的下载。它使用 `attachment_id`(通常对应 MinIO 中的存储路径/Key)来检索文件。
**请求方法**: `GET`
**接口地址**: `/api/v1/file/download/<attachment_id>`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Path Parameters
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| attachment_id | string | 是 | **附件 ID / 存储 Key**。通常对应底层存储的唯一标识符。 |
#### Query Parameters
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| ext | string | 否 | "markdown" | **文件扩展名**。用于设置响应头中的 Content-Type。 |
### 响应参数 (Response)
**Content-Type**: `application/octet-stream` (或根据 ext 参数推断)
*(返回二进制文件流)*
## 6. 重命名文件/文件夹 - `rename`
**接口描述**: 修改文件或文件夹的名称。对于文件,通常不允许修改扩展名(后缀)。
**请求方法**: `POST`
**接口地址**: `/api/v1/file/rename`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Body Parameters (JSON)
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| file_id | string | 是 | **目标文件/文件夹 ID**。 |
| name | string | 是 | **新名称**。需符合文件命名规范,且同一目录下不可重名。 |
**Request Example**:
```json
{
"file_id": "file_uuid_123",
"name": "New_Report_Final.pdf"
}
```
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": true
}
```
---
## 7. 移动文件/文件夹 - `move`
**接口描述**: 批量移动文件或文件夹到指定的目录 (Move)。
**请求方法**: `POST`
**接口地址**: `/api/v1/file/mv`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Body Parameters (JSON)
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| src_file_ids | array<string> | 是 | **源文件/文件夹 ID 列表**。支持批量移动。 |
| dest_file_id | string | 是 | **目标文件夹 ID**。必须是已存在的文件夹 ID。 |
**Request Example**:
```json
{
"src_file_ids": ["file_id_1", "file_id_2"],
"dest_file_id": "folder_id_target"
}
```
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": true
}
```
---
## 8. 删除文件/文件夹 - `rm`
**接口描述**: 批量删除文件或文件夹。如果是文件夹,将递归删除其下的所有内容。此操作不可恢复。
**请求方法**: `POST`
**接口地址**: `/api/v1/file/rm`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Body Parameters (JSON)
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| file_ids | array<string> | 是 | **待删除的文件/文件夹 ID 列表**。 |
**Request Example**:
```json
{
"file_ids": ["file_uuid_to_delete_1", "folder_uuid_to_delete_2"]
}
```
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": true
}
```
---
## 9. 文件转知识库文档 - `convert`
**接口描述**: 将已上传的文件(File)导入到指定的知识库(Dataset)中,转换为文档(Document)并进行解析。这是一个“文件 -> 知识库”的桥接操作。
**请求方法**: `POST`
**接口地址**: `/api/v1/file/convert`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Body Parameters (JSON)
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| file_ids | array<string> | 是 | **源文件 ID 列表**。必须是已存在于文件管理系统中的 ID。 |
| kb_ids | array<string> | 是 | **目标知识库 ID 列表**。文件将被同时导入到这些知识库中。 |
**Request Example**:
```json
{
"file_ids": ["file_uuid_pdf_1", "file_uuid_txt_2"],
"kb_ids": ["dataset_uuid_A"]
}
```
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": [
{
"id": "mapping_uuid_1",
"file_id": "file_uuid_pdf_1",
"document_id": "doc_uuid_created_in_kb_A",
"create_time": 1715623600123,
"create_date": "2024-05-13 10:06:40",
"update_time": 1715623600123,
"update_date": "2024-05-13 10:06:40"
},
{
"id": "mapping_uuid_2",
"file_id": "file_uuid_txt_2",
"document_id": "doc_uuid_created_in_kb_A",
"create_time": 1715623600124,
"create_date": "2024-05-13 10:06:40",
"update_time": 1715623600124,
"update_date": "2024-05-13 10:06:40"
}
]
}
```
## 10. 获取根目录信息 - `get_root_folder`
**接口描述**: 获取当前用户的根目录文件夹信息。每个用户(Tenant)都有且仅有一个系统自动创建的根目录。
**请求方法**: `GET`
**接口地址**: `/api/v1/file/root_folder`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Query Parameters
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": {
"root_folder": {
"id": "root_folder_id_123",
"parent_id": "",
"tenant_id": "tenant_uuid_456",
"created_by": "system",
"name": "ROOT",
"location": "",
"size": 0,
"type": "folder",
"source_type": "",
"create_time": 1710000000000,
"create_date": "2024-03-01 00:00:00",
"update_time": 1710000000000,
"update_date": "2024-03-01 00:00:00"
}
}
}
```
---
## 11. 获取父目录信息 - `get_parent_folder`
**接口描述**: 获取指定文件或文件夹的直接父级目录信息。
**请求方法**: `GET`
**接口地址**: `/api/v1/file/parent_folder`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Query Parameters
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| file_id | string | 是 | **当前文件/文件夹 ID**。 |
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": {
"parent_folder": {
"id": "root_folder_id_123",
"parent_id": "",
"tenant_id": "tenant_uuid_456",
"created_by": "system",
"name": "ROOT",
"location": "",
"size": 0,
"type": "folder",
"source_type": "",
"create_time": 1710000000000,
"create_date": "2024-03-01 00:00:00",
"update_time": 1710000000000,
"update_date": "2024-03-01 00:00:00"
}
}
}
```
---
## 12. 获取完整路径 (面包屑) - `get_all_parent_folders`
**接口描述**: 获取指定文件或文件夹的所有上级目录列表,形成完整的路径链。返回的列表顺序通常是从根目录到直接父目录(有序)。此接口常用于前端展示“面包屑导航” (Breadcrumbs)。
**请求方法**: `GET`
**接口地址**: `/api/v1/file/all_parent_folder`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Query Parameters
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| file_id | string | 是 | **目标文件/文件夹 ID**。 |
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": {
"parent_folders": [
{
"id": "root_folder_id_123",
"parent_id": "",
"tenant_id": "tenant_uuid_456",
"created_by": "system",
"name": "ROOT",
"location": "",
"size": 0,
"type": "folder",
"source_type": "",
"create_time": 1710000000000,
"create_date": "2024-03-01 00:00:00",
"update_time": 1710000000000,
"update_date": "2024-03-01 00:00:00"
},
{
"id": "folder_project_a_id",
"parent_id": "root_folder_id_123",
"tenant_id": "tenant_uuid_456",
"created_by": "user_id_001",
"name": "Project A Docs",
"location": "",
"size": 0,
"type": "folder",
"source_type": "",
"create_time": 1715000000000,
"create_date": "2024-05-01 09:00:00",
"update_time": 1715000000000,
"update_date": "2024-05-01 09:00:00"
}
]
}
}
```
@@ -0,0 +1,228 @@
# RAGFlow 搜索机器人 & AgentBot 接口详解 (SearchBot & AgentBot)
## 1. 搜索机器人对话 - `ask_about_embedded`
**接口描述**: 面向 **SearchBot (搜索机器人)** 的核心对话接口,通常用于嵌入式知识库问答场景。与普通 Chat 不同,它更侧重于从指定的 `kb_ids` 中直接检索答案,且鉴权使用 `Authorization: Bearer <Beta_Token>` (即 API Key)。
**请求方法**: `POST`
**接口地址**: `/api/v1/searchbots/ask`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Body Parameters (JSON)
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| question | string | 是 | - | **用户问题**。 |
| kb_ids | array<string> | 是 | - | **知识库 ID 列表**。限定从哪些知识库中检索。 |
| search_id | string | 否 | - | **搜索应用 ID**。如果指定,将使用该搜索应用的配置 (Search App Config)。 |
**Request Example**:
```json
{
"question": "What is the refund policy?",
"kb_ids": ["dataset_uuid_1", "dataset_uuid_2"],
"search_id": "search_app_uuid_abc"
}
```
### 响应参数 (Stream Response)
**Content-Type**: `text/event-stream`
```text
data:{"code": 0, "message": "", "data": {"answer": "According to the ", "reference": {}}}
data:{"code": 0, "message": "", "data": {"answer": "policy, refunds are processed within 7 days.", "reference": {"chunk_1": {"content_with_weight": "Refunds...", "doc_name": "policy.pdf"}}}}
data:{"code": 0, "message": "", "data": true} // 结束标志
```
---
## 2. 获取思维导图 - `mindmap`
**接口描述**: 根据用户的查询或对话上下文,生成用于前端展示的思维导图数据结构。这通常用于帮助用户梳理复杂的搜索结果或知识结构。
**请求方法**: `POST`
**接口地址**: `/api/v1/searchbots/mindmap`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Body Parameters (JSON)
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| question | string | 是 | **用户问题/主题**。 |
| kb_ids | array<string> | 是 | **知识库 ID 列表**。 |
| search_id | string | 否 | **搜索应用 ID**。 |
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": {
"root": {
"text": "Refund Policy", // 根节点文本
"children": [
{
"text": "Conditions",
"children": [
{ "text": "Product defect" },
{ "text": "Shipping error" }
]
},
{
"text": "Timeline",
"children": [
{ "text": "7-14 business days" }
]
}
]
}
}
}
```
---
## 3. 获取相关推荐问题 - `related_questions_embedded`
**接口描述**: 根据用户当前的问题,生成一组相关的推荐问题 (Suggest Questions)。常用于搜索结果页底部的“猜你想问”。
**请求方法**: `POST`
**接口地址**: `/api/v1/searchbots/related_questions`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Body Parameters (JSON)
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| question | string | 是 | **用户当前问题**。 |
| search_id | string | 否 | **搜索应用 ID**。 |
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": [
"How to apply for a refund online?",
"What items are non-refundable?",
"Contact customer support"
]
}
```
---
## 4. 获取 AgentBot 输入项 - `begin_inputs`
**接口描述**: 获取 **AgentBot** (嵌入式 Agent) 的初始化信息,特别是前置输入项 (Prolog/Inputs)。这用于在用户开始对话前,展示一个表单让用户输入必要信息(如姓名、邮箱、API Key 等),这些信息会被传递给 Agent 的 `Begin` 节点。
**请求方法**: `GET`
**接口地址**: `/api/v1/agentbots/<agent_id>/inputs`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Path Parameters
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| agent_id | string | 是 | **Agent ID**。 |
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": {
"title": "Booking Assistant",
"avatar": "http://...",
"prologue": "Welcome! Please tell me your details.",
"inputs": { // `Begin` 节点定义的输入变量
"user_name": {
"type": "string",
"description": "Your Name",
"required": true
},
"email": {
"type": "string",
"description": "Contact Email",
"required": false
}
},
"mode": "chat"
}
}
```
---
## 5. AgentBot 对话交互 - `agent_bot_completions`
**接口描述**: 面向 **AgentBot** 的嵌入式对话接口。与 `agent_completions` 类似,但它专为无需登录的 C 端用户设计,通过 API Key 鉴权。它支持完整的 Agent 流程执行和流式响应。
**请求方法**: `POST`
**接口地址**: `/api/v1/agentbots/<agent_id>/completions`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Path Parameters
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| agent_id | string | 是 | **Agent ID**。 |
#### Body Parameters (JSON)
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| session_id | string | 是 | **会话 ID**。 |
| inputs | object | 否 | **前置输入值**。对应 `begin_inputs` 中定义的变量,如 `{"user_name": "Alice"}`。 |
| query | string | 否 | **用户输入**。 |
| stream | boolean | 否 | **是否流式**。默认 `true`。 |
**Request Example**:
```json
{
"session_id": "session_uuid_123",
"inputs": {
"user_name": "Bob"
},
"query": "I want to book a room.",
"stream": true
}
```
### 响应参数 (Stream Response)
**Content-Type**: `text/event-stream`
```text
data:{"event": "message", "data": {"content": "Hello Bob, ", "reference": {}}}
data:{"event": "message", "data": {"content": "when do you want to check in?", "reference": {}}}
```
---
## 6. Agent OpenAI 兼容接口 - `agents_completion_openai_compatibility`
**接口描述**: 专门针对 Agent 的 **OpenAI 兼容** 接口。这使得外部工具可以像调用 OpenAI Chat Completion 一样调用 RAGFlow 配置好的复杂 Agent。
**请求方法**: `POST`
**接口地址**: `/api/v1/agents_openai/<agent_id>/chat/completions`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Path Parameters
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| agent_id | string | 是 | **Agent ID**。 |
#### Body Parameters (OpenAI Standard)
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| messages | array | 是 | 包含 `role`, `content` 的消息数组。 |
| model | string | 是 | 占位符,任意字符串。 |
| stream | boolean | 否 | 默认 `true`。 |
### 响应参数 (Stream Response - OpenAI Format)
**Content-Type**: `text/event-stream`
```text
data: {"id": "agent-chat-uuid", "object": "chat.completion.chunk", "created": 1715000000, "model": "ragflow_agent", "choices": [{"index": 0, "delta": {"role": "assistant", "content": ""}, "finish_reason": null}]}
data: {"id": "agent-chat-uuid", "object": "chat.completion.chunk", "created": 1715000001, "model": "ragflow_agent", "choices": [{"index": 0, "delta": {"content": "Processing your request..."}, "finish_reason": null}]}
data: [DONE]
```
@@ -0,0 +1,168 @@
# RAGFlow SearchBot 补充与通用会话接口详解 (Session Extras)
## 1. 获取引用详情 - `detail_share_embedded`
**接口描述**: 当用户点击 SearchBot 回复中的引用标号 (e.g., [1]) 时,调用此接口获取该引用的详细内容(包括原文片段、来源文档名等)。此接口通常用于前端展示“引用来源”侧边栏或弹窗。它使用 API Key (Beta Token) 进行鉴权。
**请求方法**: `GET`
**接口地址**: `/api/v1/searchbots/detail`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Query Parameters
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| search_id | string | 是 | **搜索应用/SearchBot ID**。此接口需要验证调用者是否有权访问该 SearchBot。 |
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": {
"id": "search_app_uuid_123",
"title": "IT Knowledge Base",
"description": "Tech support search bot",
"kb_ids": ["kb_uuid_1", "kb_uuid_2"],
"search_config": {
"top_k": 5,
"similarity_threshold": 0.5
},
// 注意:此接口目前主要返回 Search App 的详情配置,
// 前端通常使用 search_config 或其他信息来辅助展示引用。
// 具体引用内容的文本通常已包含在 `ask` 接口的 `reference` 字段中。
}
}
```
---
## 2. SearchBot 检索测试 - `retrieval_test_embedded`
**接口描述**: 面向 SearchBot 的**检索效果测试**接口。它不通过 LLM 生成答案,而是直接返回 RAG 检索到的文档片段 (`chunks`)。这用于调试 SearchBot 的检索参数(如相似度阈值、Top-K)是否合理。
**请求方法**: `POST`
**接口地址**: `/api/v1/searchbots/retrieval_test`
**鉴权方式**: Header `Authorization: Bearer <API_KEY>`
### 请求参数 (Request)
#### Body Parameters (JSON)
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| kb_id | string/array | 是 | - | **知识库 ID** (或列表)。支持单个 ID 字符串或 ID 列表。 |
| question | string | 是 | - | **测试查询词**。 |
| page | int | 否 | 1 | **页码**。 |
| size | int | 否 | 30 | **每页数量**。 |
| doc_ids | array<string> | 否 | - | **限定文档 ID**。仅在指定文档中检索。 |
| similarity_threshold | float | 否 | 0.0 | **相似度阈值**。 |
| top_k | int | 否 | 1024 | **Top-K 数量**。 |
| highlight | boolean | 否 | false | **高亮匹配**。是否在返回内容中标记匹配关键词。 |
**Request Example**:
```json
{
"kb_id": ["dataset_uuid_1"],
"question": "refund policy",
"top_k": 5,
"highlight": true
}
```
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": {
"total": 12, // 命中总是
"chunks": [
{
"content_with_weight": "Refunds are processed within <em>7 days</em>...", // 支持高亮
"doc_name": "policy.pdf",
"doc_id": "doc_uuid_101",
"similarity": 0.92,
"img_id": ""
},
{
"content_with_weight": "Product return guidelines...",
"doc_name": "guidelines.docx",
"doc_id": "doc_uuid_102",
"similarity": 0.88
}
],
"labels": [] // 如果启用了查询标签功能
}
}
```
---
## 3. 通用会话问答 - `ask_about`
**接口描述**: **内部/测试用**的通用会话问答接口。与 `ask_embedded` 不同,此接口通常用于 RAGFlow 控制台内部的“调试”或“预览”功能,鉴权依赖用户的登录 Token (User Token),且必须显式指定 `dataset_ids`。它不绑定特定的 Chat/Agent/SearchBot 配置。
**请求方法**: `POST`
**接口地址**: `/api/v1/sessions/ask`
**鉴权方式**: Header `Authorization: Bearer <USER_TOKEN>`
### 请求参数 (Request)
#### Body Parameters (JSON)
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| question | string | 是 | **用户问题**。 |
| dataset_ids | array<string> | 是 | **知识库 ID 列表**。必须是当前用户有权访问的知识库。 |
**Request Example**:
```json
{
"question": "Summary of report",
"dataset_ids": ["dataset_uuid_internal_1"]
}
```
### 响应参数 (Stream Response)
**Content-Type**: `text/event-stream`
```text
data:{"code": 0, "message": "", "data": {"answer": "Here is the summary:", "reference": {}}}
data:{"code": 0, "message": "", "data": {"answer": " The report indicates...", "reference": {}}}
data:{"code": 0, "message": "", "data": true} // 结束
```
---
## 4. 通用相关问题 - `related_questions`
**接口描述**: **内部/测试用**的通用相关问题推荐接口。根据用户的问题和行业背景,利用 LLM 生成推荐问题。通常用于内部测试台。
**请求方法**: `POST`
**接口地址**: `/api/v1/sessions/related_questions`
**鉴权方式**: Header `Authorization: Bearer <USER_TOKEN>`
### 请求参数 (Request)
#### Body Parameters (JSON)
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| question | string | 是 | - | **原始问题/关键词**。 |
| industry | string | 否 | "" | **行业背景** (e.g., "Finance", "Healthcare")。帮助 LLM 生成更专业的推荐。 |
**Request Example**:
```json
{
"question": "Data privacy",
"industry": "IT"
}
```
### 响应参数 (Response)
**Content-Type**: `application/json`
```json
{
"code": 0,
"message": "success",
"data": [
"GDPR compliance checklist",
"Data encryption standards",
"User consent management"
]
}
```
@@ -0,0 +1,98 @@
# RAGFlow External API Reference (Grouped by File)
## File: `api/apps/sdk/agents.py`
| Function Name | URL Pattern | Notes |
|---|---|---|
| `list_agents` | `/api/v1/agents` | List Agents |
| `create_agent` | `/api/v1/agents` | Create Agent |
| `update_agent` | `/api/v1/agents/<agent_id>` | Update Agent |
| `delete_agent` | `/api/v1/agents/<agent_id>` | Delete Agent |
| `webhook` | `/api/v1/webhook_test/<agent_id>` | Webhook Test |
| `webhook_trace` | `/api/v1/webhook_trace/<agent_id>` | Webhook Trace |
## File: `api/apps/sdk/chat.py`
| Function Name | URL Pattern | Notes |
|---|---|---|
| `create` | `/api/v1/chats` | Create Chat |
| `delete_chats` | `/api/v1/chats` | Delete Chat |
| `list_chat` | `/api/v1/chats` | List Chats |
| `update` | `/api/v1/chats/<chat_id>` | Update Chat |
## File: `api/apps/sdk/dataset.py`
| Function Name | URL Pattern | Notes |
|---|---|---|
| `create` | `/api/v1/datasets` | Create Dataset |
| `delete` | `/api/v1/datasets` | Delete Dataset |
| `list_datasets` | `/api/v1/datasets` | List Datasets |
| `update` | `/api/v1/datasets/<dataset_id>` | Update Dataset |
| `knowledge_graph` | `/api/v1/datasets/<dataset_id>/knowledge_graph` | Knowledge Graph |
| `delete_knowledge_graph` | `/api/v1/datasets/<dataset_id>/knowledge_graph` | Delete Knowledge Graph |
| `run_graphrag` | `/api/v1/datasets/<dataset_id>/run_graphrag` | Run GraphRAG |
| `run_raptor` | `/api/v1/datasets/<dataset_id>/run_raptor` | Run Raptor |
| `trace_graphrag` | `/api/v1/datasets/<dataset_id>/trace_graphrag` | Trace GraphRAG |
| `trace_raptor` | `/api/v1/datasets/<dataset_id>/trace_raptor` | Trace Raptor |
## File: `api/apps/sdk/dify_retrieval.py`
| Function Name | URL Pattern | Notes |
|---|---|---|
| `retrieval` | `/api/v1/dify/retrieval` | Dify Retrieval |
## File: `api/apps/sdk/doc.py`
| Function Name | URL Pattern | Notes |
|---|---|---|
| `parse` | `/api/v1/datasets/<dataset_id>/chunks` | Parse Document Chunks |
| `stop_parsing` | `/api/v1/datasets/<dataset_id>/chunks` | Stop Parsing |
| `upload` | `/api/v1/datasets/<dataset_id>/documents` | Upload Document |
| `list_docs` | `/api/v1/datasets/<dataset_id>/documents` | List Documents |
| `delete` | `/api/v1/datasets/<dataset_id>/documents` | Delete Document |
| `update_doc` | `/api/v1/datasets/<dataset_id>/documents/<document_id>` | Update Document |
| `download` | `/api/v1/datasets/<dataset_id>/documents/<document_id>` | Download Document |
| `list_chunks` | `/api/v1/datasets/<dataset_id>/documents/<document_id>/chunks` | List Chunks |
| `add_chunk` | `/api/v1/datasets/<dataset_id>/documents/<document_id>/chunks` | Add Chunk |
| `update_chunk` | `/api/v1/datasets/<dataset_id>/documents/<document_id>/chunks/<chunk_id>` | Update Chunk |
| `rm_chunk` | `/api/v1/datasets/<dataset_id>/documents/<document_id>/chunks` | Remove Chunk |
| `metadata_summary` | `/api/v1/datasets/<dataset_id>/metadata/summary` | Metadata Summary |
| `metadata_batch_update` | `/api/v1/datasets/<dataset_id>/metadata/update` | Batch Update Metadata |
| `retrieval_test` | `/api/v1/retrieval` | Retrieval Test |
## File: `api/apps/sdk/files.py`
| Function Name | URL Pattern | Notes |
|---|---|---|
| `get_all_parent_folders` | `/api/v1/file/all_parent_folder` | Get All Parent Folders |
| `convert` | `/api/v1/file/convert` | File Convert |
| `create` | `/api/v1/file/create` | File Create |
| `download_attachment` | `/api/v1/file/download/<attachment_id>` | Download Attachment |
| `get` | `/api/v1/file/get/<file_id>` | Get File |
| `list_files` | `/api/v1/file/list` | List Files |
| `move` | `/api/v1/file/mv` | Move File |
| `get_parent_folder` | `/api/v1/file/parent_folder` | Get Parent Folder |
| `rename` | `/api/v1/file/rename` | Rename File |
| `rm` | `/api/v1/file/rm` | Remove File |
| `get_root_folder` | `/api/v1/file/root_folder` | Get Root Folder |
| `upload` | `/api/v1/file/upload` | Upload File |
## File: `api/apps/sdk/session.py`
| Function Name | URL Pattern | Notes |
|---|---|---|
| `agent_bot_completions` | `/api/v1/agentbots/<agent_id>/completions` | Agent Bot completion |
| `begin_inputs` | `/api/v1/agentbots/<agent_id>/inputs` | Get Agent Bot inputs |
| `agent_completions` | `/api/v1/agents/<agent_id>/completions` | Agent completion |
| `create_agent_session` | `/api/v1/agents/<agent_id>/sessions` | Create Agent Session |
| `list_agent_session` | `/api/v1/agents/<agent_id>/sessions` | List Agent Sessions |
| `delete_agent_session` | `/api/v1/agents/<agent_id>/sessions` | Delete Agent Session |
| `agents_completion_openai_compatibility` | `/api/v1/agents_openai/<agent_id>/chat/completions` | OpenAI compatible Agent completion |
| `chatbot_completions` | `/api/v1/chatbots/<dialog_id>/completions` | Chatbot completion |
| `chatbots_inputs` | `/api/v1/chatbots/<dialog_id>/info` | Chatbot info |
| `chat_completion` | `/api/v1/chats/<chat_id>/completions` | Chat completion |
| `create` | `/api/v1/chats/<chat_id>/sessions` | Create Chat Session |
| `list_session` | `/api/v1/chats/<chat_id>/sessions` | List Chat Sessions |
| `delete` | `/api/v1/chats/<chat_id>/sessions` | Delete Chat Session |
| `update` | `/api/v1/chats/<chat_id>/sessions/<session_id>` | Update Chat Session |
| `chat_completion_openai_like` | `/api/v1/chats_openai/<chat_id>/chat/completions` | OpenAI compatible Chat completion |
| `ask_about_embedded` | `/api/v1/searchbots/ask` | Searchbot Ask |
| `detail_share_embedded` | `/api/v1/searchbots/detail` | Searchbot Detail |
| `mindmap` | `/api/v1/searchbots/mindmap` | Searchbot Mindmap |
| `related_questions_embedded` | `/api/v1/searchbots/related_questions` | Searchbot Related Questions |
| `retrieval_test_embedded` | `/api/v1/searchbots/retrieval_test` | Searchbot Retrieval Test |
| `ask_about` | `/api/v1/sessions/ask` | Session Ask |
| `related_questions` | `/api/v1/sessions/related_questions` | Session Related Questions |
@@ -0,0 +1,45 @@
# RAGFlow API 接口文档索引 (Unofficial Detailed Guide)
本文档汇集了 RAGFlow 核心模块的 API 详解。所有文档均遵循 **Zero Omissions (无省略)** 原则,全字段展开并包含中文注释。
## 📚 1. 知识库与文档管理 (Knowledge & Documents)
核心的数据管理模块,负责上传文件、解析文档与建立索引。
- **[知识库管理 (Dataset)](./RAGFlow_Dataset接口详解.md)**
- 涵盖知识库的创建、列表查询、更新、删除等接口。
- **[文档处理 (Document)](./RAGFlow_Document接口详解.md)**
- 涵盖文档的上传 (Upload)、解析配置更新 (Update)、解析状态查询 (Run Status)。
- **切片管理**: 解析后的 Chunk 列表查询、增删改查。
- **检索测试**: 直接对知识库进行召回测试 (Retrieval Test)。
- **[文件管理 (File)](./RAGFlow_File接口详解.md)**
- 类似网盘的文件操作体系。
- **CRUD**: 上传、下载、列表。
- **目录**: 文件夹创建、面包屑导航 (`get_all_parent_folders`)。
- **操作**: 移动、重命名、删除、导入知识库 (`convert`).
## 💬 2. 聊天助手 (Chat Assistant)
RAGFlow 原生的对话助手体系,基于 Assistant (Dialog) 模型。
- **[会话管理 (Chat Session)](./RAGFlow_Chat_Session接口详解.md)**
- 管理 `/chats/` 下的会话生命周期。
- 创建会话、获取历史记录、重命名、批量删除。
- **[对话交互 (Chat Completion)](./RAGFlow_Chat_Completion接口详解.md)**
- **Core Chat**: 原生流式对话 (`/chats/<id>/completions`), 支持引用 (`quote`)。
- **OpenAI Compatible**: 完美兼容 OpenAI `/v1/chat/completions` 协议。
- **Embedded Bot**: 面向 C 端嵌入窗口的对话接口 (`/chatbots/`).
## 🤖 3. Agent 与 机器人 (Agent & Bots)
基于 Graph (DAG) 编排的复杂应用与各类机器人扩展。
- **[Agent 与 Dify 兼容 (Agent & Dify)](./RAGFlow_Agent_Dify接口详解.md)**
- **Agent Session**: Agent 的会话管理与流式对话 (`agent_completions`)。
- **Dify Adapter**: 兼容 Dify 协议的检索接口 (`retrieval`).
- **[SearchBot 与 AgentBot](./RAGFlow_SearchBot_AgentBot接口详解.md)**
- **SearchBot**: 纯搜索机器人,支持思维导图 (`mindmap`)、相关问题 (`related_questions`).
- **AgentBot**: 嵌入式 Agent,支持前置表单 (`begin_inputs`).
- **Agent OpenAI**: Agent 的 OpenAI 兼容接口。
## 🛠️ 4. 其他 (Extras)
- **[通用与补充接口 (Session Extras)](./RAGFlow_Session_Extra接口详解.md)**
- **引用详情**: 获取 SearchBot 引用来源 (`detail_share_embedded`).
- **通用问答**: 内部调试用的直接问答 (`ask_about`).
@@ -0,0 +1,68 @@
package xiaozhi.modules.knowledge.dto;
import java.io.Serializable;
import java.util.Date;
import java.util.Map;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 文档 DTO
*/
@Data
@Schema(description = "知识库文档")
public class DocumentDTO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "本地ID")
private String id;
@Schema(description = "知识库ID")
private String datasetId;
@Schema(description = "RAGFlow文档ID")
private String documentId;
@Schema(description = "文档名称")
private String name;
@Schema(description = "文件大小")
private Long size;
@Schema(description = "文件类型")
private String type;
@Schema(description = "分块方法")
private String chunkMethod;
@Schema(description = "解析配置")
private Map<String, Object> parserConfig;
@Schema(description = "处理状态 (1:解析中 3:成功 4:失败)")
private Integer status;
@Schema(description = "错误信息")
private String error;
@Schema(description = "分块数量")
private Integer chunkCount;
@Schema(description = "Token数量")
private Long tokenCount;
@Schema(description = "是否启用")
private Integer enabled;
@Schema(description = "创建时间")
private Date createdAt;
@Schema(description = "更新时间")
private Date updatedAt;
@Schema(description = "上传进度 (虚拟字段)")
private Double progress;
@Schema(description = "缩略图/预览图 (虚拟字段)")
private String thumbnail;
}
@@ -26,9 +26,30 @@ public class KnowledgeBaseDTO implements Serializable {
@Schema(description = "知识库名称")
private String name;
@Schema(description = "知识库头像(Base64)")
private String avatar;
@Schema(description = "知识库描述")
private String description;
@Schema(description = "嵌入模型名称")
private String embeddingModel;
@Schema(description = "权限设置: me/team")
private String permission;
@Schema(description = "分块方法")
private String chunkMethod;
@Schema(description = "解析器配置(JSON String)")
private String parserConfig;
@Schema(description = "分块总数")
private Long chunkCount;
@Schema(description = "总Token数")
private Long tokenNum;
@Schema(description = "状态(0:禁用 1:启用)")
private Integer status;
@@ -6,10 +6,12 @@ import java.util.Date;
import java.util.Map;
import io.swagger.v3.oas.annotations.media.Schema;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
@Data
@Schema(description = "知识库文档")
@JsonIgnoreProperties(ignoreUnknown = true)
public class KnowledgeFilesDTO implements Serializable {
@Serial
@@ -35,7 +37,19 @@ public class KnowledgeFilesDTO implements Serializable {
@Schema(description = "文件路径")
private String filePath;
@Schema(description = "元数据字段")
@Schema(description = "解析进度 (0.0 ~ 1.0)")
private Double progress;
@Schema(description = "缩略图 (Base64 或 URL)")
private String thumbnail;
@Schema(description = "解析耗时 (单位: 秒)")
private Double processDuration;
@Schema(description = "来源类型 (local, s3, url 等)")
private String sourceType;
@Schema(description = "元数据字段 (Map 格式)")
private Map<String, Object> metaFields;
@Schema(description = "分块方法")
@@ -44,10 +58,10 @@ public class KnowledgeFilesDTO implements Serializable {
@Schema(description = "解析器配置")
private Map<String, Object> parserConfig;
@Schema(description = "状态")
private Integer status;
@Schema(description = "可用状态 (1: 启用/正常, 0: 禁用/失效)")
private String status;
@Schema(description = "文档解析状态")
@Schema(description = "运行状态 (UNSTART/RUNNING/CANCEL/DONE/FAIL)")
private String run;
@Schema(description = "创建者")
@@ -62,6 +76,15 @@ public class KnowledgeFilesDTO implements Serializable {
@Schema(description = "更新时间")
private Date updatedAt;
@Schema(description = "分块数量")
private Integer chunkCount;
@Schema(description = "Token数量")
private Long tokenCount;
@Schema(description = "解析错误信息")
private String error;
// 文档解析状态常量定义
private static final Integer STATUS_UNSTART = 0;
private static final Integer STATUS_RUNNING = 1;
@@ -0,0 +1,421 @@
package xiaozhi.modules.knowledge.dto.agent;
import lombok.*;
import io.swagger.v3.oas.annotations.media.Schema;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.*;
@Schema(description = "智能体 (Agent) 管理聚合 DTO")
public class AgentDTO {
// ========== 1. Agent 管理 (CRUD) - 对应 RAGFlow_Agent接口详解 ==========
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "Agent 创建请求")
public static class CreateReq implements Serializable {
@Schema(description = "Agent 标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "My Agent")
@NotBlank(message = "Agent 标题不能为空")
@JsonProperty("title")
private String title;
@Schema(description = "DSL 定义 (画布 JSON)", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "DSL 定义不能为空")
@JsonProperty("dsl")
private Map<String, Object> dsl;
@Schema(description = "描述", example = "这是一个测试 Agent")
@JsonProperty("description")
private String description;
@Schema(description = "头像 URL", example = "http://example.com/avatar.png")
@JsonProperty("avatar")
private String avatar;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "Agent 更新请求")
public static class UpdateReq implements Serializable {
@Schema(description = "Agent 标题", example = "Updated Agent")
@JsonProperty("title")
private String title;
@Schema(description = "DSL 定义 (画布 JSON)")
@JsonProperty("dsl")
private Map<String, Object> dsl;
@Schema(description = "描述")
@JsonProperty("description")
private String description;
@Schema(description = "头像 URL")
@JsonProperty("avatar")
private String avatar;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "Agent 列表请求")
public static class ListReq implements Serializable {
@Schema(description = "页码", defaultValue = "1")
@JsonProperty("page")
@Builder.Default
private Integer page = 1;
@Schema(description = "每页大小", defaultValue = "10")
@JsonProperty("page_size")
@Builder.Default
private Integer pageSize = 10;
@Schema(description = "排序字段", defaultValue = "update_time")
@JsonProperty("orderby")
@Builder.Default
private String orderby = "update_time";
@Schema(description = "是否降序", defaultValue = "true")
@JsonProperty("desc")
@Builder.Default
private Boolean desc = true;
@Schema(description = "Agent ID 过滤")
@JsonProperty("id")
private String id;
@Schema(description = "标题模糊搜索")
@JsonProperty("title")
private String title;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "Agent 响应对象")
public static class AgentVO implements Serializable {
@Schema(description = "Agent ID")
@JsonProperty("id")
private String id;
@Schema(description = "标题")
@JsonProperty("title")
private String title;
@Schema(description = "描述")
@JsonProperty("description")
private String description;
@Schema(description = "头像")
@JsonProperty("avatar")
private String avatar;
@Schema(description = "DSL 定义")
@JsonProperty("dsl")
private Map<String, Object> dsl;
@Schema(description = "创建者 ID")
@JsonProperty("user_id")
private String userId;
@Schema(description = "画布分类")
@JsonProperty("canvas_category")
private String canvasCategory;
@Schema(description = "创建时间 (时间戳)")
@JsonProperty("create_time")
private Long createTime;
@Schema(description = "更新时间 (时间戳)")
@JsonProperty("update_time")
private Long updateTime;
}
// ========== 2. Webhook 调试与追踪 - 对应 RAGFlow_Agent接口详解 ==========
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "Webhook 触发请求 (参数动态)")
public static class WebhookTriggerReq implements Serializable {
@Schema(description = "输入变量", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "输入变量不能为空")
@JsonProperty("inputs")
private Map<String, Object> inputs;
@Schema(description = "查询词", example = "Hello")
@JsonProperty("query")
private String query;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "Webhook 追踪请求")
public static class WebhookTraceReq implements Serializable {
@Schema(description = "时间戳游标", example = "1700000000.0")
@JsonProperty("since_ts")
private Double sinceTs;
@Schema(description = "Webhook ID")
@JsonProperty("webhook_id")
private String webhookId;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "Webhook 追踪响应")
public static class WebhookTraceVO implements Serializable {
@Schema(description = "Webhook ID")
@JsonProperty("webhook_id")
private String webhookId;
@Schema(description = "是否结束")
@JsonProperty("finished")
private Boolean finished;
@Schema(description = "下一次查询的时间戳游标")
@JsonProperty("next_since_ts")
private Double nextSinceTs;
@Schema(description = "事件列表")
@JsonProperty("events")
private List<TraceEvent> events;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "追踪事件项")
public static class TraceEvent implements Serializable {
@Schema(description = "时间戳")
@JsonProperty("ts")
private Double ts;
@Schema(description = "事件类型")
@JsonProperty("event")
private String event;
@Schema(description = "事件数据")
@JsonProperty("data")
private Object data;
}
}
// ========== 3. Agent 会话 (Session) - 对应 RAGFlow_Agent_Dify接口详解 ==========
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "Session 创建请求")
public static class SessionCreateReq implements Serializable {
@Schema(description = "用户 ID")
@JsonProperty("user_id")
private String userId;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "Session 列表请求")
public static class SessionListReq implements Serializable {
@Schema(description = "页码", defaultValue = "1")
@JsonProperty("page")
@Builder.Default
private Integer page = 1;
@Schema(description = "每页大小", defaultValue = "10")
@JsonProperty("page_size")
@Builder.Default
private Integer pageSize = 10;
@Schema(description = "排序字段", defaultValue = "create_time")
@JsonProperty("orderby")
@Builder.Default
private String orderby = "create_time";
@Schema(description = "是否降序", defaultValue = "true")
@JsonProperty("desc")
@Builder.Default
private Boolean desc = true;
@Schema(description = "Session ID")
@JsonProperty("id")
private String id;
@Schema(description = "用户 ID")
@JsonProperty("user_id")
private String userId;
@Schema(description = "是否返回 DSL")
@JsonProperty("dsl")
@Builder.Default
private Boolean dsl = false;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "Session 批量删除请求")
public static class SessionBatchDeleteReq implements Serializable {
@Schema(description = "会话 ID 列表", requiredMode = Schema.RequiredMode.REQUIRED)
@JsonProperty("ids")
@NotEmpty(message = "ID列表不能为空")
private List<String> ids;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "Session 响应对象")
public static class SessionVO implements Serializable {
@Schema(description = "Session ID")
@JsonProperty("id")
private String id;
@Schema(description = "Agent ID")
@JsonProperty("agent_id")
private String agentId;
@Schema(description = "用户 ID")
@JsonProperty("user_id")
private String userId;
@Schema(description = "来源")
@JsonProperty("source")
private String source;
@Schema(description = "DSL 定义")
@JsonProperty("dsl")
private Map<String, Object> dsl;
@Schema(description = "消息列表")
@JsonProperty("messages")
private List<Map<String, Object>> messages;
}
// ========== 4. Agent 对话 (Completion) - 对应 RAGFlow_Agent_Dify接口详解 ==========
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "Completion 对话请求")
public static class CompletionReq implements Serializable {
@Schema(description = "会话 ID", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "会话 ID 不能为空")
@JsonProperty("session_id")
private String sessionId;
@Schema(description = "用户问题")
@JsonProperty("question")
private String question;
@Schema(description = "是否流式返回", defaultValue = "true")
@JsonProperty("stream")
@Builder.Default
private Boolean stream = true;
@Schema(description = "是否返回追踪信息", defaultValue = "false")
@JsonProperty("return_trace")
@Builder.Default
private Boolean returnTrace = false;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "Completion 对话响应")
public static class CompletionVO implements Serializable {
@Schema(description = "会话 ID")
@JsonProperty("id")
private String id;
@Schema(description = "回复内容")
@JsonProperty("content")
private String content;
@Schema(description = "引用来源")
@JsonProperty("reference")
private Map<String, Object> reference;
@Schema(description = "追踪信息")
@JsonProperty("trace")
private List<Object> trace;
}
// ========== 5. Dify 兼容检索 - 对应 RAGFlow_Agent_Dify接口详解 ==========
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "Dify 兼容检索请求")
public static class DifyRetrievalReq implements Serializable {
@Schema(description = "知识库 ID")
@JsonProperty("knowledge_id")
private String knowledgeId;
@Schema(description = "查询词")
@JsonProperty("query")
private String query;
@Schema(description = "检索设置")
@JsonProperty("retrieval_setting")
private Map<String, Object> retrievalSetting;
@Schema(description = "元数据过滤条件")
@JsonProperty("metadata_condition")
private Map<String, Object> metadataCondition;
@Schema(description = "是否使用知识图谱")
@JsonProperty("use_kg")
private Boolean useKg;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "Dify 兼容检索响应")
public static class DifyRetrievalVO implements Serializable {
@Schema(description = "检索结果列表")
@JsonProperty("records")
private List<Record> records;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "检索记录")
public static class Record implements Serializable {
@Schema(description = "内容")
@JsonProperty("content")
private String content;
@Schema(description = "相似度分数")
@JsonProperty("score")
private Double score;
@Schema(description = "标题")
@JsonProperty("title")
private String title;
@Schema(description = "元数据")
@JsonProperty("metadata")
private Map<String, Object> metadata;
}
}
}
@@ -0,0 +1,126 @@
package xiaozhi.modules.knowledge.dto.bot;
import lombok.*;
import io.swagger.v3.oas.annotations.media.Schema;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.*;
@Schema(description = "外部机器人 (Bot) 聚合 DTO")
public class BotDTO {
// ========== 1. SearchBot (检索机器人) ==========
// 对应 /api/v1/searchbots/ask
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "SearchBot 提问请求")
public static class SearchAskReq implements Serializable {
@Schema(description = "用户问题", requiredMode = Schema.RequiredMode.REQUIRED, example = "What is RAG?")
@NotBlank(message = "问题不能为空")
@JsonProperty("question")
private String question;
@Schema(description = "是否返回引用", defaultValue = "false")
@JsonProperty("quote")
@Builder.Default
private Boolean quote = false;
@Schema(description = "是否流式返回", defaultValue = "true")
@JsonProperty("stream")
@Builder.Default
private Boolean stream = true;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "SearchBot 提问响应")
public static class SearchAskVO implements Serializable {
@Schema(description = "回答内容")
@JsonProperty("answer")
private String answer;
@Schema(description = "引用来源 (Value 结构通常对应 RetrievalDTO.HitVO)")
@JsonProperty("reference")
private Map<String, Object> reference;
}
// 对应 /api/v1/searchbots/related_questions
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "相关问题请求")
public static class RelatedQuestionReq implements Serializable {
@Schema(description = "用户问题", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "问题不能为空")
@JsonProperty("question")
private String question;
}
// 对应 /api/v1/searchbots/mindmap
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "思维导图请求")
public static class MindMapReq implements Serializable {
@Schema(description = "用户问题", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "问题不能为空")
@JsonProperty("question")
private String question;
}
// ========== 2. AgentBot (嵌入式 Agent) ==========
// 对应 /api/v1/agentbots/{id}/inputs
@Data
@Builder
@AllArgsConstructor
@Schema(description = "AgentBot 输入参数请求")
public static class AgentInputsReq implements Serializable {
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "AgentBot 输入参数定义响应")
public static class AgentInputsVO implements Serializable {
@Schema(description = "表单变量定义列表")
@JsonProperty("variables")
private List<Map<String, Object>> variables;
}
// 对应 /api/v1/agentbots/{id}/completions
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "AgentBot 对话请求")
public static class AgentCompletionReq implements Serializable {
@Schema(description = "输入参数值")
@JsonProperty("inputs")
private Map<String, Object> inputs;
@Schema(description = "用户查询", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "查询内容不能为空")
@JsonProperty("question")
private String question;
@Schema(description = "是否流式返回", defaultValue = "true")
@JsonProperty("stream")
@Builder.Default
private Boolean stream = true;
@Schema(description = "会话 ID")
@JsonProperty("session_id")
private String sessionId;
}
}
@@ -0,0 +1,50 @@
package xiaozhi.modules.knowledge.dto.chat;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 聊天对话请求 DTO (OpenAI 兼容格式)
*/
@Data
@Schema(description = "聊天对话请求")
public class ChatCompletionRequest implements Serializable {
@Schema(description = "模型标识 (对应 agent_id 或 bot_id)", requiredMode = Schema.RequiredMode.REQUIRED)
@JsonProperty("model")
private String model;
@Schema(description = "对话消息列表", requiredMode = Schema.RequiredMode.REQUIRED)
@JsonProperty("messages")
private List<Message> messages;
@Schema(description = "是否流式返回", defaultValue = "false")
@JsonProperty("stream")
private Boolean stream = false;
@Schema(description = "温度系数 (0-1)", defaultValue = "0.7")
@JsonProperty("temperature")
private Double temperature;
@Schema(description = "Session ID (可选,用于延续会话)")
@JsonProperty("session_id")
private String sessionId;
@Schema(description = "其他RAGFlow特定参数 (可选)")
private Map<String, Object> extra;
@Data
public static class Message implements Serializable {
@Schema(description = "角色 (system, user, assistant)", requiredMode = Schema.RequiredMode.REQUIRED)
private String role;
@Schema(description = "内容", requiredMode = Schema.RequiredMode.REQUIRED)
private String content;
}
}
@@ -0,0 +1,523 @@
package xiaozhi.modules.knowledge.dto.chat;
import lombok.*;
import io.swagger.v3.oas.annotations.media.Schema;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.*;
/**
* 对话管理聚合 DTO
* <p>
* 容器类,内含对话助手、会话和消息的所有请求/响应对象。
* </p>
*/
@Schema(description = "对话管理聚合 DTO")
public class ChatDTO {
// ========== 1. 对话助手 (Assistant/Bot) 相关 ==========
/**
* 提示词配置
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "提示词配置")
public static class PromptConfig implements Serializable {
@Schema(description = "系统提示词", example = "你是一个专业的客服助手...")
@JsonProperty("prompt")
private String systemPrompt;
@Schema(description = "开场白", example = "您好,我是您的智能助手,请问有什么可以帮您?")
private String opener;
@Schema(description = "空结果回复", example = "抱歉,我没有找到相关信息。")
@JsonProperty("empty_response")
private String emptyResponse;
@Schema(description = "是否展示引用", example = "true")
@JsonProperty("show_quote")
private Boolean quote;
@Schema(description = "是否启用 TTS", example = "false")
private Boolean tts;
@Schema(description = "相似度阈值 (0.0 - 1.0)", example = "0.2")
@JsonProperty("similarity_threshold")
private Float similarityThreshold;
@Schema(description = "关键词相似度权重 (0.0 - 1.0)", example = "0.7")
@JsonProperty("keywords_similarity_weight")
private Float vectorSimilarityWeight;
@Schema(description = "检索 Top N", example = "6")
@JsonProperty("top_n")
private Integer topK;
@Schema(description = "Rerank 模型", example = "rerank_model_001")
@JsonProperty("rerank_model")
private String rerankId;
@Schema(description = "是否启用多轮对话优化", example = "false")
@JsonProperty("refine_multiturn")
private Boolean refineMultigraph;
@Schema(description = "变量列表")
private List<Map<String, Object>> variables;
}
/**
* LLM 配置
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "LLM 模型配置")
public static class LLMConfig implements Serializable {
@NotBlank(message = "模型名称不能为空")
@Schema(description = "模型名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "gpt-4")
@JsonProperty("model_name")
private String modelName;
@Schema(description = "温度参数 (0.0 - 2.0)", example = "0.7")
private Float temperature;
@Schema(description = "Top P 采样", example = "0.9")
@JsonProperty("top_p")
private Float topP;
@Schema(description = "最大 Token 数", example = "4096")
@JsonProperty("max_tokens")
private Integer maxTokens;
@Schema(description = "存在惩罚", example = "0.0")
@JsonProperty("presence_penalty")
private Float presencePenalty;
@Schema(description = "频率惩罚", example = "0.0")
@JsonProperty("frequency_penalty")
private Float frequencyPenalty;
}
/**
* 创建助手请求
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "创建助手请求")
public static class AssistantCreateReq implements Serializable {
@NotBlank(message = "助手名称不能为空")
@Schema(description = "助手名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "智能客服助手")
private String name;
@Schema(description = "助手头像 (Base64 编码)", example = "")
private String avatar;
@Schema(description = "关联的知识库 ID 列表", example = "[\"kb_001\", \"kb_002\"]")
@JsonProperty("dataset_ids")
private List<String> datasetIds;
@Schema(description = "助手描述", example = "这是一个智能客服助手")
private String description;
@Schema(description = "LLM 模型配置")
@JsonProperty("llm")
private LLMConfig llm;
@Schema(description = "提示词配置")
@JsonProperty("prompt")
private PromptConfig promptConfig;
}
/**
* 更新助手请求
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "更新助手请求")
public static class AssistantUpdateReq implements Serializable {
@Schema(description = "助手名称", example = "智能客服助手 V2")
private String name;
@Schema(description = "助手头像 (Base64 编码)", example = "")
private String avatar;
@Schema(description = "关联的知识库 ID 列表", example = "[\"kb_001\", \"kb_002\"]")
@JsonProperty("dataset_ids")
private List<String> datasetIds;
@Schema(description = "助手描述", example = "这是一个智能客服助手")
private String description;
@Schema(description = "LLM 模型配置")
@JsonProperty("llm")
private LLMConfig llm;
@Schema(description = "提示词配置")
@JsonProperty("prompt")
private PromptConfig promptConfig;
}
/**
* 查询助手列表请求
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "查询助手列表请求")
public static class AssistantListReq implements Serializable {
@Schema(description = "页码 (从 1 开始)", example = "1")
private Integer page;
@Schema(description = "每页数量", example = "30")
@JsonProperty("page_size")
private Integer pageSize;
@Schema(description = "按名称过滤 (模糊匹配)", example = "客服")
private String name;
@Schema(description = "排序字段: create_time / update_time", example = "create_time")
private String orderby;
@Schema(description = "是否降序", example = "true")
private Boolean desc;
@Schema(description = "按 ID 精确筛选", example = "assistant_001")
private String id;
}
/**
* 助手详情 VO
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "助手详情 VO")
public static class AssistantVO implements Serializable {
@Schema(description = "助手 ID", example = "assistant_001")
private String id;
@Schema(description = "租户 ID", example = "tenant_001")
@JsonProperty("tenant_id")
private String tenantId;
@Schema(description = "助手名称", example = "智能客服助手")
private String name;
@Schema(description = "助手头像", example = "")
private String avatar;
@Schema(description = "关联的知识库 ID 列表")
@JsonProperty("dataset_ids")
private List<String> datasetIds;
@Schema(description = "关联的知识库列表 (详情)")
private List<SimpleDatasetVO> datasets;
@Schema(description = "助手描述")
private String description;
@Schema(description = "LLM 模型配置")
@JsonProperty("llm")
private LLMConfig llm;
@Schema(description = "提示词配置")
@JsonProperty("prompt")
private PromptConfig promptConfig;
@Schema(description = "创建时间 (时间戳)", example = "1700000000000")
@JsonProperty("create_time")
private Long createTime;
@Schema(description = "更新时间 (时间戳)", example = "1700000001000")
@JsonProperty("update_time")
private Long updateTime;
}
/**
* 删除助手请求
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "删除助手请求")
public static class AssistantDeleteReq implements Serializable {
@Schema(description = "要删除的助手 ID 列表", example = "[\"assistant_001\", \"assistant_002\"]")
private List<String> ids;
}
// ========== 2. 会话 (Session) 相关 ==========
/**
* 创建会话请求
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "创建会话请求")
public static class SessionCreateReq implements Serializable {
@Schema(description = "会话名称", example = "技术咨询会话")
private String name;
@Schema(description = "用户 ID", example = "user_001")
@JsonProperty("user_id")
private String userId;
}
/**
* 更新会话请求
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "更新会话请求")
public static class SessionUpdateReq implements Serializable {
@Schema(description = "会话名称", example = "技术咨询会话 - 更新")
private String name;
}
/**
* 查询会话列表请求
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "查询会话列表请求")
public static class SessionListReq implements Serializable {
@Schema(description = "助手 ID", example = "assistant_001")
@JsonProperty("assistant_id")
private String assistantId;
@Schema(description = "页码 (从 1 开始)", example = "1")
private Integer page;
@Schema(description = "每页数量", example = "30")
@JsonProperty("page_size")
private Integer pageSize;
@Schema(description = "按名称过滤", example = "技术")
private String name;
@Schema(description = "排序字段", example = "create_time")
private String orderby;
@Schema(description = "是否降序", example = "true")
private Boolean desc;
@Schema(description = "会话 ID 精确筛选", example = "session_001")
private String id;
@Schema(description = "用户标识筛选", example = "user_001")
@JsonProperty("user_id")
private String userId;
}
/**
* 会话详情 VO
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "会话详情 VO")
public static class SessionVO implements Serializable {
@Schema(description = "会话 ID", example = "session_001")
private String id;
@Schema(description = "助手 ID", example = "assistant_001")
@JsonProperty("chat_id")
private String chatId;
@Schema(description = "助手 ID (兼容旧版)", example = "assistant_001")
@JsonProperty("assistant_id")
private String assistantId;
@Schema(description = "会话名称", example = "技术咨询会话")
private String name;
@Schema(description = "创建时间 (时间戳)", example = "1700000000000")
@JsonProperty("create_time")
private Long createTime;
@Schema(description = "更新时间 (时间戳)", example = "1700000001000")
@JsonProperty("update_time")
private Long updateTime;
@Schema(description = "创建日期", example = "2024-05-01 10:00:00")
@JsonProperty("create_date")
private String createDate;
@Schema(description = "更新日期", example = "2024-05-01 10:00:00")
@JsonProperty("update_date")
private String updateDate;
@Schema(description = "用户 ID", example = "user_001")
@JsonProperty("user_id")
private String userId;
@Schema(description = "对话历史消息列表")
private List<Map<String, Object>> messages;
}
/**
* 删除会话请求
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "删除会话请求")
public static class SessionDeleteReq implements Serializable {
@Schema(description = "要删除的会话 ID 列表", example = "[\"session_001\", \"session_002\"]")
private List<String> ids;
}
// ========== 3. 消息/对话 (Completion) 相关 ==========
/**
* 发送消息请求
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "发送消息请求")
public static class CompletionReq implements Serializable {
@NotBlank(message = "问题内容不能为空")
@Schema(description = "用户问题", requiredMode = Schema.RequiredMode.REQUIRED, example = "请介绍一下你们的产品")
private String question;
@Schema(description = "是否使用流式响应 (SSE)", example = "true")
@Builder.Default
private Boolean stream = true;
@NotBlank(message = "会话 ID 不能为空")
@Schema(description = "会话 ID (可选,不传则创建新会话)", example = "session_001")
@JsonProperty("session_id")
private String sessionId;
@Schema(description = "是否展示引用", example = "true")
private Boolean quote;
@Schema(description = "指定检索的文档 ID 列表 (逗号分隔)", example = "doc_001,doc_002")
@JsonProperty("doc_ids")
private String docIds;
@Schema(description = "元数据过滤条件")
@JsonProperty("metadata_condition")
private Map<String, Object> metadataCondition;
}
/**
* 消息响应 VO
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "消息响应 VO")
public static class CompletionVO implements Serializable {
@Schema(description = "AI 回答内容")
private String answer;
@Schema(description = "引用信息")
private Reference reference;
@Schema(description = "会话 ID", example = "session_001")
@JsonProperty("session_id")
private String sessionId;
@Schema(description = "任务 ID (用于流式响应追踪)", example = "task_001")
@JsonProperty("task_id")
private String taskId;
/**
* 引用信息 (检索命中结果)
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "引用信息")
public static class Reference implements Serializable {
@Schema(description = "命中的文档块列表")
private List<xiaozhi.modules.knowledge.dto.document.RetrievalDTO.HitVO> chunks;
@Schema(description = "文档聚合信息")
@JsonProperty("doc_aggs")
private List<DocAgg> docAggs;
}
/**
* 文档聚合信息
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "文档聚合信息")
public static class DocAgg implements Serializable {
@Schema(description = "文档 ID", example = "doc_001")
@JsonProperty("doc_id")
private String docId;
@Schema(description = "文档名称", example = "产品手册.pdf")
@JsonProperty("doc_name")
private String docName;
@Schema(description = "命中次数", example = "3")
private Integer count;
}
}
/**
* 简易知识库 VO (用于 Assistant 列表)
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "简易知识库 VO")
public static class SimpleDatasetVO implements Serializable {
@Schema(description = "知识库 ID")
private String id;
@Schema(description = "知识库名称")
private String name;
@Schema(description = "头像")
private String avatar;
@Schema(description = "分块数量")
@JsonProperty("chunk_num")
private Integer chunkNum;
}
}
@@ -0,0 +1,79 @@
package xiaozhi.modules.knowledge.dto.common;
import lombok.*;
import io.swagger.v3.oas.annotations.media.Schema;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.*;
@Schema(description = "通用扩展功能 DTO")
public class CommonDTO {
// ========== 1. 引用详情 (detail_share_embedded) ==========
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "引用详情请求")
public static class ReferenceDetailReq implements Serializable {
@Schema(description = "切片 ID", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "切片 ID 不能为空")
@JsonProperty("chunk_id")
private String chunkId;
@Schema(description = "知识库 ID")
@JsonProperty("knowledge_id")
private String knowledgeId;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "引用详情响应")
public static class ReferenceDetailVO implements Serializable {
@Schema(description = "切片 ID")
@JsonProperty("chunk_id")
private String chunkId;
@Schema(description = "完整内容")
@JsonProperty("content_with_weight")
private String contentWithWeight;
@Schema(description = "文档名称")
@JsonProperty("doc_name")
private String docName;
@Schema(description = "图片 ID 列表")
@JsonProperty("img_id")
private String imageId; // 注意:RAGFlow 有时返回 String 有时返回 List,需根据实际情况确认,暂定 String 用于 ID
@Schema(description = "文档 ID")
@JsonProperty("doc_id")
private String docId;
}
// ========== 2. 通用问答 (ask_about) - 调试用 ==========
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "通用问答请求 (调试用)")
public static class AskAboutReq implements Serializable {
@Schema(description = "用户问题", requiredMode = Schema.RequiredMode.REQUIRED, example = "What is this dataset about?")
@NotBlank(message = "问题不能为空")
@JsonProperty("question")
private String question;
@Schema(description = "数据集 ID 列表", requiredMode = Schema.RequiredMode.REQUIRED)
@NotEmpty(message = "数据集列表不能为空")
@JsonProperty("dataset_ids")
private List<String> datasetIds;
}
// 响应通常复用 String 或者简单的 Map 结构,视具体实现而定,暂不定义专用 VO
}
@@ -0,0 +1,447 @@
package xiaozhi.modules.knowledge.dto.dataset;
import lombok.*;
import io.swagger.v3.oas.annotations.media.Schema;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import jakarta.validation.constraints.*;
/**
* 知识库管理聚合 DTO
* <p>
* 容器类,内含知识库模块所有请求/响应对象的静态内部类定义。
* </p>
*/
@Schema(description = "知识库管理聚合 DTO")
@JsonIgnoreProperties(ignoreUnknown = true)
public class DatasetDTO {
// ========== 通用内部类 ==========
/**
* 解析器配置
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "解析器配置")
@JsonIgnoreProperties(ignoreUnknown = true)
public static class ParserConfig implements Serializable {
@Schema(description = "分块 token 数量", example = "128")
@JsonProperty("chunk_token_num")
private Integer chunkTokenNum;
@Schema(description = "分隔符", example = "\\n!?;。;!?")
private String delimiter;
@Schema(description = "布局识别模型: DeepDOC / Simple", example = "DeepDOC")
@JsonProperty("layout_recognize")
private String layoutRecognize;
@Schema(description = "是否将 Excel 转为 HTML", example = "false")
private Boolean html4excel;
@Schema(description = "自动生成关键词数量 (0 表示关闭)", example = "0")
@JsonProperty("auto_keywords")
private Integer autoKeywords;
@Schema(description = "自动生成问题数量 (0 表示关闭)", example = "0")
@JsonProperty("auto_questions")
private Integer autoQuestions;
}
// ========== 请求类 ==========
/**
* 创建知识库请求 (映射接口 1: create)
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "创建知识库请求")
@JsonIgnoreProperties(ignoreUnknown = true)
public static class CreateReq implements Serializable {
@NotBlank(message = "知识库名称不能为空")
@Schema(description = "知识库名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "my_dataset")
private String name;
@Schema(description = "知识库头像 (Base64 编码)", example = "")
private String avatar;
@Schema(description = "知识库描述", example = "用于存储产品文档")
private String description;
@Schema(description = "嵌入模型名称", example = "BAAI/bge-large-zh-v1.5")
@JsonProperty("embedding_model")
private String embeddingModel;
@Schema(description = "权限设置: me / team", example = "me")
private String permission;
@Schema(description = "分块方法: naive / manual / qa / table / paper / book / laws / presentation / picture / one / knowledge_graph / email", example = "naive")
@JsonProperty("chunk_method")
private String chunkMethod;
@Schema(description = "解析器配置")
@JsonProperty("parser_config")
private ParserConfig parserConfig;
}
/**
* 更新知识库请求 (映射接口 4: update)
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "更新知识库请求")
@JsonIgnoreProperties(ignoreUnknown = true)
public static class UpdateReq implements Serializable {
@Schema(description = "知识库名称", example = "updated_dataset")
private String name;
@Schema(description = "知识库头像 (Base64 编码)", example = "")
private String avatar;
@Schema(description = "知识库描述", example = "更新后的描述")
private String description;
@Schema(description = "权限设置: me / team", example = "team")
private String permission;
@Schema(description = "嵌入模型名称", example = "BAAI/bge-large-zh-v1.5")
@JsonProperty("embedding_model")
private String embeddingModel;
@Schema(description = "分块方法: naive / manual / qa / table / paper / book / laws / presentation / picture / one / knowledge_graph / email", example = "naive")
@JsonProperty("chunk_method")
private String chunkMethod;
@Schema(description = "解析器配置")
@JsonProperty("parser_config")
private ParserConfig parserConfig;
@Schema(description = "PageRank 权重 (0-100)", example = "50")
private Integer pagerank;
}
/**
* 查询知识库列表请求 (映射接口 3: list_datasets)
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "查询知识库列表请求")
@JsonIgnoreProperties(ignoreUnknown = true)
public static class ListReq implements Serializable {
@Schema(description = "页码 (从 1 开始)", example = "1")
private Integer page;
@Schema(description = "每页数量", example = "30")
@JsonProperty("page_size")
private Integer pageSize;
@Schema(description = "排序字段: create_time / update_time", example = "create_time")
private String orderby;
@Schema(description = "是否降序", example = "true")
private Boolean desc;
@Schema(description = "按名称过滤 (模糊匹配)", example = "my_dataset")
private String name;
@Schema(description = "按知识库 ID 过滤", example = "abc123")
private String id;
}
/**
* 批量删除知识库请求 (映射接口 2: delete)
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "批量删除知识库请求")
public static class BatchIdReq implements Serializable {
@NotNull(message = "知识库 ID 列表不能为空")
@Size(min = 1, message = "至少需要一个知识库 ID")
@Schema(description = "知识库 ID 列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "[\"id1\", \"id2\"]")
private List<String> ids;
}
/**
* 运行 GraphRAG 请求
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "运行 GraphRAG 请求")
public static class RunGraphRagReq implements Serializable {
@Schema(description = "实体类型列表", example = "[\"person\", \"organization\"]")
@JsonProperty("entity_types")
private List<String> entityTypes;
@Schema(description = "构建方法: light / fast / full", example = "light")
private String method;
}
/**
* 运行 RAPTOR 请求
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "运行 RAPTOR 请求")
public static class RunRaptorReq implements Serializable {
@Schema(description = "最大聚类数", example = "64")
@JsonProperty("max_cluster")
private Integer maxCluster;
@Schema(description = "自定义提示词", example = "请总结以下内容...")
private String prompt;
}
/**
* 异步任务 ID 响应 VO (映射接口 7/8: run_graphrag/run_raptor)
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "异步任务 ID 响应")
@JsonIgnoreProperties(ignoreUnknown = true)
public static class TaskIdVO implements Serializable {
@Schema(description = "GraphRAG 任务 ID", example = "task_uuid_12345678")
@JsonProperty("graphrag_task_id")
private String graphragTaskId;
@Schema(description = "RAPTOR 任务 ID", example = "task_uuid_87654321")
@JsonProperty("raptor_task_id")
private String raptorTaskId;
}
// ========== 响应类 ==========
/**
* 知识库详情 VO (映射接口 1/3 的返回数据项)
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "知识库详情 VO")
@JsonIgnoreProperties(ignoreUnknown = true)
public static class InfoVO implements Serializable {
@Schema(description = "知识库 ID", example = "abc123")
private String id;
@Schema(description = "知识库名称", example = "my_dataset")
private String name;
@Schema(description = "知识库头像 (Base64 编码)", example = "")
private String avatar;
@Schema(description = "租户 ID", example = "tenant_001")
@JsonProperty("tenant_id")
private String tenantId;
@Schema(description = "知识库描述", example = "用于存储产品文档")
private String description;
@Schema(description = "嵌入模型名称", example = "BAAI/bge-large-zh-v1.5")
@JsonProperty("embedding_model")
private String embeddingModel;
@Schema(description = "权限设置: me / team", example = "me")
private String permission;
@Schema(description = "分块方法", example = "naive")
@JsonProperty("chunk_method")
private String chunkMethod;
@Schema(description = "解析器配置")
@JsonProperty("parser_config")
private ParserConfig parserConfig;
@Schema(description = "分块总数", example = "1024")
@JsonProperty("chunk_count")
private Long chunkCount;
@Schema(description = "文档总数", example = "50")
@JsonProperty("document_count")
private Long documentCount;
@Schema(description = "创建时间 (时间戳)", example = "1700000000000")
@JsonProperty("create_time")
private Long createTime;
@Schema(description = "更新时间 (时间戳)", example = "1700000001000")
@JsonProperty("update_time")
private Long updateTime;
@Schema(description = "总 Token 数", example = "102400")
@JsonProperty("token_num")
private Long tokenNum;
@Schema(description = "创建日期 (格式: yyyy-MM-dd HH:mm:ss)")
@JsonProperty("create_date")
private String createDate;
@Schema(description = "最后更新日期 (格式: yyyy-MM-dd HH:mm:ss)")
@JsonProperty("update_date")
private String updateDate;
}
/**
* 批量操作响应 VO
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "批量操作响应 VO")
public static class BatchOperationVO implements Serializable {
@Schema(description = "成功操作数量", example = "5")
@JsonProperty("success_count")
private Integer successCount;
@Schema(description = "错误列表")
private List<Object> errors;
}
// ========== 知识图谱相关 ==========
/**
* 知识图谱数据 VO (映射接口 5: knowledge_graph)
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "知识图谱数据 VO")
public static class GraphVO implements Serializable {
@Schema(description = "图谱节点列表")
private List<Node> nodes;
@Schema(description = "图谱边列表")
private List<Edge> edges;
@Schema(description = "思维导图数据")
@JsonProperty("mind_map")
private Map<String, Object> mindMap;
/**
* 图谱节点
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "图谱节点")
@JsonIgnoreProperties(ignoreUnknown = true)
public static class Node implements Serializable {
@Schema(description = "节点 ID", example = "node_001")
private String id;
@Schema(description = "节点标签", example = "产品")
private String label;
@Schema(description = "PageRank 值", example = "0.85")
private Double pagerank;
@Schema(description = "节点颜色", example = "#FF5733")
private String color;
@Schema(description = "节点图片 URL", example = "https://example.com/icon.png")
private String img;
}
/**
* 图谱边
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "图谱边")
@JsonIgnoreProperties(ignoreUnknown = true)
public static class Edge implements Serializable {
@Schema(description = "源节点 ID", example = "node_001")
private String source;
@Schema(description = "目标节点 ID", example = "node_002")
private String target;
@Schema(description = "边权重", example = "0.75")
private Double weight;
@Schema(description = "边标签 (关系描述)", example = "属于")
private String label;
}
}
// ========== 异步任务追踪 (GraphRAG/RAPTOR) ==========
/**
* 异步任务追踪 VO (映射接口 9/10: 任务进度返回)
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "异步任务追踪 VO")
@JsonIgnoreProperties(ignoreUnknown = true)
public static class TaskTraceVO implements Serializable {
@Schema(description = "任务 ID", example = "task_001")
private String id;
@Schema(description = "文档 ID", example = "doc_001")
@JsonProperty("doc_id")
private String docId;
@Schema(description = "起始页码", example = "1")
@JsonProperty("from_page")
private Integer fromPage;
@Schema(description = "结束页码", example = "10")
@JsonProperty("to_page")
private Integer toPage;
@Schema(description = "进度百分比 (0.0 - 1.0)", example = "0.75")
private Double progress;
@Schema(description = "进度消息", example = "正在处理第 5 页...")
@JsonProperty("progress_msg")
private String progressMsg;
@Schema(description = "创建时间 (时间戳)", example = "1700000000000")
@JsonProperty("create_time")
private Long createTime;
@Schema(description = "更新时间 (时间戳)", example = "1700000001000")
@JsonProperty("update_time")
private Long updateTime;
}
}
@@ -0,0 +1,182 @@
package xiaozhi.modules.knowledge.dto.document;
import lombok.*;
import io.swagger.v3.oas.annotations.media.Schema;
import java.io.Serializable;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import jakarta.validation.constraints.*;
/**
* 切片管理聚合 DTO
*/
@Schema(description = "切片管理聚合 DTO")
@JsonIgnoreProperties(ignoreUnknown = true)
public class ChunkDTO {
/**
* 新增切片请求参数
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "新增切片请求参数")
@JsonIgnoreProperties(ignoreUnknown = true)
public static class AddReq implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "切片内容", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "切片内容不能为空")
private String content;
@Schema(description = "重要关键词列表")
@JsonProperty("important_keywords")
private List<String> importantKeywords;
@Schema(description = "预设问题列表")
private List<String> questions;
}
/**
* 更新切片请求参数
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "更新切片请求参数")
@JsonIgnoreProperties(ignoreUnknown = true)
public static class UpdateReq implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "新的切片内容")
private String content;
@Schema(description = "更新关键词列表 (覆盖原有列表)")
@JsonProperty("important_keywords")
private List<String> importantKeywords;
@Schema(description = "启用/禁用 (true: 启用, false: 禁用)")
private Boolean available;
}
/**
* 获取切片列表请求参数
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "获取切片列表请求参数")
@JsonIgnoreProperties(ignoreUnknown = true)
public static class ListReq implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "页码 (默认 1)")
private Integer page;
@Schema(description = "每页数量 (默认 30)")
@JsonProperty("page_size")
private Integer pageSize;
@Schema(description = "搜索关键词 (全文检索)")
private String keywords;
@Schema(description = "精确切片 ID")
private String id;
}
/**
* 批量删除切片请求参数
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "批量删除切片请求参数")
@JsonIgnoreProperties(ignoreUnknown = true)
public static class RemoveReq implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "切片 ID 列表", requiredMode = Schema.RequiredMode.REQUIRED)
@JsonProperty("chunk_ids")
@NotEmpty(message = "切片ID列表不能为空")
private List<String> chunkIds;
}
/**
* 文档切片信息 VO
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "文档切片信息")
@JsonIgnoreProperties(ignoreUnknown = true)
public static class InfoVO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "切片 ID (通常为 document_id + 索引)", requiredMode = Schema.RequiredMode.REQUIRED)
private String id;
@Schema(description = "切片文本内容 (全文检索的主要对象)", requiredMode = Schema.RequiredMode.REQUIRED)
private String content;
@Schema(description = "所属文档 ID", requiredMode = Schema.RequiredMode.REQUIRED)
@JsonProperty("document_id")
private String documentId;
@Schema(description = "文档名称 / 关键词")
@JsonProperty("docnm_kwd")
private String docnmKwd;
@Schema(description = "重要关键词列表 (用于关键词增强检索)")
@JsonProperty("important_keywords")
private List<String> importantKeywords;
@Schema(description = "预设问题列表 (用于 Q&A 模式增强)")
private List<String> questions;
@Schema(description = "关联的图片 ID")
@JsonProperty("image_id")
private String imageId;
@Schema(description = "所属知识库 ID")
@JsonProperty("dataset_id")
private String datasetId;
@Schema(description = "切片是否可用 (true: 参与检索, false: 被禁用)")
private Boolean available;
@Schema(description = "切片在原文中的位置索引列表 (RAGFlow返回嵌套数组, 如 [[start, end, filename]])")
private List<List<Object>> positions;
@Schema(description = "Token ID 列表")
@JsonProperty("token")
private List<Integer> token;
}
/**
* 分片列表聚合响应
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "分片列表聚合响应")
@JsonIgnoreProperties(ignoreUnknown = true)
public static class ListVO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "切片信息列表")
private List<InfoVO> chunks;
@Schema(description = "关联的文档详细信息")
private DocumentDTO.InfoVO doc;
@Schema(description = "总记录数")
private Long total;
}
}
@@ -0,0 +1,407 @@
package xiaozhi.modules.knowledge.dto.document;
import lombok.*;
import io.swagger.v3.oas.annotations.media.Schema;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import jakarta.validation.constraints.*;
/**
* 文档管理聚合 DTO
*/
@Schema(description = "文档管理聚合 DTO")
@JsonIgnoreProperties(ignoreUnknown = true)
public class DocumentDTO {
/**
* 上传文档请求参数
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "上传文档请求参数")
@JsonIgnoreProperties(ignoreUnknown = true)
public static class UploadReq implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "知识库 ID (必须指定归属)", requiredMode = Schema.RequiredMode.REQUIRED)
@JsonProperty("dataset_id")
@NotBlank(message = "知识库ID不能为空")
private String datasetId;
@Schema(description = "文件名 (如果指定,则覆盖原始文件名)")
private String name;
@Schema(description = "分块方法")
@JsonProperty("chunk_method")
private DocumentDTO.InfoVO.ChunkMethod chunkMethod;
@Schema(description = "解析参数配置")
@JsonProperty("parser_config")
private DocumentDTO.InfoVO.ParserConfig parserConfig;
@Schema(description = "虚拟文件夹路径 (默认为 /)")
@JsonProperty("parent_path")
private String parentPath;
@Schema(description = "元数据字段")
@JsonProperty("meta")
private Map<String, Object> metaFields;
@Schema(description = "文件二进制流 (支持 PDF, DOCX, TXT, MD 等多种格式)", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "上传文件不能为空")
private org.springframework.web.multipart.MultipartFile file;
}
/**
* 更新文档请求参数
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "更新文档请求参数")
@JsonIgnoreProperties(ignoreUnknown = true)
public static class UpdateReq implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "新文档名称 (必须包含文件后缀,且不能更改原始类型)")
private String name;
@Schema(description = "启用/禁用状态 (true: 启用, false: 禁用; 禁用后不参与检索)")
private Boolean enabled;
@Schema(description = "新解析方法 (修改此项会重置解析状态)")
@JsonProperty("chunk_method")
private InfoVO.ChunkMethod chunkMethod;
@Schema(description = "新解析器详细配置 (应与 chunk_method 配套使用)")
@JsonProperty("parser_config")
private InfoVO.ParserConfig parserConfig;
}
/**
* 获取文档列表请求参数
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "获取文档列表请求参数")
@JsonIgnoreProperties(ignoreUnknown = true)
public static class ListReq implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "页码 (默认: 1)")
private Integer page;
@Schema(description = "每页数量 (默认: 30)")
@JsonProperty("page_size")
private Integer pageSize;
@Schema(description = "排序字段 (可选: create_time, name, size; 默认: create_time)")
private String orderby;
@Schema(description = "是否降序排列 (true: 最新/最大在前; false: 最旧/最小在前; 默认: true)")
private Boolean desc;
@Schema(description = "精确筛选: 文档 ID")
private String id;
@Schema(description = "精确筛选: 文档完整名称 (含后缀)")
private String name;
@Schema(description = "模糊搜索: 文档名称关键词")
private String keywords;
@Schema(description = "筛选: 文件后缀列表 (如 ['pdf', 'docx'])")
private List<String> suffix;
@Schema(description = "筛选: 运行状态列表")
private List<InfoVO.RunStatus> run;
@Schema(description = "筛选: 起始创建时间 (时间戳, 毫秒)")
@JsonProperty("create_time_from")
private Long createTimeFrom;
@Schema(description = "筛选: 结束创建时间 (时间戳, 毫秒)")
@JsonProperty("create_time_to")
private Long createTimeTo;
}
/**
* 批量文档操作请求参数 (用于删除、解析等)
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "批量文档操作请求参数")
@JsonIgnoreProperties(ignoreUnknown = true)
public static class BatchIdReq implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "文档 ID 列表", requiredMode = Schema.RequiredMode.REQUIRED)
@JsonProperty("ids") // 为了兼容性,也可以考虑支持 document_ids,但这里统一叫 ids
@JsonAlias("document_ids")
@NotEmpty(message = "文档ID列表不能为空")
private List<String> ids;
}
/**
* 知识库文档信息 VO
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "知识库文档信息")
@JsonIgnoreProperties(ignoreUnknown = true)
public static class InfoVO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "文档 ID (唯一标识)", requiredMode = Schema.RequiredMode.REQUIRED)
private String id;
@Schema(description = "文档缩略图 URL (Base64 或 链接)")
private String thumbnail;
@Schema(description = "所属知识库 ID", requiredMode = Schema.RequiredMode.REQUIRED)
@JsonProperty("dataset_id")
private String datasetId;
@Schema(description = "文档解析方法 (决定了文档如何被切片)")
@JsonProperty("chunk_method")
private ChunkMethod chunkMethod;
@Schema(description = "关联的 ETL Pipeline ID (如有)")
@JsonProperty("pipeline_id")
private String pipelineId;
@Schema(description = "文档解析器的详细配置")
@JsonProperty("parser_config")
private ParserConfig parserConfig;
@Schema(description = "来源类型 (如 local, s3, url 等)")
@JsonProperty("source_type")
private String sourceType;
@Schema(description = "文档文件类型 (如 pdf, docx, txt)", requiredMode = Schema.RequiredMode.REQUIRED)
private String type;
@Schema(description = "创建者用户 ID")
@JsonProperty("created_by")
private String createdBy;
@Schema(description = "文档名称 (包含扩展名)", requiredMode = Schema.RequiredMode.REQUIRED)
private String name;
@Schema(description = "文件存储路径或位置标识")
private String location;
@Schema(description = "文件大小 (单位: Bytes)")
private Long size;
@Schema(description = "包含的 Token 总数 (解析后统计)")
@JsonProperty("token_count")
private Long tokenCount;
@Schema(description = "包含的切片 (Chunk) 总数")
@JsonProperty("chunk_count")
private Long chunkCount;
@Schema(description = "解析进度 (0.0 ~ 1.0, 1.0 表示完成)")
private Double progress;
@Schema(description = "当前进度描述或错误信息")
@JsonProperty("progress_msg")
private String progressMsg;
@Schema(description = "开始处理的时间戳 (RAGFlow返回RFC1123格式)")
@JsonProperty("process_begin_at")
private String processBeginAt;
@Schema(description = "处理总耗时 (单位: 秒)")
@JsonProperty("process_duration")
private Double processDuration;
@Schema(description = "自定义元数据字段 (Key-Value 键值对)")
@JsonProperty("meta_fields")
private Map<String, Object> metaFields;
@Schema(description = "文件后缀名 (不含点)")
private String suffix;
@Schema(description = "文档解析运行状态")
private RunStatus run;
@Schema(description = "文档可用状态 (1: 启用/正常, 0: 禁用/失效)", requiredMode = Schema.RequiredMode.REQUIRED)
private String status;
@Schema(description = "创建时间 (时间戳, 毫秒)", requiredMode = Schema.RequiredMode.REQUIRED)
@JsonProperty("create_time")
private Long createTime;
@Schema(description = "创建日期 (RAGFlow返回RFC1123格式)")
@JsonProperty("create_date")
private String createDate;
@Schema(description = "最后更新时间 (时间戳, 毫秒)")
@JsonProperty("update_time")
private Long updateTime;
@Schema(description = "最后更新日期 (RAGFlow返回RFC1123格式)")
@JsonProperty("update_date")
private String updateDate;
/**
* 解析方法枚举 (ChunkMethod)
*/
public enum ChunkMethod {
@Schema(description = "通用模式: 适用于大多数纯文本或混合文档")
@JsonProperty("naive")
NAIVE,
@Schema(description = "手动模式: 允许用户手动编辑切片")
@JsonProperty("manual")
MANUAL,
@Schema(description = "问答模式: 专门优化 Q&A 格式的文档")
@JsonProperty("qa")
QA,
@Schema(description = "表格模式: 专门优化 Excel 或 CSV 等表格数据")
@JsonProperty("table")
TABLE,
@Schema(description = "论文模式: 针对学术论文排版优化")
@JsonProperty("paper")
PAPER,
@Schema(description = "书籍模式: 针对书籍章节结构优化")
@JsonProperty("book")
BOOK,
@Schema(description = "法律法规模式: 针对法律条文结构优化")
@JsonProperty("laws")
LAWS,
@Schema(description = "演示文稿模式: 针对 PPT 等演示文件优化")
@JsonProperty("presentation")
PRESENTATION,
@Schema(description = "图片模式: 针对图片内容进行 OCR 和描述")
@JsonProperty("picture")
PICTURE,
@Schema(description = "整体模式: 将整个文档作为一个切片")
@JsonProperty("one")
ONE,
@Schema(description = "知识图谱模式: 提取实体关系构建图谱")
@JsonProperty("knowledge_graph")
KNOWLEDGE_GRAPH,
@Schema(description = "邮件模式: 针对邮件格式优化")
@JsonProperty("email")
EMAIL;
}
/**
* 运行状态枚举 (RunStatus)
*/
public enum RunStatus {
@Schema(description = "未开始: 等待解析队列")
@JsonProperty("UNSTART")
UNSTART,
@Schema(description = "进行中: 正在解析或索引")
@JsonProperty("RUNNING")
RUNNING,
@Schema(description = "已取消: 用户手动取消")
@JsonProperty("CANCEL")
CANCEL,
@Schema(description = "已完成: 解析成功")
@JsonProperty("DONE")
DONE,
@Schema(description = "失败: 解析过程中出错")
@JsonProperty("FAIL")
FAIL;
}
/**
* 布局识别模型枚举
*/
public enum LayoutRecognize {
@Schema(description = "深度文档理解模型: 适合复杂排版")
@JsonProperty("DeepDOC")
DeepDOC,
@Schema(description = "简单规则模型: 适合纯文本")
@JsonProperty("Simple")
Simple;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "文档解析器参数配置")
@JsonIgnoreProperties(ignoreUnknown = true)
public static class ParserConfig implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "切片最大 Token 数 (建议值: 512, 1024, 2048)")
@JsonProperty("chunk_token_num")
private Integer chunkTokenNum;
@Schema(description = "分段分隔符 (支持转义字符, 如 \\n)")
private String delimiter;
@Schema(description = "布局识别模型 (DeepDOC/Simple)")
@JsonProperty("layout_recognize")
private LayoutRecognize layoutRecognize;
@Schema(description = "是否将 Excel 转换为 HTML 表格")
@JsonProperty("html4excel")
private Boolean html4excel;
@Schema(description = "自动提取关键词数量 (0 表示不提取)")
@JsonProperty("auto_keywords")
private Integer autoKeywords;
@Schema(description = "自动生成问题数量 (0 表示不生成)")
@JsonProperty("auto_questions")
private Integer autoQuestions;
@Schema(description = "自动生成标签数量")
@JsonProperty("topn_tags")
private Integer topnTags;
@Schema(description = "RAPTOR 高级索引配置")
private RaptorConfig raptor;
@Schema(description = "GraphRAG 知识图谱配置")
@JsonProperty("graphrag")
private GraphRagConfig graphRag;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "RAPTOR (递归摘要索引) 配置")
@JsonIgnoreProperties(ignoreUnknown = true)
public static class RaptorConfig implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "是否启用 RAPTOR 索引")
@JsonProperty("use_raptor")
private Boolean useRaptor;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "GraphRAG (图增强检索) 配置")
@JsonIgnoreProperties(ignoreUnknown = true)
public static class GraphRagConfig implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "是否启用 GraphRAG 索引")
@JsonProperty("use_graphrag")
private Boolean useGraphRag;
}
}
}
}
@@ -0,0 +1,307 @@
package xiaozhi.modules.knowledge.dto.document;
import lombok.*;
import io.swagger.v3.oas.annotations.media.Schema;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import jakarta.validation.constraints.*;
/**
* 检索与元数据管理聚合 DTO
*/
@Schema(description = "检索与元数据管理聚合 DTO")
@JsonIgnoreProperties(ignoreUnknown = true)
public class RetrievalDTO {
/**
* 文档聚合信息 (VO)
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "文档聚合信息")
@JsonIgnoreProperties(ignoreUnknown = true)
public static class DocAggVO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "文档名称")
@JsonProperty("doc_name")
private String docName;
@Schema(description = "文档 ID")
@JsonProperty("doc_id")
private String docId;
@Schema(description = "数量")
private Integer count;
}
/**
* 检索测试请求参数
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "检索测试请求参数")
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public static class TestReq implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "知识库 ID 列表", requiredMode = Schema.RequiredMode.REQUIRED)
@JsonProperty("dataset_ids")
@NotEmpty(message = "知识库ID列表不能为空")
private List<String> datasetIds;
@Schema(description = "文档 ID 列表 (可选,用于限定检索范围)")
@JsonProperty("document_ids")
private List<String> documentIds;
@Schema(description = "检索问题", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "检索问题不能为空")
private String question;
@Schema(description = "页码 (默认 1)")
private Integer page;
@Schema(description = "每页数量 (默认 10)")
@JsonProperty("page_size")
private Integer pageSize;
@Schema(description = "相似度阈值 (默认 0.2)")
@JsonProperty("similarity_threshold")
private Float similarityThreshold;
@Schema(description = "向量相似度权重 (默认 0.3)")
@JsonProperty("vector_similarity_weight")
private Float vectorSimilarityWeight;
@Schema(description = "返回 Top K 切片 (默认 1024)")
@JsonProperty("top_k")
private Integer topK;
@Schema(description = "重排序模型 ID")
@JsonProperty("rerank_id")
private String rerankId;
@Schema(description = "是否高亮关键词")
private Boolean highlight;
@Schema(description = "是否启用关键词检索")
private Boolean keyword;
@Schema(description = "跨语言翻译列表 (可选)")
@JsonProperty("cross_languages")
private List<String> crossLanguages;
@Schema(description = "元数据过滤条件 (JSON 对象)")
@JsonProperty("metadata_condition")
private Map<String, Object> metadataCondition;
}
/**
* 检索命中结果 (VO)
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "检索命中切片详情")
@JsonIgnoreProperties(ignoreUnknown = true)
public static class HitVO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "切片 ID", requiredMode = Schema.RequiredMode.REQUIRED)
private String id;
@Schema(description = "切片内容", requiredMode = Schema.RequiredMode.REQUIRED)
private String content;
@Schema(description = "所属文档 ID", requiredMode = Schema.RequiredMode.REQUIRED)
@JsonProperty("document_id")
private String documentId;
@Schema(description = "所属知识库 ID")
@JsonProperty("dataset_id")
private String datasetId;
@Schema(description = "文档名称")
@JsonProperty("document_name")
private String documentName;
@Schema(description = "文档关键词")
@JsonProperty("document_keyword")
private String documentKeyword;
@Schema(description = "综合相似度", requiredMode = Schema.RequiredMode.REQUIRED)
private Float similarity;
@Schema(description = "向量相似度")
@JsonProperty("vector_similarity")
private Float vectorSimilarity;
@Schema(description = "关键词相似度")
@JsonProperty("term_similarity")
private Float termSimilarity;
@Schema(description = "索引位置")
private Integer index;
@Schema(description = "高亮内容")
private String highlight;
@Schema(description = "重要关键词列表")
@JsonProperty("important_keywords")
private List<String> importantKeywords;
@Schema(description = "预设问题列表")
private List<String> questions;
@Schema(description = "图片 ID")
@JsonProperty("image_id")
private String imageId;
@Schema(description = "位置索引 (RAGFlow返回嵌套数组, 如 [[start, end, filename]])")
private Object positions;
}
/**
* 知识库元数据摘要 (VO)
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "知识库元数据摘要信息")
@JsonIgnoreProperties(ignoreUnknown = true)
public static class MetaSummaryVO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "文档总数", requiredMode = Schema.RequiredMode.REQUIRED)
@JsonProperty("total_doc_count")
private Long totalDocCount;
@Schema(description = "Token 总数", requiredMode = Schema.RequiredMode.REQUIRED)
@JsonProperty("total_token_count")
private Long totalTokenCount;
@Schema(description = "文件类型分布 (key: 文件后缀, value: 数量)")
@JsonProperty("file_type_distribution")
private Map<String, Long> fileTypeDistribution;
@Schema(description = "文状态分布 (key: 状态码, value: 数量)")
@JsonProperty("status_distribution")
private Map<String, Long> statusDistribution;
@Schema(description = "自定义元数据统计 (key: 字段名, value: 数量/值)")
@JsonProperty("custom_metadata")
private Map<String, Object> customMetadata;
}
/**
* 批量更新元数据请求参数
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "批量更新元数据请求参数")
@JsonIgnoreProperties(ignoreUnknown = true)
public static class MetaBatchReq implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "筛选器: 用于指定要更新的文档范围 (默认全部)")
private Selector selector;
@Schema(description = "新增或更新的元数据列表")
private List<UpdateItem> updates;
@Schema(description = "需要删除的元数据键列表")
private List<DeleteItem> deletes;
/**
* 文档筛选器
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "元数据更新筛选器")
@JsonIgnoreProperties(ignoreUnknown = true)
public static class Selector implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "指定文档 ID 列表")
@JsonProperty("document_ids")
private List<String> documentIds;
@Schema(description = "元数据条件匹配 (key: 字段名, value: 匹配值)")
@JsonProperty("metadata_condition")
private Map<String, Object> metadataCondition;
}
/**
* 更新项
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "元数据更新项")
@JsonIgnoreProperties(ignoreUnknown = true)
public static class UpdateItem implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "元数据键名", requiredMode = Schema.RequiredMode.REQUIRED)
private String key;
@Schema(description = "元数据值", requiredMode = Schema.RequiredMode.REQUIRED)
private Object value;
}
/**
* 删除项
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "元数据删除项")
@JsonIgnoreProperties(ignoreUnknown = true)
public static class DeleteItem implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "需删除的元数据键名", requiredMode = Schema.RequiredMode.REQUIRED)
private String key;
}
}
/**
* 召回测试结果聚合响应
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "召回测试结果聚合响应")
@JsonIgnoreProperties(ignoreUnknown = true)
public static class ResultVO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "检索命中的切片列表")
private List<HitVO> chunks;
@Schema(description = "文档分布统计")
@JsonProperty("doc_aggs")
private List<DocAggVO> docAggs;
@Schema(description = "总命中记录数")
private Long total;
}
}
@@ -0,0 +1,363 @@
package xiaozhi.modules.knowledge.dto.file;
import lombok.*;
import io.swagger.v3.oas.annotations.media.Schema;
import java.io.Serializable;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.*;
import org.springframework.web.multipart.MultipartFile;
/**
* 文件管理聚合 DTO
* <p>
* 容器类,内含文件模块所有请求/响应对象的静态内部类定义。
* </p>
*/
@Schema(description = "文件管理聚合 DTO")
public class FileDTO {
// ========== 请求类 ==========
/**
* 文件上传请求 (对应接口 1: upload)
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "文件上传请求")
public static class UploadReq implements Serializable {
@NotNull(message = "文件不能为空")
@Schema(description = "上传的文件", requiredMode = Schema.RequiredMode.REQUIRED)
private MultipartFile file;
@Schema(description = "父文件夹 ID (为空则上传到根目录)", example = "folder_001")
@JsonProperty("parent_id")
private String parentId;
}
/**
* 新建文件夹请求 (对应接口 2: create)
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "新建文件夹请求")
public static class CreateReq implements Serializable {
@NotBlank(message = "文件夹名称不能为空")
@Schema(description = "文件夹名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "新建文件夹")
private String name;
@Schema(description = "父文件夹 ID (为空则创建在根目录)", example = "folder_001")
@JsonProperty("parent_id")
private String parentId;
@NotBlank(message = "类型不能为空")
@Schema(description = "类型: FOLDER", requiredMode = Schema.RequiredMode.REQUIRED, example = "FOLDER")
@Builder.Default
private String type = "FOLDER";
}
/**
* 重命名请求 (对应接口 6: rename)
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "重命名请求")
public static class RenameReq implements Serializable {
@NotBlank(message = "文件 ID 不能为空")
@Schema(description = "文件/文件夹 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "file_001")
@JsonProperty("file_id")
private String fileId;
@NotBlank(message = "新名称不能为空")
@Schema(description = "新名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "重命名后的文件")
private String name;
}
/**
* 移动请求 (对应接口 7: move)
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "移动请求")
public static class MoveReq implements Serializable {
@NotEmpty(message = "源文件 ID 列表不能为空")
@Schema(description = "源文件/文件夹 ID 列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "[\"file_001\", \"file_002\"]")
@JsonProperty("src_file_ids")
private List<String> srcFileIds;
@NotBlank(message = "目标文件夹 ID 不能为空")
@Schema(description = "目标文件夹 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "folder_002")
@JsonProperty("dest_file_id")
private String destFileId;
}
/**
* 批量删除请求 (对应接口 8: rm)
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "批量删除请求")
public static class RemoveReq implements Serializable {
@NotEmpty(message = "文件 ID 列表不能为空")
@Schema(description = "文件/文件夹 ID 列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "[\"file_001\", \"file_002\"]")
@JsonProperty("file_ids")
private List<String> fileIds;
}
/**
* 导入知识库请求 (对应接口 9: convert)
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "导入知识库请求")
public static class ConvertReq implements Serializable {
@NotEmpty(message = "文件 ID 列表不能为空")
@Schema(description = "文件 ID 列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "[\"file_001\", \"file_002\"]")
@JsonProperty("file_ids")
private List<String> fileIds;
@NotEmpty(message = "知识库 ID 列表不能为空")
@Schema(description = "目标知识库 ID 列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "[\"kb_001\"]")
@JsonProperty("kb_ids")
private List<String> kbIds;
}
/**
* 列表查询请求 (对应接口 3: list_files)
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "列表查询请求")
public static class ListReq implements Serializable {
@Schema(description = "父文件夹 ID (为空则查询根目录)", example = "folder_001")
@JsonProperty("parent_id")
private String parentId;
@Schema(description = "关键词搜索", example = "文档")
private String keywords;
@Schema(description = "页码 (从 1 开始)", example = "1")
private Integer page;
@Schema(description = "每页数量", example = "30")
@JsonProperty("page_size")
private Integer pageSize;
@Schema(description = "排序字段: create_time / update_time / name / size", example = "create_time")
private String orderby;
@Schema(description = "是否降序", example = "true")
private Boolean desc;
}
// ========== 响应类 ==========
/**
* 文件/文件夹基础信息 VO
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "文件/文件夹基础信息")
public static class InfoVO implements Serializable {
@Schema(description = "文件/文件夹 ID", example = "file_001")
private String id;
@Schema(description = "父文件夹 ID", example = "folder_001")
@JsonProperty("parent_id")
private String parentId;
@Schema(description = "租户 ID", example = "tenant_001")
@JsonProperty("tenant_id")
private String tenantId;
@Schema(description = "创建者 ID", example = "user_001")
@JsonProperty("created_by")
private String createdBy;
@Schema(description = "类型: FOLDER / FILE", example = "FOLDER")
private String type;
@Schema(description = "名称", example = "我的文件夹")
private String name;
@Schema(description = "路径位置", example = "/root/folder")
private String location;
@Schema(description = "文件大小 (字节)", example = "1024")
private Long size;
@Schema(description = "来源类型", example = "local")
@JsonProperty("source_type")
private String sourceType;
@Schema(description = "创建时间 (时间戳)", example = "1700000000000")
@JsonProperty("create_time")
private Long createTime;
@Schema(description = "创建日期 (格式化)", example = "2024-01-15 10:30:00")
@JsonProperty("create_date")
private String createDate;
@Schema(description = "更新时间 (时间戳)", example = "1700000001000")
@JsonProperty("update_time")
private Long updateTime;
@Schema(description = "更新日期 (格式化)", example = "2024-01-15 11:00:00")
@JsonProperty("update_date")
private String updateDate;
@Schema(description = "文件扩展名", example = "pdf")
private String extension;
}
/**
* 列表响应 VO (对应接口 3: list_files)
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "文件列表响应")
public static class ListVO implements Serializable {
@Schema(description = "总记录数", example = "100")
private Long total;
@Schema(description = "当前父文件夹信息")
@JsonProperty("parent_folder")
private InfoVO parentFolder;
@Schema(description = "文件/文件夹列表")
private List<InfoVO> files;
@Schema(description = "面包屑导航路径")
private List<InfoVO> breadcrumb;
}
/**
* 转换结果项 VO (对应接口 9: convert)
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "文件转换结果项")
public static class ConvertVO implements Serializable {
@Schema(description = "转换记录 ID", example = "convert_001")
private String id;
@Schema(description = "源文件 ID", example = "file_001")
@JsonProperty("file_id")
private String fileId;
@Schema(description = "目标文档 ID", example = "doc_001")
@JsonProperty("document_id")
private String documentId;
@Schema(description = "创建时间 (时间戳)", example = "1700000000000")
@JsonProperty("create_time")
private Long createTime;
@Schema(description = "创建日期 (格式化)", example = "2024-01-15 10:30:00")
@JsonProperty("create_date")
private String createDate;
@Schema(description = "更新时间 (时间戳)", example = "1700000001000")
@JsonProperty("update_time")
private Long updateTime;
@Schema(description = "更新日期 (格式化)", example = "2024-01-15 11:00:00")
@JsonProperty("update_date")
private String updateDate;
}
/**
* 转换状态 VO (对应接口 10: get_convert_status)
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "文件转换状态")
public static class ConvertStatusVO implements Serializable {
@Schema(description = "转换状态: pending / processing / completed / failed", example = "completed")
private String status;
@Schema(description = "转换进度 (0.0 - 1.0)", example = "1.0")
private Float progress;
@Schema(description = "状态消息", example = "转换完成")
private String message;
}
/**
* 面包屑 VO (对应接口 12: all_parent_folder)
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "面包屑导航 (所有父文件夹)")
public static class BreadcrumbVO implements Serializable {
@Schema(description = "父文件夹列表 (从根到当前的路径)")
@JsonProperty("parent_folders")
private List<InfoVO> parentFolders;
}
/**
* 根目录信息 VO (对应接口 10: get_root_folder)
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "根目录信息")
public static class RootFolderVO implements Serializable {
@Schema(description = "根文件夹信息")
@JsonProperty("root_folder")
private InfoVO rootFolder;
}
/**
* 父目录信息 VO (对应接口 11: get_parent_folder)
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "父目录信息")
public static class ParentFolderVO implements Serializable {
@Schema(description = "父文件夹信息")
@JsonProperty("parent_folder")
private InfoVO parentFolder;
}
}
@@ -0,0 +1,97 @@
package xiaozhi.modules.knowledge.entity;
import java.io.Serializable;
import java.util.Date;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 文档表 (Shadow DB for RAGFlow Documents)
* 对应表名: ai_knowledge_document
*/
@Data
@TableName(value = "ai_rag_knowledge_document", autoResultMap = true)
@Schema(description = "知识库文档表")
public class DocumentEntity implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.ASSIGN_UUID)
@Schema(description = "本地唯一ID")
private String id;
@Schema(description = "知识库ID (关联 ai_rag_dataset.dataset_id)")
private String datasetId;
@Schema(description = "RAGFlow文档ID (远程ID)")
private String documentId;
@Schema(description = "文档名称")
private String name;
@Schema(description = "文件大小(Bytes)")
private Long size;
@Schema(description = "文件类型(pdf/doc/txt等)")
private String type;
@Schema(description = "分块方法")
private String chunkMethod;
@Schema(description = "解析配置(JSON String)")
private String parserConfig;
@Schema(description = "可用状态 (1: 启用/正常, 0: 禁用/失效)")
private String status;
@Schema(description = "运行状态 (UNSTART/RUNNING/CANCEL/DONE/FAIL)")
private String run;
@Schema(description = "解析进度 (0.0 ~ 1.0)")
private Double progress;
@Schema(description = "缩略图 (Base64 或 URL)")
private String thumbnail;
@Schema(description = "解析耗时 (单位: 秒)")
private Double processDuration;
@Schema(description = "自定义元数据 (JSON 格式)")
private String metaFields;
@Schema(description = "来源类型 (local, s3, url 等)")
private String sourceType;
@Schema(description = "解析错误信息")
private String error;
@Schema(description = "分块数量")
private Integer chunkCount;
@Schema(description = "Token数量")
private Long tokenCount;
@Schema(description = "是否启用 (0:禁用 1:启用)")
private Integer enabled;
@Schema(description = "创建者")
@TableField(fill = FieldFill.INSERT)
private Long creator;
@Schema(description = "创建时间")
@TableField(fill = FieldFill.INSERT)
private Date createdAt;
@Schema(description = "更新时间")
@TableField(fill = FieldFill.UPDATE)
private Date updatedAt;
@Schema(description = "最新同步时间")
private Date lastSyncAt;
}
@@ -23,15 +23,43 @@ public class KnowledgeBaseEntity {
@Schema(description = "知识库ID")
private String datasetId;
@Schema(description = "RAG模型配置ID")
// @Deprecated
@Schema(description = "RAG模型配置ID (连接RAGFlow的凭证指针)")
private String ragModelId;
@Schema(description = "租户ID")
private String tenantId;
@Schema(description = "知识库名称")
private String name;
@Schema(description = "知识库头像(Base64)")
private String avatar;
@Schema(description = "知识库描述")
private String description;
@Schema(description = "嵌入模型名称")
private String embeddingModel;
@Schema(description = "权限设置: me/team")
private String permission;
@Schema(description = "分块方法")
private String chunkMethod;
@Schema(description = "解析器配置(JSON String)")
private String parserConfig;
@Schema(description = "分块总数")
private Long chunkCount;
@Schema(description = "文档总数")
private Long documentCount;
@Schema(description = "总Token数")
private Long tokenNum;
@Schema(description = "状态(0:禁用 1:启用)")
private Integer status;
@@ -3,10 +3,14 @@ package xiaozhi.modules.knowledge.rag;
import java.util.List;
import java.util.Map;
import org.springframework.web.multipart.MultipartFile;
import xiaozhi.modules.knowledge.dto.dataset.DatasetDTO;
import xiaozhi.common.page.PageData;
import xiaozhi.modules.knowledge.dto.KnowledgeFilesDTO;
import xiaozhi.modules.knowledge.dto.document.DocumentDTO;
import xiaozhi.modules.knowledge.dto.document.ChunkDTO;
import xiaozhi.modules.knowledge.dto.document.RetrievalDTO;
import java.util.function.Consumer;
/**
* 知识库API适配器抽象基类
@@ -46,35 +50,24 @@ public abstract class KnowledgeBaseAdapter {
* @return 分页数据
*/
public abstract PageData<KnowledgeFilesDTO> getDocumentList(String datasetId,
Map<String, Object> queryParams,
Integer page,
Integer limit);
DocumentDTO.ListReq req);
/**
* 根据文档ID获取文档详情
*
* @param datasetId 知识库ID
* @return 文档详情
* @param datasetId 知识库ID
* @param documentId 文档ID
* @return 文档详情 (强类型 InfoVO)
*/
public abstract KnowledgeFilesDTO getDocumentById(String datasetId, String documentId);
public abstract DocumentDTO.InfoVO getDocumentById(String datasetId, String documentId);
/**
* 上传文档到知识库
*
* @param datasetId 知识库ID
* @param file 上传的文件
* @param name 文档名称
* @param metaFields 元数据字段
* @param chunkMethod 分块方法
* @param parserConfig 解析器配置
* @param req 上传请求参数
* @return 上传的文档信息
*/
public abstract KnowledgeFilesDTO uploadDocument(String datasetId,
MultipartFile file,
String name,
Map<String, Object> metaFields,
String chunkMethod,
Map<String, Object> parserConfig);
public abstract KnowledgeFilesDTO uploadDocument(DocumentDTO.UploadReq req);
/**
* 根据状态分页查询文档列表
@@ -91,12 +84,12 @@ public abstract class KnowledgeBaseAdapter {
Integer limit);
/**
* 删除文档
* 删除文档 (支持批量删除)
*
* @param datasetId 知识库ID
* @param documentId 文档ID
* @param datasetId 知识库ID
* @param req 包含文档ID列表的请求对象
*/
public abstract void deleteDocument(String datasetId, String documentId);
public abstract void deleteDocument(String datasetId, DocumentDTO.BatchIdReq req);
/**
* 解析文档(切块)
@@ -112,32 +105,21 @@ public abstract class KnowledgeBaseAdapter {
*
* @param datasetId 知识库ID
* @param documentId 文档ID
* @param keywords 关键词过滤
* @param page 页码
* @param pageSize 每页数量
* @param chunkId 切片ID
* @return 切片列表信息
* @param req 列表请求参数 (分页、关键词等)
* @return 切片列表VO
*/
public abstract Map<String, Object> listChunks(String datasetId,
public abstract ChunkDTO.ListVO listChunks(String datasetId,
String documentId,
String keywords,
Integer page,
Integer pageSize,
String chunkId);
ChunkDTO.ListReq req);
/**
* 召回测试 - 从知识库中检索相关切片
*
* @param question 用户查询
* @param datasetIds 数据集ID列表
* @param documentIds 文档ID列表
* @param retrievalParams 检索参数
* @param req 检索测试请求参数
* @return 召回测试结果
*/
public abstract Map<String, Object> retrievalTest(String question,
List<String> datasetIds,
List<String> documentIds,
Map<String, Object> retrievalParams);
public abstract RetrievalDTO.ResultVO retrievalTest(
RetrievalDTO.TestReq req);
/**
* 测试连接
@@ -170,25 +152,27 @@ public abstract class KnowledgeBaseAdapter {
/**
* 创建数据集
*
* @param createParams 创建参数
* @return 数据集ID
* @param req 创建参数
* @return 数据集详情
*/
public abstract String createDataset(Map<String, Object> createParams);
public abstract DatasetDTO.InfoVO createDataset(DatasetDTO.CreateReq req);
/**
* 更新数据集
*
* @param datasetId 数据集ID
* @param updateParams 更新参数
* @param datasetId 数据集ID
* @param req 更新参数
* @return 数据集详情
*/
public abstract void updateDataset(String datasetId, Map<String, Object> updateParams);
public abstract DatasetDTO.InfoVO updateDataset(String datasetId, DatasetDTO.UpdateReq req);
/**
* 删除数据集
*
* @param datasetId 数据集ID
* @param req 删除请求参数(包含ID列表)
* @return 批量操作结果
*/
public abstract void deleteDataset(String datasetId);
public abstract DatasetDTO.BatchOperationVO deleteDataset(DatasetDTO.BatchIdReq req);
/**
* 获取数据集的文档数量
@@ -197,4 +181,35 @@ public abstract class KnowledgeBaseAdapter {
* @return 文档数量
*/
public abstract Integer getDocumentCount(String datasetId);
/**
* 发送流式请求 (SSE)
*
* @param endpoint API端点
* @param body 请求体
* @param onData 数据回调
*/
public abstract void postStream(String endpoint, Object body, Consumer<String> onData);
/**
* SearchBot 提问
*
* @param config RAG配置
* @param body 请求体
* @param onData 数据回调
* @return 响应对象
*/
public abstract Object postSearchBotAsk(Map<String, Object> config, Object body,
Consumer<String> onData);
/**
* AgentBot 对话
*
* @param config RAG配置
* @param agentId Agent ID
* @param body 请求体
* @param onData 数据回调
*/
public abstract void postAgentBotCompletion(Map<String, Object> config, String agentId, Object body,
Consumer<String> onData);
}
@@ -22,6 +22,9 @@ public class KnowledgeBaseAdapterFactory {
// 适配器实例缓存
private static final Map<String, KnowledgeBaseAdapter> adapterCache = new ConcurrentHashMap<>();
// 最大缓存实例数,防止内存泄露 (Issue 9)
private static final int MAX_CACHE_SIZE = 50;
static {
// 注册内置适配器类型
registerAdapter("ragflow", xiaozhi.modules.knowledge.rag.impl.RAGFlowAdapter.class);
@@ -61,7 +64,13 @@ public class KnowledgeBaseAdapterFactory {
// 创建新的适配器实例
KnowledgeBaseAdapter adapter = createAdapter(adapterType, config);
// 缓存适配器实例
// 缓存适配器实例 (带容量限制检查)
if (adapterCache.size() >= MAX_CACHE_SIZE) {
log.warn("适配器缓存已达上限 ({}),执行内存保护性清除", MAX_CACHE_SIZE);
// 简单处理:直接清空,生产环境下建议使用 LRU
adapterCache.clear();
}
adapterCache.put(cacheKey, adapter);
log.info("创建并缓存适配器实例: {}", cacheKey);
@@ -0,0 +1,278 @@
package xiaozhi.modules.knowledge.rag;
import java.time.Duration;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Map;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import java.text.SimpleDateFormat;
import java.util.TimeZone;
import java.util.Locale;
import java.net.URLEncoder;
import java.io.UnsupportedEncodingException;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.URI;
import java.io.OutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.function.Consumer;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import xiaozhi.common.exception.ErrorCode;
import xiaozhi.common.exception.RenException;
/**
* RAGFlow HTTP Client
* 统一处理HTTP通信、鉴权、超时与错误解析
*/
@Slf4j
public class RAGFlowClient {
private final String baseUrl;
private final String apiKey;
private final RestTemplate restTemplate;
private final ObjectMapper objectMapper;
// 默认超时时间 (秒)
private static final int DEFAULT_TIMEOUT = 30;
public RAGFlowClient(String baseUrl, String apiKey) {
this(baseUrl, apiKey, DEFAULT_TIMEOUT);
}
public RAGFlowClient(String baseUrl, String apiKey, int timeoutSeconds) {
this.baseUrl = baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl;
this.apiKey = apiKey;
this.objectMapper = new ObjectMapper();
// [Reinforce] 兼容 RAGFlow 返回的 RFC 1123 日期格式 (如: Tue, 10 Feb 2026 10:27:35 GMT)
this.objectMapper
.setDateFormat(new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US));
this.objectMapper.setTimeZone(TimeZone.getTimeZone("GMT"));
// 优先从 Spring 上下文中获取池化的 RestTemplate Bean (Issue 3: 连接池化)
RestTemplate pooledTemplate = null;
try {
pooledTemplate = xiaozhi.common.utils.SpringContextUtils.getBean(RestTemplate.class);
} catch (Exception e) {
log.warn("无法从 SpringContext 获取池化 RestTemplate,将退化为简单连接模式: {}", e.getMessage());
}
if (false) { // Force new RestTemplate for debugging
this.restTemplate = pooledTemplate;
log.debug("RAGFlowClient 已成功挂载全局池化 RestTemplate");
} else {
// 兜底方案:配置超时并创建简单 RestTemplate
log.info("RAGFlowClient 初始化: 使用独立 RestTemplate (Debug Mode)");
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setConnectTimeout(timeoutSeconds * 1000);
factory.setReadTimeout(timeoutSeconds * 1000);
this.restTemplate = new RestTemplate(factory);
}
}
/**
* 发送 GET 请求
*/
public Map<String, Object> get(String endpoint, Map<String, Object> queryParams) {
String url = buildUrl(endpoint, queryParams);
log.debug("GET {}", url);
return execute(url, HttpMethod.GET, null);
}
/**
* 发送 POST 请求 (JSON)
*/
public Map<String, Object> post(String endpoint, Object body) {
String url = buildUrl(endpoint, null);
log.info("RAGFlow Client POST Request: URL={}, BodyType={}", url,
body != null ? body.getClass().getName() : "null");
try {
return execute(url, HttpMethod.POST, body);
} catch (Exception e) {
log.error("RAGFlow Client POST Failed: URL={}", url, e);
throw e;
}
}
/**
* 发送 DELETE 请求
*/
public Map<String, Object> delete(String endpoint, Object body) {
String url = buildUrl(endpoint, null);
log.debug("DELETE {}", url);
return execute(url, HttpMethod.DELETE, body);
}
/**
* 发送 PUT 请求
*/
public Map<String, Object> put(String endpoint, Object body) {
String url = buildUrl(endpoint, null);
log.debug("PUT {}", url);
return execute(url, HttpMethod.PUT, body);
}
/**
* 发送 Multipart 请求 (文件上传)
*/
public Map<String, Object> postMultipart(String endpoint, MultiValueMap<String, Object> parts) {
String url = buildUrl(endpoint, null);
log.debug("POST MULTIPART {}", url);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
headers.setBearerAuth(apiKey);
// 为了防止中文文件名乱码,某些环境可能需要设置 Charset,但在 Multipart 中通常由 Part header 控制
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(parts, headers);
return doExecute(url, HttpMethod.POST, requestEntity);
}
private Map<String, Object> execute(String url, HttpMethod method, Object body) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setBearerAuth(apiKey);
// 强制 UTF-8
headers.setAcceptCharset(Collections.singletonList(StandardCharsets.UTF_8));
HttpEntity<Object> requestEntity = new HttpEntity<>(body, headers);
return doExecute(url, method, requestEntity);
}
private Map<String, Object> doExecute(String url, HttpMethod method, HttpEntity<?> requestEntity) {
try {
ResponseEntity<String> response = restTemplate.exchange(url, method, requestEntity, String.class);
if (!response.getStatusCode().is2xxSuccessful()) {
log.error("RAGFlow API Error Status: {}", response.getStatusCode());
throw new RenException(ErrorCode.RAG_API_ERROR, "HTTP " + response.getStatusCode());
}
String responseBody = response.getBody();
if (responseBody == null) {
throw new RenException(ErrorCode.RAG_API_ERROR, "Empty Response");
}
@SuppressWarnings("unchecked")
Map<String, Object> map = objectMapper.readValue(responseBody, Map.class);
Integer code = (Integer) map.get("code");
if (code != null && code != 0) {
String msg = (String) map.get("message");
log.error("RAGFlow Business Error: code={}, msg={}", code, msg);
throw new RenException(ErrorCode.RAG_API_ERROR, msg != null ? msg : "Unknown RAGFlow Error");
}
// 返回 data 字段,如果 data 不存在则返回整个 map (视具体情况,通常 RAGFlow 返回 code=0, data=...)
// 兼容性处理:如果 external caller 需要 check code,这里已经 check 过了。
// 统一返回 wrap 了 code 的 map 还是只返回 data?
// 根据分析报告,旧逻辑 check code==0 后取 data.
// 这里我们返回整个 Map,让 Adapter 决定怎么取,或者我们直接在这里剥离?
// 建议:为了灵活性,返回全量 Map,但在 Client 层做 code!=0 的抛错。
return map;
} catch (RenException re) {
throw re;
} catch (Exception e) {
log.error("RAGFlow Client Execute Error! URL: {}, Method: {}, Body Type: {}", url, method,
requestEntity.getBody() != null ? requestEntity.getBody().getClass().getName() : "null");
log.error("Full exception stack trace: ", e);
throw new RenException(ErrorCode.RAG_API_ERROR, "Request Failed: " + e.getMessage());
}
}
private String buildUrl(String endpoint, Map<String, Object> queryParams) {
StringBuilder sb = new StringBuilder(baseUrl);
if (!endpoint.startsWith("/")) {
sb.append("/");
}
sb.append(endpoint);
if (queryParams != null && !queryParams.isEmpty()) {
sb.append("?");
queryParams.forEach((k, v) -> {
if (v != null) {
try {
sb.append(k).append("=")
.append(URLEncoder.encode(v.toString(),
StandardCharsets.UTF_8.name()))
.append("&");
} catch (UnsupportedEncodingException e) {
log.warn("参数编码失败: k={}, v={}", k, v);
sb.append(k).append("=").append(v).append("&");
}
}
});
// 移除最后一个 &
sb.setLength(sb.length() - 1);
}
return sb.toString();
}
/**
* 发送流式 POST 请求 (SSE)
* 使用 Java 21 HttpClient 实现
*
* @param endpoint API端点
* @param body 请求体
* @param onData 数据回调(每收到一行数据调用一次)
*/
public void postStream(String endpoint, Object body, Consumer<String> onData) {
try {
String url = buildUrl(endpoint, null);
log.debug("POST STREAM {}", url);
String jsonBody = objectMapper.writeValueAsString(body);
HttpClient httpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(DEFAULT_TIMEOUT))
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Content-Type", "application/json")
.header("Authorization", "Bearer " + apiKey)
.POST(HttpRequest.BodyPublishers.ofString(jsonBody, StandardCharsets.UTF_8))
.build();
// 发送请求并处理流式响应
httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream())
.body()
.transferTo(new OutputStream() {
private final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
@Override
public void write(int b) throws IOException {
if (b == '\n') {
String line = buffer.toString(StandardCharsets.UTF_8);
if (!line.trim().isEmpty()) {
onData.accept(line);
}
buffer.reset();
} else {
buffer.write(b);
}
}
});
} catch (Exception e) {
log.error("RAGFlow Stream Request Error", e);
throw new RenException(ErrorCode.RAG_API_ERROR, "Stream Request Failed: " + e.getMessage());
}
}
}
File diff suppressed because it is too large Load Diff
@@ -93,4 +93,14 @@ public interface KnowledgeBaseService extends BaseService<KnowledgeBaseEntity> {
* @return RAG模型列表
*/
List<ModelConfigEntity> getRAGModels();
/**
* 更新知识库统计信息 (用于被文件服务回调)
*
* @param datasetId 知识库ID
* @param docDelta 文档数增量
* @param chunkDelta 分块数增量
* @param tokenDelta Token数增量
*/
void updateStatistics(String datasetId, Integer docDelta, Long chunkDelta, Long tokenDelta);
}
@@ -7,6 +7,9 @@ import org.springframework.web.multipart.MultipartFile;
import xiaozhi.common.page.PageData;
import xiaozhi.modules.knowledge.dto.KnowledgeFilesDTO;
import xiaozhi.modules.knowledge.dto.document.ChunkDTO;
import xiaozhi.modules.knowledge.dto.document.RetrievalDTO;
import xiaozhi.modules.knowledge.dto.document.DocumentDTO;
/**
* 知识库文档服务接口
@@ -28,9 +31,9 @@ public interface KnowledgeFilesService {
*
* @param documentId 文档ID
* @param datasetId 知识库ID
* @return 文档详情
* @return 文档详情 (强类型 InfoVO)
*/
KnowledgeFilesDTO getByDocumentId(String documentId, String datasetId);
DocumentDTO.InfoVO getByDocumentId(String documentId, String datasetId);
/**
* 上传文档到知识库
@@ -48,12 +51,12 @@ public interface KnowledgeFilesService {
Map<String, Object> parserConfig);
/**
* 根据文档ID和知识库ID删除文档
* 批量删除文档
*
* @param documentId 文档ID
* @param datasetId 知识库ID
* @param datasetId 知识库ID
* @param req 删除请求参数 (含文档ID列表)
*/
void deleteByDocumentId(String documentId, String datasetId);
void deleteDocuments(String datasetId, DocumentDTO.BatchIdReq req);
/**
* 获取RAG配置信息
@@ -77,36 +80,44 @@ public interface KnowledgeFilesService {
*
* @param datasetId 知识库ID
* @param documentId 文档ID
* @param keywords 关键词过滤
* @param page 页码
* @param pageSize 每页数量
* @param chunkId 切片ID
* @param req 切片列表请求参数
* @return 切片列表信息
*/
Map<String, Object> listChunks(String datasetId, String documentId, String keywords,
Integer page, Integer pageSize, String chunkId);
ChunkDTO.ListVO listChunks(String datasetId, String documentId, ChunkDTO.ListReq req);
/**
* 召回测试 - 从指定数据集或文档中检索相关切片
* 召回测试
*
* @param question 用户查询或查询关键词
* @param datasetIds 数据集ID列表
* @param documentIds 文档ID列表
* @param page 页码
* @param pageSize 每页数量
* @param similarityThreshold 最小相似度阈值
* @param vectorSimilarityWeight 向量相似度权重
* @param topK 参与向量余弦计算的切片数量
* @param rerankId 重排模型ID
* @param keyword 是否启用关键词匹配
* @param highlight 是否启用高亮显示
* @param crossLanguages 跨语言翻译列表
* @param metadataCondition 元数据过滤条件
* @param req 检索测试请求参数
* @return 召回测试结果
*/
Map<String, Object> retrievalTest(String question, List<String> datasetIds, List<String> documentIds,
Integer page, Integer pageSize, Float similarityThreshold,
Float vectorSimilarityWeight, Integer topK, String rerankId,
Boolean keyword, Boolean highlight, List<String> crossLanguages,
Map<String, Object> metadataCondition);
RetrievalDTO.ResultVO retrievalTest(RetrievalDTO.TestReq req);
/**
* 保存文档影子记录
*/
void saveDocumentShadow(String datasetId, KnowledgeFilesDTO result, String originalName, String chunkMethod,
Map<String, Object> parserConfig);
/**
* 批量删除文档影子记录并同步统计数据
*
* @param documentIds 文档ID列表
* @param datasetId 数据集ID
* @param chunkDelta 待扣减的总分块数
* @param tokenDelta 待扣减的总Token数
*/
void deleteDocumentShadows(List<String> documentIds, String datasetId, Long chunkDelta, Long tokenDelta);
/**
* 根据数据集ID清理所有关联文档 (级联删除专用)
*
* @param datasetId 数据集ID
*/
void deleteDocumentsByDatasetId(String datasetId);
/**
* 同步所有处于 RUNNING 状态的文档 (供定时任务调用)
*/
void syncRunningDocuments();
}
@@ -0,0 +1,24 @@
package xiaozhi.modules.knowledge.service;
import java.util.List;
/**
* 知识库模块领域编排服务
* 用于处理跨 KnowledgeBase 和 KnowledgeFiles 的复杂业务流程,彻底解决 Service 间的循环依赖问题。
*/
public interface KnowledgeManagerService {
/**
* 级联删除知识库及其下属所有文档 (包括本地 DB 和 RAGFlow 远程数据)
*
* @param datasetId 知识库 ID
*/
void deleteDatasetWithFiles(String datasetId);
/**
* 批量级联删除知识库
*
* @param datasetIds 知识库 ID 列表
*/
void batchDeleteDatasetsWithFiles(List<String> datasetIds);
}
@@ -1,19 +1,14 @@
package xiaozhi.modules.knowledge.service.impl;
import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.beans.BeanUtils;
import xiaozhi.common.constant.Constant;
import xiaozhi.common.exception.ErrorCode;
import xiaozhi.common.exception.RenException;
@@ -22,10 +17,10 @@ import xiaozhi.common.redis.RedisKeys;
import xiaozhi.common.redis.RedisUtils;
import xiaozhi.common.service.impl.BaseServiceImpl;
import xiaozhi.common.utils.ConvertUtils;
import xiaozhi.common.utils.MessageUtils;
import xiaozhi.common.utils.ToolUtil;
import xiaozhi.common.utils.JsonUtils;
import xiaozhi.modules.knowledge.dao.KnowledgeBaseDao;
import xiaozhi.modules.knowledge.dto.KnowledgeBaseDTO;
import xiaozhi.modules.knowledge.dto.dataset.DatasetDTO;
import xiaozhi.modules.knowledge.entity.KnowledgeBaseEntity;
import xiaozhi.modules.knowledge.rag.KnowledgeBaseAdapter;
import xiaozhi.modules.knowledge.rag.KnowledgeBaseAdapterFactory;
@@ -35,6 +30,15 @@ import xiaozhi.modules.model.entity.ModelConfigEntity;
import xiaozhi.modules.model.service.ModelConfigService;
import xiaozhi.modules.security.user.SecurityUser;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 知识库服务实现类 (Refactored)
* 集成 RAGFlow Adapter 与 Shadow DB 模式
*/
@Service
@AllArgsConstructor
@Slf4j
@@ -46,200 +50,50 @@ public class KnowledgeBaseServiceImpl extends BaseServiceImpl<KnowledgeBaseDao,
private final ModelConfigDao modelConfigDao;
private final RedisUtils redisUtils;
@Override
public KnowledgeBaseEntity selectById(Serializable datasetId) {
if (datasetId == null) {
return null;
}
// 先从Redis获取缓存
String key = RedisKeys.getKnowledgeBaseCacheKey(datasetId.toString());
KnowledgeBaseEntity cachedEntity = (KnowledgeBaseEntity) redisUtils.get(key);
if (cachedEntity != null) {
return cachedEntity;
}
// 如果缓存中没有,则从数据库获取
KnowledgeBaseEntity entity = knowledgeBaseDao.selectById(datasetId);
if (entity == null) {
return null;
}
// 存入Redis缓存
redisUtils.set(key, entity);
return entity;
}
@Override
public PageData<KnowledgeBaseDTO> getPageList(KnowledgeBaseDTO knowledgeBaseDTO, Integer page, Integer limit) {
long curPage = page;
long pageSize = limit;
Page<KnowledgeBaseEntity> pageInfo = new Page<>(curPage, pageSize);
Page<KnowledgeBaseEntity> pageInfo = new Page<>(page, limit);
QueryWrapper<KnowledgeBaseEntity> queryWrapper = new QueryWrapper<>();
// 添加查询条件
if (knowledgeBaseDTO != null) {
queryWrapper.like(StringUtils.isNotBlank(knowledgeBaseDTO.getName()), "name", knowledgeBaseDTO.getName());
queryWrapper.eq(knowledgeBaseDTO.getStatus() != null, "status", knowledgeBaseDTO.getStatus());
queryWrapper.eq("creator", knowledgeBaseDTO.getCreator());
}
// 添加排序规则:按创建时间降序
queryWrapper.orderByDesc("created_at");
IPage<KnowledgeBaseEntity> knowledgeBaseEntityIPage = knowledgeBaseDao.selectPage(pageInfo, queryWrapper);
IPage<KnowledgeBaseEntity> iPage = knowledgeBaseDao.selectPage(pageInfo, queryWrapper);
PageData<KnowledgeBaseDTO> pageData = getPageData(iPage, KnowledgeBaseDTO.class);
// 获取分页数据
PageData<KnowledgeBaseDTO> pageData = getPageData(knowledgeBaseEntityIPage, KnowledgeBaseDTO.class);
// 为每个知识库获取文档数量
// Enrich with Document Count from RAG (Optional / Lazy)
if (pageData != null && pageData.getList() != null) {
for (KnowledgeBaseDTO knowledgeBase : pageData.getList()) {
try {
Integer documentCount = getDocumentCountFromRAG(knowledgeBase.getDatasetId(),
knowledgeBase.getRagModelId());
knowledgeBase.setDocumentCount(documentCount);
} catch (Exception e) {
// 构建详细的错误信息,包含异常类型和消息
String baseErrorMessage = e.getClass().getSimpleName() + " - 获取知识库文档数量失败";
String errorMessage = baseErrorMessage + (e.getMessage() != null ? ": " + e.getMessage() : "");
log.warn("知识库 {} {}", knowledgeBase.getDatasetId(), errorMessage);
knowledgeBase.setDocumentCount(0); // 设置默认值
}
for (KnowledgeBaseDTO dto : pageData.getList()) {
enrichDocumentCount(dto);
}
}
return pageData;
}
private void enrichDocumentCount(KnowledgeBaseDTO dto) {
try {
if (StringUtils.isNotBlank(dto.getDatasetId()) && StringUtils.isNotBlank(dto.getRagModelId())) {
KnowledgeBaseAdapter adapter = getAdapterByModelId(dto.getRagModelId());
if (adapter != null) {
dto.setDocumentCount(adapter.getDocumentCount(dto.getDatasetId()));
}
}
} catch (Exception e) {
log.warn("无法获取知识库 {} 的文档计数: {}", dto.getName(), e.getMessage());
dto.setDocumentCount(0);
}
}
@Override
public KnowledgeBaseDTO getById(String id) {
if (StringUtils.isBlank(id)) {
throw new RenException(ErrorCode.IDENTIFIER_NOT_NULL);
}
KnowledgeBaseEntity entity = knowledgeBaseDao.selectById(id);
if (entity == null) {
throw new RenException(ErrorCode.Knowledge_Base_RECORD_NOT_EXISTS);
}
return ConvertUtils.sourceToTarget(entity, KnowledgeBaseDTO.class);
}
@Override
public KnowledgeBaseDTO save(KnowledgeBaseDTO knowledgeBaseDTO) {
if (knowledgeBaseDTO == null) {
throw new RenException(ErrorCode.PARAMS_GET_ERROR);
}
// 检查是否存在同名知识库
checkDuplicateKnowledgeBaseName(knowledgeBaseDTO, null);
String datasetId = null;
// 调用RAG API创建数据集
try {
Map<String, Object> ragConfig = getValidatedRAGConfig(knowledgeBaseDTO.getRagModelId());
datasetId = createDatasetInRAG(
knowledgeBaseDTO.getName(),
knowledgeBaseDTO.getDescription(),
ragConfig);
} catch (Exception e) {
// 如果RAG API调用失败,直接抛出异常
throw e;
}
// 验证数据集ID是否已存在
KnowledgeBaseEntity existingEntity = knowledgeBaseDao.selectOne(
new QueryWrapper<KnowledgeBaseEntity>().eq("dataset_id", datasetId));
if (existingEntity != null) {
// 如果datasetId已存在,删除RAG中的数据集并抛出异常
try {
Map<String, Object> ragConfig = getValidatedRAGConfig(knowledgeBaseDTO.getRagModelId());
deleteDatasetInRAG(datasetId, ragConfig);
} catch (Exception deleteException) {
// 提供更详细的错误信息,包括异常类型和消息
String errorMessage = "删除重复datasetId的RAG数据集失败: " + deleteException.getClass().getSimpleName();
if (deleteException.getMessage() != null) {
errorMessage += " - " + deleteException.getMessage();
}
log.warn(errorMessage, deleteException);
}
throw new RenException(ErrorCode.DB_RECORD_EXISTS);
}
// 创建本地实体并保存
KnowledgeBaseEntity entity = ConvertUtils.sourceToTarget(knowledgeBaseDTO, KnowledgeBaseEntity.class);
entity.setDatasetId(datasetId);
knowledgeBaseDao.insert(entity);
return ConvertUtils.sourceToTarget(entity, KnowledgeBaseDTO.class);
}
@Override
public KnowledgeBaseDTO update(KnowledgeBaseDTO knowledgeBaseDTO) {
if (knowledgeBaseDTO == null || StringUtils.isBlank(knowledgeBaseDTO.getId())) {
throw new RenException(ErrorCode.IDENTIFIER_NOT_NULL);
}
// 检查记录是否存在
KnowledgeBaseEntity existingEntity = knowledgeBaseDao.selectById(knowledgeBaseDTO.getId());
if (existingEntity == null) {
throw new RenException(ErrorCode.Knowledge_Base_RECORD_NOT_EXISTS);
}
// 检查是否存在同名知识库(排除当前记录)
checkDuplicateKnowledgeBaseName(knowledgeBaseDTO, knowledgeBaseDTO.getId());
// 验证数据集ID是否与其他记录冲突
if (StringUtils.isNotBlank(knowledgeBaseDTO.getDatasetId())) {
KnowledgeBaseEntity conflictEntity = knowledgeBaseDao.selectOne(
new QueryWrapper<KnowledgeBaseEntity>()
.eq("dataset_id", knowledgeBaseDTO.getDatasetId())
.ne("id", knowledgeBaseDTO.getId()));
if (conflictEntity != null) {
throw new RenException(ErrorCode.DB_RECORD_EXISTS);
}
}
boolean needRagValidation = StringUtils.isNotBlank(knowledgeBaseDTO.getDatasetId())
&& StringUtils.isNotBlank(knowledgeBaseDTO.getRagModelId());
if (needRagValidation) {
try {
// 先校验RAG配置
Map<String, Object> ragConfig = getValidatedRAGConfig(knowledgeBaseDTO.getRagModelId());
// 调用RAG API更新数据集
updateDatasetInRAG(
knowledgeBaseDTO.getDatasetId(),
knowledgeBaseDTO.getName(),
knowledgeBaseDTO.getDescription(),
ragConfig);
log.info("RAG API更新成功,datasetId: {}", knowledgeBaseDTO.getDatasetId());
} catch (Exception e) {
// 提供更详细的错误信息,包括异常类型和消息
String errorMessage = "更新RAG数据集失败: " + e.getClass().getSimpleName();
if (e.getMessage() != null) {
errorMessage += " - " + e.getMessage();
}
log.error(errorMessage, e);
throw e;
}
} else {
log.warn("datasetId或ragModelId为空,跳过RAG更新");
}
KnowledgeBaseEntity entity = ConvertUtils.sourceToTarget(knowledgeBaseDTO, KnowledgeBaseEntity.class);
knowledgeBaseDao.updateById(entity);
// 删除缓存
if (entity.getDatasetId() != null) {
redisUtils.delete(RedisKeys.getKnowledgeBaseCacheKey(entity.getId()));
}
return ConvertUtils.sourceToTarget(entity, KnowledgeBaseDTO.class);
}
@@ -248,400 +102,316 @@ public class KnowledgeBaseServiceImpl extends BaseServiceImpl<KnowledgeBaseDao,
if (StringUtils.isBlank(datasetId)) {
throw new RenException(ErrorCode.PARAMS_GET_ERROR);
}
KnowledgeBaseEntity entity = knowledgeBaseDao.selectOne(
new QueryWrapper<KnowledgeBaseEntity>().eq("dataset_id", datasetId));
// [Production Fix] 兼容性查找:优先通过 dataset_id 找,找不到通过主键 id 找,确保前端传哪种 UUID 都能命中
KnowledgeBaseEntity entity = knowledgeBaseDao
.selectOne(new QueryWrapper<KnowledgeBaseEntity>()
.eq("dataset_id", datasetId)
.or()
.eq("id", datasetId));
if (entity == null) {
throw new RenException(ErrorCode.Knowledge_Base_RECORD_NOT_EXISTS);
}
return ConvertUtils.sourceToTarget(entity, KnowledgeBaseDTO.class);
}
@Override
@Transactional(rollbackFor = Exception.class)
public KnowledgeBaseDTO save(KnowledgeBaseDTO dto) {
// 1. Validation
checkDuplicateName(dto.getName(), null);
KnowledgeBaseAdapter adapter = null;
// 2. RAG Creation
String datasetId = null;
try {
// 若未指定 RAG 模型,自动使用系统默认
if (StringUtils.isBlank(dto.getRagModelId())) {
List<ModelConfigEntity> models = getRAGModels();
if (models != null && !models.isEmpty()) {
dto.setRagModelId(models.get(0).getId());
} else {
throw new RenException(ErrorCode.RAG_CONFIG_NOT_FOUND, "未指定且无可用默认 RAG 模型");
}
}
Map<String, Object> ragConfig = getValidatedRAGConfig(dto.getRagModelId());
adapter = KnowledgeBaseAdapterFactory.getAdapter((String) ragConfig.get("type"),
ragConfig);
DatasetDTO.CreateReq createReq = ConvertUtils.sourceToTarget(dto, DatasetDTO.CreateReq.class);
createReq.setName(SecurityUser.getUser().getUsername() + "_" + dto.getName());
DatasetDTO.InfoVO ragResponse = adapter.createDataset(createReq);
if (ragResponse == null || StringUtils.isBlank(ragResponse.getId())) {
throw new RenException(ErrorCode.RAG_API_ERROR, "RAG创建返回无效: 缺失ID");
}
datasetId = ragResponse.getId();
// 3. Local Save (Shadow)
KnowledgeBaseEntity entity = ConvertUtils.sourceToTarget(dto, KnowledgeBaseEntity.class);
// [Production Fix] 统一本地 ID 与 RAGFlow ID,防止前端调用 /delete 或 /update 时因 ID 混淆(本地
// UUID vs RAG UUID)导致 10163 错误
entity.setId(datasetId);
entity.setDatasetId(datasetId);
entity.setStatus(1); // Default Enabled
// ✅ FULL PERSISTENCE: 严格全量回写 (User Requirement)
// 使用强类型 DTO 属性获取,不再从 Map 中手动解析 Key
entity.setTenantId(ragResponse.getTenantId());
entity.setChunkMethod(ragResponse.getChunkMethod());
entity.setEmbeddingModel(ragResponse.getEmbeddingModel());
entity.setPermission(ragResponse.getPermission());
if (StringUtils.isBlank(entity.getAvatar())) {
entity.setAvatar(ragResponse.getAvatar());
}
// Parse Config (JSON)
if (ragResponse.getParserConfig() != null) {
entity.setParserConfig(JsonUtils.toJsonString(ragResponse.getParserConfig()));
}
// Numeric fields
entity.setChunkCount(ragResponse.getChunkCount() != null ? ragResponse.getChunkCount() : 0L);
entity.setDocumentCount(ragResponse.getDocumentCount() != null ? ragResponse.getDocumentCount() : 0L);
entity.setTokenNum(ragResponse.getTokenNum() != null ? ragResponse.getTokenNum() : 0L);
// 清空 creator/updater,让 FieldMetaObjectHandler 从 SecurityUser 自动填充
// ConvertUtils 会把 DTO 中的 creator=0 拷贝过来,导致 strictInsertFill 跳过填充
entity.setCreator(null);
entity.setUpdater(null);
knowledgeBaseDao.insert(entity);
return ConvertUtils.sourceToTarget(entity, KnowledgeBaseDTO.class);
} catch (Exception e) {
log.error("RAG创建或本地保存失败", e);
// 如果datasetId已生成但在保存本地时失败,尝试回滚RAG (Best Effort)
if (StringUtils.isNotBlank(datasetId)) {
try {
if (adapter != null)
adapter.deleteDataset(
DatasetDTO.BatchIdReq.builder().ids(Collections.singletonList(datasetId)).build());
} catch (Exception rollbackEx) {
log.error("RAG回滚失败: {}", datasetId, rollbackEx);
}
}
if (e instanceof RenException) {
throw (RenException) e;
}
throw new RenException(ErrorCode.RAG_API_ERROR, "创建知识库失败: " + e.getMessage());
}
}
@Override
@Transactional(rollbackFor = Exception.class)
@SuppressWarnings("deprecation")
public KnowledgeBaseDTO update(KnowledgeBaseDTO dto) {
log.info("Update Service Called: ID={}, DatasetID={}", dto.getId(), dto.getDatasetId());
KnowledgeBaseEntity entity = knowledgeBaseDao.selectById(dto.getId());
if (entity == null) {
log.error("Update failed: Entity not found for ID={}", dto.getId());
throw new RenException(ErrorCode.Knowledge_Base_RECORD_NOT_EXISTS);
}
checkDuplicateName(dto.getName(), dto.getId());
// 验证数据集ID是否与其他记录冲突
if (StringUtils.isNotBlank(dto.getDatasetId())) {
KnowledgeBaseEntity conflictEntity = knowledgeBaseDao.selectOne(
new QueryWrapper<KnowledgeBaseEntity>()
.eq("dataset_id", dto.getDatasetId())
.ne("id", dto.getId()));
if (conflictEntity != null) {
throw new RenException(ErrorCode.DB_RECORD_EXISTS);
}
}
// RAG Update if needed
if (StringUtils.isNotBlank(entity.getDatasetId()) && StringUtils.isNotBlank(dto.getRagModelId())) {
try {
// 🤖 AUTO-FILL: 若 DTO 未传 ragModelId (极少情况),尝试复用 Entity 中的
if (StringUtils.isBlank(dto.getRagModelId())) {
dto.setRagModelId(entity.getRagModelId());
}
// [FIX] 智能补全:如果 DTO 里的关键字段为空,则使用 Entity 里的旧值
// 确保发给 RAGFlow 的请求包含所有必填项 (Partial Update Support)
if (StringUtils.isBlank(dto.getPermission())) {
dto.setPermission(entity.getPermission());
}
if (StringUtils.isBlank(dto.getChunkMethod())) {
dto.setChunkMethod(entity.getChunkMethod());
}
KnowledgeBaseAdapter adapter = getAdapterByModelId(dto.getRagModelId());
if (adapter != null) {
DatasetDTO.UpdateReq updateReq = ConvertUtils.sourceToTarget(dto, DatasetDTO.UpdateReq.class);
// 1. 必填/核心字段前缀处理
if (StringUtils.isNotBlank(dto.getName())) {
updateReq.setName(SecurityUser.getUser().getUsername() + "_" + dto.getName());
}
// 2. 解析器配置支持 (如果 DTO 里有字符串形式的配置,尝试转换,但优先建议 DTO 化)
if (StringUtils.isNotBlank(dto.getParserConfig())) {
try {
DatasetDTO.ParserConfig parserConfig = JsonUtils.parseObject(dto.getParserConfig(),
DatasetDTO.ParserConfig.class);
updateReq.setParserConfig(parserConfig);
} catch (Exception e) {
log.warn("解析 parser_config 失败,跳过同步", e);
}
}
adapter.updateDataset(entity.getDatasetId(), updateReq);
log.info("RAG更新成功: {}", entity.getDatasetId());
}
} catch (Exception e) {
log.error("RAG更新失败", e);
// 恢复事务一致性:RAG失败则整体回滚
if (e instanceof RenException) {
throw (RenException) e;
}
throw new RenException(ErrorCode.RAG_API_ERROR, "RAG更新失败: " + e.getMessage());
}
}
BeanUtils.copyProperties(dto, entity);
knowledgeBaseDao.updateById(entity);
// Clean cache
redisUtils.delete(RedisKeys.getKnowledgeBaseCacheKey(entity.getId()));
return ConvertUtils.sourceToTarget(entity, KnowledgeBaseDTO.class);
}
/**
* 根据知识库ID集合查询知识库
* @param datasetIdList 知识库ID集合
* @return
*/
@Override
public List<KnowledgeBaseDTO> getByDatasetIdList(List<String> datasetIdList) {
//判断参数
if (ToolUtil.isEmpty(datasetIdList)) {
throw new RenException(ErrorCode.PARAMS_GET_ERROR);
}
//批量查询
List<KnowledgeBaseEntity> entityList = knowledgeBaseDao.selectList(
new QueryWrapper<KnowledgeBaseEntity>().in("dataset_id", datasetIdList));
if (ToolUtil.isEmpty(entityList)) {
throw new RenException(ErrorCode.Knowledge_Base_RECORD_NOT_EXISTS);
}
return ConvertUtils.sourceToTarget(entityList, KnowledgeBaseDTO.class);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteByDatasetId(String datasetId) {
if (StringUtils.isBlank(datasetId)) {
throw new RenException(ErrorCode.PARAMS_GET_ERROR);
}
log.info("=== 开始通过datasetId删除操作 ===");
log.info("删除datasetId: {}", datasetId);
KnowledgeBaseEntity entity = knowledgeBaseDao.selectOne(
new QueryWrapper<KnowledgeBaseEntity>().eq("dataset_id", datasetId));
KnowledgeBaseEntity entity = knowledgeBaseDao
.selectOne(new QueryWrapper<KnowledgeBaseEntity>().eq("dataset_id", datasetId));
// 1. 恢复 404 校验:找不到记录抛异常
if (entity == null) {
log.warn("记录不存在,datasetId: {}", datasetId);
throw new RenException(ErrorCode.Knowledge_Base_RECORD_NOT_EXISTS);
}
redisUtils.delete(RedisKeys.getKnowledgeBaseCacheKey(entity.getId()));
log.info("找到记录: ID={}, datasetId={}, ragModelId={}",
entity.getId(), entity.getDatasetId(), entity.getRagModelId());
// 先调用RAG API删除数据集
// 2. RAG Delete (Strict Mode)
// 恢复严格一致性:RAG 删除失败则抛出异常,触发事务回滚,不允许已删除本地但保留远程的脏数据
boolean apiDeleteSuccess = false;
if (StringUtils.isNotBlank(entity.getDatasetId()) && StringUtils.isNotBlank(entity.getRagModelId())) {
if (StringUtils.isNotBlank(entity.getRagModelId()) && StringUtils.isNotBlank(entity.getDatasetId())) {
try {
log.info("开始调用RAG API删除数据集");
// 在删除前进行RAG配置校验
Map<String, Object> ragConfig = getValidatedRAGConfig(entity.getRagModelId());
deleteDatasetInRAG(entity.getDatasetId(), ragConfig);
log.info("RAG API删除调用完成");
KnowledgeBaseAdapter adapter = getAdapterByModelId(entity.getRagModelId());
if (adapter != null) {
adapter.deleteDataset(
DatasetDTO.BatchIdReq.builder().ids(Collections.singletonList(datasetId)).build());
}
apiDeleteSuccess = true;
} catch (Exception e) {
// 提供更详细的错误信息,包括异常类型和消息
String errorMessage = "删除RAG数据集失败: " + e.getClass().getSimpleName();
if (e.getMessage() != null) {
errorMessage += " - " + e.getMessage();
log.error("RAG删除失败,触发回滚", e);
if (e instanceof RenException) {
throw (RenException) e;
}
log.error(errorMessage, e);
throw e;
throw new RenException(ErrorCode.RAG_API_ERROR, "RAG删除失败: " + e.getMessage());
}
} else {
log.warn("datasetId或ragModelId为空,跳过RAG删除");
apiDeleteSuccess = true; // 没有RAG数据集,视为成功
}
// API删除成功后再删除本地记录
// 3. Local Delete (Safe Order)
// 恢复正确顺序:先删子表 (Plugin Mapping),再删主表 (Entity)
if (apiDeleteSuccess) {
log.info("开始删除ai_agent_plugin_mapping表中与知识库ID '{}' 相关的映射记录", entity.getId());
// 先删除相关的插件映射记录
log.info("开始删除关联数据, entityId: {}", entity.getId());
knowledgeBaseDao.deletePluginMappingByKnowledgeBaseId(entity.getId());
log.info("插件映射记录删除完成");
int deleteCount = knowledgeBaseDao.deleteById(entity.getId());
log.info("本地数据库删除结果: {}", deleteCount > 0 ? "成功" : "失败");
redisUtils.delete(RedisKeys.getKnowledgeBaseCacheKey(entity.getId()));
}
}
log.info("=== 通过datasetId删除操作结束 ===");
@Override
public List<KnowledgeBaseDTO> getByDatasetIdList(List<String> datasetIdList) {
if (datasetIdList == null || datasetIdList.isEmpty()) {
return Collections.emptyList();
}
// [Production Fix] 批量兼容性查找
QueryWrapper<KnowledgeBaseEntity> queryWrapper = new QueryWrapper<>();
queryWrapper.in("dataset_id", datasetIdList).or().in("id", datasetIdList);
List<KnowledgeBaseEntity> list = knowledgeBaseDao.selectList(queryWrapper);
return ConvertUtils.sourceToTarget(list, KnowledgeBaseDTO.class);
}
@Override
public Map<String, Object> getRAGConfig(String ragModelId) {
if (StringUtils.isBlank(ragModelId)) {
throw new RenException(ErrorCode.PARAMS_GET_ERROR);
}
// 从缓存获取模型配置
ModelConfigEntity modelConfig = modelConfigService.getModelByIdFromCache(ragModelId);
if (modelConfig == null || modelConfig.getConfigJson() == null) {
throw new RenException(ErrorCode.RAG_CONFIG_NOT_FOUND);
}
// 验证是否为RAG类型配置
if (!Constant.RAG_CONFIG_TYPE.equals(modelConfig.getModelType().toUpperCase())) {
throw new RenException(ErrorCode.RAG_CONFIG_TYPE_ERROR);
}
Map<String, Object> config = modelConfig.getConfigJson();
// 验证必要的配置参数
validateRagConfig(config);
// 返回配置信息
return config;
return getValidatedRAGConfig(ragModelId);
}
@Override
public Map<String, Object> getRAGConfigByDatasetId(String datasetId) {
if (StringUtils.isBlank(datasetId)) {
throw new RenException(ErrorCode.RAG_DATASET_ID_NOT_NULL);
}
// 根据datasetId查询知识库信息
KnowledgeBaseDTO knowledgeBase = getByDatasetId(datasetId);
if (knowledgeBase == null) {
log.warn("未找到datasetId为{}的知识库", datasetId);
throw new RenException(ErrorCode.Knowledge_Base_RECORD_NOT_EXISTS);
}
// 如果知识库指定了ragModelId,使用该配置
String ragModelId = knowledgeBase.getRagModelId();
if (StringUtils.isBlank(ragModelId)) {
log.warn("知识库datasetId为{}未配置ragModelId", datasetId);
KnowledgeBaseEntity entity = knowledgeBaseDao
.selectOne(new QueryWrapper<KnowledgeBaseEntity>().eq("dataset_id", datasetId));
if (entity == null || StringUtils.isBlank(entity.getRagModelId())) {
throw new RenException(ErrorCode.RAG_CONFIG_NOT_FOUND);
}
return getRAGConfig(entity.getRagModelId());
}
// 获取并返回RAG配置
return getRAGConfig(ragModelId);
@Override
@Transactional(rollbackFor = Exception.class)
public void updateStatistics(String datasetId, Integer docDelta, Long chunkDelta, Long tokenDelta) {
log.info("递增更新知识库统计: datasetId={}, docs={}, chunks={}, tokens={}", datasetId, docDelta, chunkDelta, tokenDelta);
knowledgeBaseDao.updateStatsAfterChange(datasetId, docDelta, chunkDelta, tokenDelta);
}
@Override
public List<ModelConfigEntity> getRAGModels() {
// 查询RAG类型的模型配置
QueryWrapper<ModelConfigEntity> queryWrapper = new QueryWrapper<ModelConfigEntity>()
.select("id", "model_name")
return modelConfigDao.selectList(new QueryWrapper<ModelConfigEntity>()
.select("id", "model_name", "config_json") // Explicitly select needed fields
.eq("model_type", Constant.RAG_CONFIG_TYPE)
.eq("is_enabled", 1)
.orderByDesc("is_default")
.orderByDesc("create_date");
List<ModelConfigEntity> modelConfigs = modelConfigDao.selectList(queryWrapper);
return modelConfigs;
.orderByDesc("create_date"));
}
/**
* 验证RAG配置中是否包含必要的参数
*/
private void validateRagConfig(Map<String, Object> config) {
if (config == null) {
// --- Helpers ---
private void checkDuplicateName(String name, String excludeId) {
if (StringUtils.isBlank(name))
return;
QueryWrapper<KnowledgeBaseEntity> qw = new QueryWrapper<>();
qw.eq("name", name).eq("creator", SecurityUser.getUserId());
if (excludeId != null)
qw.ne("id", excludeId);
if (knowledgeBaseDao.selectCount(qw) > 0) {
throw new RenException(ErrorCode.KNOWLEDGE_BASE_NAME_EXISTS);
}
}
private KnowledgeBaseAdapter getAdapterByModelId(String modelId) {
Map<String, Object> config = getValidatedRAGConfig(modelId);
return KnowledgeBaseAdapterFactory.getAdapter((String) config.get("type"), config);
}
private Map<String, Object> getValidatedRAGConfig(String modelId) {
ModelConfigEntity configEntity = modelConfigService.getModelByIdFromCache(modelId);
if (configEntity == null || configEntity.getConfigJson() == null) {
throw new RenException(ErrorCode.RAG_CONFIG_NOT_FOUND);
}
// 从配置中提取必要的参数
String baseUrl = (String) config.get("base_url");
String apiKey = (String) config.get("api_key");
// 验证base_url是否存在且非空
if (StringUtils.isBlank(baseUrl)) {
throw new RenException(ErrorCode.RAG_API_ERROR_URL_NULL);
}
// 验证api_key是否存在且非空
if (StringUtils.isBlank(apiKey)) {
throw new RenException(ErrorCode.RAG_API_ERROR_API_KEY_NULL);
}
// 检查api_key是否包含占位符
if (apiKey.contains("")) {
throw new RenException(ErrorCode.RAG_API_ERROR_API_KEY_INVALID);
}
// 验证base_url格式
if (!baseUrl.startsWith("http://") && !baseUrl.startsWith("https://")) {
throw new RenException(ErrorCode.RAG_API_ERROR_URL_INVALID);
Map<String, Object> config = new HashMap<>(configEntity.getConfigJson());
if (!config.containsKey("type")) {
config.put("type", "ragflow");
}
return config;
}
/**
* 从RAG配置中提取适配器类型
*
* @param config RAG配置
* @return 适配器类型
*/
private String extractAdapterType(Map<String, Object> config) {
if (config == null) {
throw new RenException(ErrorCode.RAG_CONFIG_NOT_FOUND);
}
// 从配置中提取适配器类型
String adapterType = (String) config.get("type");
// 验证适配器类型是否存在且非空
if (StringUtils.isBlank(adapterType)) {
throw new RenException(ErrorCode.RAG_ADAPTER_TYPE_NOT_FOUND);
}
// 验证适配器类型是否已注册
if (!KnowledgeBaseAdapterFactory.isAdapterTypeRegistered(adapterType)) {
throw new RenException(ErrorCode.RAG_ADAPTER_TYPE_NOT_SUPPORTED,
"不支持的适配器类型: " + adapterType);
}
return adapterType;
}
/**
* 使用适配器创建数据集
*/
private String createDatasetInRAG(String name, String description, Map<String, Object> ragConfig) {
log.info("开始使用适配器创建数据集, name: {}", name);
try {
// 从RAG配置中提取适配器类型
String adapterType = extractAdapterType(ragConfig);
// 使用适配器工厂获取适配器实例
KnowledgeBaseAdapter adapter = KnowledgeBaseAdapterFactory.getAdapter(adapterType, ragConfig);
// 构建数据集创建参数
Map<String, Object> createParams = new HashMap<>();
String username = SecurityUser.getUser().getUsername();
createParams.put("name", username + "_" + name);
if (StringUtils.isNotBlank(description)) {
createParams.put("description", description);
}
// 调用适配器的创建数据集方法
String datasetId = adapter.createDataset(createParams);
log.info("数据集创建成功,datasetId: {}", datasetId);
return datasetId;
} catch (Exception e) {
// 直接传递底层适配器的详细错误信息
log.error("创建数据集失败", e);
if (e instanceof RenException) {
throw (RenException) e;
}
throw new RenException(ErrorCode.RAG_API_ERROR, e.getMessage());
}
}
/**
* 使用适配器更新数据集
*/
private void updateDatasetInRAG(String datasetId, String name, String description,
Map<String, Object> ragConfig) {
log.info("开始使用适配器更新数据集,datasetId: {}, name: {}", datasetId, name);
try {
// 从RAG配置中提取适配器类型
String adapterType = extractAdapterType(ragConfig);
// 使用适配器工厂获取适配器实例
KnowledgeBaseAdapter adapter = KnowledgeBaseAdapterFactory.getAdapter(adapterType, ragConfig);
// 构建数据集更新参数
Map<String, Object> updateParams = new HashMap<>();
String username = SecurityUser.getUser().getUsername();
updateParams.put("name", username + "_" + name);
if (StringUtils.isNotBlank(description)) {
updateParams.put("description", description);
}
// 调用适配器的更新数据集方法
adapter.updateDataset(datasetId, updateParams);
log.info("数据集更新成功,datasetId: {}", datasetId);
} catch (Exception e) {
// 直接传递底层适配器的详细错误信息
log.error("更新数据集失败", e);
if (e instanceof RenException) {
throw (RenException) e;
}
throw new RenException(ErrorCode.RAG_API_ERROR, e.getMessage());
}
}
/**
* 使用适配器删除数据集
*/
private void deleteDatasetInRAG(String datasetId, Map<String, Object> ragConfig) {
log.info("开始使用适配器删除数据集,datasetId: {}", datasetId);
try {
// 从RAG配置中提取适配器类型
String adapterType = extractAdapterType(ragConfig);
// 使用适配器工厂获取适配器实例
KnowledgeBaseAdapter adapter = KnowledgeBaseAdapterFactory.getAdapter(adapterType, ragConfig);
// 调用适配器的删除数据集方法
adapter.deleteDataset(datasetId);
log.info("数据集删除成功,datasetId: {}", datasetId);
} catch (Exception e) {
// 直接传递底层适配器的详细错误信息
log.error("删除数据集失败", e);
if (e instanceof RenException) {
throw (RenException) e;
}
throw new RenException(ErrorCode.RAG_API_ERROR, e.getMessage());
}
}
/**
* 获取RAG配置并验证
*/
private Map<String, Object> getValidatedRAGConfig(String ragModelId) {
if (StringUtils.isBlank(ragModelId)) {
throw new RenException(ErrorCode.RAG_MODEL_ID_NOT_NULL);
}
Map<String, Object> ragConfig = getRAGConfig(ragModelId);
// 验证RAG配置参数
validateRagConfig(ragConfig);
return ragConfig;
}
/**
* 检查是否存在同名知识库
*
* @param knowledgeBaseDTO 知识库DTO
* @param excludeId 排除的ID(更新时使用)
*/
private void checkDuplicateKnowledgeBaseName(KnowledgeBaseDTO knowledgeBaseDTO, String excludeId) {
if (StringUtils.isNotBlank(knowledgeBaseDTO.getName())) {
Long currentUserId = SecurityUser.getUserId();
QueryWrapper<KnowledgeBaseEntity> queryWrapper = new QueryWrapper<KnowledgeBaseEntity>()
.eq("name", knowledgeBaseDTO.getName())
.eq("creator", currentUserId);
// 如果提供了排除ID,则排除该记录
if (StringUtils.isNotBlank(excludeId)) {
queryWrapper.ne("id", excludeId);
}
long count = knowledgeBaseDao.selectCount(queryWrapper);
if (count > 0) {
throw new RenException(ErrorCode.KNOWLEDGE_BASE_NAME_EXISTS,
MessageUtils.getMessage(ErrorCode.KNOWLEDGE_BASE_NAME_EXISTS));
}
}
}
/**
* 从适配器获取知识库的文档数量
*/
private Integer getDocumentCountFromRAG(String datasetId, String ragModelId) {
if (StringUtils.isBlank(datasetId) || StringUtils.isBlank(ragModelId)) {
log.warn("datasetId或ragModelId为空,无法获取文档数量");
return 0;
}
log.info("开始获取知识库 {} 的文档数量", datasetId);
try {
// 获取RAG配置
Map<String, Object> ragConfig = getValidatedRAGConfig(ragModelId);
// 从RAG配置中提取适配器类型
String adapterType = extractAdapterType(ragConfig);
// 使用适配器工厂获取适配器实例
KnowledgeBaseAdapter adapter = KnowledgeBaseAdapterFactory.getAdapter(adapterType, ragConfig);
// 调用适配器的获取文档数量方法
Integer documentCount = adapter.getDocumentCount(datasetId);
log.info("获取知识库 {} 的文档数量成功: {}", datasetId, documentCount);
return documentCount;
} catch (Exception e) {
// 构建详细的错误信息,包含异常类型和消息
String baseErrorMessage = e.getClass().getSimpleName() + " - 获取知识库文档数量失败";
String errorMessage = baseErrorMessage + (e.getMessage() != null ? ": " + e.getMessage() : "");
log.error(errorMessage, e);
return 0;
}
}
}
@@ -0,0 +1,47 @@
package xiaozhi.modules.knowledge.service.impl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import xiaozhi.modules.knowledge.service.KnowledgeBaseService;
import xiaozhi.modules.knowledge.service.KnowledgeFilesService;
import xiaozhi.modules.knowledge.service.KnowledgeManagerService;
import java.util.List;
@Service
@Slf4j
@RequiredArgsConstructor
public class KnowledgeManagerServiceImpl implements KnowledgeManagerService {
private final KnowledgeBaseService knowledgeBaseService;
private final KnowledgeFilesService knowledgeFilesService;
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteDatasetWithFiles(String datasetId) {
log.info("=== 级联删除开始: datasetId={} ===", datasetId);
// 1. 先调用文件服务,清理该数据集下的所有文档记录 (含 RAGFlow 端)
log.info("Step 1: 清理关联文档...");
knowledgeFilesService.deleteDocumentsByDatasetId(datasetId);
// 2. 再调用知识库服务,彻底注销数据集 (含 RAGFlow 端)
log.info("Step 2: 删除数据集主体...");
knowledgeBaseService.deleteByDatasetId(datasetId);
log.info("=== 级联删除成功: datasetId={} ===", datasetId);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void batchDeleteDatasetsWithFiles(List<String> datasetIds) {
if (datasetIds == null || datasetIds.isEmpty())
return;
log.info("=== 批量级联删除开始: count={} ===", datasetIds.size());
for (String id : datasetIds) {
deleteDatasetWithFiles(id);
}
}
}
@@ -0,0 +1,39 @@
package xiaozhi.modules.knowledge.task;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import xiaozhi.modules.knowledge.service.KnowledgeFilesService;
/**
* 知识库文档状态同步定时任务
*
* 作用:
* 1. 自动扫描处于 "RUNNING" (解析中) 状态的文档
* 2. 调用 RAGFlow 接口获取最新状态
* 3. 状态翻转 (RUNNING -> SUCCESS/FAIL) 时,同步更新数据库
* 4. [关键] 解析成功时,补偿更新知识库的统计信息 (TokenCount)
*/
@Component
@AllArgsConstructor
@Slf4j
public class DocumentStatusSyncTask {
private final KnowledgeFilesService knowledgeFilesService;
/**
* 每 30 秒执行一次同步
* 采用 fixedDelay,确保上一次执行完 30 秒后才开始下一次,防止积压
*/
@Scheduled(fixedDelay = 30000)
public void syncRunningDocuments() {
try {
// log.debug("开始执行文档状态同步任务...");
knowledgeFilesService.syncRunningDocuments();
} catch (Exception e) {
log.error("文档状态同步任务异常", e);
}
}
}
@@ -56,7 +56,7 @@ public class SysUserServiceImpl extends BaseServiceImpl<SysUserDao, SysUserEntit
if (users == null || users.isEmpty()) {
return null;
}
SysUserEntity entity = users.getFirst();
SysUserEntity entity = users.get(0);
return ConvertUtils.sourceToTarget(entity, SysUserDTO.class);
}
@@ -197,7 +197,7 @@ public class SysUserServiceImpl extends BaseServiceImpl<SysUserDao, SysUserEntit
*/
private String generatePassword() {
StringBuilder password = new StringBuilder();
// 确保包含至少一个数字
password.append("0123456789".charAt(random.nextInt(10)));
// 确保包含至少一个小写字母
@@ -206,12 +206,12 @@ public class SysUserServiceImpl extends BaseServiceImpl<SysUserDao, SysUserEntit
password.append("ABCDEFGHIJKLMNOPQRSTUVWXYZ".charAt(random.nextInt(26)));
// 确保包含至少一个特殊符号
password.append("!@#$%^&*()".charAt(random.nextInt(10)));
// 生成剩余的8个字符
for (int i = 4; i < 12; i++) {
password.append(CHARACTERS.charAt(random.nextInt(CHARACTERS.length())));
}
// 打乱密码中字符的顺序
char[] passwordChars = password.toString().toCharArray();
for (int i = 0; i < passwordChars.length; i++) {
@@ -220,7 +220,7 @@ public class SysUserServiceImpl extends BaseServiceImpl<SysUserDao, SysUserEntit
passwordChars[i] = passwordChars[randomIndex];
passwordChars[randomIndex] = temp;
}
return new String(passwordChars);
}
@@ -17,4 +17,7 @@ public class VoiceCloneDTO {
@Schema(description = "用户ID")
private Long userId;
@Schema(description = "语言")
private String languages;
}
@@ -28,6 +28,9 @@ public class VoiceCloneResponseDTO {
@Schema(description = "声音id")
private String voiceId;
@Schema(description = "语言")
private String languages;
@Schema(description = "用户ID(关联用户表)")
private Long userId;
@@ -31,6 +31,9 @@ public class VoiceCloneEntity {
@Schema(description = "声音id")
private String voiceId;
@Schema(description = "语言")
private String languages;
@Schema(description = "用户 ID(关联用户表)")
private Long userId;
@@ -116,6 +116,7 @@ public class VoiceCloneServiceImpl extends BaseServiceImpl<VoiceCloneDao, VoiceC
entity.setVoiceId(voiceId);
entity.setName(namePrefix + "_" + index);
entity.setUserId(dto.getUserId());
entity.setLanguages(dto.getLanguages());
entity.setTrainStatus(0); // 默认训练中
batchInsertList.add(entity);
}
@@ -0,0 +1,4 @@
-- 给声音克隆表添加语言字段
SET @col_exists = (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'ai_voice_clone' AND COLUMN_NAME = 'languages');
SET @sql = IF(@col_exists = 0, 'ALTER TABLE `ai_voice_clone` ADD COLUMN `languages` VARCHAR(50) DEFAULT NULL COMMENT ''语言'' AFTER `voice_id`', 'SELECT ''Column languages already exists'' AS msg');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
@@ -0,0 +1,69 @@
-- 增加一下ragflow返回的参数(创建/查询知识库时返回)
SET @col_exists = (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'ai_rag_dataset' AND COLUMN_NAME = 'tenant_id');
SET @sql = IF(@col_exists = 0, 'ALTER TABLE `ai_rag_dataset` ADD COLUMN `tenant_id` varchar(32) DEFAULT NULL COMMENT ''租户ID''', 'SELECT ''Column tenant_id already exists'' AS msg');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @col_exists = (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'ai_rag_dataset' AND COLUMN_NAME = 'avatar');
SET @sql = IF(@col_exists = 0, 'ALTER TABLE `ai_rag_dataset` ADD COLUMN `avatar` text DEFAULT NULL COMMENT ''知识库头像 (Base64)''', 'SELECT ''Column avatar already exists'' AS msg');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @col_exists = (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'ai_rag_dataset' AND COLUMN_NAME = 'embedding_model');
SET @sql = IF(@col_exists = 0, 'ALTER TABLE `ai_rag_dataset` ADD COLUMN `embedding_model` varchar(50) DEFAULT NULL COMMENT ''嵌入模型名称''', 'SELECT ''Column embedding_model already exists'' AS msg');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @col_exists = (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'ai_rag_dataset' AND COLUMN_NAME = 'permission');
SET @sql = IF(@col_exists = 0, 'ALTER TABLE `ai_rag_dataset` ADD COLUMN `permission` varchar(20) DEFAULT ''me'' COMMENT ''权限设置:me/team''', 'SELECT ''Column permission already exists'' AS msg');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @col_exists = (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'ai_rag_dataset' AND COLUMN_NAME = 'chunk_method');
SET @sql = IF(@col_exists = 0, 'ALTER TABLE `ai_rag_dataset` ADD COLUMN `chunk_method` varchar(50) DEFAULT NULL COMMENT ''分块方法''', 'SELECT ''Column chunk_method already exists'' AS msg');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @col_exists = (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'ai_rag_dataset' AND COLUMN_NAME = 'parser_config');
SET @sql = IF(@col_exists = 0, 'ALTER TABLE `ai_rag_dataset` ADD COLUMN `parser_config` text DEFAULT NULL COMMENT ''解析器配置 (JSON)''', 'SELECT ''Column parser_config already exists'' AS msg');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @col_exists = (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'ai_rag_dataset' AND COLUMN_NAME = 'chunk_count');
SET @sql = IF(@col_exists = 0, 'ALTER TABLE `ai_rag_dataset` ADD COLUMN `chunk_count` bigint(20) DEFAULT 0 COMMENT ''分块总数''', 'SELECT ''Column chunk_count already exists'' AS msg');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @col_exists = (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'ai_rag_dataset' AND COLUMN_NAME = 'document_count');
SET @sql = IF(@col_exists = 0, 'ALTER TABLE `ai_rag_dataset` ADD COLUMN `document_count` bigint(20) DEFAULT 0 COMMENT ''文档总数''', 'SELECT ''Column document_count already exists'' AS msg');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @col_exists = (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'ai_rag_dataset' AND COLUMN_NAME = 'token_num');
SET @sql = IF(@col_exists = 0, 'ALTER TABLE `ai_rag_dataset` ADD COLUMN `token_num` bigint(20) DEFAULT 0 COMMENT ''总 Token 数''', 'SELECT ''Column token_num already exists'' AS msg');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-- 文档表 (Shadow DB for RAGFlow)
-- 留存一份文档id,把ragflow远端文档id和本地id关联起来(只是备份一份元信息链接,实际上文件内容的存储还是在ragflow)
DROP TABLE IF EXISTS `ai_rag_knowledge_document`;
CREATE TABLE `ai_rag_knowledge_document` (
`id` varchar(36) NOT NULL COMMENT '本地唯一ID',
`dataset_id` varchar(36) NOT NULL COMMENT '知识库ID (关联 ai_rag_dataset)',
`document_id` varchar(64) NOT NULL COMMENT 'RAGFlow文档ID (远程ID)',
`name` varchar(255) DEFAULT NULL COMMENT '文档名称',
`size` bigint(20) DEFAULT NULL COMMENT '文件大小(Bytes)',
`type` varchar(20) DEFAULT NULL COMMENT '文件类型',
`chunk_method` varchar(50) DEFAULT NULL COMMENT '分块方法',
`parser_config` text COMMENT '解析配置(JSON)',
`status` varchar(10) DEFAULT '1' COMMENT '可用状态 (1:启用 0:禁用)',
`run` varchar(32) DEFAULT 'UNSTART' COMMENT '运行状态 (UNSTART/RUNNING/CANCEL/DONE/FAIL)',
`progress` double DEFAULT '0' COMMENT '解析进度 (0.0 ~ 1.0)',
`thumbnail` mediumtext COMMENT '缩略图 (Base64 或 URL)',
`process_duration` double DEFAULT '0' COMMENT '解析耗时 (单位: 秒)',
`meta_fields` text COMMENT '自定义元数据 (JSON)',
`source_type` varchar(32) DEFAULT 'local' COMMENT '来源类型 (local, s3, url 等)',
`error` text COMMENT '错误信息',
`chunk_count` int(11) DEFAULT '0' COMMENT '分块数量',
`token_count` bigint(20) DEFAULT '0' COMMENT 'Token数量',
`enabled` tinyint(1) DEFAULT '1' COMMENT '启用状态',
`creator` bigint(20) DEFAULT NULL COMMENT '创建者',
`created_at` datetime DEFAULT NULL COMMENT '创建时间',
`updated_at` datetime DEFAULT NULL COMMENT '更新时间',
`last_sync_at` datetime DEFAULT NULL COMMENT '最后同步时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_doc_id` (`document_id`),
KEY `idx_dataset_id` (`dataset_id`),
KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='知识库文档表';
@@ -557,4 +557,17 @@ databaseChangeLog:
- sqlFile:
encoding: utf8
path: classpath:db/changelog/202603091051.sql
- changeSet:
id: 202603101723
author: RanChen
changes:
- sqlFile:
encoding: utf8
path: classpath:db/changelog/202603101723.sql
- changeSet:
id: 202603111131
author: DaGou12138
changes:
- sqlFile:
encoding: utf8
path: classpath:db/changelog/202603111131.sql
@@ -204,4 +204,6 @@
10195=OTA\u4E0A\u4F20\u6B21\u6570\u8D85\u8FC7\u9650\u5236
10196=\u6807\u7B7E\u540D\u79F0\u5DF2\u5B58\u5728
10197=\u6807\u7B7E\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A
10198=\u6807\u7B7E\u4E0D\u5B58\u5728
10198=\u6807\u7B7E\u4E0D\u5B58\u5728
10199=\u89E3\u6790\u4E2D\u6587\u4EF6\u6682\u4E0D\u652F\u6301\u6B64\u64CD\u4F5C
@@ -204,4 +204,6 @@
10195=OTA-Upload-Anzahl \u00FCberschreitet das Limit
10196=Tag-Name existiert bereits
10197=Tag-Name darf nicht leer sein
10198=Tag nicht gefunden
10198=Tag nicht gefunden
10199=Dateianalyse l\u00E4uft, dieser Vorgang wird nicht unterst\u00FCtzt
@@ -204,4 +204,6 @@
10195=OTA upload times exceed the limit
10196=Tag name already exists
10197=Tag name cannot be empty
10198=Tag not found
10198=Tag not found
10199=Parsing in progress, this operation is not supported
@@ -202,6 +202,8 @@
10193=ID thi\u1EBFt b\u1ECB kh\u00F4ng th\u1EC3 tr\u1ED1ng
10194=Kh\u00F4ng t\u00ECm th\u1EA5y thi\u1EBFt b\u1ECB ho\u1EB7c thi\u1EBFt b\u1ECB kh\u00F4ng tr\u1EF1c tuy\u1EBFn
10195=S\u1ED1 l\u1EA7n t\u1EA3i l\u00EAn OTA v\u01B0\u1EE3t qu\u00E1 gi\u1EDBi h\u1EA1n
10196=T\u00EAn th\u1EB9 \u0111\u00E3 t\u1ED3n t\u1EA1i
10197=T\u00EAn th\u1EB9 kh\u00F4ng th\u1EC3 \u0111\u1EC3 tr\u1ED1ng
10198=Kh\u00F4ng t\u00ECm th\u1EA5y th\u1EB9
10196=T\u00EAn th\u1EB9\uFFFD \u0111\u00E3 t\u1ED3n t\u1EA1i
10197=T\u00EAn th\u1EB9\uFFFD kh\u00F4ng th\u1EC3 \u0111\u1EC3 tr\u1ED1ng
10198=Kh\u00F4ng t\u00ECm th\u1EA5y th\u1EB9\uFFFD
10199=T\u1EC7p \u0111ang \u0111\u01B0\u1EE3c ph\u00E2n t\u00EDch, thao t\u00E1c n\u00E0y kh\u00F4ng \u0111\u01B0\u1EE3c h\u1ED7 tr\u1EE3
@@ -201,4 +201,5 @@
10192=\u9002\u914D\u5668\u7C7B\u578B\u672A\u627E\u5230
10193=\u8BBE\u5907ID\u4E0D\u80FD\u4E3A\u7A7A
10194=\u8BBE\u5907\u4E0D\u5B58\u5728\u6216\u4E0D\u5728\u7EBF
10195=OTA\u4E0A\u4F20\u6B21\u6570\u8D85\u8FC7\u9650\u5236
10195=OTA\u4E0A\u4F20\u6B21\u6570\u8D85\u8FC7\u9650\u5236
10196=\u89E3\u6790\u4E2D\u6587\u4EF6\u6682\u4E0D\u652F\u6301\u6B64\u64CD\u4F5C
@@ -204,4 +204,6 @@
10195=OTA\u4E0A\u4F20\u6B21\u6578\u8D85\u904E\u9650\u5236
10196=\u6A19\u7C64\u540D\u7A31\u5DF2\u5B58\u5728
10197=\u6A19\u7C64\u540D\u7A31\u4E0D\u80FD\u4E3A\u7A7A
10198=\u6A19\u7C64\u4E0D\u5B58\u5728
10198=\u6A19\u7C64\u4E0D\u5B58\u5728
10199=\u89E3\u6790\u4E2D\u6587\u4EF6\u66AB\u4E0D\u652F\u6301\u6B64\u64CD\u4F5C
@@ -23,4 +23,14 @@
WHERE plugin_id = #{knowledgeBaseId}
</delete>
<!-- 通用维度原子更新知识库统计信息 -->
<update id="updateStatsAfterChange">
UPDATE ai_rag_dataset
SET document_count = document_count + #{docDelta},
chunk_count = chunk_count + #{chunkDelta},
token_num = token_num + #{tokenDelta},
updated_at = NOW()
WHERE dataset_id = #{datasetId}
</update>
</mapper>
@@ -3,7 +3,7 @@
<mapper namespace="xiaozhi.modules.voiceclone.dao.VoiceCloneDao">
<select id="getTrainSuccess" resultType="xiaozhi.modules.model.dto.VoiceDTO">
select id, name, voice_id as voiceDemo
select id, name, voice_id as voiceDemo, languages
from ai_voice_clone
where model_id = #{modelId} and user_id = #{userId} and train_status = 2
</select>
+2 -5
View File
@@ -208,11 +208,8 @@ export function updateAgentTags(agentId: string, data) {
}
// 获取所有语言
export function getAllLanguage(modelId, voiceName) {
const queryParams = new URLSearchParams({
voiceName: voiceName || '',
}).toString()
return http.Get(`/models/${modelId}/voices?${queryParams}`, {
export function getAllLanguage(modelId: string) {
return http.Get<{ id: string, name: string, languages: string }[]>(`/models/${modelId}/voices`, {
meta: {
ignoreAuth: false,
toast: false,
@@ -48,6 +48,15 @@ export interface AgentDetail {
ttsRate: number
ttsPitch: number
functions: AgentFunction[]
contextProviders: Providers[]
}
export interface Providers {
url: string
headers: Array<{
key: string
value: string
}>
}
export interface AgentFunction {
@@ -33,6 +33,22 @@ export function bindDevice(agentId: string, code: string) {
return http.Post(`/device/bind/${agentId}/${code}`, null)
}
/**
* 手动添加设备
* @param agentId 智能体ID
* @param board 设备类型
* @param appVersion 固件版本
* @param macAddress MAC地址
*/
export function bindDeviceManual(data: {
agentId: string
board: string
appVersion: string
macAddress: string
}) {
return http.Post('/device/manual-add', data)
}
/**
* 设置设备OTA升级开关
* @param deviceId 设备ID (MAC地址)
@@ -1,467 +0,0 @@
<script lang="ts" setup>
import { ref, watch } from 'vue'
import { t } from '@/i18n'
interface Props {
visible?: boolean
providers?: Array<{
url: string
headers: Record<string, string>
}>
}
const props = withDefaults(defineProps<Props>(), {
visible: false,
providers: () => [],
})
const emit = defineEmits<{
'update:visible': [value: boolean]
'confirm': [value: Array<{
url: string
headers: Record<string, string>
}>]
}>()
const localProviders = ref<Array<{
url: string
headers: Array<{
key: string
value: string
}>
}>>([])
function initLocalData() {
localProviders.value = props.providers.map((p) => {
const headers = p.headers || {}
return {
url: p.url || '',
headers: Object.entries(headers).map(([key, value]) => ({ key, value })),
}
})
if (localProviders.value.length === 0) {
localProviders.value.push({ url: '', headers: [{ key: '', value: '' }] })
}
}
function addProvider(index: number) {
localProviders.value.splice(index, 0, {
url: '',
headers: [{ key: '', value: '' }],
})
}
function removeProvider(index: number) {
localProviders.value.splice(index, 1)
}
function addHeader(pIndex: number, hIndex: number) {
localProviders.value[pIndex].headers.splice(hIndex, 0, { key: '', value: '' })
}
function removeHeader(pIndex: number, hIndex: number) {
localProviders.value[pIndex].headers.splice(hIndex, 1)
}
function handleConfirm() {
const result = localProviders.value
.filter(p => p.url.trim() !== '')
.map((p) => {
const headersObj: Record<string, string> = {}
p.headers.forEach((h) => {
if (h.key.trim()) {
headersObj[h.key.trim()] = h.value
}
})
return {
url: p.url.trim(),
headers: headersObj,
}
})
emit('confirm', result)
emit('update:visible', false)
}
function handleClose() {
emit('update:visible', false)
}
watch(() => props.visible, (val) => {
if (val) {
initLocalData()
}
})
</script>
<template>
<view v-if="visible" class="context-provider-dialog-mask" @click="handleClose">
<view class="context-provider-dialog" @click.stop>
<view class="dialog-header">
<text class="dialog-title">
{{ t('contextProviderDialog.title') }}
</text>
</view>
<view class="dialog-content">
<view v-if="localProviders.length === 0" class="empty-container">
<text class="empty-text">
{{ t('contextProviderDialog.noContextApi') }}
</text>
<wd-button type="primary" size="small" @click="addProvider(0)">
{{ t('contextProviderDialog.add') }}
</wd-button>
</view>
<view v-else class="providers-list">
<view v-for="(provider, pIndex) in localProviders" :key="pIndex" class="provider-item">
<view class="provider-card">
<view class="card-header">
<view class="card-controls">
<wd-button
type="icon"
icon="add"
size="small"
class="!h-[60rpx] !w-[60rpx] !bg-[#66b1ff] !text-[#fff]"
@click="addProvider(pIndex + 1)"
/>
<wd-button
type="icon"
icon="decrease"
size="small"
class="!h-[60rpx] !w-[60rpx] !bg-[#F56C6C] !text-[#fff]"
@click="removeProvider(pIndex)"
/>
</view>
</view>
<view class="input-row">
<text class="label-text">
{{ t('contextProviderDialog.apiUrl') }}
</text>
<wd-input
v-model="provider.url"
class="flex-1"
:placeholder="t('contextProviderDialog.apiUrlPlaceholder')"
/>
</view>
<view class="headers-section">
<view class="label-text" style="margin-top: 6px;">
{{ t('contextProviderDialog.requestHeaders') }}
</view>
<view class="headers-list">
<view
v-for="(header, hIndex) in provider.headers"
:key="hIndex"
class="header-row-vertical"
>
<view class="header-input-group">
<wd-input
v-model="header.key"
placeholder="key"
class="header-input-full"
/>
</view>
<view class="header-input-group">
<wd-input
v-model="header.value"
placeholder="value"
class="header-input-full"
/>
</view>
<view class="header-controls">
<wd-button
type="icon"
icon="add"
size="small"
class="!h-[50rpx] !w-[50rpx] !border-[1rpx] !border-solid !bg-[#ecf5ff] !text-[#b3d8ff]"
@click="addHeader(pIndex, hIndex + 1)"
/>
<wd-button
type="icon"
icon="decrease"
size="small"
class="!h-[50rpx] !w-[50rpx] !border-[1rpx] !border-solid !bg-[#fef0f0] !text-[#F56C6C]"
@click="removeHeader(pIndex, hIndex)"
/>
</view>
</view>
<view v-if="provider.headers.length === 0" class="header-row empty-header">
<text class="no-header-text">
{{ t('contextProviderDialog.noHeaders') }}
</text>
<wd-button type="text" size="small" @click="addHeader(pIndex, 0)">
{{ t('contextProviderDialog.addHeader') }}
</wd-button>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
<view class="dialog-footer">
<wd-button class="cancel-btn" @click="handleClose">
{{ t('contextProviderDialog.cancel') }}
</wd-button>
<wd-button type="primary" class="confirm-btn" @click="handleConfirm">
{{ t('contextProviderDialog.confirm') }}
</wd-button>
</view>
</view>
</view>
</template>
<style scoped lang="scss">
.context-provider-dialog-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
padding: 0;
}
.context-provider-dialog {
background: #fff;
// border-radius: 20rpx;
width: 100%;
height: 100vh;
display: flex;
flex-direction: column;
overflow: hidden;
}
.dialog-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx;
border-bottom: 1px solid #eee;
}
.dialog-title {
font-size: 32rpx;
font-weight: 600;
color: #232338;
}
.close-icon {
font-size: 40rpx;
color: #9d9ea3;
cursor: pointer;
}
.dialog-content {
flex: 1;
overflow-y: auto;
padding: 20rpx;
}
.empty-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 100rpx 0;
gap: 30rpx;
}
.empty-text {
font-size: 28rpx;
color: #9d9ea3;
}
.providers-list {
display: flex;
flex-direction: column;
}
.provider-item {
margin-bottom: 30rpx;
}
.provider-card {
flex: 1;
background: #fff;
border-radius: 16rpx;
border: 1px solid #eee;
border-left: 6rpx solid #336cff;
padding: 20rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.05);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30rpx;
}
.card-title {
font-size: 30rpx;
font-weight: 600;
color: #232338;
}
.card-controls {
display: flex;
gap: 16rpx;
}
.input-row {
display: flex;
align-items: center;
gap: 20rpx;
margin-bottom: 40rpx;
}
.headers-section {
display: flex;
gap: 20rpx;
align-items: flex-start;
}
.headers-list {
flex: 1;
display: flex;
flex-direction: column;
gap: 20rpx;
background: #fcfcfc;
padding: 4rpx;
border-radius: 12rpx;
border: 1px dashed #dcdfe6;
}
.header-row-vertical {
display: flex;
flex-direction: column;
gap: 16rpx;
background: #fff;
// padding: 20rpx;
border-radius: 12rpx;
// border: 1px solid #e8e8e8;
}
.header-input-group {
display: flex;
align-items: center;
gap: 8rpx;
}
.header-key-label,
.header-value-label {
font-size: 24rpx;
font-weight: 500;
color: #606266;
}
.header-input-full {
width: 100%;
}
.header-controls {
display: flex;
gap: 12rpx;
// margin-top: 8rpx;
align-self: flex-start;
}
.block-controls {
display: flex;
flex-direction: column;
gap: 16rpx;
padding-top: 10rpx;
}
.input-row {
display: flex;
align-items: center;
gap: 20rpx;
margin-bottom: 20rpx;
}
.label-text {
font-size: 28rpx;
font-weight: 600;
color: #606266;
width: 140rpx;
}
.headers-section {
display: flex;
gap: 20rpx;
align-items: flex-start;
}
.headers-list {
flex: 1;
display: flex;
flex-direction: column;
gap: 20rpx;
background: #fcfcfc;
padding: 16rpx;
border-radius: 12rpx;
border: 1px dashed #dcdfe6;
}
.header-row {
display: flex;
align-items: center;
gap: 16rpx;
}
.header-input {
width: 200rpx;
}
.separator {
color: #909399;
font-weight: bold;
font-size: 32rpx;
}
.row-controls {
display: flex;
gap: 12rpx;
margin-left: 8rpx;
flex-shrink: 0;
}
.empty-header {
justify-content: center;
padding: 20rpx;
color: #909399;
font-size: 26rpx;
}
.no-header-text {
margin-right: 16rpx;
}
.flex-1 {
flex: 1;
}
.dialog-footer {
display: flex;
gap: 20rpx;
padding: 30rpx;
border-top: 1px solid #eee;
}
.cancel-btn,
.confirm-btn {
flex: 1;
height: 80rpx;
border-radius: 12rpx;
font-size: 28rpx;
}
</style>
@@ -1,270 +0,0 @@
<script lang="ts" setup>
import { ref, watch } from 'vue'
import { t } from '@/i18n'
interface Props {
visible?: boolean
settings?: {
volume: number
speed: number
pitch: number
}
}
const props = withDefaults(defineProps<Props>(), {
visible: false,
settings: () => ({
volume: 0,
speed: 0,
pitch: 0,
}),
})
const emit = defineEmits<{
'update:visible': [value: boolean]
'confirm': [value: {
volume: number
speed: number
pitch: number
}]
}>()
const localSettings = ref({
volume: 0,
speed: 0,
pitch: 0,
})
function initLocalData() {
localSettings.value = {
volume: props.settings.volume || 0,
speed: props.settings.speed || 0,
pitch: props.settings.pitch || 0,
}
}
function handleConfirm() {
emit('confirm', localSettings.value)
emit('update:visible', false)
}
function handleClose() {
emit('update:visible', false)
}
watch(() => props.visible, (val) => {
if (val) {
initLocalData()
}
})
</script>
<template>
<view v-if="visible" class="voice-settings-dialog-mask" @click="handleClose">
<view class="voice-settings-dialog" @click.stop>
<view class="dialog-header">
<text class="dialog-title">
{{ t('agent.languageConfig') }}
</text>
</view>
<view class="dialog-content">
<!-- 音量调节 -->
<view class="setting-item">
<text class="setting-label">
{{ t('agent.ttsVolume') }}
</text>
<view class="slider-container">
<wd-slider
v-model="localSettings.volume"
:min="-100"
:max="100"
:step="1"
:show-value="false"
custom-class="voice-slider"
/>
<text class="slider-value">
{{ localSettings.volume }}
</text>
</view>
<text class="setting-desc">
{{ t('agent.volumeHint') }}
</text>
</view>
<!-- 语速调节 -->
<view class="setting-item">
<text class="setting-label">
{{ t('agent.ttsRate') }}
</text>
<view class="slider-container">
<wd-slider
v-model="localSettings.speed"
:min="-100"
:max="100"
:step="1"
:show-value="false"
custom-class="voice-slider"
/>
<text class="slider-value">
{{ localSettings.speed }}
</text>
</view>
<text class="setting-desc">
{{ t('agent.speedHint') }}
</text>
</view>
<!-- 音调调节 -->
<view class="setting-item">
<text class="setting-label">
{{ t('agent.ttsPitch') }}
</text>
<view class="slider-container">
<wd-slider
v-model="localSettings.pitch"
:min="-100"
:max="100"
:step="1"
:show-value="false"
custom-class="voice-slider"
/>
<text class="slider-value">
{{ localSettings.pitch }}
</text>
</view>
<text class="setting-desc">
{{ t('agent.pitchHint') }}
</text>
</view>
</view>
<view class="dialog-footer">
<wd-button class="cancel-btn" @click="handleClose">
{{ t('agent.cancel') }}
</wd-button>
<wd-button type="primary" class="confirm-btn" @click="handleConfirm">
{{ t('agent.save') }}
</wd-button>
</view>
</view>
</view>
</template>
<style scoped lang="scss">
.voice-settings-dialog-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
padding: 0;
}
.voice-settings-dialog {
background: #fff;
width: 100%;
height: 100vh;
display: flex;
flex-direction: column;
overflow: hidden;
}
.dialog-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
border-bottom: 1px solid #eee;
}
.dialog-title {
font-size: 32rpx;
font-weight: 600;
color: #232338;
}
.close-icon {
font-size: 40rpx;
color: #9d9ea3;
cursor: pointer;
}
.dialog-content {
flex: 1;
overflow-y: auto;
padding: 40rpx;
display: flex;
flex-direction: column;
gap: 50rpx;
}
.setting-item {
display: flex;
flex-direction: column;
gap: 20rpx;
}
.setting-label {
font-size: 30rpx;
font-weight: 600;
color: #232338;
}
.slider-container {
display: flex;
align-items: center;
gap: 20rpx;
}
.voice-slider {
flex: 1;
}
.slider-value {
font-size: 28rpx;
color: #336cff;
font-weight: 500;
min-width: 80rpx;
text-align: right;
}
.setting-desc {
font-size: 24rpx;
color: #9d9ea3;
margin-top: 10rpx;
}
.dialog-footer {
display: flex;
gap: 20rpx;
padding: 30rpx;
border-top: 1px solid #eee;
}
.cancel-btn,
.confirm-btn {
flex: 1;
height: 80rpx;
border-radius: 12rpx;
font-size: 28rpx;
}
.confirm-btn {
background-color: #336cff !important;
}
/* 自定义滑块样式 */
:deep(.wd-slider) {
--wd-slider-bar-background: #e6ebff;
--wd-slider-bar-active-background: #336cff;
--wd-slider-thumb-border-color: #336cff;
--wd-slider-thumb-background: #336cff;
--wd-slider-thumb-size: 32rpx;
}
</style>
+17 -3
View File
@@ -42,6 +42,20 @@ const alovaInstance = createAlova({
statesHook: VueHook,
beforeRequest: onAuthRequired((method) => {
// h5动态获取最新的 baseURL,确保使用用户设置的服务器地址
const currentBaseUrl = getEnvBaseUrl()
if (currentBaseUrl !== method.baseURL) {
method.baseURL = currentBaseUrl
}
// 检查混合内容错误(HTTPS页面请求HTTP接口)
const currentProtocol = typeof window !== 'undefined' && window.location.protocol
const requestProtocol = method.baseURL?.split(':')[0]
if (currentProtocol === 'https:' && requestProtocol === 'http') {
const errorMessage = '无法配置http协议地址,请检查接口地址'
throw new Error(errorMessage)
}
// 设置默认 Content-Type
method.config.headers = {
'Content-Type': ContentTypeEnum.JSON,
@@ -55,14 +69,14 @@ const alovaInstance = createAlova({
// 处理认证信息
if (!ignoreAuth) {
const token = uni.getStorageSync('token')
if (!token) {
const authInfo = JSON.parse(uni.getStorageSync('token') || '{}')
if (!authInfo.token) {
// 跳转到登录页
uni.reLaunch({ url: '/pages/login/index' })
throw new Error('[请求错误]:未登录')
}
// 添加 Authorization 头
method.config.headers.Authorization = `Bearer ${token}`
method.config.headers.Authorization = `Bearer ${authInfo.token}`
}
// 处理动态域名
@@ -12,6 +12,7 @@ export enum ResultEnum {
ServiceUnavailable = 503, // 服务不可用(原为serviceUnavailable
GatewayTimeout = 504, // 网关超时(原为gatewayTimeout
HttpVersionNotSupported = 505, // HTTP版本不支持(原为httpVersionNotSupported
MixedContent = 600, // 混合内容错误(HTTPS页面请求HTTP接口)
}
export enum ContentTypeEnum {
JSON = 'application/json;charset=UTF-8',
@@ -59,6 +60,9 @@ export function ShowMessage(status: number | string): string {
case 505:
message = 'HTTP版本不受支持(505)'
break
case 600:
message = '混合内容错误(600)'
break
default:
message = `连接出错(${status})!`
}
+22 -1
View File
@@ -30,6 +30,8 @@ export default {
'login.requiredMobile': 'Bitte gültige Handynummer eingeben',
'login.captchaError': 'Grafischer Bestätigungscode Fehler',
'login.forgotPassword': 'Passwort vergessen',
'login.userAgreement': 'Nutzungsbedingungen',
'login.privacyPolicy': 'Datenschutzrichtlinie',
// Registrierungsseite
'register.pageTitle': 'Registrieren',
@@ -150,6 +152,25 @@ export default {
'contextProviderDialog.cancel': 'Abbrechen',
'contextProviderDialog.confirm': 'Bestätigen',
// Manual add device dialog related
'manualAddDeviceDialog.title': 'Manuell Gerät hinzufügen',
'manualAddDeviceDialog.deviceType': 'Gerätetyp',
'manualAddDeviceDialog.deviceTypePlaceholder': 'Bitte Gerätetyp auswählen',
'manualAddDeviceDialog.firmwareVersion': 'Firmware-Version',
'manualAddDeviceDialog.firmwareVersionPlaceholder': 'Bitte Firmware-Version eingeben',
'manualAddDeviceDialog.macAddress': 'Mac-Adresse',
'manualAddDeviceDialog.macAddressPlaceholder': 'Bitte Mac-Adresse eingeben',
'manualAddDeviceDialog.confirm': 'Bestätigen',
'manualAddDeviceDialog.cancel': 'Abbrechen',
'manualAddDeviceDialog.requiredMacAddress': 'Bitte Mac-Adresse eingeben',
'manualAddDeviceDialog.invalidMacAddress': 'Bitte korrektes Mac-Adressformat eingeben, z.B.: 00:1A:2B:3C:4D:5E',
'manualAddDeviceDialog.requiredDeviceType': 'Bitte Gerätetyp auswählen',
'manualAddDeviceDialog.requiredFirmwareVersion': 'Bitte Firmware-Version eingeben',
'manualAddDeviceDialog.getFirmwareTypeFailed': 'Firmware-Typ konnte nicht abgerufen werden',
'manualAddDeviceDialog.addSuccess': 'Gerät erfolgreich hinzugefügt',
'manualAddDeviceDialog.addFailed': 'Hinzufügen fehlgeschlagen',
'manualAddDeviceDialog.bindWithCode': 'Mit 6-stelligem Code binden',
// Chat-Verlauf Seite
'chatHistory.getChatSessions': 'Chat-Sitzungsliste abrufen',
'chatHistory.noSelectedAgent': 'Kein Agent ausgewählt',
@@ -335,7 +356,7 @@ export default {
'message.unbindFail': 'Entbinden fehlgeschlagen',
'message.networkError': 'Netzwerkfehler, bitte Verbindung prüfen',
'message.serverError': 'Serverfehler, bitte später erneut versuchen',
'message.invalidAddress': 'Ungültige Adresse, bitte prüfen ob Server gestartet oder Netzwerkverbindung normal ist',
'message.invalidAddress': 'Die Adresse kann ungültig sein. Bitte überprüfen Sie, ob der Server gestartet ist oder ob die Netzwerkverbindung funktioniert. Es kann auch sein, dass die Anfrage aufgrund eines Problems mit dem HTTPS-Protokoll nicht gesendet werden kann.',
'message.languageChanged': 'Sprache geändert',
'message.passwordError': 'Konto oder Passwort Fehler',
'message.phoneRegistered': 'Diese Handynummer wurde bereits registriert',
+22 -1
View File
@@ -30,6 +30,8 @@ export default {
'login.requiredMobile': 'Please enter a valid phone number',
'login.captchaError': 'Graphic verification code error',
'login.forgotPassword': 'Forgot Password',
'login.userAgreement': 'User Agreement',
'login.privacyPolicy': 'Privacy Policy',
// Register page
'register.pageTitle': 'Register',
@@ -150,6 +152,25 @@ export default {
'contextProviderDialog.cancel': 'Cancel',
'contextProviderDialog.confirm': 'Confirm',
// Manual add device dialog related
'manualAddDeviceDialog.title': 'Manual Add Device',
'manualAddDeviceDialog.deviceType': 'Device Type',
'manualAddDeviceDialog.deviceTypePlaceholder': 'Please select device type',
'manualAddDeviceDialog.firmwareVersion': 'Firmware Version',
'manualAddDeviceDialog.firmwareVersionPlaceholder': 'Please enter firmware version',
'manualAddDeviceDialog.macAddress': 'Mac Address',
'manualAddDeviceDialog.macAddressPlaceholder': 'Please enter Mac address',
'manualAddDeviceDialog.confirm': 'Confirm',
'manualAddDeviceDialog.cancel': 'Cancel',
'manualAddDeviceDialog.requiredMacAddress': 'Please enter Mac address',
'manualAddDeviceDialog.invalidMacAddress': 'Please enter correct Mac address format, e.g.: 00:1A:2B:3C:4D:5E',
'manualAddDeviceDialog.requiredDeviceType': 'Please select device type',
'manualAddDeviceDialog.requiredFirmwareVersion': 'Please enter firmware version',
'manualAddDeviceDialog.getFirmwareTypeFailed': 'Failed to get firmware type',
'manualAddDeviceDialog.addSuccess': 'Device added successfully',
'manualAddDeviceDialog.addFailed': 'Failed to add',
'manualAddDeviceDialog.bindWithCode': 'Bind with 6-digit code',
// Chat History Page
'chatHistory.getChatSessions': 'Get chat session list',
'chatHistory.noSelectedAgent': 'No agent selected',
@@ -335,7 +356,7 @@ export default {
'message.unbindFail': 'Unbinding failed',
'message.networkError': 'Network error, please check your connection',
'message.serverError': 'Server error, please try again later',
'message.invalidAddress': 'Invalid address, please check if the server is started or network connection is normal',
'message.invalidAddress': 'The address may be invalid. Please check if the server is started or if the network connection is normal; It is also possible that requests cannot be sent due to HTTPS protocol issues',
'message.languageChanged': 'Language changed',
'message.passwordError': 'Account or password error',
'message.phoneRegistered': 'This phone number has been registered',
+23 -2
View File
@@ -29,7 +29,9 @@ export default {
'login.requiredCaptcha': 'O código de verificação não pode estar vazio',
'login.requiredMobile': 'Por favor, insira um número de telefone válido',
'login.captchaError': 'Erro no código de verificação gráfico',
'login.forgotPassword': 'Esqueceu a Senha',
'login.forgotPassword': 'Esqueceu a Senha',
'login.userAgreement': 'Termos de Uso',
'login.privacyPolicy': 'Política de Privacidade',
// Register page
'register.pageTitle': 'Cadastro',
@@ -150,6 +152,25 @@ export default {
'contextProviderDialog.cancel': 'Cancelar',
'contextProviderDialog.confirm': 'Confirmar',
// Diálogo de adição manual de dispositivo
'manualAddDeviceDialog.title': 'Adicionar Dispositivo Manualmente',
'manualAddDeviceDialog.deviceType': 'Tipo de Dispositivo',
'manualAddDeviceDialog.deviceTypePlaceholder': 'Por favor, selecione o tipo de dispositivo',
'manualAddDeviceDialog.firmwareVersion': 'Versão do Firmware',
'manualAddDeviceDialog.firmwareVersionPlaceholder': 'Por favor, insira a versão do firmware',
'manualAddDeviceDialog.macAddress': 'Endereço MAC',
'manualAddDeviceDialog.macAddressPlaceholder': 'Por favor, insira o endereço MAC',
'manualAddDeviceDialog.confirm': 'Confirmar',
'manualAddDeviceDialog.cancel': 'Cancelar',
'manualAddDeviceDialog.requiredMacAddress': 'Por favor, insira o endereço MAC',
'manualAddDeviceDialog.invalidMacAddress': 'Por favor, insira o formato correto de endereço MAC, ex.: 00:1A:2B:3C:4D:5E',
'manualAddDeviceDialog.requiredDeviceType': 'Por favor, selecione o tipo de dispositivo',
'manualAddDeviceDialog.requiredFirmwareVersion': 'Por favor, insira a versão do firmware',
'manualAddDeviceDialog.getFirmwareTypeFailed': 'Falha ao obter tipo de firmware',
'manualAddDeviceDialog.addSuccess': 'Dispositivo adicionado com sucesso',
'manualAddDeviceDialog.addFailed': 'Falha ao adicionar',
'manualAddDeviceDialog.bindWithCode': 'Vincular com Código de 6 Dígitos',
// Chat History Page
'chatHistory.getChatSessions': 'Obter lista de sessões de conversa',
'chatHistory.noSelectedAgent': 'Nenhum agente selecionado',
@@ -335,7 +356,7 @@ export default {
'message.unbindFail': 'Falha na desvinculação',
'message.networkError': 'Erro de rede, por favor verifique sua conexão',
'message.serverError': 'Erro no servidor, por favor tente novamente mais tarde',
'message.invalidAddress': 'Endereço inválido, por favor verifique se o servidor está iniciado ou se a conexão de rede está normal',
'message.invalidAddress': 'O endereço pode ser inválido, verifique se o servidor está iniciado ou se a conexão de rede está correta; Também pode ser impossível enviar solicitações devido a problemas com o protocolo HTTPS',
'message.languageChanged': 'Idioma alterado',
'message.passwordError': 'Conta ou senha incorretos',
'message.phoneRegistered': 'Este número de telefone já está cadastrado',
+22 -1
View File
@@ -30,6 +30,8 @@ export default {
'login.requiredMobile': 'Vui lòng nhập số điện thoại hợp lệ',
'login.captchaError': 'Lỗi mã xác minh đồ họa',
'login.forgotPassword': 'Quên mật khẩu',
'login.userAgreement': 'Thỏa thuận người dùng',
'login.privacyPolicy': 'Chính sách bảo mật',
// Trang đăng ký
'register.pageTitle': 'Đăng ký',
@@ -150,6 +152,25 @@ export default {
'contextProviderDialog.cancel': 'Hủy bỏ',
'contextProviderDialog.confirm': 'Xác nhận',
// Manual add device dialog related
'manualAddDeviceDialog.title': 'Thêm thiết bị thủ công',
'manualAddDeviceDialog.deviceType': 'Loại thiết bị',
'manualAddDeviceDialog.deviceTypePlaceholder': 'Vui lòng chọn loại thiết bị',
'manualAddDeviceDialog.firmwareVersion': 'Phiên bản firmware',
'manualAddDeviceDialog.firmwareVersionPlaceholder': 'Vui lòng nhập phiên bản firmware',
'manualAddDeviceDialog.macAddress': 'Địa chỉ Mac',
'manualAddDeviceDialog.macAddressPlaceholder': 'Vui lòng nhập địa chỉ Mac',
'manualAddDeviceDialog.confirm': 'Xác nhận',
'manualAddDeviceDialog.cancel': 'Hủy bỏ',
'manualAddDeviceDialog.requiredMacAddress': 'Vui lòng nhập địa chỉ Mac',
'manualAddDeviceDialog.invalidMacAddress': 'Vui lòng nhập đúng định dạng địa chỉ Mac, ví dụ: 00:1A:2B:3C:4D:5E',
'manualAddDeviceDialog.requiredDeviceType': 'Vui lòng chọn loại thiết bị',
'manualAddDeviceDialog.requiredFirmwareVersion': 'Vui lòng nhập phiên bản firmware',
'manualAddDeviceDialog.getFirmwareTypeFailed': 'Không thể lấy loại firmware',
'manualAddDeviceDialog.addSuccess': 'Đã thêm thiết bị thành công',
'manualAddDeviceDialog.addFailed': 'Thêm thất bại',
'manualAddDeviceDialog.bindWithCode': 'Liên kết bằng mã xác minh 6 chữ số',
// Trang lịch sử trò chuyện
'chatHistory.getChatSessions': 'Lấy danh sách phiên trò chuyện',
'chatHistory.noSelectedAgent': 'Chưa chọn đại lý',
@@ -335,7 +356,7 @@ export default {
'message.unbindFail': 'Hủy liên kết thất bại',
'message.networkError': 'Lỗi mạng, vui lòng kiểm tra kết nối',
'message.serverError': 'Lỗi máy chủ, vui lòng thử lại sau',
'message.invalidAddress': 'Địa chỉ không hợp lệ, vui lòng kiểm tra máy chủ đã khởi động hoặc kết nối mạng bình thường',
'message.invalidAddress': 'Địa chỉ có thể không hợp lệ. Vui lòng kiểm tra xem máy chủ đã khởi động hay kết nối mạng chưa; Có thể không gửi được yêu cầu do lỗi giao thức HTTPS',
'message.languageChanged': 'Đã thay đổi ngôn ngữ',
'message.passwordError': 'Lỗi tài khoản hoặc mật khẩu',
'message.phoneRegistered': 'Số điện thoại này đã được đăng ký',
+22 -1
View File
@@ -30,6 +30,8 @@ export default {
'login.requiredMobile': '请输入正确的手机号码',
'login.captchaError': '图形验证码错误',
'login.forgotPassword': '忘记密码',
'login.userAgreement': '用户协议',
'login.privacyPolicy': '隐私政策',
// 注册页面
'register.pageTitle': '注册',
@@ -150,6 +152,25 @@ export default {
'contextProviderDialog.cancel': '取消',
'contextProviderDialog.confirm': '确定',
// 手動添加設備對話框相關
'manualAddDeviceDialog.title': '手动添加设备',
'manualAddDeviceDialog.deviceType': '设备型号',
'manualAddDeviceDialog.deviceTypePlaceholder': '请选择设备型号',
'manualAddDeviceDialog.firmwareVersion': '固件版本',
'manualAddDeviceDialog.firmwareVersionPlaceholder': '请输入固件版本',
'manualAddDeviceDialog.macAddress': 'Mac地址',
'manualAddDeviceDialog.macAddressPlaceholder': '请输入Mac地址',
'manualAddDeviceDialog.confirm': '确定',
'manualAddDeviceDialog.cancel': '取消',
'manualAddDeviceDialog.requiredMacAddress': '请输入Mac地址',
'manualAddDeviceDialog.invalidMacAddress': '请输入正确的Mac地址格式,例如:00:1A:2B:3C:4D:5E',
'manualAddDeviceDialog.requiredDeviceType': '请选择设备型号',
'manualAddDeviceDialog.requiredFirmwareVersion': '请输入固件版本',
'manualAddDeviceDialog.getFirmwareTypeFailed': '获取固件类型失败',
'manualAddDeviceDialog.addSuccess': '设备添加成功',
'manualAddDeviceDialog.addFailed': '添加失败',
'manualAddDeviceDialog.bindWithCode': '6位验证码绑定',
// 聊天历史页面
'chatHistory.getChatSessions': '获取聊天会话列表',
'chatHistory.noSelectedAgent': '没有选中的智能体',
@@ -335,7 +356,7 @@ export default {
'message.unbindFail': '解绑失败',
'message.networkError': '网络错误,请检查网络连接',
'message.serverError': '服务器错误,请稍后再试',
'message.invalidAddress': '无效地址,请检查服务端是否启动或网络连接是否正常',
'message.invalidAddress': '地址可能无效,请检查服务端是否启动或网络连接是否正常;也可能因 HTTPS 协议问题导致无法发送请求',
'message.languageChanged': '语言已切换',
'message.passwordError': '账号或密码错误',
'message.phoneRegistered': '此手机号码已经注册过',
+23 -2
View File
@@ -29,7 +29,9 @@ export default {
'login.requiredCaptcha': '驗證碼不能為空',
'login.requiredMobile': '請輸入正確的手機號碼',
'login.captchaError': '圖形驗證碼錯誤',
'login.forgotPassword': '忘記密碼',
'login.forgotPassword': '忘記密碼',
'login.userAgreement': '用戶協議',
'login.privacyPolicy': '隱私政策',
// 忘記密碼頁面
'retrievePassword.title': '重置密碼',
@@ -171,6 +173,25 @@ export default {
'contextProviderDialog.cancel': '取消',
'contextProviderDialog.confirm': '確定',
// 手動添加設備對話框相關
'manualAddDeviceDialog.title': '手動添加設備',
'manualAddDeviceDialog.deviceType': '設備型號',
'manualAddDeviceDialog.deviceTypePlaceholder': '請選擇設備型號',
'manualAddDeviceDialog.firmwareVersion': '固件版本',
'manualAddDeviceDialog.firmwareVersionPlaceholder': '請輸入固件版本',
'manualAddDeviceDialog.macAddress': 'Mac地址',
'manualAddDeviceDialog.macAddressPlaceholder': '請輸入Mac地址',
'manualAddDeviceDialog.confirm': '確定',
'manualAddDeviceDialog.cancel': '取消',
'manualAddDeviceDialog.requiredMacAddress': '請輸入Mac地址',
'manualAddDeviceDialog.invalidMacAddress': '請輸入正確的Mac地址格式,例如:00:1A:2B:3C:4D:5E',
'manualAddDeviceDialog.requiredDeviceType': '請選擇設備型號',
'manualAddDeviceDialog.requiredFirmwareVersion': '請輸入固件版本',
'manualAddDeviceDialog.getFirmwareTypeFailed': '獲取固件類型失敗',
'manualAddDeviceDialog.addSuccess': '設備添加成功',
'manualAddDeviceDialog.addFailed': '添加失敗',
'manualAddDeviceDialog.bindWithCode': '6位驗證碼綁定',
// 聊天歷史頁面
'chatHistory.getChatSessions': '獲取聊天會話列表',
'chatHistory.noSelectedAgent': '沒有選中的智能體',
@@ -335,7 +356,7 @@ export default {
'message.unbindFail': '解除綁定失敗',
'message.networkError': '網絡錯誤,請檢查網絡連接',
'message.serverError': '服務器錯誤,請稍後再試',
'message.invalidAddress': '無效地址,請檢查服務端是否啟動或網絡連接是否正常',
'message.invalidAddress': '地址可能無效,請檢查服務端是否啟動或網絡連接是否正常; 也可能因HTTPS協定問題導致無法發送請求常',
'message.languageChanged': '語言已切換',
'message.passwordError': '帳號或密碼錯誤',
'message.phoneRegistered': '此手機號已經註冊過',
+31 -71
View File
@@ -2,10 +2,8 @@
import type { AgentDetail, ModelOption, PluginDefinition, RoleTemplate } from '@/api/agent/types'
import { computed, nextTick, onMounted, ref, watch } from 'vue'
import { getAgentDetail, getAgentTags, getAllLanguage, getModelOptions, getPluginFunctions, getRoleTemplates, updateAgent, updateAgentTags } from '@/api/agent/agent'
import ContextProviderDialog from '@/components/ContextProviderDialog.vue'
import VoiceSettingsDialog from '@/components/VoiceSettingsDialog.vue'
import { t } from '@/i18n'
import { usePluginStore } from '@/store'
import { usePluginStore, useProvider, useSpeedPitch } from '@/store'
import { toast } from '@/utils/toast'
defineOptions({
@@ -105,29 +103,17 @@ const pickerShow = ref<{
report: false,
})
const ttsSettings = ref({
volume: 0,
speed: 0,
pitch: 0,
})
const allFunctions = ref<PluginDefinition[]>([])
const dynamicTags = ref([])
const inputValue = ref('')
const inputVisible = ref(false)
const showContextProviderDialog = ref(false)
const currentContextProviders = ref([])
const showVoiceSettingsDialog = ref(false)
const voiceSettings = ref({
volume: 0,
speed: 0,
pitch: 0,
})
const languageOptions = ref([])
const isVisibleReport = ref(false)
// 使用插件store
const pluginStore = usePluginStore()
const speedPitchStore = useSpeedPitch()
const providerStore = useProvider()
// tabs
const tabList = [
@@ -174,27 +160,15 @@ function handleInputConfirm() {
// 打开上下文源编辑弹窗
function openContextProviderDialog() {
showContextProviderDialog.value = true
}
// 处理上下文源更新
function handleUpdateContext(providers: any[]) {
currentContextProviders.value = providers
}
function openVoiceSettingsDialog() {
showVoiceSettingsDialog.value = true
}
function handleUpdateVoiceSettings(settings: any) {
ttsSettings.value = settings
formData.value.ttsVolume = settings.volume
formData.value.ttsRate = settings.speed
formData.value.ttsPitch = settings.pitch
uni.navigateTo({
url: '/pages/agent/provider',
})
}
function handleRegulate() {
openVoiceSettingsDialog()
uni.navigateTo({
url: '/pages/agent/speedPitch',
})
}
// 加载智能体详情
@@ -211,14 +185,15 @@ async function loadAgentDetail() {
pluginStore.setCurrentAgentId(agentId.value)
pluginStore.setCurrentFunctions(detail.functions || [])
// 更新语速音调
speedPitchStore.updateSpeedPitch({
ttsVolume: detail.ttsVolume || 0,
ttsRate: detail.ttsRate || 0,
ttsPitch: detail.ttsPitch || 0,
})
// 加载上下文配置
currentContextProviders.value = (detail as any).contextProviders || []
// 加载语音设置
voiceSettings.value = {
volume: (detail as any).volume || 0,
speed: (detail as any).speed || 0,
pitch: (detail as any).pitch || 0,
}
providerStore.updateProviders(detail.contextProviders || [])
// 如果有TTS模型,加载对应的音色选项
if (detail.ttsModelId) {
@@ -373,17 +348,17 @@ function filterVoicesByLanguage() {
}
// 同步到ttsSettings(如果值为null,使用0作为显示默认值,但不修改form中的值)
ttsSettings.value = {
volume: formData.value.ttsVolume !== null && formData.value.ttsVolume !== undefined ? formData.value.ttsVolume : 0,
speed: formData.value.ttsRate !== null && formData.value.ttsRate !== undefined ? formData.value.ttsRate : 0,
pitch: formData.value.ttsPitch !== null && formData.value.ttsPitch !== undefined ? formData.value.ttsPitch : 0,
}
speedPitchStore.updateSpeedPitch({
ttsVolume: formData.value.ttsVolume !== null && formData.value.ttsVolume !== undefined ? formData.value.ttsVolume : 0,
ttsRate: formData.value.ttsRate !== null && formData.value.ttsRate !== undefined ? formData.value.ttsRate : 0,
ttsPitch: formData.value.ttsPitch !== null && formData.value.ttsPitch !== undefined ? formData.value.ttsPitch : 0,
})
}
// 根据语音合成模型加载语言
async function fetchAllLanguag(ttsModelId: string) {
try {
const res = await getAllLanguage(ttsModelId, '') as any[]
const res = await getAllLanguage(ttsModelId)
// 保存完整的音色信息
voiceDetails.value = res.reduce((acc, voice) => {
acc[voice.id] = voice
@@ -564,8 +539,9 @@ async function saveAgent() {
// 构建保存数据,包含上下文配置和语音设置
const saveData = {
...formData.value,
...speedPitchStore.speedPitch,
ttsLanguage: formData.value.language,
contextProviders: currentContextProviders.value,
contextProviders: providerStore.providers,
}
await updateAgent(agentId.value, saveData)
loadAgentDetail()
@@ -611,15 +587,6 @@ function handleTools() {
})
}
// 监听插件配置更新
function watchPluginUpdates() {
// 监听store中的插件配置变化
watch(() => pluginStore.currentFunctions, (newFunctions) => {
console.log('插件配置已更新:', newFunctions)
formData.value.functions = newFunctions
}, { deep: true })
}
// 获取智能体标签
async function loadAgentTags() {
try {
@@ -635,9 +602,12 @@ async function handleUpdateAgentTags() {
await updateAgentTags(agentId.value, { tagNames })
}
// 监听store中的插件配置变化
watch(() => pluginStore.currentFunctions, (newFunctions) => {
formData.value.functions = newFunctions
}, { deep: true })
onMounted(async () => {
// 初始化插件配置监听
watchPluginUpdates()
loadAgentTags()
// 先加载模型选项和角色模板
@@ -726,7 +696,7 @@ onMounted(async () => {
</text>
<view class="mt-0 flex flex-wrap items-center gap-[12rpx]">
<text class="text-[26rpx] text-[#65686f]">
{{ t('agent.contextProviderSuccess', { count: currentContextProviders.length }) }}
{{ t('agent.contextProviderSuccess', { count: providerStore.providers.length }) }}
</text>
<a class="text-[26rpx] text-[#5778ff] no-underline" href="https://github.com/xinnan-tech/xiaozhi-esp32-server/blob/main/docs/context-provider-integration.md" target="_blank">
{{ t('agent.contextProviderDocLink') }}
@@ -994,16 +964,6 @@ onMounted(async () => {
@close="onPickerCancel('report')"
@select="({ item }) => onPickerConfirm('report', item.value, item.name)"
/>
<ContextProviderDialog
v-model:visible="showContextProviderDialog"
:providers="currentContextProviders"
@confirm="handleUpdateContext"
/>
<VoiceSettingsDialog
v-model:visible="showVoiceSettingsDialog"
:settings="ttsSettings"
@confirm="handleUpdateVoiceSettings"
/>
</view>
</template>
@@ -0,0 +1,208 @@
<route lang="jsonc" type="page">
{
"layout": "default",
"style": {
"navigationBarTitleText": "编辑源",
"navigationStyle": "custom"
}
}
</route>
<script lang="ts" setup>
import type { Providers } from '@/api/agent/types'
import { onMounted, ref } from 'vue'
import { t } from '@/i18n'
import { useProvider } from '@/store/provider'
defineOptions({
name: 'Provider',
})
const providerStore = useProvider()
const localProviders = ref<Providers[]>([])
function initLocalData() {
localProviders.value = providerStore.providers.map((p) => {
const headers = p.headers || {}
return {
url: p.url,
headers: Object.entries(headers).map(([key, value]: [string, string]) => ({ key, value })),
}
})
if (localProviders.value.length === 0) {
localProviders.value.push({ url: '', headers: [{ key: '', value: '' }] })
}
}
function addProvider(index: number) {
localProviders.value.splice(index, 0, {
url: '',
headers: [{ key: '', value: '' }],
})
}
function removeProvider(index: number) {
localProviders.value.splice(index, 1)
}
function addHeader(pIndex: number, hIndex: number) {
localProviders.value[pIndex].headers.splice(hIndex, 0, { key: '', value: '' })
}
function removeHeader(pIndex: number, hIndex: number) {
localProviders.value[pIndex].headers.splice(hIndex, 1)
}
function handleConfirm() {
const result = localProviders.value
.filter(p => p.url.trim() !== '')
.map((p) => {
const headersObj = {}
p.headers.forEach((h) => {
if (h.key.trim()) {
headersObj[h.key.trim()] = h.value
}
})
return {
url: p.url.trim(),
headers: headersObj,
}
})
providerStore.updateProviders(result as Providers[])
goBack()
}
function goBack() {
uni.navigateBack()
}
onMounted(() => {
initLocalData()
})
</script>
<template>
<view class="h-screen flex flex-col bg-[#f5f7fb]">
<!-- 头部导航 -->
<wd-navbar
:title="t('contextProviderDialog.title')"
safe-area-inset-top
left-arrow
:bordered="false"
@click-left="goBack"
>
<template #left>
<wd-icon name="arrow-left" size="18" />
</template>
</wd-navbar>
<view class="flex-1 overflow-y-auto px-[20rpx] pt-[20rpx]">
<view v-if="localProviders.length === 0" class="flex flex-col items-center justify-center gap-[30rpx] py-[100rpx]">
<text class="text-[28rpx] text-[#9d9ea3]">
{{ t('contextProviderDialog.noContextApi') }}
</text>
<wd-button type="primary" size="small" @click="addProvider(0)">
{{ t('contextProviderDialog.add') }}
</wd-button>
</view>
<view v-else class="flex flex-col">
<view v-for="(provider, pIndex) in localProviders" :key="pIndex" class="mb-[30rpx]">
<view class="flex-1 border border-l-[6rpx] border-[#eee] border-l-[#336cff] rounded-[16rpx] bg-[#fff] p-[20rpx] shadow-[0_4rpx_16rpx_rgba(0,0,0,0.05)]">
<view class="mb-[30rpx] flex items-center justify-between">
<view class="flex gap-[16rpx]">
<wd-button
type="icon"
icon="add"
size="small"
class="!h-[60rpx] !w-[60rpx] !bg-[#66b1ff] !text-[#fff]"
@click="addProvider(pIndex + 1)"
/>
<wd-button
type="icon"
icon="decrease"
size="small"
class="!h-[60rpx] !w-[60rpx] !bg-[#F56C6C] !text-[#fff]"
@click="removeProvider(pIndex)"
/>
</view>
</view>
<view class="mb-[40rpx] flex items-center gap-[20rpx]">
<text class="w-[140rpx] text-[28rpx] text-[#606266] font-semibold">
{{ t('contextProviderDialog.apiUrl') }}
</text>
<wd-input
v-model="provider.url"
class="flex-1"
:placeholder="t('contextProviderDialog.apiUrlPlaceholder')"
/>
</view>
<view class="flex items-start gap-[20rpx]">
<view class="mt-[6rpx] w-[140rpx] text-[28rpx] text-[#606266] font-semibold">
{{ t('contextProviderDialog.requestHeaders') }}
</view>
<view class="flex flex-1 flex-col gap-[20rpx] border border-[#dcdfe6] rounded-[12rpx] border-dashed bg-[#fcfcfc] p-[4rpx]">
<view
v-for="(header, hIndex) in provider.headers"
:key="hIndex"
class="flex flex-col gap-[16rpx] rounded-[12rpx] bg-[#fff]"
>
<view class="flex items-center gap-[8rpx]">
<wd-input
v-model="header.key"
placeholder="key"
class="w-full"
/>
</view>
<view class="flex items-center gap-[8rpx]">
<wd-input
v-model="header.value"
placeholder="value"
class="w-full"
/>
</view>
<view class="flex self-start gap-[12rpx]">
<wd-button
type="icon"
icon="add"
size="small"
class="!h-[50rpx] !w-[50rpx] !border-[1rpx] !border-solid !bg-[#ecf5ff] !text-[#b3d8ff]"
@click="addHeader(pIndex, hIndex + 1)"
/>
<wd-button
type="icon"
icon="decrease"
size="small"
class="!h-[50rpx] !w-[50rpx] !border-[1rpx] !border-solid !bg-[#fef0f0] !text-[#F56C6C]"
@click="removeHeader(pIndex, hIndex)"
/>
</view>
</view>
<view v-if="provider.headers.length === 0" class="flex items-center justify-center py-[20rpx] text-[26rpx] text-[#909399]">
<text class="mr-[16rpx]">
{{ t('contextProviderDialog.noHeaders') }}
</text>
<wd-button type="text" size="small" @click="addHeader(pIndex, 0)">
{{ t('contextProviderDialog.addHeader') }}
</wd-button>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
<view class="flex gap-[20rpx] border-t border-[#eee] px-[30rpx] py-[30rpx]">
<wd-button type="primary" class="h-[80rpx] flex-1 rounded-[12rpx] text-[28rpx]" @click="handleConfirm">
{{ t('contextProviderDialog.confirm') }}
</wd-button>
</view>
</view>
</template>
<style scoped lang="scss">
</style>
@@ -0,0 +1,153 @@
<route lang="jsonc" type="page">
{
"layout": "default",
"style": {
"navigationBarTitleText": "语音设置",
"navigationStyle": "custom"
}
}
</route>
<script lang="ts" setup>
import { t } from '@/i18n'
import { useSpeedPitch } from '@/store'
defineOptions({
name: 'SpeedPitch',
})
const speedPitchStore = useSpeedPitch()
const localSettings = ref({
ttsVolume: 0,
ttsRate: 0,
ttsPitch: 0,
})
function handleConfirm() {
speedPitchStore.updateSpeedPitch(localSettings.value)
goBack()
}
// 返回上一页并更新配置
function goBack() {
uni.navigateBack()
}
onMounted(() => {
localSettings.value = {
ttsVolume: speedPitchStore.speedPitch.ttsVolume,
ttsRate: speedPitchStore.speedPitch.ttsRate,
ttsPitch: speedPitchStore.speedPitch.ttsPitch,
}
})
</script>
<template>
<view class="h-screen flex flex-col bg-[#f5f7fb]">
<!-- 头部导航 -->
<wd-navbar
:title="t('agent.languageConfig')"
safe-area-inset-top
left-arrow
:bordered="false"
@click-left="goBack"
>
<template #left>
<wd-icon name="arrow-left" size="18" />
</template>
</wd-navbar>
<view class="flex flex-1 flex-col overflow-hidden">
<view class="flex flex-1 flex-col gap-[50rpx] overflow-y-auto px-[40rpx] py-[50rpx]">
<!-- 音量调节 -->
<view class="flex flex-col gap-[20rpx]">
<text class="text-[30rpx] text-[#232338] font-semibold">
{{ t('agent.ttsVolume') }}
</text>
<view class="flex items-center gap-[20rpx]">
<wd-slider
v-model="localSettings.ttsVolume"
:min="-100"
:max="100"
:step="1"
:show-value="false"
custom-class="voice-slider"
class="flex-1"
/>
<text class="min-w-[80rpx] text-right text-[28rpx] text-[#336cff] font-medium">
{{ localSettings.ttsVolume }}
</text>
</view>
<text class="mt-[10rpx] text-[24rpx] text-[#9d9ea3]">
{{ t('agent.volumeHint') }}
</text>
</view>
<!-- 语速调节 -->
<view class="flex flex-col gap-[20rpx]">
<text class="text-[30rpx] text-[#232338] font-semibold">
{{ t('agent.ttsRate') }}
</text>
<view class="flex items-center gap-[20rpx]">
<wd-slider
v-model="localSettings.ttsRate"
:min="-100"
:max="100"
:step="1"
:show-value="false"
custom-class="voice-slider"
class="flex-1"
/>
<text class="min-w-[80rpx] text-right text-[28rpx] text-[#336cff] font-medium">
{{ localSettings.ttsRate }}
</text>
</view>
<text class="mt-[10rpx] text-[24rpx] text-[#9d9ea3]">
{{ t('agent.speedHint') }}
</text>
</view>
<!-- 音调调节 -->
<view class="flex flex-col gap-[20rpx]">
<text class="text-[30rpx] text-[#232338] font-semibold">
{{ t('agent.ttsPitch') }}
</text>
<view class="flex items-center gap-[20rpx]">
<wd-slider
v-model="localSettings.ttsPitch"
:min="-100"
:max="100"
:step="1"
:show-value="false"
custom-class="voice-slider"
class="flex-1"
/>
<text class="min-w-[80rpx] text-right text-[28rpx] text-[#336cff] font-medium">
{{ localSettings.ttsPitch }}
</text>
</view>
<text class="mt-[10rpx] text-[24rpx] text-[#9d9ea3]">
{{ t('agent.pitchHint') }}
</text>
</view>
</view>
<view class="flex gap-[20rpx] border-t border-[#eee] px-[30rpx] py-[30rpx]">
<wd-button type="primary" class="h-[80rpx] flex-1 rounded-[12rpx] text-[28rpx] !bg-[#336cff]" @click="handleConfirm">
{{ t('agent.save') }}
</wd-button>
</view>
</view>
</view>
</template>
<style scoped lang="scss">
/* 自定义滑块样式 */
:deep(.wd-slider) {
--wd-slider-bar-background: #e6ebff;
--wd-slider-bar-active-background: #336cff;
--wd-slider-thumb-border-color: #336cff;
--wd-slider-thumb-background: #336cff;
--wd-slider-thumb-size: 32rpx;
}
</style>
+29 -27
View File
@@ -11,8 +11,8 @@
<script lang="ts" setup>
import { useMessage } from 'wot-design-uni'
import { getMcpAddress, getMcpTools } from '@/api/agent/agent'
import { usePluginStore } from '@/store'
import { t } from '@/i18n'
import { usePluginStore } from '@/store'
const message = useMessage()
const pluginStore = usePluginStore()
@@ -30,8 +30,8 @@ const mcpAddress = ref('')
const mcpTools = ref<string[]>([])
// 初始化时从本地存储加载MCP地址
if (uni.getStorageSync('cachedMcpAddress_' + agentId.value)) {
mcpAddress.value = uni.getStorageSync('cachedMcpAddress_' + agentId.value)
if (uni.getStorageSync(`cachedMcpAddress_${agentId.value}`)) {
mcpAddress.value = uni.getStorageSync(`cachedMcpAddress_${agentId.value}`)
}
// 参数编辑相关
@@ -67,16 +67,18 @@ async function mergeFunctions() {
const address = await getMcpAddress(agentId.value)
mcpAddress.value = address
// 缓存到本地存储,下次打开页面可以立即显示
uni.setStorageSync('cachedMcpAddress_' + agentId.value, address)
} catch (error) {
uni.setStorageSync(`cachedMcpAddress_${agentId.value}`, address)
}
catch (error) {
console.error('获取MCP地址失败:', error)
}
// 异步获取MCP工具列表,不阻塞UI显示
try {
const tools = await getMcpTools(agentId.value)
mcpTools.value = tools || []
} catch (error) {
}
catch (error) {
console.error('获取MCP工具列表失败:', error)
}
}
@@ -85,12 +87,12 @@ async function mergeFunctions() {
// 添加插件到已选
function selectFunction(func: any) {
// 添加到已选列表
selectedList.value.push({
selectedList.value = [...selectedList.value, {
id: func.id,
name: func.name,
params: { ...func.params },
fieldsMeta: func.fieldsMeta,
})
}]
// 从未选列表中移除
notSelectedList.value = notSelectedList.value.filter(
@@ -206,15 +208,6 @@ function closeParamEdit() {
// 返回上一页并更新配置
function goBack() {
const finalFunctions = selectedList.value.map(f => ({
pluginId: f.id,
paramInfo: f.params,
}))
// 更新到store中
pluginStore.updateFunctions(finalFunctions)
// 直接返回
uni.navigateBack()
}
@@ -229,11 +222,11 @@ function copyMcpAddress() {
data: mcpAddress.value,
showToast: false,
success: () => {
message.alert(t('agent.tools.mcpAddressCopied'))
},
message.alert(t('agent.tools.mcpAddressCopied'))
},
fail: () => {
message.alert(t('agent.tools.copyFailed'))
},
message.alert(t('agent.tools.copyFailed'))
},
})
}
@@ -254,6 +247,15 @@ function getFieldRemark(field: any) {
return description
}
// 监听已选列表变化,实时更新插件配置,避免用户不点击返回按钮导致配置丢失
watch(() => selectedList.value, (newSelectedList) => {
const finalFunctions = newSelectedList.map(f => ({
pluginId: f.id,
paramInfo: f.params,
}))
pluginStore.updateFunctions(finalFunctions)
})
onMounted(async () => {
// 直接从store获取数据并合并
await mergeFunctions()
@@ -407,11 +409,11 @@ onMounted(async () => {
class="flex-1 rounded-[10rpx] bg-[#f5f7fb] p-[20rpx]"
>
<view
class="ml-[20rpx] h-[70rpx] flex items-center justify-center rounded-[10rpx] bg-[#1677ff] px-[20rpx] text-[24rpx] text-white"
@click="copyMcpAddress"
>
{{ t('agent.tools.copy') }}
</view>
class="ml-[20rpx] h-[70rpx] flex items-center justify-center rounded-[10rpx] bg-[#1677ff] px-[20rpx] text-[24rpx] text-white"
@click="copyMcpAddress"
>
{{ t('agent.tools.copy') }}
</view>
</view>
<!-- 工具列表 -->
<view class="mt-[20rpx] flex-1 overflow-hidden">
+317 -20
View File
@@ -2,23 +2,28 @@
import type { Device, FirmwareType } from '@/api/device'
import { computed, onMounted, ref } from 'vue'
import { useMessage } from 'wot-design-uni'
import { bindDevice, getBindDevices, getFirmwareTypes, unbindDevice, updateDeviceAutoUpdate } from '@/api/device'
import { toast } from '@/utils/toast'
import { bindDevice, bindDeviceManual, getBindDevices, getFirmwareTypes, unbindDevice, updateDeviceAutoUpdate } from '@/api/device'
import { t } from '@/i18n'
import { toast } from '@/utils/toast'
defineOptions({
name: 'DeviceManage',
})
const props = withDefaults(defineProps<Props>(), {
agentId: 'default',
})
const actions = [
{ key: 'code', name: t('manualAddDeviceDialog.bindWithCode') },
{ key: 'manual', name: t('manualAddDeviceDialog.title') },
]
// 接收props
interface Props {
agentId?: string
}
const props = withDefaults(defineProps<Props>(), {
agentId: 'default'
})
// 获取屏幕边界到安全区域距离
let safeAreaInsets: any
let systemInfo: any
@@ -44,6 +49,45 @@ safeAreaInsets = systemInfo.safeAreaInsets
const deviceList = ref<Device[]>([])
const firmwareTypes = ref<FirmwareType[]>([])
const loading = ref(false)
const isBindDevice = ref(false)
// 手动绑定弹窗
const isManualBindDialog = ref(false)
const manualBindForm = ref({
board: '',
appVersion: '',
macAddress: '',
})
// 表单校验错误提示
const formErrors = ref({
board: '',
appVersion: '',
macAddress: '',
})
// MAC地址正则校验
const macRegex = /^(?:[0-9A-F]{2}[:-]){5}[0-9A-F]{2}$/i
function selectBindMode(row) {
if (row.item.key === 'code') {
openBindDialog()
}
else if (row.item.key === 'manual') {
// 打开弹窗前重置表单和错误提示
manualBindForm.value = {
board: '',
appVersion: '',
macAddress: '',
}
formErrors.value = {
board: '',
appVersion: '',
macAddress: '',
}
isManualBindDialog.value = true
}
}
// 消息组件
const message = useMessage()
@@ -56,11 +100,8 @@ const currentAgentId = computed(() => {
// 获取设备列表
async function loadDeviceList() {
try {
console.log('获取设备列表')
// 检查是否有当前选中的智能体
if (!currentAgentId.value) {
console.warn('没有选中的智能体')
deviceList.value = []
return
}
@@ -164,8 +205,8 @@ async function handleBindDevice(code: string) {
}
catch (error: any) {
console.error('绑定设备失败:', error)
const errorMessage = error?.message || '绑定失败,请检查验证码是否正确'
toast.error(errorMessage || t('device.bindFailed'))
const errorMessage = error?.message || t('device.bindFailed')
toast.error(errorMessage)
}
}
@@ -190,6 +231,128 @@ function openBindDialog() {
})
}
// 手动绑定设备
async function handleManualBind() {
try {
// 先校验整个表单
const isValid = validateForm()
if (!isValid) {
return
}
if (!currentAgentId.value) {
toast.error(t('device.pleaseSelectAgent'))
return
}
await bindDeviceManual({
agentId: currentAgentId.value,
board: manualBindForm.value.board,
appVersion: manualBindForm.value.appVersion,
macAddress: manualBindForm.value.macAddress,
})
await loadDeviceList()
toast.success(t('manualAddDeviceDialog.addSuccess'))
isManualBindDialog.value = false
// 重置表单和错误提示
manualBindForm.value = {
board: '',
appVersion: '',
macAddress: '',
}
formErrors.value = {
board: '',
appVersion: '',
macAddress: '',
}
}
catch (error: any) {
const errorMessage = error?.message || t('manualAddDeviceDialog.addFailed')
toast.error(errorMessage)
}
}
// 校验单个字段
function validateField(field: string) {
switch (field) {
case 'board':
if (!manualBindForm.value.board) {
formErrors.value.board = t('manualAddDeviceDialog.deviceTypePlaceholder')
}
else {
formErrors.value.board = ''
}
break
case 'appVersion':
if (!manualBindForm.value.appVersion) {
formErrors.value.appVersion = t('manualAddDeviceDialog.firmwareVersionPlaceholder')
}
else {
formErrors.value.appVersion = ''
}
break
case 'macAddress':
if (!manualBindForm.value.macAddress) {
formErrors.value.macAddress = t('manualAddDeviceDialog.macAddressPlaceholder')
}
else if (!macRegex.test(manualBindForm.value.macAddress)) {
formErrors.value.macAddress = t('manualAddDeviceDialog.invalidMacAddress')
}
else {
formErrors.value.macAddress = ''
}
break
}
}
// 清除字段错误提示
function clearFieldError(field: string) {
formErrors.value[field] = ''
}
// 处理选择器变化
function handlePickerChange() {
clearFieldError('board')
}
// 校验整个表单
function validateForm(): boolean {
let isValid = true
// 校验设备类型
if (!manualBindForm.value.board) {
formErrors.value.board = t('manualAddDeviceDialog.deviceTypePlaceholder')
isValid = false
}
else {
formErrors.value.board = ''
}
// 校验固件版本
if (!manualBindForm.value.appVersion) {
formErrors.value.appVersion = t('manualAddDeviceDialog.firmwareVersionPlaceholder')
isValid = false
}
else {
formErrors.value.appVersion = ''
}
// 校验MAC地址
if (!manualBindForm.value.macAddress) {
formErrors.value.macAddress = t('manualAddDeviceDialog.macAddressPlaceholder')
isValid = false
}
else if (!macRegex.test(manualBindForm.value.macAddress)) {
formErrors.value.macAddress = t('manualAddDeviceDialog.invalidMacAddress')
isValid = false
}
else {
formErrors.value.macAddress = ''
}
return isValid
}
// 获取设备类型列表
async function loadFirmwareTypes() {
try {
@@ -241,14 +404,14 @@ defineExpose({
<view class="mb-[20rpx]">
<text class="mb-[12rpx] block text-[28rpx] text-[#65686f] leading-[1.4]">
{{ t('device.macAddress') }}{{ device.macAddress }}
</text>
<text class="mb-[12rpx] block text-[28rpx] text-[#65686f] leading-[1.4]">
{{ t('device.firmwareVersion') }}{{ device.appVersion }}
</text>
<text class="block text-[28rpx] text-[#65686f] leading-[1.4]">
{{ t('device.lastConnection') }}{{ formatTime(device.lastConnectedAt) }}
</text>
{{ t('device.macAddress') }}{{ device.macAddress }}
</text>
<text class="mb-[12rpx] block text-[28rpx] text-[#65686f] leading-[1.4]">
{{ t('device.firmwareVersion') }}{{ device.appVersion }}
</text>
<text class="block text-[28rpx] text-[#65686f] leading-[1.4]">
{{ t('device.lastConnection') }}{{ formatTime(device.lastConnectedAt) }}
</text>
</view>
<view class="flex items-center justify-between border-[1rpx] border-[#eeeeee] rounded-[12rpx] bg-[#f5f7fb] p-[16rpx_20rpx]">
@@ -295,10 +458,88 @@ defineExpose({
</view>
<!-- FAB 绑定设备按钮 -->
<wd-fab type="primary" size="small" icon="add" :draggable="true" :expandable="false" @click="openBindDialog" />
<wd-fab type="primary" size="small" icon="add" :draggable="true" :expandable="false" @click="isBindDevice = true" />
<!-- MessageBox 组件 -->
<wd-message-box />
<wd-action-sheet v-model="isBindDevice" :actions="actions" @close="isBindDevice = false" @select="selectBindMode" />
<!-- 手动绑定设备弹窗 -->
<wd-popup v-model="isManualBindDialog" position="bottom" :close-on-click-modal="false" custom-style="border-radius: 24rpx 24rpx 0 0;">
<view class="manual-bind-dialog">
<view class="dialog-header">
<text class="dialog-title">
{{ t('manualAddDeviceDialog.title') }}
</text>
<wd-icon name="close" size="20" @click="isManualBindDialog = false" />
</view>
<view class="dialog-content">
<view class="form-item">
<text class="form-label">
{{ t('manualAddDeviceDialog.deviceType') }}
<text class="required">
*
</text>
</text>
<wd-picker
v-model="manualBindForm.board"
class="custom-wd-picker"
:columns="firmwareTypes.map(item => ({ value: item.key, label: item.name }))"
:placeholder="t('manualAddDeviceDialog.deviceTypePlaceholder')"
:cancel-button-text="t('common.cancel')"
:confirm-button-text="t('common.confirm')"
@confirm="handlePickerChange"
/>
<text v-if="formErrors.board" class="error-text">
{{ formErrors.board }}
</text>
</view>
<view class="form-item">
<text class="form-label">
{{ t('manualAddDeviceDialog.firmwareVersion') }}
<text class="required">
*
</text>
</text>
<wd-input
v-model="manualBindForm.appVersion"
:placeholder="t('manualAddDeviceDialog.firmwareVersionPlaceholder')"
@input="clearFieldError('appVersion')"
@blur="validateField('appVersion')"
/>
<text v-if="formErrors.appVersion" class="error-text">
{{ formErrors.appVersion }}
</text>
</view>
<view class="form-item">
<text class="form-label">
{{ t('manualAddDeviceDialog.macAddress') }}
<text class="required">
*
</text>
</text>
<wd-input
v-model="manualBindForm.macAddress"
:placeholder="t('manualAddDeviceDialog.macAddressPlaceholder')"
@input="validateField('macAddress')"
@blur="validateField('macAddress')"
/>
<text v-if="formErrors.macAddress" class="error-text">
{{ formErrors.macAddress }}
</text>
</view>
</view>
<view class="dialog-footer">
<wd-button block type="primary" @click="handleManualBind">
{{ t('manualAddDeviceDialog.confirm') }}
</wd-button>
</view>
</view>
</wd-popup>
</view>
</template>
@@ -327,8 +568,64 @@ defineExpose({
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
border: 1rpx solid #eeeeee;
}
::v-deep .wd-action-sheet__popup,
::v-deep .wd-popup {
z-index: 100 !important;
}
.custom-wd-picker ::v-deep .wd-picker__cell {
padding-left: 0 !important;
}
:deep(.wd-icon) {
font-size: 32rpx;
}
.manual-bind-dialog {
padding: 32rpx;
background: #ffffff;
}
.dialog-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 32rpx;
}
.dialog-title {
font-size: 36rpx;
font-weight: 600;
color: #232338;
}
.dialog-content {
margin-bottom: 32rpx;
}
.form-item {
margin-bottom: 24rpx;
}
.form-label {
display: block;
font-size: 28rpx;
color: #65686f;
margin-bottom: 12rpx;
}
.required {
color: #ff4d4f;
margin-left: 4rpx;
}
.error-text {
display: block;
font-size: 24rpx;
color: #ff4d4f;
margin-top: 8rpx;
}
.dialog-footer {
padding-top: 16rpx;
}
</style>
+91 -53
View File
@@ -9,17 +9,16 @@
</route>
<script lang="ts" setup>
import type { LoginData } from '@/api/auth';
import { computed, onMounted, ref } from 'vue';
import { login } from '@/api/auth';
import { useConfigStore } from '@/store';
import { getEnvBaseUrl } from '@/utils';
import { toast } from '@/utils/toast';
import type { LoginData } from '@/api/auth'
import type { Language } from '@/store/lang'
import { computed, onMounted, ref } from 'vue'
import { login } from '@/api/auth'
// 导入国际化相关功能
import { t, changeLanguage, getSupportedLanguages, initI18n } from '@/i18n';
import type { Language } from '@/store/lang';
import { changeLanguage, getCurrentLanguage, getSupportedLanguages, initI18n, t } from '@/i18n'
import { useConfigStore } from '@/store'
// 导入SM2加密工具
import { sm2Encrypt } from '@/utils'
import { getEnvBaseUrl, sm2Encrypt } from '@/utils'
import { toast } from '@/utils/toast'
// 获取屏幕边界到安全区域距离
let safeAreaInsets
@@ -78,11 +77,6 @@ const areaCodeList = computed(() => {
return configStore.config.mobileAreaList || [{ name: '中国大陆', key: '+86' }]
})
// SM2公钥
const sm2PublicKey = computed(() => {
return configStore.config.sm2PublicKey
})
// 切换登录方式
function toggleLoginType() {
loginType.value = loginType.value === 'username' ? 'mobile' : 'username'
@@ -123,6 +117,22 @@ function goToForgotPassword() {
})
}
// 跳转到用户协议
function goToUserAgreement() {
const lang = getCurrentLanguage() === 'zh_CN' ? 'zh' : 'en'
uni.navigateTo({
url: `/pages/login/user-agreement-${lang}`,
})
}
// 跳转到隐私政策
function goToPrivacyPolicy() {
const lang = getCurrentLanguage() === 'zh_CN' ? 'zh' : 'en'
uni.navigateTo({
url: `/pages/login/privacy-policy-${lang}`,
})
}
// 生成UUID
function generateUUID() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
@@ -132,9 +142,7 @@ function generateUUID() {
})
}
let skipReLaunch = false // 全局或组件作用域
//跳转至服务端设置页面
// 跳转至服务端设置页面
function goToServerSetting() {
uni.switchTab({
url: '/pages/settings/index',
@@ -143,9 +151,9 @@ function goToServerSetting() {
// 获取验证码
async function refreshCaptcha() {
const uuid = generateUUID();
formData.value.captchaId = uuid;
captchaImage.value = `${getEnvBaseUrl()}/user/captcha?uuid=${uuid}&t=${Date.now()}`;
const uuid = generateUUID()
formData.value.captchaId = uuid
captchaImage.value = `${getEnvBaseUrl()}/user/captcha?uuid=${uuid}&t=${Date.now()}`
}
// 登录
@@ -179,7 +187,8 @@ async function handleLogin() {
}
// 检查SM2公钥是否配置
if (!sm2PublicKey.value) {
const sm2PublicKey = configStore.config.sm2PublicKey
if (!sm2PublicKey) {
toast.warning(t('sm2.publicKeyNotConfigured'))
return
}
@@ -192,8 +201,9 @@ async function handleLogin() {
try {
// 拼接验证码和密码
const captchaAndPassword = formData.value.captcha + formData.value.password
encryptedPassword = sm2Encrypt(sm2PublicKey.value, captchaAndPassword)
} catch (error) {
encryptedPassword = sm2Encrypt(sm2PublicKey, captchaAndPassword)
}
catch (error) {
console.error('密码加密失败:', error)
toast.warning(t('sm2.encryptionFailed'))
return
@@ -203,20 +213,20 @@ async function handleLogin() {
const loginData: LoginData = {
username: '',
password: encryptedPassword,
captchaId: formData.value.captchaId
captchaId: formData.value.captchaId,
}
// 如果是手机号登录,将区号+手机号拼接到username字段
if (loginType.value === 'mobile') {
loginData.username = `${selectedAreaCode.value}${formData.value.mobile}`
} else {
}
else {
loginData.username = formData.value.username
}
const response = await login(loginData)
// 存储token
uni.setStorageSync('token', response.token)
uni.setStorageSync('expire', response.expire)
uni.setStorageSync('token', JSON.stringify(response))
toast.success(t('message.loginSuccess'))
@@ -230,14 +240,6 @@ async function handleLogin() {
catch (error: any) {
// 登录失败重新获取验证码
refreshCaptcha()
// 处理验证码错误 - 从error.message中解析错误码
if (error.message.includes('请求错误[10067]')) {
toast.warning(t('login.captchaError'))
}
// 处理账号或密码错误
else if (error.message.includes('请求错误[10004]')) {
toast.warning(t('message.passwordError'))
}
}
finally {
loading.value = false
@@ -267,7 +269,8 @@ onMounted(async () => {
if (!configStore.config.name) {
try {
await configStore.fetchPublicConfig()
} catch (error) {
}
catch (error) {
console.error(t('login.fetchConfigError'), error)
}
}
@@ -287,19 +290,21 @@ onMounted(async () => {
</text>
</view>
</view>
<!-- 右上角按钮组 -->
<view class="top-right-buttons" :style="{ top: `${safeAreaInsets?.top + 10}px` }">
<!-- 语言切换按钮 -->
<view class="lang-btn" @click="showLanguageSheet = true">
<text class="lang-text-icon">{{ t('login.selectLanguageTip') }}</text>
</view>
<!-- 服务端设置按钮 -->
<view class="server-btn" @click="goToServerSetting">
<wd-icon name="setting" custom-class="server-icon" />
</view>
</view>
<!-- 右上角按钮组 -->
<view class="top-right-buttons" :style="{ top: `${safeAreaInsets?.top + 10}px` }">
<!-- 语言切换按钮 -->
<view class="lang-btn" @click="showLanguageSheet = true">
<text class="lang-text-icon">
{{ t('login.selectLanguageTip') }}
</text>
</view>
<!-- 服务端设置按钮 -->
<view class="server-btn" @click="goToServerSetting">
<wd-icon name="setting" custom-class="server-icon" />
</view>
</view>
<view class="form-container">
<view class="form">
@@ -390,6 +395,16 @@ onMounted(async () => {
</view>
</view>
<view class="policy-links">
<text class="policy-link" @click="goToUserAgreement">
{{ t('login.userAgreement') }}
</text>
<text class="policy-divider">|</text>
<text class="policy-link" @click="goToPrivacyPolicy">
{{ t('login.privacyPolicy') }}
</text>
</view>
<!-- 登录方式切换 -->
<view v-if="enableMobileLogin" class="login-type-switch">
<view class="switch-tabs">
@@ -684,6 +699,29 @@ onMounted(async () => {
margin-bottom: 30rpx;
}
.policy-links {
display: flex;
justify-content: center;
align-items: center;
gap: 20rpx;
margin-bottom: 30rpx;
.policy-link {
color: #667eea;
font-size: 26rpx;
cursor: pointer;
&:hover {
text-decoration: underline;
}
}
.policy-divider {
color: #999999;
font-size: 26rpx;
}
}
.forgot-password {
.forgot-text {
color: #667eea;
@@ -893,7 +931,7 @@ onMounted(async () => {
cursor: pointer;
background: rgba(255, 255, 255, 0.15);
border-radius: 24rpx;
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.2);
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.2);
&:active {
transform: scale(0.95);
@@ -901,7 +939,7 @@ onMounted(async () => {
.lang-text-icon {
font-size: 18rpx;
color: #FFFFFF;
color: #ffffff;
}
&:hover {
@@ -919,7 +957,7 @@ onMounted(async () => {
cursor: pointer;
background: rgba(255, 255, 255, 0.15);
border-radius: 24rpx;
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.2);
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.2);
&:active {
transform: scale(0.95);
@@ -927,7 +965,7 @@ onMounted(async () => {
.server-icon {
font-size: 28rpx;
color: #FFFFFF;
color: #ffffff;
}
&:hover {
@@ -0,0 +1,402 @@
<route lang="jsonc" type="page">
{
"layout": "default",
"style": {
"navigationStyle": "custom",
"navigationBarTitleText": "Privacy Policy"
}
}
</route>
<script lang="ts" setup>
import { t } from '@/i18n'
function goBack() {
uni.navigateBack()
}
</script>
<template>
<view class="h-screen flex flex-col bg-[#f5f8fd]">
<wd-navbar
:title="t('login.privacyPolicy')"
safe-area-inset-top
left-arrow
:bordered="false"
@click-left="goBack"
>
<template #left>
<wd-icon name="arrow-left" size="18" />
</template>
</wd-navbar>
<scroll-view scroll-y class="box-border flex-1 px-[30rpx] py-[30rpx]">
<view class="rounded-[16rpx] bg-white px-[30rpx] py-[40rpx] shadow-[0_4rpx_20rpx_rgba(0,0,0,0.05)]">
<view class="mb-[40rpx] text-center">
<text class="text-[36rpx] text-[#1a1a1a] font-bold">
Privacy Policy
</text>
</view>
<view class="mb-[40rpx] text-center">
<text class="text-[24rpx] text-[#666666]">
Last Updated: March 10, 2026
</text>
</view>
<view class="mb-[32rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
Introduction
</text>
</view>
<view class="mb-[24rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
Welcome to use the XiaoZhi Backend Service (hereinafter referred to as "the Service"). The operator of this Service is the actual deployer and administrator of the Service (hereinafter referred to as "Operator" or "we"). We fully understand the importance of your personal information and will do our best to protect your personal information security.
</text>
</view>
<view class="mb-[24rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
Please carefully read and fully understand all contents of this Privacy Policy before using the Service. Once you start using the Service, it signifies that you have read and agreed to this Privacy Policy.
</text>
</view>
<view class="mb-[40rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
This Privacy Policy applies to our collection, storage, use, sharing, and protection of your personal information when you use the Service through the Admin Console (management backend), API interfaces, and other means.
</text>
</view>
<view class="mb-[32rpx]">
<text class="text-[30rpx] text-[#1a1a1a] font-bold">
I. Information We Collect
</text>
</view>
<view class="mb-[24rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
To provide services to you, we may need to collect the following information:
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
1.1 Account Registration Information: When you register an account, we collect your phone number or username, password, and other information to create and verify your account.
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
1.2 Device Information: When you bind hardware devices, we collect device identification information (such as device code, MAC address), device model, firmware version, etc., for device management and service provision.
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
1.3 Agent Configuration Information: When you create and configure Agents, we collect role templates, language model selections, voice parameter configurations, etc., to provide personalized AI interaction services.
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
1.4 Voice Interaction Data: When you use the voice interaction function, the Service uses Voice Activity Detection (VAD) to determine the start and end of your voice, processes your voice input data, and transmits it to third-party Automatic Speech Recognition (ASR) and Large Language Model (LLM) services for processing to achieve voice interaction capabilities.
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
1.5 Image Data: When you use the vision model function, we may process image data captured through the device camera and transmit it to third-party vision model services for analysis and understanding to achieve image recognition, scene understanding, and other functions.
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
1.6 Conversation Memory Data: When you enable the memory function, the Service stores summaries of your interaction history with the Agent to provide more coherent and personalized interaction experiences in subsequent conversations.
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
1.7 Knowledge Base Data: When you use the knowledge base function, we collect and store documents, texts, and other knowledge base content you upload for Agents to perform knowledge retrieval and Q&A during conversations.
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
1.8 Voice Print Data: When you use the voice print recognition function, we collect and store your voice print characteristic samples for speaker identity verification and personalized services.
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
1.9 Chat History Data: We store conversation history records between you and the Agents, including text conversation content, conversation time, interaction context, etc., to provide continuous conversation experience and history query functions.
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
1.10 Conversation Audio Data: When you use the voice interaction function, we may store audio data of your interactions with the Agents to improve voice interaction quality and enable retrieval queries.
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
1.11 Agent Configuration Data: When you configure Agents, we collect tags, plugin configurations, context provider settings, etc., to provide Agent customization services.
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
1.12 Device Firmware Data: When you use the OTA upgrade function, we record device firmware version, upgrade history, etc., for device management and firmware traceability.
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
1.13 Log Information: When you use the Service, we automatically collect service log information, including but not limited to access time, IP address, browser type, operation records, etc., for service operation and security assurance.
</text>
</view>
<view class="mb-[40rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
1.14 Verification Code Information: When you use phone number login, we send verification codes via SMS service for identity verification.
</text>
</view>
<view class="mb-[32rpx]">
<text class="text-[30rpx] text-[#1a1a1a] font-bold">
II. How We Use the Collected Information
</text>
</view>
<view class="mb-[24rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
The information we collect will be used for the following purposes:
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
(1) Providing, maintaining, and improving the Service, including core functions such as account management, device management, and Agent configuration.
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
(2) Processing your voice interaction requests, using Voice Activity Detection (VAD) to identify voice input, calling third-party AI model services to complete speech recognition, intent recognition, semantic understanding, and speech synthesis.
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
(3) Processing your image data, calling third-party vision model services to complete image recognition and scene understanding.
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
(4) Storing and managing your conversation memory data to provide more coherent service experiences in subsequent interactions.
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
(5) Storing and retrieving knowledge base content you upload so that Agents can provide more accurate knowledge Q&A during conversations.
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
(6) Ensuring service security and stability, including identity verification, security protection, troubleshooting, etc.
</text>
</view>
<view class="mb-[40rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
(7) Complying with applicable laws, regulations, and regulatory requirements.
</text>
</view>
<view class="mb-[24rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
We will not use your personal information for purposes unrelated to the above. If we need to use information for other purposes not stated in this Privacy Policy, we will obtain your consent in advance.
</text>
</view>
<view class="mb-[32rpx]">
<text class="text-[30rpx] text-[#1a1a1a] font-bold">
III. Sharing and Disclosure of Information
</text>
</view>
<view class="mb-[24rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
We will not sell your personal information to third parties. Only in the following circumstances may we share or disclose your information:
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
3.1 Third-party Service Calls: To achieve voice interaction, visual understanding, and other functions, your voice data, image data, and text content may be transmitted to third-party AI service providers for processing.
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
3.2 Legal Requirements: According to applicable laws, regulations, legal procedures, or requirements from government authorities, we may need to disclose your personal information.
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
3.3 Security Assurance: To protect the Operator, other users, or the public from damage, we may use or disclose personal information within a reasonably necessary scope.
</text>
</view>
<view class="mb-[40rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
3.4 With Consent: We may share your personal information with third parties if we obtain your explicit consent.
</text>
</view>
<view class="mb-[32rpx]">
<text class="text-[30rpx] text-[#1a1a1a] font-bold">
IV. Storage and Protection of Information
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
4.1 Storage Location: Your personal information will be stored on the server where the Operator deploys the Service, including but not limited to databases (MySQL/PostgreSQL) and cache services (Redis).
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
4.2 Storage Period: We will retain your personal information for the period necessary to provide you with services. After you cancel your account, we will delete or anonymize your personal information within a reasonable time.
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
4.3 Security Measures: We take reasonable technical and management measures to protect the security of your personal information, including but not limited to data encryption, access control, security auditing, etc.
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
4.4 Security Incident Handling: If a personal information security incident occurs, we will promptly inform you of the basic situation of the security incident, possible impacts, and measures taken or to be taken.
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
4.5 Special Note for Open-source Projects: The Service is an open-source project with two operation modes:
</text>
</view>
<view class="mb-[8rpx] ml-[40rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
(1) Self-deployment: If you self-deploy the Service, the Operator is only the code provider, and actual data storage and processing are your responsibility.
</text>
</view>
<view class="mb-[8rpx] ml-[40rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
(2) Test Platform: If you use the test platform deployed by the Operator, your data will be managed and protected by the Operator.
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
4.6 Cross-border Data Transfer: When providing intelligent interaction functions, the Service may need to call interfaces from third-party AI service providers outside mainland China.
</text>
</view>
<view class="mb-[32rpx]">
<text class="text-[30rpx] text-[#1a1a1a] font-bold">
V. Your Rights
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
5.1 Query and Access: You can view and manage your personal information such as account information, device information, and Agent configuration through the Admin Console.
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
5.2 Correction and Modification: When you find that your personal information is incorrect, you can correct it through the Admin Console or contact the Operator for assistance.
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
5.3 Deletion: Under certain circumstances, you may request us to delete your personal information.
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
5.4 Account Cancellation: You can cancel your account through the account settings in the Admin Console or contact the Operator for processing.
</text>
</view>
<view class="mb-[40rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
5.5 Withdrawal of Consent: You may withdraw your consent granted to us at any time.
</text>
</view>
<view class="mb-[32rpx]">
<text class="text-[30rpx] text-[#1a1a1a] font-bold">
VI. Protection of Minor Information
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
6.1 We attach great importance to the protection of minors' personal information. If you are a minor under 18 years of age, please use the Service under the guidance and consent of your guardian.
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
6.2 If a guardian discovers that a minor has provided personal information to us without consent, please contact the Operator. We will delete the relevant information as soon as possible.
</text>
</view>
<view class="mb-[40rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
6.3 For minors who use the Service with guardian consent, we will provide stricter protection for their personal information in accordance with laws and regulations.
</text>
</view>
<view class="mb-[32rpx]">
<text class="text-[30rpx] text-[#1a1a1a] font-bold">
VII. Third-party Services Description
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
7.1 When providing intelligent interaction functions, the Service needs to call third-party services, including but not limited to: Voice Activity Detection Service (VAD), Automatic Speech Recognition Service (ASR), Large Language Model Service (LLM), Text-to-Speech Service (TTS), Vision Model Service, SMS Verification Code Service, MQTT Message Broker Service, Database Service.
</text>
</view>
<view class="mb-[40rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
7.2 The above third-party service providers will process data according to their respective privacy policies. We recommend that you understand the privacy policies of relevant third-party service providers before using the Service.
</text>
</view>
<view class="mb-[32rpx]">
<text class="text-[30rpx] text-[#1a1a1a] font-bold">
VIII. Use of Cookies and Similar Technologies
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
8.1 The Service may use Cookies and similar technologies to save your login status, record your preference settings, etc., to provide you with a better user experience.
</text>
</view>
<view class="mb-[40rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
8.2 You can manage Cookies through browser settings. However, please note that disabling Cookies may affect some functions of the Service.
</text>
</view>
<view class="mb-[32rpx]">
<text class="text-[30rpx] text-[#1a1a1a] font-bold">
IX. Changes to the Privacy Policy
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
9.1 We may revise this Privacy Policy based on business adjustments, changes in laws and regulations, and other reasons. The revised Privacy Policy will be published through the Service's announcement mechanisms.
</text>
</view>
<view class="mb-[40rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
9.2 If you continue to use the Service after the Privacy Policy revision, it shall be deemed that you have accepted the revised Privacy Policy.
</text>
</view>
<view class="mb-[32rpx]">
<text class="text-[30rpx] text-[#1a1a1a] font-bold">
X. Information Security Warning
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
10.1 Please do not disclose sensitive personal information such as your property account numbers, bank card numbers, credit card numbers, passwords, ID card numbers, etc., in voice or text interactions with AI. Any losses resulting from this shall be borne by you.
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
10.2 Please properly keep your account and password secure, and do not share your account information with others or share accounts with others.
</text>
</view>
<view class="mb-[40rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
10.3 If you discover that personal information may have been leaked, please contact the Operator promptly to take measures.
</text>
</view>
<view class="mb-[32rpx]">
<text class="text-[30rpx] text-[#1a1a1a] font-bold">
XI. Applicable Law
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
11.1 The formulation, interpretation, execution, and dispute resolution of this Privacy Policy shall be governed by the laws of the People's Republic of China (excluding the laws of Hong Kong, Macau, and Taiwan for the purposes of this Privacy Policy).
</text>
</view>
<view class="mb-[40rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
11.2 If any provision of this Privacy Policy conflicts with the current laws and regulations of the People's Republic of China, the provisions of laws and regulations shall prevail, and the remaining provisions shall remain valid.
</text>
</view>
</view>
</scroll-view>
</view>
</template>
@@ -0,0 +1,402 @@
<route lang="jsonc" type="page">
{
"layout": "default",
"style": {
"navigationStyle": "custom",
"navigationBarTitleText": "隐私政策"
}
}
</route>
<script lang="ts" setup>
import { t } from '@/i18n'
function goBack() {
uni.navigateBack()
}
</script>
<template>
<view class="h-screen flex flex-col bg-[#f5f8fd]">
<wd-navbar
:title="t('login.privacyPolicy')"
safe-area-inset-top
left-arrow
:bordered="false"
@click-left="goBack"
>
<template #left>
<wd-icon name="arrow-left" size="18" />
</template>
</wd-navbar>
<scroll-view scroll-y class="box-border flex-1 px-[20rpx] py-[30rpx]" :scroll-with-animation="true">
<view class="rounded-[16rpx] bg-white px-[30rpx] py-[40rpx] shadow-[0_4rpx_20rpx_rgba(0,0,0,0.05)]">
<view class="mb-[40rpx] text-center">
<text class="text-[36rpx] text-[#1a1a1a] font-bold">
{{ t('login.privacyPolicy') }}
</text>
</view>
<view class="mb-[40rpx] text-center">
<text class="text-[24rpx] text-[#666666]">
更新日期2026年3月10日
</text>
</view>
<view class="mb-[32rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
引言
</text>
</view>
<view class="mb-[24rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
欢迎您使用小智后端服务以下简称"本服务"本服务的运营方为本服务的实际部署者和管理者以下简称"运营者""我们"我们深知个人信息对您的重要性将尽全力保护您的个人信息安全
</text>
</view>
<view class="mb-[24rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
请您在使用本服务前仔细阅读并充分理解本隐私政策的全部内容一旦您开始使用本服务即表示您已阅读并同意本隐私政策
</text>
</view>
<view class="mb-[40rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
本隐私政策适用于您通过智控台管理后台API接口及其他方式使用本服务时我们对您个人信息的收集存储使用共享保护等行为
</text>
</view>
<view class="mb-[32rpx]">
<text class="text-[30rpx] text-[#1a1a1a] font-bold">
我们收集的信息
</text>
</view>
<view class="mb-[24rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
为向您提供服务我们可能需要收集以下信息
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
1.1 账号注册信息当您注册账号时我们会收集您的手机号码或用户名密码等信息用于创建和验证您的账号
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
1.2 设备信息当您绑定硬件设备时我们会收集设备的标识信息如设备编码MAC地址等设备型号固件版本等用于设备管理和服务提供
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
1.3 智能体配置信息当您创建和配置智能体时我们会收集您设定的角色模板语言模型选择语音参数配置等信息用于提供个性化的AI交互服务
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
1.4 语音交互数据在您使用语音交互功能时本服务会通过语音活动检测VAD判断您的语音起止状态并处理您的语音输入数据将其传输至第三方语音识别ASR和语言模型LLM服务进行处理以实现语音交互功能
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
1.5 图像数据当您使用视觉模型功能时我们可能会处理您通过设备摄像头采集的图像数据并将其传输至第三方视觉模型服务进行分析和理解用于实现图像识别场景理解等功能
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
1.6 对话记忆数据当您开启记忆功能时本服务会存储您与智能体的交互历史摘要用于在后续对话中提供更连贯个性化的交互体验
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
1.7 知识库数据当您使用知识库功能时我们会收集和存储您上传的文档文本等知识库内容用于智能体在对话中进行知识检索和问答
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
1.8 声纹数据当您使用声纹识别功能时我们会收集和存储您的声纹特征样本用于说话人身份验证和个性化服务
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
1.9 聊天历史数据我们会存储您与智能体的对话历史记录包括文本对话内容对话时间交互上下文等信息用于提供连续性对话体验和历史查询功能
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
1.10 对话音频数据在您使用语音交互功能时我们可能会存储您与智能体交互的音频数据用于优化语音交互质量和回溯查询
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
1.11 智能体配置数据当您配置智能体时我们会收集您设定的标签插件配置上下文提供者设置等信息用于提供智能体定制化服务
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
1.12 设备固件数据当您使用OTA升级功能时我们会记录设备固件版本升级历史等信息用于设备管理和固件追溯
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
1.13 日志信息当您使用本服务时我们会自动收集服务日志信息包括但不限于访问时间IP地址浏览器类型操作记录等用于服务运维和安全保障
</text>
</view>
<view class="mb-[40rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
1.14 验证码信息当您使用手机号登录时我们会通过短信服务向您发送验证码用于身份验证
</text>
</view>
<view class="mb-[32rpx]">
<text class="text-[30rpx] text-[#1a1a1a] font-bold">
我们如何使用收集的信息
</text>
</view>
<view class="mb-[24rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
我们收集的信息将用于以下目的
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
1提供维护和改进本服务包括账号管理设备管理智能体配置等核心功能
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
2处理您的语音交互请求通过语音活动检测VAD识别语音输入调用第三方AI模型服务完成语音识别意图识别语义理解和语音合成
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
3处理您的图像数据调用第三方视觉模型服务完成图像识别和场景理解
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
4存储和管理您的对话记忆数据以便在后续交互中提供更连贯的服务体验
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
5存储和检索您上传的知识库内容以便智能体在对话中为您提供更准确的知识问答
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
6保障服务的安全性和稳定性包括身份验证安全防护故障排查等
</text>
</view>
<view class="mb-[40rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
7遵守适用的法律法规和监管要求
</text>
</view>
<view class="mb-[24rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
我们不会将您的个人信息用于与上述目的无关的其他用途如需将信息用于本隐私政策未载明的其他目的我们会事先征得您的同意
</text>
</view>
<view class="mb-[32rpx]">
<text class="text-[30rpx] text-[#1a1a1a] font-bold">
信息的共享与披露
</text>
</view>
<view class="mb-[24rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
我们不会向第三方出售您的个人信息仅在以下情形下我们可能会共享或披露您的信息
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
3.1 第三方服务调用为实现语音交互视觉理解等功能您的语音数据图像数据和文本内容可能会被传输至第三方AI服务提供商如语言模型语音识别语音合成视觉模型服务商进行处理我们会选择具备合理安全保障能力的服务商但请您知悉第三方服务商将按照其自身的隐私政策处理相关数据
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
3.2 法律要求根据适用的法律法规法律程序或政府主管部门的要求我们可能需要披露您的个人信息
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
3.3 安全保障为保护运营者其他用户或公众的权益财产或安全免遭损害在合理必要的范围内使用或披露个人信息
</text>
</view>
<view class="mb-[40rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
3.4 征得同意在获得您明确同意的前提下我们可能会与第三方共享您的个人信息
</text>
</view>
<view class="mb-[32rpx]">
<text class="text-[30rpx] text-[#1a1a1a] font-bold">
信息的存储与保护
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
4.1 存储地点您的个人信息将存储在运营者部署本服务的服务器上包括但不限于数据库MySQL/PostgreSQL和缓存服务Redis
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
4.2 存储期限我们将在为您提供服务所必需的期限内保留您的个人信息当您注销账号后我们将在合理时间内删除或匿名化处理您的个人信息法律法规另有规定的除外
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
4.3 安全措施我们采取合理的技术和管理措施保护您的个人信息安全包括但不限于数据加密访问控制安全审计等但请您理解互联网并非绝对安全的环境我们无法保证信息传输和存储的绝对安全性
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
4.4 安全事件处理如发生个人信息安全事件我们将按照法律法规的要求及时向您告知安全事件的基本情况可能的影响已采取或将要采取的处置措施等
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
4.5 开源项目特别说明本服务为开源项目存在两种运营模式
</text>
</view>
<view class="mb-[8rpx] ml-[40rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
1自行部署如您自行部署本服务运营者仅为代码提供方实际的数据存储和处理由您自行负责
</text>
</view>
<view class="mb-[8rpx] ml-[40rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
2测试平台如您使用运营者部署的测试平台您的数据将由运营人员负责管理和保护
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
4.6 数据跨境传输本服务在提供智能交互功能时可能需要调用境外第三方AI服务提供商的接口
</text>
</view>
<view class="mb-[32rpx]">
<text class="text-[30rpx] text-[#1a1a1a] font-bold">
您的权利
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
5.1 查询与访问您可通过智控台查看和管理您的账号信息设备信息智能体配置等个人信息
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
5.2 更正与修改当您发现个人信息有误时您可通过智控台自行更正或联系运营者协助处理
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
5.3 删除在以下情形下您可请求我们删除您的个人信息1处理目的已实现无法实现或不再必要2我们违反法律法规或与您的约定收集使用个人信息3注销账号
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
5.4 账号注销您可通过智控台的账号设置功能注销账号或联系运营者进行处理
</text>
</view>
<view class="mb-[40rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
5.5 撤回同意您可随时撤回您此前向我们作出的同意授权
</text>
</view>
<view class="mb-[32rpx]">
<text class="text-[30rpx] text-[#1a1a1a] font-bold">
未成年人信息保护
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
6.1 我们高度重视未成年人个人信息的保护若您是未满18周岁的未成年人请在监护人的指导和同意下使用本服务
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
6.2 如果监护人发现未成年人未经其同意向我们提供了个人信息请联系运营者我们将尽快删除相关信息
</text>
</view>
<view class="mb-[40rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
6.3 对于经监护人同意使用本服务的未成年人我们将按照法律法规的规定给予其个人信息更严格的保护
</text>
</view>
<view class="mb-[32rpx]">
<text class="text-[30rpx] text-[#1a1a1a] font-bold">
第三方服务说明
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
7.1 本服务在提供智能交互功能时需要调用第三方服务包括但不限于语音活动检测服务VAD语音识别服务ASR大语言模型服务LLM语音合成服务TTS视觉模型服务短信验证码服务MQTT消息代理服务数据库服务
</text>
</view>
<view class="mb-[40rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
7.2 上述第三方服务提供商将按照其各自的隐私政策对数据进行处理我们建议您在使用本服务前了解相关第三方服务商的隐私政策
</text>
</view>
<view class="mb-[32rpx]">
<text class="text-[30rpx] text-[#1a1a1a] font-bold">
Cookie及类似技术的使用
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
8.1 本服务可能使用Cookie及类似技术来保存您的登录状态记录您的偏好设置等以便为您提供更好的使用体验
</text>
</view>
<view class="mb-[40rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
8.2 您可以通过浏览器设置管理Cookie但请注意如果禁用Cookie可能会影响本服务的部分功能
</text>
</view>
<view class="mb-[32rpx]">
<text class="text-[30rpx] text-[#1a1a1a] font-bold">
隐私政策的变更
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
9.1 我们可能会根据业务调整法律法规变化等原因对本隐私政策进行修订修订后的隐私政策将通过本平台公告等方式予以发布
</text>
</view>
<view class="mb-[40rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
9.2 如您在隐私政策修订后继续使用本服务则视为您已接受修订后的隐私政策
</text>
</view>
<view class="mb-[32rpx]">
<text class="text-[30rpx] text-[#1a1a1a] font-bold">
信息安全提示
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
10.1 请勿在与AI的语音或文字交互中透露您的财产账户银行卡号信用卡号密码身份证号等敏感个人信息否则由此带来的损失由您自行承担
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
10.2 请妥善保管您的账号和密码不要将账号信息告知他人或与他人共享账号
</text>
</view>
<view class="mb-[40rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
10.3 如您发现个人信息可能被泄露请及时联系运营者采取措施
</text>
</view>
<view class="mb-[32rpx]">
<text class="text-[30rpx] text-[#1a1a1a] font-bold">
十一适用法律
</text>
</view>
<view class="mb-[16rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
11.1 本隐私政策的制定解释执行及争议解决均适用中华人民共和国法律法规为本隐私政策之目的不包括港澳台地区法律
</text>
</view>
<view class="mb-[40rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
11.2 如本隐私政策的任何条款与中华人民共和国现行法律法规相抵触以法律法规的规定为准其余条款仍然有效
</text>
</view>
</view>
</scroll-view>
</view>
</template>
@@ -0,0 +1,539 @@
<route lang="jsonc" type="page">
{
"layout": "default",
"style": {
"navigationStyle": "custom",
"navigationBarTitleText": "User Agreement"
}
}
</route>
<script lang="ts" setup>
import { t } from '@/i18n'
function goBack() {
uni.navigateBack()
}
</script>
<template>
<view class="h-screen flex flex-col bg-[#f5f8fd]">
<wd-navbar
:title="t('login.userAgreement')"
safe-area-inset-top
left-arrow
:bordered="false"
@click-left="goBack"
>
<template #left>
<wd-icon name="arrow-left" size="18" />
</template>
</wd-navbar>
<scroll-view scroll-y class="box-border flex-1 px-[30rpx] py-[30rpx]">
<view class="rounded-[16rpx] bg-white px-[30rpx] py-[40rpx] shadow-[0_4rpx_20rpx_rgba(0,0,0,0.05)]">
<view class="mb-[40rpx] text-center">
<text class="text-[36rpx] text-[#1a1a1a] font-bold">
User Agreement
</text>
</view>
<view class="mb-[40rpx] text-center">
<text class="text-[24rpx] text-[#666666]">
Last Updated: March 10, 2026
</text>
</view>
<view class="mb-[32rpx]">
<text class="text-[28rpx] text-[#333333] font-semibold">
Important Notice
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
Welcome to use the XiaoZhi Backend Service (hereinafter referred to as "the Service"). The Service is designed to provide backend service support for XiaoZhi AI hardware devices, including but not limited to intelligent voice interaction, visual understanding, intent recognition, conversation memory, knowledge base Q&A, device management, and agent management. The operator of this Service is the actual deployer and administrator of the Service (hereinafter referred to as "Operator" or "we").
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
Before registering, logging in, or using the Service, please carefully read and fully understand all terms of this Agreement, especially the terms that are highlighted in bold and may have significant impact on your interests, including but not limited to disclaimers and liability limitations.
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
When you complete registration, login, or otherwise use the Service following the on-screen instructions, it signifies that you have fully read, understood, and accepted all contents of this Agreement. If you disagree with this Agreement or any of its terms, please stop using the Service immediately.
</text>
</view>
<view class="mb-[40rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
This Agreement may be updated and revised based on actual circumstances. The revised Agreement will be published through the Service's announcement mechanisms. The revised Agreement shall take effect from the date of publication. If you continue to use the Service after the Agreement revision, it shall be deemed that you have accepted the revised Agreement.
</text>
</view>
<view class="mb-[32rpx]">
<text class="text-[28rpx] text-[#333333] font-semibold">
I. Definitions
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
XiaoZhi Backend Service: refers to the XiaoZhi Backend Service and its backend service system, including but not limited to the Admin Console (management backend), API interfaces, WebSocket communication services, etc.
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
Admin Console: refers to the web management interface provided by the Service for device management, agent configuration, user management, and other functions.
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
User: refers to natural persons, legal persons, or other organizations who register or otherwise use the Service, hereinafter referred to as "you".
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
Intelligent Agent (Agent): refers to an AI character created and configured within the Service, containing a collection of capabilities such as language models, speech recognition, speech synthesis, visual understanding, intent recognition, conversation memory, and knowledge base.
</text>
</view>
<view class="mb-[40rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
Device: refers to ESP32 and other hardware devices connected and managed through the Service.
</text>
</view>
<view class="mb-[32rpx]">
<text class="text-[28rpx] text-[#333333] font-semibold">
II. Scope of Agreement
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
This Agreement is a legal agreement between you and the Operator regarding your use of the Service. This Agreement applies to all your actions when using the Service through the Admin Console or other means.
</text>
</view>
<view class="mb-[40rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
The source code of the Service follows the corresponding open-source license. This Agreement governs your use of the Service and does not affect the rights granted to you by the open-source license itself.
</text>
</view>
<view class="mb-[32rpx]">
<text class="text-[28rpx] text-[#333333] font-semibold">
III. Account Registration and Use
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
3.1 User Eligibility: You confirm that before using the Service, you should have the capacity for civil conduct as required by the laws of the People's Republic of China. If you are a minor, you should use the Service under the company and guidance of your legal guardian.
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
3.2 Account Registration: You may register and log in to the Service through mobile phone verification code, username/password, or other methods. You shall provide true, accurate, and complete registration information and update it promptly.
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
3.3 Account Security: You shall properly keep your account and password safe. All consequences arising from account leakage or unauthorized use by others due to your reasons shall be borne by you. If you discover any security risks with your account, please contact the Operator immediately.
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
3.4 Account Usage Restrictions: Your account is for your personal use only. Without the Operator's consent, you shall not transfer, rent, or lend your account to any third party in any way.
</text>
</view>
<view class="mb-[40rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
3.5 Account Cancellation: If you need to cancel your account, you may do so through the account settings in the Admin Console or contact the Operator for processing. After account cancellation, related data will be deleted and cannot be recovered. Please proceed with caution.
</text>
</view>
<view class="mb-[32rpx]">
<text class="text-[28rpx] text-[#333333] font-semibold">
IV. Service Content
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
4.1 Service Overview: Relying on artificial intelligence technology, the Service provides intelligent interaction services for connected hardware devices through API calls to third-party generative AI models, including but not limited to Voice Activity Detection (VAD), Automatic Speech Recognition (ASR), Large Language Model (LLM), Text-to-Speech (TTS), Vision Large Language Model (VLLM), Intent Recognition, Conversation Memory, and Retrieval-Augmented Generation (RAG).
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
4.2 Agent Management: You can create and configure Agents through the Admin Console, including setting role templates, selecting language models, configuring voice parameters, setting intent recognition rules, managing knowledge bases, enabling conversation memory, configuring Agent plugins, etc. The Agent settings shall not violate national laws and regulations.
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
4.3 Device Management: You can bind and manage your hardware devices through the Admin Console, perform device configuration, firmware updates, OTA remote upgrades, etc. You shall ensure that the devices you manage are lawfully owned or authorized by you.
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
4.4 Visual Understanding: The Service supports image capture through device cameras and uses third-party vision models for image recognition and scene understanding. You shall ensure that image capture complies with relevant laws and regulations and does not infringe upon others' privacy.
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
4.5 Intent Recognition: The Service can understand the intent of user commands through intent recognition and trigger corresponding operations or services, such as controlling smart devices, querying information, etc.
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
4.6 Conversation Memory: The Service can record and store summaries of your interaction history with the Agent to provide more coherent and personalized interaction experiences in subsequent conversations. You can manage or clear memory data in the Admin Console.
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
4.7 Knowledge Base: The Service supports uploading documents, texts, and other content to build knowledge bases. Agents can retrieve knowledge base content during conversations to provide Q&A services. You shall ensure that uploaded knowledge base content does not infringe upon third-party intellectual property rights and does not contain content that violates laws and regulations.
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
4.8 Voice Print Recognition: The Service supports voice print recognition. You can register voice print samples to achieve speaker identity verification and personalized services. Voice print data will be used for identity verification purposes.
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
4.9 Voice Cloning: The Service supports voice cloning. You can clone custom voice timbres by providing audio samples. Cloned voices are for your personal use only and shall not be used to impersonate others or engage in illegal activities.
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
4.10 MCP Endpoints: the Service supports MCP (Model Context Protocol) and can serve as an MCP Server to provide tool calling capabilities to external systems, or as an MCP Client to call external MCP services, achieving standardized integration with external systems.
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
4.11 Context Providers: The Service supports Context Provider functionality, which can obtain real-time information from external data sources, such as weather, news, stocks, etc., providing Agents with richer knowledge sources.
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
4.12 MQTT Protocol: The Service supports MQTT (Message Queuing Telemetry Transport) protocol for message communication between IoT devices. You can use the MQTT protocol to send device control commands, monitor device status, etc.
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
4.13 Service Disclaimer: The conversations and responses generated by third-party AI models that the Service depends on are for reference only and do not constitute professional advice in any field. You shall not use the output content as professional advice in medical, legal, financial, or other professional fields. Any judgments or decisions you make based on the output content shall be entirely at your own responsibility.
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
4.14 Service Fees: The Service is a free open-source project and does not charge any usage fees from users. It only provides platform capabilities for calling third-party services and does not involve any paid functions or value-added services.
</text>
</view>
<view class="mb-[24rpx] ml-[40rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
The Service supports multiple AI service providers with different billing models as follows:
</text>
</view>
<view class="mb-[16rpx] ml-[60rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
(1) Free Services: Some service providers offer free quotas or completely free services, such as Zhipu AI (free models like glm-4-flash), Microsoft EdgeTTS voice synthesis, etc.
</text>
</view>
<view class="mb-[16rpx] ml-[60rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
(2) Pay-as-you-go: Some service providers charge based on API usage, such as OpenAI, Alibaba Cloud Intelligent Speech, Baidu Wenxin, iFlytek, etc. You need to pay the corresponding fees to third-party service providers yourself. Such fees are unrelated to this Service.
</text>
</view>
<view class="mb-[40rpx] ml-[60rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
(3) Local Deployment: The Service supports fully local AI deployment solutions, such as FunASR speech recognition, FishSpeech voice synthesis, Ollama local large models, etc. Local deployment does not require network fees or API usage fees but requires sufficient local computing resources.
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
4.15 Service Changes: The Operator reserves the right to adjust, upgrade, or terminate the Service content as actual circumstances require, and will provide notice as far in advance as possible.
</text>
</view>
<view class="mb-[32rpx]">
<text class="text-[28rpx] text-[#333333] font-semibold">
V. User Conduct Standards
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
5.1 Legal Use: When using the Service, you shall comply with the laws and regulations of the People's Republic of China and relevant international treaties, and shall not use the Service for any illegal activities.
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
5.2 Prohibited Conduct: You promise not to engage in the following behaviors when using the Service:
</text>
</view>
<view class="mb-[16rpx] ml-[40rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
(1) Publishing, transmitting, or storing content that endangers national security or social stability, including but not limited to content involving subverting state power, damaging national honor and interests, or inciting ethnic hatred.
</text>
</view>
<view class="mb-[16rpx] ml-[40rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
(2) Publishing, transmitting, or storing content that infringes upon the legitimate rights and interests of others, including but not limited to infringing upon intellectual property rights, privacy rights, reputation rights, etc.
</text>
</view>
<view class="mb-[16rpx] ml-[40rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
(3) Publishing, transmitting, or storing obscene, pornographic, violent, terrorist, or crime-inciting content.
</text>
</view>
<view class="mb-[16rpx] ml-[40rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
(4) Publishing false information, spreading rumors, or engaging in fraudulent activities.
</text>
</view>
<view class="mb-[16rpx] ml-[40rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
(5) Interfering with the normal operation of the Service in any way, including but not limited to attacking servers, spreading malicious programs, or malicious consuming system resources.
</text>
</view>
<view class="mb-[16rpx] ml-[40rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
(6) Unauthorized reverse engineering, decompiling, or attempting to obtain system source code of the Service (this clause does not limit your lawful use of open-source code according to the open-source license).
</text>
</view>
<view class="mb-[16rpx] ml-[40rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
(7) Using the Service for automated batch operations, malicious consuming API call quotas or other system resources.
</text>
</view>
<view class="mb-[16rpx] ml-[40rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
(8) Entering content that violates laws, regulations, or public order and good customs in Agent settings.
</text>
</view>
<view class="mb-[16rpx] ml-[40rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
(9) Using the Service to harm or attempt to harm minors.
</text>
</view>
<view class="mb-[40rpx] ml-[40rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
(10) Other behaviors that violate laws, regulations, this Agreement, or may harm the legitimate rights and interests of the Operator or third parties.
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
5.3 Handling of Violations: If you violate the above conduct standards, the Operator has the right to take measures such as warnings, feature restrictions, service suspension, or account banning based on the severity of the situation, and reserves the right to pursue legal liability.
</text>
</view>
<view class="mb-[32rpx]">
<text class="text-[28rpx] text-[#333333] font-semibold">
VI. Intellectual Property
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
6.1 Open-source License: The Service is an open-source project, and its source code follows the corresponding open-source license. You may use the relevant source code in compliance with the license terms.
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
6.2 Service Content Ownership: Except for open-source code, the intellectual property rights of interface designs, icons, copy, and other independently created content in the Service belong to the Operator.
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
6.3 User Content: The intellectual property rights of content you input, upload, or generate through the Service shall be determined according to relevant laws and regulations. You grant the Operator the right to store and process your input content for the purpose of maintaining and improving the Service.
</text>
</view>
<view class="mb-[40rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
6.4 Third-party Rights: When using the Service, you should respect the intellectual property rights and other legitimate rights of third parties. You shall bear all responsibilities for any disputes arising from your infringement of third-party rights.
</text>
</view>
<view class="mb-[32rpx]">
<text class="text-[28rpx] text-[#333333] font-semibold">
VII. Privacy Protection
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
7.1 Information Collection: To provide the Service, the Operator may collect your registration information (such as phone number, username), device information, usage logs, etc. For specific personal information collection and usage rules, please refer to the Privacy Policy.
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
7.2 Information Protection: The Operator will take reasonable technical and management measures to protect the security of your personal information. However, due to the openness of the Internet, the Operator cannot absolutely guarantee information security. Please pay attention to protecting your personal sensitive information.
</text>
</view>
<view class="mb-[40rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
7.3 Information Security Warning: Please do not disclose your sensitive personal information such as property accounts, bank card numbers, passwords, etc. to AI during your use of the Service. Any losses resulting from this shall be borne by you.
</text>
</view>
<view class="mb-[32rpx]">
<text class="text-[28rpx] text-[#333333] font-semibold">
VIII. Disclaimer
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
8.1 AI-Generated Content: The Service relies on third-party AI models to provide intelligent interaction capabilities. The content generated by AI may be inaccurate, incomplete, or inappropriate. The Operator does not make any guarantees regarding the authenticity, accuracy, or completeness of AI-generated content.
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
8.2 Service Interruption: The Operator shall not be responsible for service interruptions or abnormalities caused by the following reasons:
</text>
</view>
<view class="mb-[16rpx] ml-[40rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
(1) Force majeure factors, such as natural disasters, pandemics, wars, etc.
</text>
</view>
<view class="mb-[16rpx] ml-[40rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
(2) Public service factors such as power supply failures or communication network failures.
</text>
</view>
<view class="mb-[16rpx] ml-[40rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
(3) Service failures or policy adjustments of third-party AI model service providers.
</text>
</view>
<view class="mb-[16rpx] ml-[40rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
(4) Temporary service interruptions caused by system maintenance or upgrades.
</text>
</view>
<view class="mb-[16rpx] ml-[40rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
(5) Network security incidents such as hacker attacks or computer viruses.
</text>
</view>
<view class="mb-[16rpx] ml-[40rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
(6) Service adjustments due to laws, regulations, or government controls.
</text>
</view>
<view class="mb-[40rpx] ml-[40rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
(7) Other situations not caused by the Operator's fault.
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
8.3 Third-party Services: The Service may involve third-party provided language models, speech recognition, speech synthesis, vision models, intent recognition, and other services. You should also comply with the terms of service of third parties when using them. For issues arising from third-party services, please contact the third party directly.
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
8.4 Open-source Statement: The Service is an open-source project provided "as is". To the maximum extent permitted by law, the Operator does not make any express or implied guarantees regarding the merchantability, fitness for a particular purpose, or non-infringement of the Service.
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
8.5 Special Open-source Software Statement: This project is open-source software. This Service has no commercial cooperation relationship with any third-party API service providers it connects to (including but not limited to platforms such as speech recognition, large language models, speech synthesis, etc.) and does not provide any form of guarantee for their service quality and capital security. This software does not host any account keys, does not participate in capital flow, and does not bear the risk of recharge capital losses.
</text>
</view>
<view class="mb-[40rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
8.6 Security Warning: This project functions are not fully developed and have not passed network security assessment. Please do not use it in production environments. If you deploy this project for learning in a public network environment, you must take necessary protective measures, including but not limited to setting strong passwords, restricting access permissions, enabling HTTPS encrypted transmission, etc.
</text>
</view>
<view class="mb-[32rpx]">
<text class="text-[28rpx] text-[#333333] font-semibold">
IX. Protection of Minors
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
9.1 If you are a minor under 18 years of age, you should use the Service under the guidance and consent of your guardian.
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
9.2 Guardians should strengthen supervision of minors using the Service, guide minors to use it reasonably, and avoid excessive reliance.
</text>
</view>
<view class="mb-[40rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
9.3 Minors should pay attention to personal information protection when using the Service and avoid uploading or disclosing personal sensitive information.
</text>
</view>
<view class="mb-[32rpx]">
<text class="text-[28rpx] text-[#333333] font-semibold">
X. Agreement Termination
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
10.1 User Termination: You have the right to stop using the Service and cancel your account at any time.
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
10.2 Operator Termination: The Operator has the right to terminate providing services to you under the following circumstances:
</text>
</view>
<view class="mb-[16rpx] ml-[40rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
(1) You violate the terms of this Agreement.
</text>
</view>
<view class="mb-[16rpx] ml-[40rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
(2) You use the Service for illegal activities.
</text>
</view>
<view class="mb-[16rpx] ml-[40rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
(3) As required by laws, regulations, or policies.
</text>
</view>
<view class="mb-[40rpx] ml-[40rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
(4) The Operator decides to stop operating the Service.
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
10.3 Post-termination Handling: After the Agreement is terminated, the Operator is not obligated to retain your account information and related data. The Operator still has the right to pursue your breach of contract liability during the validity period of the Agreement.
</text>
</view>
<view class="mb-[32rpx]">
<text class="text-[28rpx] text-[#333333] font-semibold">
XI. Governing Law and Dispute Resolution
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
11.1 The formation, effectiveness, interpretation, revision, termination, and dispute resolution of this Agreement are governed by the laws of the People's Republic of China.
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
11.2 Any disputes arising from this Agreement or the Service shall be resolved through friendly negotiation between both parties; if negotiation fails, either party has the right to bring a lawsuit to the people's court with jurisdiction at the Operator's location.
</text>
</view>
<view class="mb-[40rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
11.3 If any provision of this Agreement is determined to be invalid or unenforceable, it does not affect the validity of the remaining provisions.
</text>
</view>
<view class="mb-[32rpx]">
<text class="text-[28rpx] text-[#333333] font-semibold">
XII. Miscellaneous
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
12.1 This Agreement constitutes the complete agreement between you and the Operator regarding your use of the Service.
</text>
</view>
<view class="mb-[40rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
12.2 The Operator's failure to exercise or delay in exercising any rights under this Agreement does not constitute a waiver of such rights.
</text>
</view>
</view>
</scroll-view>
</view>
</template>
@@ -0,0 +1,542 @@
<route lang="jsonc" type="page">
{
"layout": "default",
"style": {
"navigationStyle": "custom",
"navigationBarTitleText": "用户协议"
}
}
</route>
<script lang="ts" setup>
import { t } from '@/i18n'
function goBack() {
uni.navigateBack()
}
</script>
<template>
<view class="h-screen flex flex-col bg-[#f5f8fd]">
<wd-navbar
:title="t('login.userAgreement')"
safe-area-inset-top
left-arrow
:bordered="false"
@click-left="goBack"
>
<template #left>
<wd-icon name="arrow-left" size="18" />
</template>
</wd-navbar>
<scroll-view scroll-y class="box-border flex-1 px-[30rpx] py-[30rpx]">
<view class="rounded-[16rpx] bg-white px-[30rpx] py-[40rpx] shadow-[0_4rpx_20rpx_rgba(0,0,0,0.05)]">
<view class="mb-[40rpx] text-center">
<text class="text-[36rpx] text-[#1a1a1a] font-bold">
{{ t('login.userAgreement') }}
</text>
</view>
<view class="mb-[40rpx] text-center">
<text class="text-[24rpx] text-[#666666]">
更新日期2026年3月10日
</text>
</view>
<view class="mb-[32rpx]">
<text class="text-[28rpx] text-[#333333] font-semibold">
提示条款
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
欢迎您使用小智后端服务以下简称"本服务"本服务旨在为小智AI硬件设备提供后端服务支持包括但不限于智能语音交互视觉理解意图识别对话记忆知识库问答设备管理智能体管理等功能本服务的运营方为本服务的实际部署者和管理者以下简称"运营者""我们"
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
在您注册登录或使用本服务之前请您务必审慎阅读充分理解本协议各条款内容特别是以加粗形式提示您注意的可能与您利益有重大关系的条款包括但不限于免责声明责任限制等条款
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
当您按照页面提示完成注册登录或以其他方式使用本服务时即表示您已充分阅读理解并接受本协议的全部内容如果您不同意本协议或其中任何条款请立即停止使用本服务
</text>
</view>
<view class="mb-[40rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
本协议可能根据实际情况进行更新和修订修订后的协议将通过本平台公告等方式予以公示修订后的协议自公示之日起生效如您在协议修订后继续使用本服务则视为您已接受修订后的协议
</text>
</view>
<view class="mb-[32rpx]">
<text class="text-[28rpx] text-[#333333] font-semibold">
定义
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
小智后端服务指小智后端服务及其部署运行的后端服务系统包括但不限于智控台管理后台API接口WebSocket通信服务等
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
智控台指本服务提供的Web管理界面用于设备管理智能体配置用户管理等功能
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
用户指通过注册或其他方式使用本服务的自然人法人或其他组织以下简称"您"
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
智能体指在本服务中创建和配置的AI角色包含语言模型语音识别语音合成视觉理解意图识别对话记忆知识库等能力的集合
</text>
</view>
<view class="mb-[40rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
设备指通过本服务进行连接和管理的ESP32等硬件设备
</text>
</view>
<view class="mb-[32rpx]">
<text class="text-[28rpx] text-[#333333] font-semibold">
协议范围
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
本协议是您与运营者之间关于使用本服务的法律协议本协议适用于您通过智控台或其他方式使用本服务的全部行为
</text>
</view>
<view class="mb-[40rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
本服务的源代码遵循相应的开源许可证本协议约束您对本服务的使用行为不影响开源许可证本身赋予您的权利
</text>
</view>
<view class="mb-[32rpx]">
<text class="text-[28rpx] text-[#333333] font-semibold">
账号注册与使用
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
3.1 用户资格您确认在使用本服务前您应当具备中华人民共和国法律规定的与您行为相适应的民事行为能力若您为未成年人应在法定监护人的陪同和指导下使用本服务
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
3.2 账号注册您可通过手机号验证码或用户名密码等方式注册和登录本服务您应当提供真实准确完整的注册信息并及时更新
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
3.3 账号安全您应妥善保管您的账号和密码因您的原因导致账号泄露或被他人冒用所产生的一切后果由您自行承担如发现账号存在安全隐患请立即联系运营者
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
3.4 账号使用限制您的账号仅限您本人使用未经运营者同意不得以任何方式将账号转让出租或借给第三方使用
</text>
</view>
<view class="mb-[40rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
3.5 账号注销如您需要注销账号可通过智控台的账号设置功能进行操作或联系运营者进行处理账号注销后相关数据将被删除且无法恢复请谨慎操作
</text>
</view>
<view class="mb-[32rpx]">
<text class="text-[28rpx] text-[#333333] font-semibold">
服务内容
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
4.1 服务概述本服务依托人工智能技术通过API接口调用第三方生成式人工智能模型为连接的硬件设备提供智能交互服务包括但不限于语音活动检测VAD语音识别ASR语义理解LLM语音合成TTS视觉理解VLLM意图识别Intent对话记忆Memory知识库RAG
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
4.2 智能体管理您可通过智控台创建和配置智能体包括设定角色模板选择语言模型配置语音参数设置意图识别规则管理知识库开启对话记忆配置智能体插件等智能体的设定内容不得违反国家法律法规
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
4.3 设备管理您可通过智控台绑定和管理您的硬件设备进行设备配置固件更新OTA远程升级等操作您应确保所管理的设备为您合法拥有或已获得授权
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
4.4 视觉理解本服务支持通过设备摄像头采集图像调用第三方视觉模型进行图像识别和场景理解您应确保采集图像的行为符合相关法律法规不得侵犯他人隐私
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
4.5 意图识别本服务可通过意图识别功能理解用户指令的意图并据此触发相应的操作或服务如控制智能设备查询信息等
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
4.6 对话记忆本服务可记录和存储您与智能体的交互历史摘要以便在后续对话中提供更连贯个性化的交互体验您可在智控台管理或清除记忆数据
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
4.7 知识库本服务支持您上传文档文本等内容构建知识库智能体可在对话中检索知识库内容为您提供问答服务您应确保上传的知识库内容不侵犯第三方知识产权且不包含违反法律法规的内容
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
4.8 声纹识别本服务支持声纹识别功能您可注册声纹样本以实现说话人身份验证和个性化服务声纹数据将用于身份验证目的
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
4.9 语音克隆本服务支持语音克隆功能您可通过提供音频样本克隆自定义音色克隆音色仅供您本人使用不得用于仿冒他人或从事违法违规活动
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
4.10 MCP接入点本服务支持MCPModel Context Protocol协议可作为MCP Server向外部提供工具调用能力也可作为MCP Client调用外部MCP服务实现与外部系统的标准化集成
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
4.11 上下文提供者本服务支持上下文提供者Context Provider功能可从外部数据源获取实时信息如天气新闻股票等为智能体提供更丰富的知识来源
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
4.12 MQTT协议本服务支持MQTTMessage Queuing Telemetry Transport协议用于与物联网设备之间的消息通信您可通过MQTT协议实现设备控制指令下发设备状态监控等功能
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
4.13 服务说明本服务所依赖的第三方AI模型生成的对话答复内容仅供参考不构成任何专业建议您不得将输出内容作为医疗法律金融等领域的专业建议您根据输出内容所作的任何判断或决策由您自行承担全部责任
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
4.14 服务费用本服务为免费开源项目不向用户收取任何使用费用仅提供调用第三方服务的平台能力不涉及任何付费功能或增值服务
</text>
</view>
<view class="mb-[24rpx] ml-[40rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
本服务支持多种AI服务提供商不同提供商的收费模式如下
</text>
</view>
<view class="mb-[16rpx] ml-[60rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
1免费服务部分服务商提供免费额度或完全免费的服务如智谱AIglm-4-flash等免费模型微软EdgeTTS语音合成等
</text>
</view>
<view class="mb-[16rpx] ml-[60rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
2按量付费服务部分服务商按API调用量计费如OpenAI阿里云智能语音百度文心讯飞等您需要自行向第三方服务商支付相应费用该等费用与本服务无关
</text>
</view>
<view class="mb-[40rpx] ml-[60rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
3本地部署方案本服务支持完全本地部署的AI方案如FunASR语音识别FishSpeech语音合成Ollama本地大模型等本地部署无需网络费用和API调用费用但需要本地具备足够的计算资源
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
4.15 服务变更运营者保留根据实际情况对服务内容进行调整升级或终止的权利并将尽可能提前予以通知
</text>
</view>
<view class="mb-[32rpx]">
<text class="text-[28rpx] text-[#333333] font-semibold">
用户行为规范
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
5.1 合法使用您在使用本服务时应遵守中华人民共和国的法律法规及相关国际条约不得利用本服务从事任何违法违规活动
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
5.2 禁止行为您承诺在使用本服务时不得实施以下行为
</text>
</view>
<view class="mb-[16rpx] ml-[40rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
1发布传输存储危害国家安全社会稳定的内容包括但不限于涉及颠覆国家政权损害国家荣誉和利益煽动民族仇恨等内容
</text>
</view>
<view class="mb-[16rpx] ml-[40rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
2发布传输存储侵犯他人合法权益的内容包括但不限于侵犯知识产权隐私权名誉权等
</text>
</view>
<view class="mb-[16rpx] ml-[40rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
3发布传输存储淫秽色情暴力恐怖或教唆犯罪的内容
</text>
</view>
<view class="mb-[16rpx] ml-[40rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
4发布虚假信息散布谣言或从事欺诈行为
</text>
</view>
<view class="mb-[16rpx] ml-[40rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
5以任何方式干扰本服务的正常运行包括但不限于攻击服务器传播恶意程序恶意消耗系统资源等
</text>
</view>
<view class="mb-[16rpx] ml-[40rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
6未经授权对本服务进行反向工程反编译或其他试图获取系统源代码的行为本条不限制您依据开源许可证对开源代码的合法使用
</text>
</view>
<view class="mb-[16rpx] ml-[40rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
7利用本服务进行自动化批量操作恶意消耗API调用配额或其他系统资源
</text>
</view>
<view class="mb-[16rpx] ml-[40rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
8在智能体设定中输入违反法律法规或社会公序良俗的内容
</text>
</view>
<view class="mb-[16rpx] ml-[40rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
9利用本服务伤害或企图伤害未成年人
</text>
</view>
<view class="mb-[40rpx] ml-[40rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
10其他违反法律法规本协议约定或可能损害运营者及第三方合法权益的行为
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
5.3 违规处理如您违反上述行为规范运营者有权视情节严重程度采取警告限制功能暂停服务封禁账号等措施并保留追究法律责任的权利
</text>
</view>
<view class="mb-[32rpx]">
<text class="text-[28rpx] text-[#333333] font-semibold">
知识产权
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
6.1 开源许可本服务为开源项目其源代码遵循相应的开源许可证您可以在遵守该许可证条款的前提下使用相关源代码
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
6.2 服务内容权属除开源代码外本服务中的界面设计图标文案及其他运营者独立创作的内容其知识产权归运营者所有
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
6.3 用户内容您通过本服务输入上传或生成的内容其知识产权归属依照相关法律法规确定您授予运营者为维护和改进服务之目的对您输入内容进行存储和必要处理的权利
</text>
</view>
<view class="mb-[40rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
6.4 第三方权益您在使用本服务时应尊重第三方的知识产权及其他合法权益因您侵犯第三方权益而引发的纠纷由您自行承担全部责任
</text>
</view>
<view class="mb-[32rpx]">
<text class="text-[28rpx] text-[#333333] font-semibold">
隐私保护
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
7.1 信息收集为提供本服务运营者可能会收集您的注册信息如手机号用户名设备信息使用日志等具体的个人信息收集和使用规则请参阅隐私政策
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
7.2 信息保护运营者将采取合理的技术和管理措施保护您的个人信息安全但由于互联网的开放性运营者不能绝对保证信息安全请您注意保护个人敏感信息
</text>
</view>
<view class="mb-[40rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
7.3 信息安全提示请勿在使用本服务过程中向AI透露您的财产账户银行卡密码等敏感个人信息否则由此带来的损失由您自行承担
</text>
</view>
<view class="mb-[32rpx]">
<text class="text-[28rpx] text-[#333333] font-semibold">
免责声明
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
8.1 AI生成内容本服务依赖第三方AI模型提供智能交互能力AI生成的内容可能存在不准确不完整或不恰当之处运营者不对AI生成内容的真实性准确性完整性作任何保证
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
8.2 服务中断因以下原因导致服务中断或异常运营者不承担责任
</text>
</view>
<view class="mb-[16rpx] ml-[40rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
1不可抗力因素如自然灾害疫情战争等
</text>
</view>
<view class="mb-[16rpx] ml-[40rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
2电力供应故障通信网络故障等公共服务因素
</text>
</view>
<view class="mb-[16rpx] ml-[40rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
3第三方AI模型服务商的服务故障或政策调整
</text>
</view>
<view class="mb-[16rpx] ml-[40rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
4系统维护升级导致的临时性服务中断
</text>
</view>
<view class="mb-[16rpx] ml-[40rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
5黑客攻击计算机病毒等网络安全事件
</text>
</view>
<view class="mb-[16rpx] ml-[40rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
6法律法规或政府管制导致的服务调整
</text>
</view>
<view class="mb-[40rpx] ml-[40rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
7其他非运营者过错导致的情形
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
8.3 第三方服务本服务可能涉及第三方提供的语言模型语音识别语音合成视觉模型意图识别等服务您在使用时应同时遵守第三方的服务条款因第三方服务引发的问题请直接与第三方联系
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
8.4 开源声明本服务为开源项目"现状"提供在法律允许的最大范围内运营者不就本服务的适销性特定用途适用性或不侵权性作任何明示或暗示的保证
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
8.5 开源软件特别声明本项目为开源软件本服务与对接的任何第三方API服务商包括但不限于语音识别大模型语音合成等平台均不存在商业合作关系不为其服务质量及资金安全提供任何形式的担保本软件不托管任何账户密钥不参与资金流转不承担充值资金损失风险
</text>
</view>
<view class="mb-[40rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
8.6 安全警告本项目功能未完善且未通过网络安全测评请勿在生产环境中使用如果您在公网环境中部署学习本项目请务必做好必要的防护措施包括但不限于设置强密码限制访问权限启用HTTPS加密传输等
</text>
</view>
<view class="mb-[32rpx]">
<text class="text-[28rpx] text-[#333333] font-semibold">
未成年人保护
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
9.1 若您是未满18周岁的未成年人应在监护人的指导和同意下使用本服务
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
9.2 监护人应加强对未成年人使用本服务的监督引导未成年人合理使用避免过度依赖
</text>
</view>
<view class="mb-[40rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
9.3 未成年人在使用本服务时应注意个人信息保护避免上传或透露个人敏感信息
</text>
</view>
<view class="mb-[32rpx]">
<text class="text-[28rpx] text-[#333333] font-semibold">
协议终止
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
10.1 用户终止您有权随时停止使用本服务并注销账号
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
10.2 运营者终止出现以下情形时运营者有权终止向您提供服务
</text>
</view>
<view class="mb-[16rpx] ml-[40rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
1您违反本协议的约定
</text>
</view>
<view class="mb-[16rpx] ml-[40rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
2您利用本服务从事违法违规活动
</text>
</view>
<view class="mb-[16rpx] ml-[40rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
3根据法律法规或政策要求
</text>
</view>
<view class="mb-[40rpx] ml-[40rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
4运营者决定停止运营本服务
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
10.3 终止后处理协议终止后运营者无义务保留您的账号信息及相关数据运营者仍有权依据本协议追究您在协议有效期内的违约责任
</text>
</view>
<view class="mb-[32rpx]">
<text class="text-[28rpx] text-[#333333] font-semibold">
十一法律适用与争议解决
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
11.1 本协议的订立生效解释修订终止及争议解决均适用中华人民共和国法律
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
11.2 因本协议或本服务引发的争议双方应友好协商解决协商不成的任何一方有权向运营者所在地有管辖权的人民法院提起诉讼
</text>
</view>
<view class="mb-[40rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
11.3 本协议的任何条款被认定为无效或不可执行不影响其余条款的效力
</text>
</view>
<view class="mb-[32rpx]">
<text class="text-[28rpx] text-[#333333] font-semibold">
十二其他
</text>
</view>
<view class="mb-[24rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
12.1 本协议构成您与运营者之间关于使用本服务的完整协议
</text>
</view>
<view class="mb-[40rpx] ml-[20rpx]">
<text class="text-[28rpx] text-[#333333] leading-[1.8]">
12.2 运营者未行使或延迟行使本协议项下的任何权利不构成对该权利的放弃
</text>
</view>
</view>
</scroll-view>
</view>
</template>
<style lang="scss" scoped>
</style>
@@ -1,18 +1,21 @@
<route lang="jsonc" type="page">{
<route lang="jsonc" type="page">
{
"layout": "default",
"style": {
"navigationBarTitleText": "设置",
"navigationStyle": "custom"
}
}</route>
}
</route>
<script lang="ts" setup>
import { changeLanguage, getCurrentLanguage, getSupportedLanguages, t } from '@/i18n'
import type { Language } from '@/store/lang'
import { clearServerBaseUrlOverride, getEnvBaseUrl, getServerBaseUrlOverride, setServerBaseUrlOverride } from '@/utils'
import { isMp } from '@/utils/platform'
import { computed, onMounted, reactive, ref } from 'vue'
import { useToast } from 'wot-design-uni'
import { changeLanguage, getCurrentLanguage, getSupportedLanguages, t } from '@/i18n'
import { useConfigStore } from '@/store'
import { clearServerBaseUrlOverride, getEnvBaseUrl, getServerBaseUrlOverride, setServerBaseUrlOverride } from '@/utils'
import { isMp } from '@/utils/platform'
defineOptions({
name: 'SettingsPage',
@@ -27,6 +30,8 @@ const cacheInfo = reactive({
dataCache: '0MB',
})
const configStore = useConfigStore()
//
const baseUrlInput = ref('')
const urlError = ref('')
@@ -81,18 +86,26 @@ async function testServerBaseUrl() {
const response = await uni.request({
url: `${baseUrlInput.value}/api/ping`,
method: 'GET',
timeout: 3000
timeout: 3000,
})
if (response.statusCode === 200) {
return true
} else {
toast.error(t('message.invalidAddress'))
}
else {
toast.error({
msg: t('message.invalidAddress'),
duration: 3000,
})
return false
}
} catch (error) {
}
catch (error) {
console.error('测试服务端地址失败:', error)
toast.error(t('message.invalidAddress'))
toast.error({
msg: t('message.invalidAddress'),
duration: 3000,
})
return false
}
}
@@ -110,6 +123,20 @@ async function saveServerBaseUrl() {
return
}
setServerBaseUrlOverride(baseUrlInput.value)
// config
uni.request({
url: `${getEnvBaseUrl()}/user/pub-config`,
method: 'GET',
success: (res) => {
if (res.statusCode === 200) {
configStore.setConfig(res.data.data)
uni.setStorageSync('config', res.data.data.sm2PubKey)
}
},
fail: (err) => {
console.error('获取SM2公钥失败:', err)
},
})
//
clearAllCacheAfterUrlChange()
+2
View File
@@ -15,5 +15,7 @@ export default store
export * from './config'
export * from './plugin'
export * from './provider'
export * from './speedPitch'
// 模块统一导出
export * from './user'
+23
View File
@@ -0,0 +1,23 @@
import type { Providers } from '@/api/agent/types'
import { defineStore } from 'pinia'
export const useProvider = defineStore('provider', () => {
const providers = ref<Providers[]>([])
const updateProviders = (val: Providers[]) => {
providers.value = val
}
return {
providers,
updateProviders,
}
}, {
persist: {
key: 'providers',
serializer: {
serialize: state => JSON.stringify(state.providers),
deserialize: value => ({ providers: JSON.parse(value) }),
},
},
})
@@ -0,0 +1,26 @@
import { defineStore } from 'pinia'
export const useSpeedPitch = defineStore('speedPitch', () => {
const speedPitch = ref({
ttsVolume: 0,
ttsRate: 0,
ttsPitch: 0,
})
const updateSpeedPitch = (val: typeof speedPitch.value) => {
speedPitch.value = val
}
return {
speedPitch,
updateSpeedPitch,
}
}, {
persist: {
key: 'speedPitch',
serializer: {
serialize: state => JSON.stringify(state.speedPitch),
deserialize: value => ({ speedPitch: JSON.parse(value) }),
},
},
})
+2 -1
View File
@@ -51,10 +51,11 @@ export const useUserStore = defineStore(
*/
const getUserInfo = async () => {
const userData = await _getUserInfo()
const authInfo = JSON.parse(uni.getStorageSync('token') || '{}')
const userInfoWithExtras = {
...userData,
avatar: userInfoState.avatar,
token: uni.getStorageSync('token') || '',
token: authInfo.token || '',
}
setUserInfo(userInfoWithExtras)
uni.setStorageSync('userInfo', userInfoWithExtras)
@@ -25,6 +25,11 @@
</el-option>
</el-select>
</el-form-item>
<el-form-item :label="$t('voiceClone.languages')" prop="languages">
<el-input v-model="form.languages" :placeholder="$t('voiceClone.languagesPlaceholder')" style="width: 100%">
</el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
@@ -70,6 +75,9 @@ export default {
],
userId: [
{ required: true, message: this.$t('voiceClone.userIdRequired'), trigger: 'change' }
],
languages: [
{ required: true, message: this.$t('voiceClone.languagesRequired'), trigger: 'blur' }
]
}
}
+3
View File
@@ -1132,6 +1132,9 @@ export default {
'voiceClone.platformNamePlaceholder': 'Bitte Plattformname auswählen',
'voiceClone.voiceIdPlaceholder': 'Bitte Stimmen-ID eingeben und Enter drücken',
'voiceClone.userIdPlaceholder': 'Bitte Schlüsselwort eingeben, um Kontoinhaber auszuwählen',
'voiceClone.languages': 'Sprachen',
'voiceClone.languagesPlaceholder': 'Bitte Sprachen eingeben, z.B.: Deutsch, English',
'voiceClone.languagesRequired': 'Bitte Sprachen eingeben',
'voiceClone.waitingUpload': 'Wartet auf Upload',
'voiceClone.waitingTraining': 'Wartet auf Klon',
'voiceClone.training': 'Training',
+3
View File
@@ -1132,6 +1132,9 @@ export default {
'voiceClone.platformNamePlaceholder': 'Please select platform name',
'voiceClone.voiceIdPlaceholder': 'Please enter voice ID and press Enter',
'voiceClone.userIdPlaceholder': 'Please enter keyword to select account owner',
'voiceClone.languages': 'Languages',
'voiceClone.languagesPlaceholder': 'Please enter languages, e.g.: Chinese, English',
'voiceClone.languagesRequired': 'Please enter languages',
'voiceClone.waitingUpload': 'Waiting for upload',
'voiceClone.waitingTraining': 'Waiting for clone',
'voiceClone.training': 'Training',
+3
View File
@@ -1132,6 +1132,9 @@ export default {
'voiceClone.platformNamePlaceholder': 'Por favor, selecione o nome da plataforma',
'voiceClone.voiceIdPlaceholder': 'Por favor, insira o ID da voz e pressione Enter',
'voiceClone.userIdPlaceholder': 'Por favor, insira palavra-chave para selecionar o proprietário da conta',
'voiceClone.languages': 'Idiomas',
'voiceClone.languagesPlaceholder': 'Por favor, insira idiomas, ex.: Português, English',
'voiceClone.languagesRequired': 'Por favor, insira idiomas',
'voiceClone.waitingUpload': 'Aguardando envio',
'voiceClone.waitingTraining': 'Aguardando clonagem',
'voiceClone.training': 'Treinando',
+3
View File
@@ -1132,6 +1132,9 @@ export default {
'voiceClone.platformNamePlaceholder': 'Vui lòng chọn tên nền tảng',
'voiceClone.voiceIdPlaceholder': 'Vui lòng nhập ID giọng nói và nhấn Enter',
'voiceClone.userIdPlaceholder': 'Vui lòng nhập từ khóa để chọn chủ sở hữu tài khoản',
'voiceClone.languages': 'Ngôn ngữ',
'voiceClone.languagesPlaceholder': 'Vui lòng nhập ngôn ngữ, ví dụ: Tiếng Việt, English',
'voiceClone.languagesRequired': 'Vui lòng nhập ngôn ngữ',
'voiceClone.waitingUpload': 'Đang chờ tải lên',
'voiceClone.waitingTraining': 'Đang chờ nhân bản',
'voiceClone.training': 'Đang huấn luyện',

Some files were not shown because too many files have changed in this diff Show More