mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-23 23:53:55 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3510282cd5 | ||
|
|
bdf7c7facf | ||
|
|
fa91c97685 | ||
|
|
a92d6ccdfb | ||
|
|
f5ce755fea | ||
|
|
550397de98 | ||
|
|
ff66b815a5 | ||
|
|
8c18e48b97 | ||
|
|
4acd02e2b0 | ||
|
|
ddfbacc717 | ||
|
|
2a4dbdb69b | ||
|
|
e9a2489e48 | ||
|
|
ce13a7e3e6 | ||
|
|
8a156e99f4 | ||
|
|
4313216816 | ||
|
|
dc7615c008 | ||
|
|
b03f9374ea | ||
|
|
cacfc9bfee | ||
|
|
ce3c8a2bd8 | ||
|
|
e1a7e50e1f | ||
|
|
ad4d32a05d | ||
|
|
08d6bb4056 | ||
|
|
1f0db7dc47 | ||
|
|
6f52d004e8 | ||
|
|
0681550bab | ||
|
|
97b9c95c2d | ||
|
|
6111c3b5a0 | ||
|
|
123c1e8880 | ||
|
|
6ce4503b22 | ||
|
|
b209de36f5 | ||
|
|
fc3e0869fd | ||
|
|
3436d50b43 | ||
|
|
7a58c04df3 | ||
|
|
150be65436 | ||
|
|
bacf4cd5e8 | ||
|
|
7049e8cce7 | ||
|
|
61f137ce4b | ||
|
|
35ea14b95b | ||
|
|
b7d07f92a2 | ||
|
|
d2cbec0981 | ||
|
|
6348f12d60 | ||
|
|
11caf6bc8a | ||
|
|
3cae9e3c32 | ||
|
|
0161e642d0 | ||
|
|
d41938fe07 | ||
|
|
e20c59e3df | ||
|
|
fbaf28de5c | ||
|
|
2daf492e00 | ||
|
|
ac9083cfc7 | ||
|
|
f36e605aed | ||
|
|
c1b836aaed | ||
|
|
07170f09eb | ||
|
|
8211de242f | ||
|
|
eea6807355 | ||
|
|
0ba7e81438 | ||
|
|
703c707b4c | ||
|
|
3ab14578c2 | ||
|
|
a15efa3090 | ||
|
|
8af8930997 | ||
|
|
ad01b05940 | ||
|
|
a0497fdc48 | ||
|
|
9634f84a98 | ||
|
|
27e5e1e9d7 | ||
|
|
8fd92729f2 | ||
|
|
736ffd80bf | ||
|
|
3783351f5d | ||
|
|
2cd9484c54 | ||
|
|
0f1e0d0d2e | ||
|
|
0e23672be7 | ||
|
|
a6904f6ebd | ||
|
|
784ba359f2 | ||
|
|
e08eb48d9a | ||
|
|
b9a1b154b7 | ||
|
|
64630e1b4e | ||
|
|
7dcc014ce4 | ||
|
|
389e8559aa | ||
|
|
c5f82369e5 | ||
|
|
52e63706a4 | ||
|
|
a42a8b2b4b | ||
|
|
11142104af | ||
|
|
a9f5f1ab81 | ||
|
|
a8f68c42fc | ||
|
|
1d3afb3c9a | ||
|
|
943622969a | ||
|
|
9d39168a35 | ||
|
|
61ab7f25d7 | ||
|
|
140fcd5887 | ||
|
|
3c2024d00d | ||
|
|
2aee1bf567 | ||
|
|
d89a26889c | ||
|
|
d611b2db73 | ||
|
|
b0e911b248 | ||
|
|
fb3b7830ee | ||
|
|
10eeffdf31 | ||
|
|
17847188dd | ||
|
|
7b2bfc087d | ||
|
|
24a8839753 | ||
|
|
64d25533d9 | ||
|
|
6f6c788cd5 | ||
|
|
8aa12d0961 | ||
|
|
d770632152 | ||
|
|
a30903b644 | ||
|
|
5469168d18 | ||
|
|
3c31231781 | ||
|
|
cc2e30205f | ||
|
|
f01de027cd | ||
|
|
fb195fc57b | ||
|
|
cc504fe542 | ||
|
|
fd93116d69 | ||
|
|
5622a13735 | ||
|
|
079c8630d9 | ||
|
|
11830b2f22 | ||
|
|
861b4dee4c | ||
|
|
603fa67824 | ||
|
|
4d8279d916 | ||
|
|
f91c4acb8f | ||
|
|
1342eccf86 | ||
|
|
87f9ab7bb4 | ||
|
|
94cc8564aa | ||
|
|
8fc2d05c99 | ||
|
|
1a6d9d7490 | ||
|
|
6c19f1bd26 | ||
|
|
6d89ae1c66 | ||
|
|
2a49f0804c | ||
|
|
d64bb36606 | ||
|
|
852e097e0d | ||
|
|
3aecb99f70 | ||
|
|
6eecf5d0cb | ||
|
|
a4d02ca2ab | ||
|
|
7ef5a67119 | ||
|
|
1c5dacd2d5 | ||
|
|
7732b7b1b9 | ||
|
|
6e7b938f51 | ||
|
|
52f5859b1f | ||
|
|
73d174bc4a | ||
|
|
9512fe642c | ||
|
|
0ef3a60a4c | ||
|
|
f40dc4a658 | ||
|
|
4747e54f6c | ||
|
|
9278fdb016 | ||
|
|
5b23804954 | ||
|
|
b2ed5eb2c0 | ||
|
|
07113dc4d2 | ||
|
|
1a5dd2d9d9 | ||
|
|
8a01432e9d | ||
|
|
7a39fd0152 | ||
|
|
0d2cb7b3c9 | ||
|
|
9123a174da | ||
|
|
2e165747d6 | ||
|
|
3bdf634e37 | ||
|
|
20ac6f3f96 | ||
|
|
818ac1eefb | ||
|
|
980930cd42 | ||
|
|
956ae37e56 | ||
|
|
15731892ad | ||
|
|
d6d50e0432 | ||
|
|
286079c93a | ||
|
|
860cbc36fd | ||
|
|
bcf03a0378 | ||
|
|
4f615b3807 | ||
|
|
c2b95ca896 |
@@ -214,8 +214,8 @@ Websocket接口地址: wss://2662r3426b.vicp.fun/xiaozhi/v1/
|
||||
|:---:|:---:|:---:|
|
||||
| ASR(语音识别) | FunASR(本地) | 👍XunfeiStreamASR(讯飞流式) |
|
||||
| LLM(大模型) | glm-4-flash(智谱) | 👍qwen-flash(阿里百炼) |
|
||||
| VLLM(视觉大模型) | glm-4v-flash(智谱) | 👍qwen2.5-vl-3b-instructh(阿里百炼) |
|
||||
| TTS(语音合成) | ✅LinkeraiTTS(灵犀流式) | 👍HuoshanDoubleStreamTTS(火山流式) |
|
||||
| VLLM(视觉大模型) | glm-4v-flash(智谱) | 👍qwen3.5-flash(阿里百炼) |
|
||||
| TTS(语音合成) | EdgeTTS(微软) | 👍HuoshanDoubleStreamTTS(火山流式) |
|
||||
| Intent(意图识别) | function_call(函数调用) | function_call(函数调用) |
|
||||
| Memory(记忆功能) | mem_local_short(本地短期记忆) | mem_local_short(本地短期记忆) |
|
||||
|
||||
|
||||
+2
-2
@@ -212,8 +212,8 @@ Websocket-Schnittstellenadresse: wss://2662r3426b.vicp.fun/xiaozhi/v1/
|
||||
|:---:|:---:|:---:|
|
||||
| ASR (Spracherkennung) | FunASR (Lokal) | 👍XunfeiStreamASR (Xunfei-Streaming) |
|
||||
| LLM (Großes Modell) | glm-4-flash (Zhipu) | 👍qwen-flash (Alibaba Bailian) |
|
||||
| VLLM (Vision Large Model) | glm-4v-flash (Zhipu) | 👍qwen2.5-vl-3b-instructh (Alibaba Bailian) |
|
||||
| TTS (Sprachsynthese) | ✅LinkeraiTTS (Lingxi-Streaming) | 👍HuoshanDoubleStreamTTS (Volcano-Streaming) |
|
||||
| VLLM (Vision Large Model) | glm-4v-flash (Zhipu) | 👍qwen3.5-flash (Alibaba Bailian) |
|
||||
| TTS (Sprachsynthese) | EdgeTTS (Microsoft) | 👍HuoshanDoubleStreamTTS (Volcano-Streaming) |
|
||||
| Intent (Absichtserkennung) | function_call (Funktionsaufruf) | function_call (Funktionsaufruf) |
|
||||
| Memory (Gedächtnisfunktion) | mem_local_short (Lokales Kurzzeitgedächtnis) | mem_local_short (Lokales Kurzzeitgedächtnis) |
|
||||
|
||||
|
||||
+2
-2
@@ -212,8 +212,8 @@ Websocket Interface Address: wss://2662r3426b.vicp.fun/xiaozhi/v1/
|
||||
|:---:|:---:|:---:|
|
||||
| ASR(Speech Recognition) | FunASR(Local) | 👍XunfeiStreamASR(Xunfei Streaming) |
|
||||
| LLM(Large Model) | glm-4-flash(Zhipu) | 👍qwen-flash(Alibaba Bailian) |
|
||||
| VLLM(Vision Large Model) | glm-4v-flash(Zhipu) | 👍qwen2.5-vl-3b-instructh(Alibaba Bailian) |
|
||||
| TTS(Speech Synthesis) | ✅LinkeraiTTS(Lingxi streaming) | 👍HuoshanDoubleStreamTTS(Volcano Streaming) |
|
||||
| VLLM(Vision Large Model) | glm-4v-flash(Zhipu) | 👍qwen3.5-flash(Alibaba Bailian) |
|
||||
| TTS(Speech Synthesis) | EdgeTTS(Microsoft) | 👍HuoshanDoubleStreamTTS(Volcano Streaming) |
|
||||
| Intent(Intent Recognition) | function_call(Function calling) | function_call(Function calling) |
|
||||
| Memory(Memory function) | mem_local_short(Local short-term memory) | mem_local_short(Local short-term memory) |
|
||||
|
||||
|
||||
+2
-2
@@ -212,8 +212,8 @@ Endereço da Interface WebSocket: wss://2662r3426b.vicp.fun/xiaozhi/v1/
|
||||
|:---:|:---:|:---:|
|
||||
| ASR(Reconhecimento de Fala) | FunASR(Local) | 👍XunfeiStreamASR(Xunfei Streaming) |
|
||||
| LLM(Modelo de Linguagem) | glm-4-flash(Zhipu) | 👍qwen-flash(Alibaba Bailian) |
|
||||
| VLLM(Modelo de Visão) | glm-4v-flash(Zhipu) | 👍qwen2.5-vl-3b-instructh(Alibaba Bailian) |
|
||||
| TTS(Síntese de Voz) | ✅LinkeraiTTS(Lingxi streaming) | 👍HuoshanDoubleStreamTTS(Volcano Streaming) |
|
||||
| VLLM(Modelo de Visão) | glm-4v-flash(Zhipu) | 👍qwen3.5-flash(Alibaba Bailian) |
|
||||
| TTS(Síntese de Voz) | EdgeTTS(Microsoft) | 👍HuoshanDoubleStreamTTS(Volcano Streaming) |
|
||||
| Intent(Reconhecimento de Intenção) | function_call(Chamada de função) | function_call(Chamada de função) |
|
||||
| Memory(Função de Memória) | mem_local_short(Memória local de curto prazo) | mem_local_short(Memória local de curto prazo) |
|
||||
|
||||
|
||||
+2
-2
@@ -213,8 +213,8 @@ Công cụ kiểm tra dịch vụ: https://2662r3426b.vicp.fun/test/
|
||||
|:---:|:---:|:---:|
|
||||
| ASR(Nhận dạng giọng nói) | FunASR(Local) | 👍XunfeiStreamASR(Xunfei Streaming) |
|
||||
| LLM(Mô hình lớn) | glm-4-flash(Zhipu) | 👍qwen-flash(Alibaba Bailian) |
|
||||
| VLLM(Mô hình lớn thị giác) | glm-4v-flash(Zhipu) | 👍qwen2.5-vl-3b-instructh(Alibaba Bailian) |
|
||||
| TTS(Tổng hợp giọng nói) | ✅LinkeraiTTS(Lingxi streaming) | 👍HuoshanDoubleStreamTTS(Volcano Streaming) |
|
||||
| VLLM(Mô hình lớn thị giác) | glm-4v-flash(Zhipu) | 👍qwen3.5-flash(Alibaba Bailian) |
|
||||
| TTS(Tổng hợp giọng nói) | EdgeTTS(Microsoft) | 👍HuoshanDoubleStreamTTS(Volcano Streaming) |
|
||||
| Intent(Nhận dạng ý định) | function_call(Gọi hàm) | function_call(Gọi hàm) |
|
||||
| Memory(Chức năng bộ nhớ) | mem_local_short(Bộ nhớ ngắn hạn cục bộ) | mem_local_short(Bộ nhớ ngắn hạn cục bộ) |
|
||||
|
||||
|
||||
+3
-2
@@ -40,8 +40,8 @@ conda install conda-forge::ffmpeg
|
||||
|:---:|:---:|:---:|
|
||||
| ASR(语音识别) | FunASR(本地) | 👍XunfeiStreamASR(讯飞流式) |
|
||||
| LLM(大模型) | glm-4-flash(智谱) | 👍qwen-flash(阿里百炼) |
|
||||
| VLLM(视觉大模型) | glm-4v-flash(智谱) | 👍qwen2.5-vl-3b-instructh(阿里百炼) |
|
||||
| TTS(语音合成) | ✅LinkeraiTTS(灵犀流式) | 👍HuoshanDoubleStreamTTS(火山流式) |
|
||||
| VLLM(视觉大模型) | glm-4v-flash(智谱) | 👍qwen3.5-flash(阿里百炼) |
|
||||
| TTS(语音合成) | EdgeTTS(微软) | 👍HuoshanDoubleStreamTTS(火山流式) |
|
||||
| Intent(意图识别) | function_call(函数调用) | function_call(函数调用) |
|
||||
| Memory(记忆功能) | mem_local_short(本地短期记忆) | mem_local_short(本地短期记忆) |
|
||||
|
||||
@@ -83,6 +83,7 @@ VAD:
|
||||
9、[知识库ragflow集成指南](./ragflow-integration.md)<br/>
|
||||
10、[如何部署上下文源](./context-provider-integration.md)<br/>
|
||||
11、[如何集成PowerMem智能记忆](./powermem-integration.md)<br/>
|
||||
12、[如何配置天气插件查询天气](./weather-integration.md)<br/>
|
||||
|
||||
### 11、语音克隆、本地语音部署相关教程
|
||||
1、[如何在智控台克隆音色](./huoshan-streamTTS-voice-cloning.md)<br/>
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
# 知识库模块全量集成测试报告
|
||||
|
||||
## 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*
|
||||
@@ -151,6 +151,21 @@ public interface Constant {
|
||||
*/
|
||||
String MEMORY_NO_MEM = "Memory_nomem";
|
||||
|
||||
/**
|
||||
* 仅上报聊天记录(不总结记忆)
|
||||
*/
|
||||
String MEMORY_MEM_REPORT_ONLY = "Memory_mem_report_only";
|
||||
|
||||
/**
|
||||
* Mem0AI记忆
|
||||
*/
|
||||
String MEMORY_MEM0AI = "Memory_mem0ai";
|
||||
|
||||
/**
|
||||
* PowerMem记忆
|
||||
*/
|
||||
String MEMORY_POWERMEM = "Memory_powermem";
|
||||
|
||||
/**
|
||||
* 火山引擎双声道语音克隆
|
||||
*/
|
||||
@@ -304,7 +319,7 @@ public interface Constant {
|
||||
/**
|
||||
* 版本号
|
||||
*/
|
||||
public static final String VERSION = "0.9.2";
|
||||
public static final String VERSION = "0.9.3";
|
||||
|
||||
/**
|
||||
* 无效固件URL
|
||||
|
||||
@@ -251,4 +251,9 @@ public interface ErrorCode {
|
||||
int AGENT_TAG_NOT_EXIST = 10198; // 标签不存在
|
||||
|
||||
int RAG_DOCUMENT_PARSING_DELETE_ERROR = 10199; // 文档解析中,禁止删除
|
||||
|
||||
// 智能体MCP相关错误码
|
||||
int MCP_ACCESS_POINT_ADDRESS_NO_PERMISSION = 10200; // 没有权限查看该智能体的MCP接入点地址
|
||||
int MCP_ACCESS_POINT_ADDRESS_NOT_CONFIGURED = 10201; // 请联系管理员进入参数管理配置mcp接入点地址
|
||||
int MCP_ACCESS_POINT_TOOLS_LIST_NO_PERMISSION = 10202; // 没有权限查看该智能体的MCP工具列表
|
||||
}
|
||||
|
||||
@@ -187,4 +187,5 @@ public class RedisKeys {
|
||||
public static String getOtaUploadCountKey(Long username) {
|
||||
return "ota:upload:count:" + username;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -150,6 +150,13 @@ public class AgentController {
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/chat-title/{sessionId}/generate")
|
||||
@Operation(summary = "根据会话ID生成聊天标题")
|
||||
public Result<Void> generateAndSaveChatTitle(@PathVariable String sessionId) {
|
||||
agentChatSummaryService.generateAndSaveChatTitle(sessionId);
|
||||
return new Result<Void>().ok(null);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
@Operation(summary = "更新智能体")
|
||||
@RequiresPermissions("sys:role:normal")
|
||||
|
||||
+4
-3
@@ -11,6 +11,7 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import xiaozhi.common.exception.ErrorCode;
|
||||
import xiaozhi.common.user.UserDetail;
|
||||
import xiaozhi.common.utils.Result;
|
||||
import xiaozhi.modules.agent.service.AgentMcpAccessPointService;
|
||||
@@ -40,11 +41,11 @@ public class AgentMcpAccessPointController {
|
||||
|
||||
// 检查权限
|
||||
if (!agentService.checkAgentPermission(agentId, user.getId())) {
|
||||
return new Result<String>().error("没有权限查看该智能体的MCP接入点地址");
|
||||
return new Result<String>().error(ErrorCode.MCP_ACCESS_POINT_ADDRESS_NO_PERMISSION);
|
||||
}
|
||||
String agentMcpAccessAddress = agentMcpAccessPointService.getAgentMcpAccessAddress(agentId);
|
||||
if (agentMcpAccessAddress == null) {
|
||||
return new Result<String>().ok("请联系管理员进入参数管理配置mcp接入点地址");
|
||||
return new Result<String>().error(ErrorCode.MCP_ACCESS_POINT_ADDRESS_NOT_CONFIGURED);
|
||||
}
|
||||
return new Result<String>().ok(agentMcpAccessAddress);
|
||||
}
|
||||
@@ -58,7 +59,7 @@ public class AgentMcpAccessPointController {
|
||||
|
||||
// 检查权限
|
||||
if (!agentService.checkAgentPermission(agentId, user.getId())) {
|
||||
return new Result<List<String>>().error("没有权限查看该智能体的MCP工具列表");
|
||||
return new Result<List<String>>().error(ErrorCode.MCP_ACCESS_POINT_TOOLS_LIST_NO_PERMISSION);
|
||||
}
|
||||
List<String> agentMcpToolsList = agentMcpAccessPointService.getAgentMcpToolsList(agentId);
|
||||
return new Result<List<String>>().ok(agentMcpToolsList);
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package xiaozhi.modules.agent.dao;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
|
||||
import xiaozhi.modules.agent.entity.AgentChatTitleEntity;
|
||||
|
||||
@Mapper
|
||||
public interface AgentChatTitleDao extends BaseMapper<AgentChatTitleEntity> {
|
||||
|
||||
}
|
||||
@@ -23,4 +23,9 @@ public class AgentChatSessionDTO {
|
||||
* 聊天条数
|
||||
*/
|
||||
private Integer chatCount;
|
||||
|
||||
/**
|
||||
* 会话标题
|
||||
*/
|
||||
private String title;
|
||||
}
|
||||
@@ -33,6 +33,9 @@ public class AgentUpdateDTO implements Serializable {
|
||||
@Schema(description = "大语言模型标识", example = "llm_model_02", nullable = true)
|
||||
private String llmModelId;
|
||||
|
||||
@Schema(description = "小模型标识", example = "slm_model_02", nullable = true)
|
||||
private String slmModelId;
|
||||
|
||||
@Schema(description = "VLLM模型标识", example = "vllm_model_02", required = false)
|
||||
private String vllmModelId;
|
||||
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package xiaozhi.modules.agent.entity;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
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 lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@TableName(value = "ai_agent_chat_title")
|
||||
public class AgentChatTitleEntity {
|
||||
|
||||
@TableId(type = IdType.ASSIGN_UUID)
|
||||
private String id;
|
||||
|
||||
@TableField(value = "session_id")
|
||||
private String sessionId;
|
||||
|
||||
@TableField(value = "title")
|
||||
private String title;
|
||||
|
||||
@TableField(value = "created_at")
|
||||
private Date createdAt;
|
||||
|
||||
@TableField(value = "updated_at")
|
||||
private Date updatedAt;
|
||||
}
|
||||
@@ -37,6 +37,9 @@ public class AgentEntity {
|
||||
@Schema(description = "大语言模型标识")
|
||||
private String llmModelId;
|
||||
|
||||
@Schema(description = "小模型标识")
|
||||
private String slmModelId;
|
||||
|
||||
@Schema(description = "VLLM模型标识")
|
||||
private String vllmModelId;
|
||||
|
||||
|
||||
+8
@@ -12,4 +12,12 @@ public interface AgentChatSummaryService {
|
||||
* @return 保存结果
|
||||
*/
|
||||
boolean generateAndSaveChatSummary(String sessionId);
|
||||
|
||||
/**
|
||||
* 根据会话ID生成聊天标题并保存
|
||||
*
|
||||
* @param sessionId 会话ID
|
||||
* @return 是否成功
|
||||
*/
|
||||
boolean generateAndSaveChatTitle(String sessionId);
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package xiaozhi.modules.agent.service;
|
||||
|
||||
import xiaozhi.modules.agent.entity.AgentChatTitleEntity;
|
||||
|
||||
public interface AgentChatTitleService {
|
||||
|
||||
void saveOrUpdateTitle(String sessionId, String title);
|
||||
|
||||
String getTitleBySessionId(String sessionId);
|
||||
}
|
||||
+1
-1
@@ -52,7 +52,7 @@ public class AgentChatHistoryBizServiceImpl implements AgentChatHistoryBizServic
|
||||
public Boolean report(AgentChatHistoryReportDTO report) {
|
||||
String macAddress = report.getMacAddress();
|
||||
Byte chatType = report.getChatType();
|
||||
Long reportTimeMillis = null != report.getReportTime() ? report.getReportTime() * 1000
|
||||
Long reportTimeMillis = null != report.getReportTime() ? report.getReportTime()
|
||||
: System.currentTimeMillis();
|
||||
log.info("小智设备聊天上报请求: macAddress={}, type={} reportTime={}", macAddress, chatType, reportTimeMillis);
|
||||
|
||||
|
||||
+7
-1
@@ -6,6 +6,7 @@ import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import cn.hutool.core.collection.ListUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@@ -26,6 +27,7 @@ import xiaozhi.modules.agent.dto.AgentChatHistoryDTO;
|
||||
import xiaozhi.modules.agent.dto.AgentChatSessionDTO;
|
||||
import xiaozhi.modules.agent.entity.AgentChatHistoryEntity;
|
||||
import xiaozhi.modules.agent.service.AgentChatHistoryService;
|
||||
import xiaozhi.modules.agent.service.AgentChatTitleService;
|
||||
import xiaozhi.modules.agent.vo.AgentChatHistoryUserVO;
|
||||
|
||||
/**
|
||||
@@ -36,9 +38,12 @@ import xiaozhi.modules.agent.vo.AgentChatHistoryUserVO;
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class AgentChatHistoryServiceImpl extends ServiceImpl<AiAgentChatHistoryDao, AgentChatHistoryEntity>
|
||||
implements AgentChatHistoryService {
|
||||
|
||||
private final AgentChatTitleService agentChatTitleService;
|
||||
|
||||
@Override
|
||||
public PageData<AgentChatSessionDTO> getSessionListByAgentId(Map<String, Object> params) {
|
||||
String agentId = (String) params.get("agentId");
|
||||
@@ -61,6 +66,7 @@ public class AgentChatHistoryServiceImpl extends ServiceImpl<AiAgentChatHistoryD
|
||||
dto.setSessionId((String) map.get("session_id"));
|
||||
dto.setCreatedAt((LocalDateTime) map.get("created_at"));
|
||||
dto.setChatCount(((Number) map.get("chat_count")).intValue());
|
||||
dto.setTitle(agentChatTitleService.getTitleBySessionId(dto.getSessionId()));
|
||||
return dto;
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
@@ -91,7 +97,7 @@ public class AgentChatHistoryServiceImpl extends ServiceImpl<AiAgentChatHistoryD
|
||||
if (ToolUtil.isNotEmpty(audioIds)) {
|
||||
// 每批删除1000条
|
||||
List<List<String>> batch = ListUtil.split(audioIds, 1000);
|
||||
batch.forEach(dataList->{
|
||||
batch.forEach(dataList -> {
|
||||
baseMapper.deleteAudioByIds(dataList);
|
||||
});
|
||||
}
|
||||
|
||||
+121
-23
@@ -14,6 +14,7 @@ import org.springframework.stereotype.Service;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import xiaozhi.common.constant.Constant;
|
||||
import xiaozhi.modules.agent.dto.AgentChatHistoryDTO;
|
||||
import xiaozhi.modules.agent.dto.AgentChatSummaryDTO;
|
||||
import xiaozhi.modules.agent.dto.AgentMemoryDTO;
|
||||
@@ -21,6 +22,7 @@ import xiaozhi.modules.agent.dto.AgentUpdateDTO;
|
||||
import xiaozhi.modules.agent.entity.AgentChatHistoryEntity;
|
||||
import xiaozhi.modules.agent.service.AgentChatHistoryService;
|
||||
import xiaozhi.modules.agent.service.AgentChatSummaryService;
|
||||
import xiaozhi.modules.agent.service.AgentChatTitleService;
|
||||
import xiaozhi.modules.agent.service.AgentService;
|
||||
import xiaozhi.modules.agent.vo.AgentInfoVO;
|
||||
import xiaozhi.modules.device.entity.DeviceEntity;
|
||||
@@ -41,6 +43,7 @@ public class AgentChatSummaryServiceImpl implements AgentChatSummaryService {
|
||||
|
||||
private final AgentChatHistoryService agentChatHistoryService;
|
||||
private final AgentService agentService;
|
||||
private final AgentChatTitleService agentChatTitleService;
|
||||
private final DeviceService deviceService;
|
||||
private final LLMService llmService;
|
||||
private final ModelConfigService modelConfigService;
|
||||
@@ -90,33 +93,40 @@ public class AgentChatSummaryServiceImpl implements AgentChatSummaryService {
|
||||
@Override
|
||||
public boolean generateAndSaveChatSummary(String sessionId) {
|
||||
try {
|
||||
// 1. 生成总结
|
||||
AgentChatSummaryDTO summaryDTO = generateChatSummary(sessionId);
|
||||
if (!summaryDTO.isSuccess()) {
|
||||
log.info("生成总结失败: {}", summaryDTO.getErrorMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 获取设备信息(通过会话关联的设备)
|
||||
DeviceEntity device = getDeviceBySessionId(sessionId);
|
||||
if (device == null) {
|
||||
log.info("未找到与会话 {} 关联的设备", sessionId);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. 更新智能体记忆
|
||||
AgentMemoryDTO memoryDTO = new AgentMemoryDTO();
|
||||
memoryDTO.setSummaryMemory(summaryDTO.getSummary());
|
||||
String agentId = device.getAgentId();
|
||||
String memModelId = agentService.getAgentById(agentId).getMemModelId();
|
||||
|
||||
// 调用现有接口更新记忆
|
||||
agentService.updateAgentById(device.getAgentId(),
|
||||
new AgentUpdateDTO() {
|
||||
if (memModelId == null || memModelId.equals(Constant.MEMORY_MEM_REPORT_ONLY)) {
|
||||
log.info("会话 {} 使用仅上报聊天记录模式,跳过记忆总结", sessionId);
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean shouldSummarizeMemory = !memModelId.equals(Constant.MEMORY_NO_MEM)
|
||||
&& !memModelId.equals(Constant.MEMORY_MEM0AI)
|
||||
&& !memModelId.equals(Constant.MEMORY_POWERMEM);
|
||||
|
||||
if (shouldSummarizeMemory) {
|
||||
AgentChatSummaryDTO summaryDTO = generateChatSummary(sessionId);
|
||||
if (summaryDTO.isSuccess()) {
|
||||
agentService.updateAgentById(agentId, new AgentUpdateDTO() {
|
||||
{
|
||||
setSummaryMemory(summaryDTO.getSummary());
|
||||
}
|
||||
});
|
||||
log.info("成功保存会话 {} 的聊天记录总结到智能体 {}", sessionId, agentId);
|
||||
} else {
|
||||
log.info("生成总结失败: {}", summaryDTO.getErrorMessage());
|
||||
}
|
||||
} else {
|
||||
log.info("会话 {} 使用 {} 模式,跳过记忆总结", sessionId, memModelId);
|
||||
}
|
||||
|
||||
log.info("成功保存会话 {} 的聊天记录总结到智能体 {}", sessionId, device.getAgentId());
|
||||
return true;
|
||||
|
||||
} catch (Exception e) {
|
||||
@@ -125,6 +135,98 @@ public class AgentChatSummaryServiceImpl implements AgentChatSummaryService {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean generateAndSaveChatTitle(String sessionId) {
|
||||
try {
|
||||
// 自动获取agentId
|
||||
String agentId = findAgentIdBySessionId(sessionId);
|
||||
if (StringUtils.isBlank(agentId)) {
|
||||
log.warn("会话 {} 无法获取智能体信息,跳过标题生成", sessionId);
|
||||
return false;
|
||||
}
|
||||
|
||||
List<AgentChatHistoryDTO> chatHistory = getChatHistoryBySessionId(sessionId);
|
||||
if (chatHistory == null || chatHistory.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<String> meaningfulMessages = extractMeaningfulMessages(chatHistory);
|
||||
if (meaningfulMessages.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
StringBuilder conversation = new StringBuilder();
|
||||
for (int i = 0; i < meaningfulMessages.size(); i++) {
|
||||
conversation.append("消息").append(i + 1).append(": ").append(meaningfulMessages.get(i)).append("\n");
|
||||
}
|
||||
|
||||
String slmModelId = getSlmModelId(agentId);
|
||||
String title = llmService.generateTitle(conversation.toString(), slmModelId);
|
||||
|
||||
if (StringUtils.isNotBlank(title)) {
|
||||
agentChatTitleService.saveOrUpdateTitle(sessionId, title);
|
||||
log.info("成功保存会话 {} 的标题: {}", sessionId, title);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
log.error("生成会话 {} 的标题时发生错误: {}", sessionId, e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private String getSlmModelId(String agentId) {
|
||||
try {
|
||||
if (StringUtils.isBlank(agentId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
AgentInfoVO agentInfo = agentService.getAgentById(agentId);
|
||||
if (agentInfo == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String slmModelId = agentInfo.getSlmModelId();
|
||||
if (StringUtils.isNotBlank(slmModelId)) {
|
||||
log.info("会话 {} 使用SLM模型: {}", agentId, slmModelId);
|
||||
return slmModelId;
|
||||
}
|
||||
|
||||
ModelConfigEntity defaultLlmConfig = getDefaultLLMConfig();
|
||||
if (defaultLlmConfig != null) {
|
||||
log.info("会话 {} 使用默认LLM模型: {}", agentId, defaultLlmConfig.getId());
|
||||
return defaultLlmConfig.getId();
|
||||
}
|
||||
|
||||
String llmModelId = agentInfo.getLlmModelId();
|
||||
log.info("会话 {} 使用LLM模型(最终回退): {}", agentId, llmModelId);
|
||||
return llmModelId;
|
||||
} catch (Exception e) {
|
||||
log.error("获取智能体slm模型ID失败,agentId: {}, 错误: {}", agentId, e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private ModelConfigEntity getDefaultLLMConfig() {
|
||||
try {
|
||||
List<ModelConfigEntity> llmConfigs = modelConfigService.getEnabledModelsByType("LLM");
|
||||
if (llmConfigs == null || llmConfigs.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (ModelConfigEntity config : llmConfigs) {
|
||||
if (config.getIsDefault() != null && config.getIsDefault() == 1) {
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
return llmConfigs.get(0);
|
||||
} catch (Exception e) {
|
||||
log.error("获取默认LLM配置失败: {}", e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据会话ID获取聊天记录
|
||||
*/
|
||||
@@ -305,15 +407,13 @@ public class AgentChatSummaryServiceImpl implements AgentChatSummaryService {
|
||||
*/
|
||||
private String callJavaLLMForSummaryWithHistory(String conversation, String historyMemory, String agentId) {
|
||||
try {
|
||||
// 获取智能体配置,从中提取记忆总结的模型ID
|
||||
String modelId = getMemorySummaryModelId(agentId);
|
||||
String modelId = getSlmModelId(agentId);
|
||||
|
||||
if (StringUtils.isBlank(modelId)) {
|
||||
log.info("未找到记忆总结的LLM模型配置,使用默认LLM服务");
|
||||
log.info("未找到SLM模型,使用默认LLM服务");
|
||||
return llmService.generateSummaryWithHistory(conversation, historyMemory, null, null);
|
||||
}
|
||||
|
||||
// 使用指定的模型ID调用LLM服务(支持历史记忆合并)
|
||||
String summary = llmService.generateSummaryWithHistory(conversation, historyMemory, null, modelId);
|
||||
|
||||
if (StringUtils.isNotBlank(summary) && !summary.equals("服务暂不可用") && !summary.equals("总结生成失败")) {
|
||||
@@ -333,15 +433,13 @@ public class AgentChatSummaryServiceImpl implements AgentChatSummaryService {
|
||||
*/
|
||||
private String callJavaLLMForSummary(String conversation, String agentId) {
|
||||
try {
|
||||
// 获取智能体配置,从中提取记忆总结的模型ID
|
||||
String modelId = getMemorySummaryModelId(agentId);
|
||||
String modelId = getSlmModelId(agentId);
|
||||
|
||||
if (StringUtils.isBlank(modelId)) {
|
||||
log.info("未找到记忆总结的LLM模型配置,使用默认LLM服务");
|
||||
log.info("未找到SLM模型,使用默认LLM服务");
|
||||
return llmService.generateSummary(conversation);
|
||||
}
|
||||
|
||||
// 使用指定的模型ID调用LLM服务
|
||||
String summary = llmService.generateSummaryWithModel(conversation, modelId);
|
||||
|
||||
if (StringUtils.isNotBlank(summary) && !summary.equals("服务暂不可用") && !summary.equals("总结生成失败")) {
|
||||
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package xiaozhi.modules.agent.service.impl;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import xiaozhi.modules.agent.dao.AgentChatTitleDao;
|
||||
import xiaozhi.modules.agent.entity.AgentChatTitleEntity;
|
||||
import xiaozhi.modules.agent.service.AgentChatTitleService;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class AgentChatTitleServiceImpl implements AgentChatTitleService {
|
||||
|
||||
private final AgentChatTitleDao agentChatTitleDao;
|
||||
|
||||
@Override
|
||||
public void saveOrUpdateTitle(String sessionId, String title) {
|
||||
if (StringUtils.isBlank(sessionId) || StringUtils.isBlank(title)) {
|
||||
return;
|
||||
}
|
||||
|
||||
QueryWrapper<AgentChatTitleEntity> wrapper = new QueryWrapper<>();
|
||||
wrapper.eq("session_id", sessionId);
|
||||
|
||||
AgentChatTitleEntity existing = agentChatTitleDao.selectOne(wrapper);
|
||||
|
||||
if (existing != null) {
|
||||
existing.setTitle(title);
|
||||
existing.setUpdatedAt(new Date());
|
||||
agentChatTitleDao.updateById(existing);
|
||||
} else {
|
||||
AgentChatTitleEntity newEntity = AgentChatTitleEntity.builder()
|
||||
.id(java.util.UUID.randomUUID().toString().replace("-", ""))
|
||||
.sessionId(sessionId)
|
||||
.title(title)
|
||||
.createdAt(new Date())
|
||||
.updatedAt(new Date())
|
||||
.build();
|
||||
agentChatTitleDao.insert(newEntity);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitleBySessionId(String sessionId) {
|
||||
if (StringUtils.isBlank(sessionId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
QueryWrapper<AgentChatTitleEntity> wrapper = new QueryWrapper<>();
|
||||
wrapper.eq("session_id", sessionId);
|
||||
|
||||
AgentChatTitleEntity entity = agentChatTitleDao.selectOne(wrapper);
|
||||
return entity != null ? entity.getTitle() : null;
|
||||
}
|
||||
}
|
||||
+35
-5
@@ -300,6 +300,9 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
|
||||
if (dto.getLlmModelId() != null) {
|
||||
existingEntity.setLlmModelId(dto.getLlmModelId());
|
||||
}
|
||||
if (dto.getSlmModelId() != null) {
|
||||
existingEntity.setSlmModelId(dto.getSlmModelId());
|
||||
}
|
||||
if (dto.getVllmModelId() != null) {
|
||||
existingEntity.setVllmModelId(dto.getVllmModelId());
|
||||
}
|
||||
@@ -406,13 +409,14 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
|
||||
existingEntity.setUpdatedAt(new Date());
|
||||
|
||||
// 更新记忆策略
|
||||
if (existingEntity.getMemModelId() == null || existingEntity.getMemModelId().equals(Constant.MEMORY_NO_MEM)) {
|
||||
// 删除所有记录
|
||||
// 删除所有记录
|
||||
if (existingEntity.getMemModelId() != null && existingEntity.getMemModelId().equals(Constant.MEMORY_NO_MEM)) {
|
||||
agentChatHistoryService.deleteByAgentId(existingEntity.getId(), true, true);
|
||||
existingEntity.setSummaryMemory("");
|
||||
} else if (existingEntity.getChatHistoryConf() != null && existingEntity.getChatHistoryConf() == 1) {
|
||||
// 删除音频数据
|
||||
agentChatHistoryService.deleteByAgentId(existingEntity.getId(), true, false);
|
||||
// 删除记忆
|
||||
} else if (existingEntity.getMemModelId() != null
|
||||
&& existingEntity.getMemModelId().equals(Constant.MEMORY_MEM_REPORT_ONLY)) {
|
||||
existingEntity.setSummaryMemory("");
|
||||
}
|
||||
|
||||
// 更新上下文源配置
|
||||
@@ -505,6 +509,13 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
|
||||
entity.setLanguage(template.getLanguage());
|
||||
}
|
||||
|
||||
if (entity.getSlmModelId() == null) {
|
||||
String defaultSlmModelId = getDefaultLLMModelId();
|
||||
if (defaultSlmModelId != null) {
|
||||
entity.setSlmModelId(defaultSlmModelId);
|
||||
}
|
||||
}
|
||||
|
||||
// 设置用户ID和创建者信息
|
||||
UserDetail user = SecurityUser.getUser();
|
||||
entity.setUserId(user.getId());
|
||||
@@ -543,4 +554,23 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
|
||||
return entity.getId();
|
||||
}
|
||||
|
||||
private String getDefaultLLMModelId() {
|
||||
try {
|
||||
List<ModelConfigEntity> llmConfigs = modelConfigService.getEnabledModelsByType("LLM");
|
||||
if (llmConfigs == null || llmConfigs.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (ModelConfigEntity config : llmConfigs) {
|
||||
if (config.getIsDefault() != null && config.getIsDefault() == 1) {
|
||||
return config.getId();
|
||||
}
|
||||
}
|
||||
|
||||
return llmConfigs.get(0).getId();
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+20
-7
@@ -99,6 +99,7 @@ public class ConfigServiceImpl implements ConfigService {
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
result,
|
||||
isCache);
|
||||
|
||||
@@ -113,7 +114,7 @@ public class ConfigServiceImpl implements ConfigService {
|
||||
// 检查是否为管理控制台请求
|
||||
String redisKey = RedisKeys.getTmpRegisterMacKey(macAddress);
|
||||
Object isAdminRequest = redisUtils.get(redisKey);
|
||||
|
||||
|
||||
if (isAdminRequest != null && "true".equals(isAdminRequest)) {
|
||||
// 管理控制台请求,返回getConfig的结果
|
||||
redisUtils.delete(redisKey); // 使用后清理
|
||||
@@ -203,10 +204,11 @@ public class ConfigServiceImpl implements ConfigService {
|
||||
mcpEndpoint = mcpEndpoint.replace("/mcp/", "/call/");
|
||||
result.put("mcp_endpoint", mcpEndpoint);
|
||||
}
|
||||
|
||||
|
||||
// 获取上下文源配置
|
||||
AgentContextProviderEntity contextProviderEntity = agentContextProviderService.getByAgentId(agent.getId());
|
||||
if (contextProviderEntity != null && contextProviderEntity.getContextProviders() != null && !contextProviderEntity.getContextProviders().isEmpty()) {
|
||||
if (contextProviderEntity != null && contextProviderEntity.getContextProviders() != null
|
||||
&& !contextProviderEntity.getContextProviders().isEmpty()) {
|
||||
result.put("context_providers", contextProviderEntity.getContextProviders());
|
||||
}
|
||||
|
||||
@@ -229,6 +231,7 @@ public class ConfigServiceImpl implements ConfigService {
|
||||
agent.getAsrModelId(),
|
||||
agent.getLlmModelId(),
|
||||
agent.getVllmModelId(),
|
||||
agent.getSlmModelId(),
|
||||
agent.getTtsModelId(),
|
||||
agent.getMemModelId(),
|
||||
agent.getIntentModelId(),
|
||||
@@ -410,6 +413,7 @@ public class ConfigServiceImpl implements ConfigService {
|
||||
String asrModelId,
|
||||
String llmModelId,
|
||||
String vllmModelId,
|
||||
String slmModelId,
|
||||
String ttsModelId,
|
||||
String memModelId,
|
||||
String intentModelId,
|
||||
@@ -418,9 +422,9 @@ public class ConfigServiceImpl implements ConfigService {
|
||||
boolean isCache) {
|
||||
Map<String, String> selectedModule = new HashMap<>();
|
||||
|
||||
String[] modelTypes = { "VAD", "ASR", "TTS", "Memory", "Intent", "LLM", "VLLM", "RAG" };
|
||||
String[] modelTypes = { "VAD", "ASR", "TTS", "Memory", "Intent", "LLM", "VLLM", "SLM", "RAG" };
|
||||
String[] modelIds = { vadModelId, asrModelId, ttsModelId, memModelId, intentModelId, llmModelId, vllmModelId,
|
||||
ragModelId };
|
||||
slmModelId, ragModelId };
|
||||
String intentLLMModelId = null;
|
||||
String memLocalShortLLMModelId = null;
|
||||
|
||||
@@ -456,7 +460,7 @@ public class ConfigServiceImpl implements ConfigService {
|
||||
// 火山引擎声音克隆需要替换resource_id
|
||||
Map<String, Object> map = (Map<String, Object>) model.getConfigJson();
|
||||
if (Constant.VOICE_CLONE_HUOSHAN_DOUBLE_STREAM.equals(map.get("type"))) {
|
||||
// 如果voice是”S_“开头的,使用seed-icl-1.0
|
||||
// 如果voice是”S_”开头的,使用seed-icl-1.0
|
||||
if (voice != null && voice.startsWith("S_")) {
|
||||
map.put("resource_id", "seed-icl-1.0");
|
||||
}
|
||||
@@ -474,7 +478,7 @@ public class ConfigServiceImpl implements ConfigService {
|
||||
if (map.get("functions") != null) {
|
||||
String functionStr = (String) map.get("functions");
|
||||
if (StringUtils.isNotBlank(functionStr)) {
|
||||
String[] functions = functionStr.split("\\;");
|
||||
String[] functions = functionStr.split(";");
|
||||
map.put("functions", functions);
|
||||
}
|
||||
}
|
||||
@@ -507,6 +511,15 @@ public class ConfigServiceImpl implements ConfigService {
|
||||
typeConfig.put(memLocalShortLLM.getId(), memLocalShortLLM.getConfigJson());
|
||||
}
|
||||
}
|
||||
// LLM也返回所选的SLM,如果同名id则不重复显示
|
||||
if (StringUtils.isNotBlank(slmModelId) && !slmModelId.equals(llmModelId)) {
|
||||
if (!typeConfig.containsKey(slmModelId)) {
|
||||
ModelConfigEntity slmModel = modelConfigService.getModelByIdFromCache(slmModelId);
|
||||
if (slmModel != null && slmModel.getConfigJson() != null) {
|
||||
typeConfig.put(slmModel.getId(), slmModel.getConfigJson());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
result.put(modelTypes[i], typeConfig);
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
# 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)*
|
||||
-279
@@ -1,279 +0,0 @@
|
||||
# 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
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -1,233 +0,0 @@
|
||||
## 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`。
|
||||
-164
@@ -1,164 +0,0 @@
|
||||
# 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?" // 开场白
|
||||
}
|
||||
}
|
||||
```
|
||||
-208
@@ -1,208 +0,0 @@
|
||||
# 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"]
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -1,213 +0,0 @@
|
||||
## 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 不存在)
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -1,420 +0,0 @@
|
||||
## 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
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -1,757 +0,0 @@
|
||||
## 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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -1,503 +0,0 @@
|
||||
# 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
-228
@@ -1,228 +0,0 @@
|
||||
# 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]
|
||||
```
|
||||
-168
@@ -1,168 +0,0 @@
|
||||
# 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"
|
||||
]
|
||||
}
|
||||
```
|
||||
@@ -1,98 +0,0 @@
|
||||
# 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 |
|
||||
@@ -1,45 +0,0 @@
|
||||
# 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`).
|
||||
@@ -7,6 +7,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import jakarta.validation.constraints.*;
|
||||
|
||||
/**
|
||||
@@ -129,6 +130,7 @@ public class DatasetDTO {
|
||||
@JsonProperty("parser_config")
|
||||
private ParserConfig parserConfig;
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@Schema(description = "PageRank 权重 (0-100)", example = "50")
|
||||
private Integer pagerank;
|
||||
}
|
||||
|
||||
+10
-1
@@ -176,12 +176,21 @@ public abstract class KnowledgeBaseAdapter {
|
||||
|
||||
/**
|
||||
* 获取数据集的文档数量
|
||||
*
|
||||
*
|
||||
* @param datasetId 数据集ID
|
||||
* @return 文档数量
|
||||
*/
|
||||
public abstract Integer getDocumentCount(String datasetId);
|
||||
|
||||
/**
|
||||
* 获取数据集完整信息(名称、简介、文档数量等)
|
||||
* 用于检测 RAGFlow 端是否已删除、同步名称/简介变更
|
||||
*
|
||||
* @param datasetId 数据集ID
|
||||
* @return 数据集详情,若 RAGFlow 端不存在则返回 null
|
||||
*/
|
||||
public abstract DatasetDTO.InfoVO getDatasetInfo(String datasetId);
|
||||
|
||||
/**
|
||||
* 发送流式请求 (SSE)
|
||||
*
|
||||
|
||||
+19
-12
@@ -486,7 +486,20 @@ public class RAGFlowAdapter extends KnowledgeBaseAdapter {
|
||||
@Override
|
||||
public Integer getDocumentCount(String datasetId) {
|
||||
try {
|
||||
// [Fix] 使用列表过滤接口获取详情 (GET /datasets?id={id})
|
||||
DatasetDTO.InfoVO info = getDatasetInfo(datasetId);
|
||||
if (info != null && info.getDocumentCount() != null) {
|
||||
return info.getDocumentCount().intValue();
|
||||
}
|
||||
return 0;
|
||||
} catch (Exception e) {
|
||||
log.warn("获取文档数量失败: {}", e.getMessage());
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public DatasetDTO.InfoVO getDatasetInfo(String datasetId) {
|
||||
try {
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("id", datasetId);
|
||||
params.put("page", 1);
|
||||
@@ -498,20 +511,14 @@ public class RAGFlowAdapter extends KnowledgeBaseAdapter {
|
||||
if (dataObj instanceof List) {
|
||||
List<?> list = (List<?>) dataObj;
|
||||
if (!list.isEmpty()) {
|
||||
Object firstItem = list.get(0);
|
||||
if (firstItem instanceof Map) {
|
||||
Object countObj = ((Map<?, ?>) firstItem).get("document_count");
|
||||
if (countObj instanceof Number) {
|
||||
return ((Number) countObj).intValue();
|
||||
}
|
||||
}
|
||||
return objectMapper.convertValue(list.get(0), DatasetDTO.InfoVO.class);
|
||||
}
|
||||
}
|
||||
// 降级:未找到或结构不匹配
|
||||
return 0;
|
||||
// RAGFlow 端不存在该数据集
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
log.warn("获取文档数量失败: {}", e.getMessage());
|
||||
return 0;
|
||||
log.warn("获取数据集信息失败: datasetId={}, error={}", datasetId, e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+10
-1
@@ -96,7 +96,7 @@ public interface KnowledgeFilesService {
|
||||
/**
|
||||
* 保存文档影子记录
|
||||
*/
|
||||
void saveDocumentShadow(String datasetId, KnowledgeFilesDTO result, String originalName, String chunkMethod,
|
||||
boolean saveDocumentShadow(String datasetId, KnowledgeFilesDTO result, String originalName, String chunkMethod,
|
||||
Map<String, Object> parserConfig);
|
||||
|
||||
/**
|
||||
@@ -120,4 +120,13 @@ public interface KnowledgeFilesService {
|
||||
* 同步所有处于 RUNNING 状态的文档 (供定时任务调用)
|
||||
*/
|
||||
void syncRunningDocuments();
|
||||
|
||||
/**
|
||||
* 从RAGFlow全量同步文档到本地影子表
|
||||
* 拉取远端所有文档,与本地影子表对比,插入缺失的记录
|
||||
*
|
||||
* @param datasetId 数据集ID
|
||||
* @return 新同步的文档数量
|
||||
*/
|
||||
int syncDocumentsFromRAG(String datasetId);
|
||||
}
|
||||
+90
-7
@@ -19,6 +19,8 @@ import xiaozhi.common.service.impl.BaseServiceImpl;
|
||||
import xiaozhi.common.utils.ConvertUtils;
|
||||
import xiaozhi.common.utils.JsonUtils;
|
||||
import xiaozhi.modules.knowledge.dao.KnowledgeBaseDao;
|
||||
import xiaozhi.modules.knowledge.dao.DocumentDao;
|
||||
import xiaozhi.modules.knowledge.entity.DocumentEntity;
|
||||
import xiaozhi.modules.knowledge.dto.KnowledgeBaseDTO;
|
||||
import xiaozhi.modules.knowledge.dto.dataset.DatasetDTO;
|
||||
import xiaozhi.modules.knowledge.entity.KnowledgeBaseEntity;
|
||||
@@ -46,6 +48,7 @@ public class KnowledgeBaseServiceImpl extends BaseServiceImpl<KnowledgeBaseDao,
|
||||
implements KnowledgeBaseService {
|
||||
|
||||
private final KnowledgeBaseDao knowledgeBaseDao;
|
||||
private final DocumentDao documentDao;
|
||||
private final ModelConfigService modelConfigService;
|
||||
private final ModelConfigDao modelConfigDao;
|
||||
private final RedisUtils redisUtils;
|
||||
@@ -67,27 +70,107 @@ public class KnowledgeBaseServiceImpl extends BaseServiceImpl<KnowledgeBaseDao,
|
||||
|
||||
// Enrich with Document Count from RAG (Optional / Lazy)
|
||||
if (pageData != null && pageData.getList() != null) {
|
||||
for (KnowledgeBaseDTO dto : pageData.getList()) {
|
||||
pageData.getList().removeIf(dto -> {
|
||||
enrichDocumentCount(dto);
|
||||
}
|
||||
// syncDatasetFromRAG 检测到 RAGFlow 端已删除时,会将本地记录清理
|
||||
// 此时 datasetId 被置空作为标记,需要在列表中移除该条目
|
||||
return dto.getDatasetId() == null;
|
||||
});
|
||||
}
|
||||
return pageData;
|
||||
}
|
||||
|
||||
private void enrichDocumentCount(KnowledgeBaseDTO dto) {
|
||||
syncDatasetFromRAG(dto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 RAGFlow 同步数据集信息:检测删除、同步名称/简介、获取文档数量
|
||||
* 每次列表刷新时实时查询 RAGFlow,确保立即感知远端变更
|
||||
*/
|
||||
private void syncDatasetFromRAG(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()));
|
||||
if (StringUtils.isBlank(dto.getDatasetId()) || StringUtils.isBlank(dto.getRagModelId())) {
|
||||
return;
|
||||
}
|
||||
|
||||
KnowledgeBaseAdapter adapter = getAdapterByModelId(dto.getRagModelId());
|
||||
if (adapter == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
DatasetDTO.InfoVO datasetInfo = adapter.getDatasetInfo(dto.getDatasetId());
|
||||
|
||||
if (datasetInfo == null) {
|
||||
// RAGFlow 端已删除 → 本地级联清理
|
||||
log.info("数据集 {} 在 RAGFlow 端不存在,执行本地清理", dto.getDatasetId());
|
||||
cleanupLocalDataset(dto.getDatasetId(), dto.getId());
|
||||
// 标记为已删除,让上层从列表中移除
|
||||
dto.setDatasetId(null);
|
||||
return;
|
||||
}
|
||||
|
||||
// 同步名称(去掉 username_ 前缀)
|
||||
String ragflowName = datasetInfo.getName();
|
||||
if (StringUtils.isNotBlank(ragflowName)) {
|
||||
String localName = ragflowName.contains("_") ? ragflowName.substring(ragflowName.indexOf('_') + 1) : ragflowName;
|
||||
if (!localName.equals(dto.getName())) {
|
||||
log.info("同步知识库名称: {} -> {}", dto.getName(), localName);
|
||||
KnowledgeBaseEntity entity = knowledgeBaseDao.selectById(dto.getId());
|
||||
if (entity != null) {
|
||||
entity.setName(localName);
|
||||
knowledgeBaseDao.updateById(entity);
|
||||
dto.setName(localName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 同步简介
|
||||
String ragflowDesc = datasetInfo.getDescription();
|
||||
String localDesc = dto.getDescription();
|
||||
boolean descChanged = (ragflowDesc == null && localDesc != null) || (ragflowDesc != null && !ragflowDesc.equals(localDesc));
|
||||
if (descChanged) {
|
||||
log.info("同步知识库简介: datasetId={}", dto.getDatasetId());
|
||||
KnowledgeBaseEntity entity = knowledgeBaseDao.selectById(dto.getId());
|
||||
if (entity != null) {
|
||||
entity.setDescription(ragflowDesc);
|
||||
knowledgeBaseDao.updateById(entity);
|
||||
dto.setDescription(ragflowDesc);
|
||||
}
|
||||
}
|
||||
|
||||
// 设置文档数量(保留原有功能)
|
||||
if (datasetInfo.getDocumentCount() != null) {
|
||||
dto.setDocumentCount(datasetInfo.getDocumentCount().intValue());
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.warn("无法获取知识库 {} 的文档计数: {}", dto.getName(), e.getMessage());
|
||||
log.warn("同步数据集信息失败 {}: {}", dto.getName(), e.getMessage());
|
||||
dto.setDocumentCount(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 本地级联清理:RAGFlow 端已删除时,清理本地所有关联数据
|
||||
* 不调用 RAGFlow 删除 API
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void cleanupLocalDataset(String datasetId, String entityId) {
|
||||
try {
|
||||
// 1. 删除文档影子记录
|
||||
documentDao.delete(new QueryWrapper<DocumentEntity>().eq("dataset_id", datasetId));
|
||||
// 2. 删除插件映射
|
||||
knowledgeBaseDao.deletePluginMappingByKnowledgeBaseId(entityId);
|
||||
// 3. 删除知识库记录
|
||||
knowledgeBaseDao.deleteById(entityId);
|
||||
// 4. 清理缓存
|
||||
redisUtils.delete(RedisKeys.getKnowledgeBaseCacheKey(entityId));
|
||||
log.info("本地级联清理完成: datasetId={}, entityId={}", datasetId, entityId);
|
||||
} catch (Exception e) {
|
||||
log.error("本地级联清理失败: datasetId={}, entityId={}", datasetId, entityId, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public KnowledgeBaseDTO getById(String id) {
|
||||
KnowledgeBaseEntity entity = knowledgeBaseDao.selectById(id);
|
||||
|
||||
+192
-8
@@ -4,6 +4,8 @@ import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
@@ -76,6 +78,13 @@ public class KnowledgeFilesServiceImpl extends BaseServiceImpl<DocumentDao, Docu
|
||||
throw new RenException(ErrorCode.RAG_DATASET_ID_AND_MODEL_ID_NOT_NULL);
|
||||
}
|
||||
|
||||
// 全量对账同步: 从RAGFlow拉取远端文档,实时同步确保立即感知远端变更
|
||||
try {
|
||||
self.syncDocumentsFromRAG(datasetId);
|
||||
} catch (Exception e) {
|
||||
log.warn("从RAGFlow全量同步文档失败(不影响本地查询): datasetId={}, error={}", datasetId, e.getMessage());
|
||||
}
|
||||
|
||||
// 1. 获取本地影子表数据 (MyBatis-Plus 分页)
|
||||
Page<DocumentEntity> pageParams = new Page<>(page, limit);
|
||||
QueryWrapper<DocumentEntity> queryWrapper = new QueryWrapper<>();
|
||||
@@ -408,10 +417,13 @@ public class KnowledgeFilesServiceImpl extends BaseServiceImpl<DocumentDao, Docu
|
||||
}
|
||||
|
||||
/**
|
||||
* 原子化保存影子记录,确保本地数据绝对一致
|
||||
* 原子化保存影子记录(Upsert 语义)
|
||||
* 若 document_id 已存在则更新,不存在则插入,避免 UNIQUE 约束冲突
|
||||
*
|
||||
* @return true=新插入, false=更新已有记录
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void saveDocumentShadow(String datasetId, KnowledgeFilesDTO result, String originalName, String chunkMethod,
|
||||
public boolean saveDocumentShadow(String datasetId, KnowledgeFilesDTO result, String originalName, String chunkMethod,
|
||||
Map<String, Object> parserConfig) {
|
||||
DocumentEntity entity = new DocumentEntity();
|
||||
entity.setDatasetId(datasetId);
|
||||
@@ -453,12 +465,23 @@ public class KnowledgeFilesServiceImpl extends BaseServiceImpl<DocumentDao, Docu
|
||||
entity.setCreatedAt(result.getCreatedAt() != null ? result.getCreatedAt() : new Date());
|
||||
entity.setUpdatedAt(result.getUpdatedAt() != null ? result.getUpdatedAt() : new Date());
|
||||
|
||||
// 插入影子表 (若失败将抛出异常,触发调用方报错,确保 Local-First 列表一致性)
|
||||
documentDao.insert(entity);
|
||||
// Upsert: 检查 document_id 是否已存在,存在则更新,不存在则插入
|
||||
DocumentEntity existing = documentDao.selectOne(
|
||||
new QueryWrapper<DocumentEntity>().eq("document_id", entity.getDocumentId()));
|
||||
|
||||
// Issue 4: 同步递增数据集文档总数统计,保持父子表一致
|
||||
knowledgeBaseService.updateStatistics(datasetId, 1, 0L, 0L);
|
||||
log.info("已同步递增数据集统计: datasetId={}", datasetId);
|
||||
if (existing != null) {
|
||||
entity.setId(existing.getId());
|
||||
entity.setCreatedAt(existing.getCreatedAt()); // 保留原始创建时间
|
||||
documentDao.updateById(entity);
|
||||
log.info("影子记录已更新: documentId={}", entity.getDocumentId());
|
||||
return false;
|
||||
} else {
|
||||
documentDao.insert(entity);
|
||||
// 新增记录时递增数据集文档总数统计
|
||||
knowledgeBaseService.updateStatistics(datasetId, 1, 0L, 0L);
|
||||
log.info("影子记录已插入: documentId={}, datasetId={}", entity.getDocumentId(), datasetId);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -738,6 +761,167 @@ public class KnowledgeFilesServiceImpl extends BaseServiceImpl<DocumentDao, Docu
|
||||
this.deleteDocuments(datasetId, req);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int syncDocumentsFromRAG(String datasetId) {
|
||||
log.info("=== 开始从RAGFlow全量同步文档到本地影子表: datasetId={} ===", datasetId);
|
||||
|
||||
// 1. 获取适配器
|
||||
Map<String, Object> ragConfig = knowledgeBaseService.getRAGConfigByDatasetId(datasetId);
|
||||
KnowledgeBaseAdapter adapter = KnowledgeBaseAdapterFactory.getAdapter(extractAdapterType(ragConfig), ragConfig);
|
||||
|
||||
// 2. 分页拉取远端所有文档
|
||||
List<KnowledgeFilesDTO> allRemoteDocs = new ArrayList<>();
|
||||
int pageNum = 1;
|
||||
int pageSize = 100;
|
||||
long totalRemote = Long.MAX_VALUE;
|
||||
|
||||
while ((long) (pageNum - 1) * pageSize < totalRemote) {
|
||||
DocumentDTO.ListReq req = DocumentDTO.ListReq.builder()
|
||||
.page(pageNum)
|
||||
.pageSize(pageSize)
|
||||
.build();
|
||||
PageData<KnowledgeFilesDTO> remotePage = adapter.getDocumentList(datasetId, req);
|
||||
if (remotePage == null || remotePage.getList() == null || remotePage.getList().isEmpty()) {
|
||||
break;
|
||||
}
|
||||
allRemoteDocs.addAll(remotePage.getList());
|
||||
totalRemote = remotePage.getTotal();
|
||||
pageNum++;
|
||||
}
|
||||
|
||||
// 3. 获取本地已有文档
|
||||
List<DocumentEntity> localDocs = documentDao.selectList(
|
||||
new QueryWrapper<DocumentEntity>().eq("dataset_id", datasetId));
|
||||
Set<String> localDocIds = localDocs.stream()
|
||||
.map(DocumentEntity::getDocumentId)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
// 4. 远端文档ID集合
|
||||
Set<String> remoteDocIds = allRemoteDocs.stream()
|
||||
.map(KnowledgeFilesDTO::getDocumentId)
|
||||
.filter(id -> id != null)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
// 5. 补充: 插入远端存在但本地缺失的文档
|
||||
List<KnowledgeFilesDTO> newDocs = allRemoteDocs.stream()
|
||||
.filter(doc -> doc.getDocumentId() != null && !localDocIds.contains(doc.getDocumentId()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
int syncCount = 0;
|
||||
if (!newDocs.isEmpty()) {
|
||||
for (KnowledgeFilesDTO doc : newDocs) {
|
||||
try {
|
||||
self.saveDocumentShadow(datasetId, doc, doc.getName(), doc.getChunkMethod(), doc.getParserConfig());
|
||||
// 同步远端已有的 token/chunk 统计
|
||||
Long tokenCount = doc.getTokenCount() != null ? doc.getTokenCount() : 0L;
|
||||
long chunkCount = doc.getChunkCount() != null ? doc.getChunkCount().longValue() : 0L;
|
||||
if (tokenCount > 0 || chunkCount > 0) {
|
||||
knowledgeBaseService.updateStatistics(datasetId, 0, chunkCount, tokenCount);
|
||||
}
|
||||
syncCount++;
|
||||
} catch (Exception e) {
|
||||
log.warn("同步单个文档影子记录失败: docId={}, error={}", doc.getDocumentId(), e.getMessage());
|
||||
}
|
||||
}
|
||||
log.info("从RAGFlow新增同步 {} 个文档影子记录, datasetId={}", syncCount, datasetId);
|
||||
}
|
||||
|
||||
// 6. 清理: 删除远端已不存在但本地仍保留的影子记录
|
||||
List<DocumentEntity> deletedDocs = localDocs.stream()
|
||||
.filter(entity -> !remoteDocIds.contains(entity.getDocumentId()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (!deletedDocs.isEmpty()) {
|
||||
List<String> deletedDocIds = new ArrayList<>();
|
||||
long totalChunkDelta = 0;
|
||||
long totalTokenDelta = 0;
|
||||
|
||||
for (DocumentEntity entity : deletedDocs) {
|
||||
deletedDocIds.add(entity.getDocumentId());
|
||||
totalChunkDelta += entity.getChunkCount() != null ? entity.getChunkCount() : 0L;
|
||||
totalTokenDelta += entity.getTokenCount() != null ? entity.getTokenCount() : 0L;
|
||||
}
|
||||
try {
|
||||
self.deleteDocumentShadows(deletedDocIds, datasetId, totalChunkDelta, totalTokenDelta);
|
||||
log.info("清理远端已删除的影子记录: {} 个, datasetId={}", deletedDocs.size(), datasetId);
|
||||
} catch (Exception e) {
|
||||
log.warn("清理远端已删除的影子记录失败: datasetId={}, error={}", datasetId, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 7. 全量更新: 远端和本地都存在的文档,以远端为准同步所有字段
|
||||
// 处理 RAGFlow 复用 documentId 重传、远端编辑后元数据变化等场景
|
||||
Map<String, KnowledgeFilesDTO> remoteDocMap = allRemoteDocs.stream()
|
||||
.filter(doc -> doc.getDocumentId() != null)
|
||||
.collect(Collectors.toMap(KnowledgeFilesDTO::getDocumentId, doc -> doc, (a, b) -> b));
|
||||
|
||||
Map<String, DocumentEntity> localDocMap = localDocs.stream()
|
||||
.collect(Collectors.toMap(DocumentEntity::getDocumentId, e -> e, (a, b) -> b));
|
||||
|
||||
int updateCount = 0;
|
||||
for (Map.Entry<String, KnowledgeFilesDTO> entry : remoteDocMap.entrySet()) {
|
||||
String docId = entry.getKey();
|
||||
DocumentEntity local = localDocMap.get(docId);
|
||||
if (local == null) {
|
||||
continue; // 不在本地,由步骤5处理
|
||||
}
|
||||
KnowledgeFilesDTO remote = entry.getValue();
|
||||
|
||||
// 全量字段更新(以远端为准),确保本地与 RAGFlow 完全一致
|
||||
UpdateWrapper<DocumentEntity> updateWrapper = new UpdateWrapper<DocumentEntity>()
|
||||
.set("run", remote.getRun())
|
||||
.set("status", remote.getStatus() != null ? remote.getStatus() : local.getStatus())
|
||||
.set("progress", remote.getProgress())
|
||||
.set("chunk_count", remote.getChunkCount())
|
||||
.set("token_count", remote.getTokenCount())
|
||||
.set("size", remote.getFileSize())
|
||||
.set("error", remote.getError())
|
||||
.set("process_duration", remote.getProcessDuration())
|
||||
.set("updated_at", new Date())
|
||||
.set("last_sync_at", new Date())
|
||||
.eq("document_id", docId)
|
||||
.eq("dataset_id", datasetId);
|
||||
|
||||
if (remote.getName() != null) {
|
||||
updateWrapper.set("name", remote.getName());
|
||||
}
|
||||
if (remote.getThumbnail() != null) {
|
||||
updateWrapper.set("thumbnail", remote.getThumbnail());
|
||||
}
|
||||
if (remote.getMetaFields() != null) {
|
||||
try {
|
||||
updateWrapper.set("meta_fields", objectMapper.writeValueAsString(remote.getMetaFields()));
|
||||
} catch (Exception e) {
|
||||
log.warn("同步更新元数据序列化失败: docId={}, error={}", docId, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
documentDao.update(null, updateWrapper);
|
||||
|
||||
// 同步统计差异(chunk/token 计数变化时修正父表)
|
||||
Long remoteTokenCount = remote.getTokenCount() != null ? remote.getTokenCount() : 0L;
|
||||
Long localTokenCount = local.getTokenCount() != null ? local.getTokenCount() : 0L;
|
||||
long remoteChunkCount = remote.getChunkCount() != null ? remote.getChunkCount().longValue() : 0L;
|
||||
long localChunkCount = local.getChunkCount() != null ? local.getChunkCount().longValue() : 0L;
|
||||
long tokenDelta = remoteTokenCount - localTokenCount;
|
||||
long chunkDelta = remoteChunkCount - localChunkCount;
|
||||
if (tokenDelta != 0 || chunkDelta != 0) {
|
||||
knowledgeBaseService.updateStatistics(datasetId, 0, chunkDelta, tokenDelta);
|
||||
log.info("影子更新: 修正知识库统计, docId={}, chunkDelta={}, tokenDelta={}", docId, chunkDelta, tokenDelta);
|
||||
}
|
||||
|
||||
updateCount++;
|
||||
}
|
||||
|
||||
if (syncCount == 0 && deletedDocs.isEmpty() && updateCount == 0) {
|
||||
log.info("本地影子表已与RAGFlow完全同步, datasetId={}", datasetId);
|
||||
} else {
|
||||
log.info("同步完成: 新增={}, 清理={}, 更新={}, datasetId={}", syncCount, deletedDocs.size(), updateCount, datasetId);
|
||||
}
|
||||
|
||||
return syncCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void syncRunningDocuments() {
|
||||
// 1. 查询所有 RUNNING 状态的文档
|
||||
@@ -755,7 +939,7 @@ public class KnowledgeFilesServiceImpl extends BaseServiceImpl<DocumentDao, Docu
|
||||
|
||||
// 2. 按 DatasetID 分组,复用 Adapter
|
||||
Map<String, List<DocumentEntity>> groupedDocs = runningDocs.stream()
|
||||
.collect(java.util.stream.Collectors.groupingBy(DocumentEntity::getDatasetId));
|
||||
.collect(Collectors.groupingBy(DocumentEntity::getDatasetId));
|
||||
|
||||
groupedDocs.forEach((datasetId, docs) -> {
|
||||
KnowledgeBaseAdapter adapter = null;
|
||||
|
||||
@@ -67,4 +67,13 @@ public interface LLMService {
|
||||
* @return 是否可用
|
||||
*/
|
||||
boolean isAvailable(String modelId);
|
||||
|
||||
/**
|
||||
* 生成会话标题
|
||||
*
|
||||
* @param conversation 对话内容
|
||||
* @param modelId 模型ID
|
||||
* @return 标题(约15字)
|
||||
*/
|
||||
String generateTitle(String conversation, String modelId);
|
||||
}
|
||||
+89
@@ -37,6 +37,8 @@ public class OpenAIStyleLLMServiceImpl implements LLMService {
|
||||
|
||||
private static final String DEFAULT_SUMMARY_PROMPT = "你是一个经验丰富的记忆总结者,擅长将对话内容进行总结摘要,遵循以下规则:\n1、总结用户的重要信息,以便在未来的对话中提供更个性化的服务\n2、不要重复总结,不要遗忘之前记忆,除非原来的记忆超过了1800字,否则不要遗忘、不要压缩用户的历史记忆\n3、用户操控的设备音量、播放音乐、天气、退出、不想对话等和用户本身无关的内容,这些信息不需要加入到总结中\n4、聊天内容中的今天的日期时间、今天的天气情况与用户事件无关的数据,这些信息如果当成记忆存储会影响后续对话,这些信息不需要加入到总结中\n5、不要把设备操控的成果结果和失败结果加入到总结中,也不要把用户的一些废话加入到总结中\n6、不要为了总结而总结,如果用户的聊天没有意义,请返回原来的历史记录也是可以的\n7、只需要返回总结摘要,严格控制在1800字内\n8、不要包含代码、xml,不需要解释、注释和说明,保存记忆时仅从对话提取信息,不要混入示例内容\n9、如果提供了历史记忆,请将新对话内容与历史记忆进行智能合并,保留有价值的历史信息,同时添加新的重要信息\n\n历史记忆:\n{history_memory}\n\n新对话内容:\n{conversation}";
|
||||
|
||||
private static final String DEFAULT_TITLE_PROMPT = "请根据以下对话内容,生成一个简洁的会话标题(约15字以内),只返回标题,不要包含任何解释或标点符号:\n{conversation}";
|
||||
|
||||
@Override
|
||||
public String generateSummary(String conversation) {
|
||||
return generateSummary(conversation, null, null);
|
||||
@@ -302,4 +304,91 @@ public class OpenAIStyleLLMServiceImpl implements LLMService {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generateTitle(String conversation, String modelId) {
|
||||
if (!isAvailable()) {
|
||||
log.warn("LLM服务不可用,无法生成标题");
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
ModelConfigEntity llmConfig;
|
||||
if (modelId != null && !modelId.trim().isEmpty()) {
|
||||
llmConfig = modelConfigService.getModelByIdFromCache(modelId);
|
||||
} else {
|
||||
llmConfig = getDefaultLLMConfig();
|
||||
}
|
||||
|
||||
if (llmConfig == null || llmConfig.getConfigJson() == null) {
|
||||
log.error("未找到可用的LLM模型配置,modelId: {}", modelId);
|
||||
return null;
|
||||
}
|
||||
|
||||
JSONObject configJson = llmConfig.getConfigJson();
|
||||
String baseUrl = configJson.getStr("base_url");
|
||||
String model = configJson.getStr("model_name");
|
||||
String apiKey = configJson.getStr("api_key");
|
||||
|
||||
if (StringUtils.isBlank(baseUrl) || StringUtils.isBlank(apiKey)) {
|
||||
log.error("LLM配置不完整,baseUrl或apiKey为空");
|
||||
return null;
|
||||
}
|
||||
|
||||
String prompt = DEFAULT_TITLE_PROMPT.replace("{conversation}", conversation);
|
||||
|
||||
Map<String, Object> requestBody = new HashMap<>();
|
||||
requestBody.put("model", model != null ? model : "gpt-3.5-turbo");
|
||||
|
||||
Map<String, Object>[] messages = new Map[1];
|
||||
Map<String, Object> message = new HashMap<>();
|
||||
message.put("role", "user");
|
||||
message.put("content", prompt);
|
||||
messages[0] = message;
|
||||
|
||||
requestBody.put("messages", messages);
|
||||
requestBody.put("temperature", 0.3);
|
||||
requestBody.put("max_tokens", 50);
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
headers.set("Authorization", "Bearer " + apiKey);
|
||||
|
||||
HttpEntity<Map<String, Object>> entity = new HttpEntity<>(requestBody, headers);
|
||||
|
||||
String apiUrl = baseUrl;
|
||||
if (!apiUrl.endsWith("/chat/completions")) {
|
||||
if (!apiUrl.endsWith("/")) {
|
||||
apiUrl += "/";
|
||||
}
|
||||
apiUrl += "chat/completions";
|
||||
}
|
||||
|
||||
ResponseEntity<String> response = restTemplate.exchange(
|
||||
apiUrl, HttpMethod.POST, entity, String.class);
|
||||
|
||||
if (response.getStatusCode().is2xxSuccessful()) {
|
||||
JSONObject responseJson = JSONUtil.parseObj(response.getBody());
|
||||
JSONArray choices = responseJson.getJSONArray("choices");
|
||||
if (choices != null && choices.size() > 0) {
|
||||
JSONObject choice = choices.getJSONObject(0);
|
||||
JSONObject messageObj = choice.getJSONObject("message");
|
||||
String title = messageObj.getStr("content");
|
||||
if (StringUtils.isNotBlank(title)) {
|
||||
title = title.trim().replaceAll("[,。!?、:;''\"\"【】()]", "");
|
||||
if (title.length() > 15) {
|
||||
title = title.substring(0, 15);
|
||||
}
|
||||
return title;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.error("LLM API调用失败,状态码:{},响应:{}", response.getStatusCode(), response.getBody());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("调用LLM服务生成标题时发生异常,modelId: {}", modelId, e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -57,7 +57,8 @@ public class ModelConfigServiceImpl extends BaseServiceImpl<ModelConfigDao, Mode
|
||||
.eq("model_type", modelType)
|
||||
.eq("is_enabled", 1)
|
||||
.like(StringUtils.isNotBlank(modelName), "model_name", modelName)
|
||||
.select("id", "model_name"));
|
||||
.select("id", "model_name")
|
||||
.orderByAsc("sort"));
|
||||
return ConvertUtils.sourceToTarget(entities, ModelBasicInfoDTO.class);
|
||||
}
|
||||
|
||||
|
||||
+3
-1
@@ -95,6 +95,7 @@ public class ModelProviderServiceImpl extends BaseServiceImpl<ModelProviderDao,
|
||||
|
||||
QueryWrapper<ModelProviderEntity> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("model_type", StringUtils.isBlank(modelType) ? "" : modelType);
|
||||
queryWrapper.orderByAsc("sort");
|
||||
List<ModelProviderEntity> providerEntities = modelProviderDao.selectList(queryWrapper);
|
||||
return ConvertUtils.sourceToTarget(providerEntities, ModelProviderDTO.class);
|
||||
}
|
||||
@@ -147,7 +148,8 @@ public class ModelProviderServiceImpl extends BaseServiceImpl<ModelProviderDao,
|
||||
UserDetail user = SecurityUser.getUser();
|
||||
modelProviderDTO.setUpdater(user.getId());
|
||||
modelProviderDTO.setUpdateDate(new Date());
|
||||
if (modelProviderDao.updateById(ConvertUtils.sourceToTarget(modelProviderDTO, ModelProviderEntity.class)) == 0) {
|
||||
if (modelProviderDao
|
||||
.updateById(ConvertUtils.sourceToTarget(modelProviderDTO, ModelProviderEntity.class)) == 0) {
|
||||
throw new RenException(ErrorCode.UPDATE_DATA_FAILED);
|
||||
}
|
||||
return ConvertUtils.sourceToTarget(modelProviderDTO, ModelProviderDTO.class);
|
||||
|
||||
@@ -90,6 +90,7 @@ public class ShiroConfig {
|
||||
filterMap.put("/agent/chat-history/report", "server");
|
||||
filterMap.put("/agent/chat-history/download/**", "anon");
|
||||
filterMap.put("/agent/chat-summary/**", "server");
|
||||
filterMap.put("/agent/chat-title/**", "server");
|
||||
filterMap.put("/agent/play/**", "anon");
|
||||
filterMap.put("/voiceClone/play/**", "anon");
|
||||
filterMap.put("/**", "oauth2");
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
-- 新增仅上报聊天记录记忆模型供应器
|
||||
|
||||
delete from `ai_model_provider` where `id` = 'SYSTEM_Memory_mem_report_only';
|
||||
delete from `ai_model_config` where `id` = 'Memory_mem_report_only';
|
||||
|
||||
INSERT INTO `ai_model_provider` VALUES ('SYSTEM_Memory_mem_report_only', 'Memory', 'mem_report_only', '仅上报聊天记录', '[]', 4, 1, NOW(), 1, NOW());
|
||||
INSERT INTO `ai_model_config` VALUES ('Memory_mem_report_only', 'Memory', 'mem_report_only', '仅上报聊天记录', 0, 1, '{"type": "mem_report_only"}', NULL, '仅上报聊天记录,不总结记忆', 3, NULL, NULL, NULL, NULL);
|
||||
@@ -0,0 +1,2 @@
|
||||
-- 修改聊天内容字段类型
|
||||
ALTER TABLE ai_agent_chat_history MODIFY COLUMN content TEXT COMMENT '聊天内容';
|
||||
@@ -0,0 +1,7 @@
|
||||
-- 修改记忆模型名称
|
||||
|
||||
UPDATE `ai_model_config` SET `model_name` = '本地短期记忆(总结记忆)' WHERE `id` = 'Memory_mem_local_short';
|
||||
UPDATE `ai_model_provider` SET `name` = '本地短期记忆(总结记忆)' WHERE `id` = 'SYSTEM_Memory_mem_local_short';
|
||||
|
||||
UPDATE `ai_model_config` SET `model_name` = '仅上报聊天记录(不总结记忆)' WHERE `id` = 'Memory_mem_report_only';
|
||||
UPDATE `ai_model_provider` SET `name` = '仅上报聊天记录(不总结记忆)' WHERE `id` = 'SYSTEM_Memory_mem_report_only';
|
||||
@@ -0,0 +1 @@
|
||||
INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (312, 'tool_call_timeout', '30', 'number', 1, '工具调用超时时间(秒)');
|
||||
@@ -0,0 +1,16 @@
|
||||
-- 智能体表添加小模型ID字段
|
||||
SET @col_exists = (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'ai_agent' AND COLUMN_NAME = 'slm_model_id');
|
||||
SET @sql = IF(@col_exists = 0, 'ALTER TABLE `ai_agent` ADD COLUMN `slm_model_id` VARCHAR(255) NULL COMMENT ''小模型ID'' AFTER `llm_model_id`', 'SELECT ''Column slm_model_id already exists'' AS msg');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- 创建聊天标题表
|
||||
DROP TABLE IF EXISTS `ai_agent_chat_title`;
|
||||
CREATE TABLE `ai_agent_chat_title` (
|
||||
`id` VARCHAR(32) NOT NULL COMMENT '主键ID',
|
||||
`session_id` VARCHAR(255) NOT NULL COMMENT '会话ID',
|
||||
`title` VARCHAR(255) DEFAULT NULL COMMENT '聊天标题',
|
||||
`created_at` DATETIME DEFAULT NULL COMMENT '创建时间',
|
||||
`updated_at` DATETIME DEFAULT NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_session_id` (`session_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='智能体聊天标题表';
|
||||
@@ -0,0 +1,17 @@
|
||||
-- 更新模型名称:qwen2.5-vl-3b-instruct 改为 qwen3.5-flash
|
||||
UPDATE `ai_model_config`
|
||||
SET `config_json` = JSON_SET(`config_json`, '$.model_name', 'qwen3.5-flash')
|
||||
WHERE `id` = 'VLLM_QwenVLVLLM'
|
||||
AND JSON_EXTRACT(`config_json`, '$.model_name') = 'qwen2.5-vl-3b-instruct';
|
||||
|
||||
-- 更新模型名称:qwen-turbo 改为 qwen-flash
|
||||
UPDATE `ai_model_config`
|
||||
SET `config_json` = JSON_SET(`config_json`, '$.model_name', 'qwen-flash')
|
||||
WHERE `id` = 'LLM_AliLLM'
|
||||
AND JSON_EXTRACT(`config_json`, '$.model_name') = 'qwen-turbo';
|
||||
|
||||
-- 更新备注:qwen-turbo 改为 qwen-flash
|
||||
UPDATE `ai_model_config`
|
||||
SET `remark` = REPLACE(`remark`, 'qwen-turbo', 'qwen-flash')
|
||||
WHERE `id` = 'LLM_AliLLM'
|
||||
AND `remark` LIKE '%qwen-turbo%';
|
||||
@@ -0,0 +1,102 @@
|
||||
-- 新增豆包语音合成模型2.0供应器(使用seed-tts-2.0资源ID)
|
||||
-- 与火山双流式TTS配置相同,但resource_id固定为seed-tts-2.0
|
||||
|
||||
-- 插入豆包语音合成模型2.0供应器
|
||||
delete from `ai_model_provider` where id = 'SYSTEM_TTS_HSDSTTS_V2';
|
||||
INSERT INTO `ai_model_provider` (`id`, `model_type`, `provider_code`, `name`, `fields`, `sort`, `creator`, `create_date`, `updater`, `update_date`) VALUES
|
||||
('SYSTEM_TTS_HSDSTTS_V2', 'TTS', 'huoshan_double_stream', '豆包语音合成模型2.0', '[
|
||||
{"key": "ws_url", "type": "string", "label": "WebSocket地址"},
|
||||
{"key": "appid", "type": "string", "label": "应用ID"},
|
||||
{"key": "access_token", "type": "string", "label": "访问令牌"},
|
||||
{"key": "resource_id", "type": "string", "label": "资源ID"},
|
||||
{"key": "speaker", "type": "string", "label": "默认音色"},
|
||||
{"key": "enable_ws_reuse", "type": "boolean", "label": "是否开启链接复用", "default": true},
|
||||
{"key": "audio_params", "type": "dict", "label": "音频输出配置"},
|
||||
{"key": "additions", "type": "dict", "label": "高级文本处理配置"},
|
||||
{"key": "mix_speaker", "type": "dict", "label": "混音控制配置"}
|
||||
]', 14, 1, NOW(), 1, NOW());
|
||||
|
||||
-- 插入豆包语音合成模型2.0配置
|
||||
delete from `ai_model_config` where id = 'TTS_HSDSTTS_V2';
|
||||
INSERT INTO `ai_model_config` VALUES ('TTS_HSDSTTS_V2', 'TTS', 'HuoshanDoubleStreamTTSV2', '豆包语音合成模型2.0', 0, 1, '{
|
||||
"type": "huoshan_double_stream",
|
||||
"ws_url": "wss://openspeech.bytedance.com/api/v3/tts/bidirection",
|
||||
"appid": "",
|
||||
"access_token": "",
|
||||
"resource_id": "seed-tts-2.0",
|
||||
"speaker": "zh_female_xiaohe_uranus_bigtts",
|
||||
"enable_ws_reuse": true,
|
||||
"audio_params": {
|
||||
"speech_rate": 0,
|
||||
"loudness_rate": 0
|
||||
},
|
||||
"additions": {
|
||||
"aigc_metadata": {},
|
||||
"cache_config": {},
|
||||
"post_process": {
|
||||
"pitch": 0
|
||||
}
|
||||
},
|
||||
"mix_speaker": {}
|
||||
}', NULL, NULL, 17, NULL, NULL, NULL, NULL);
|
||||
|
||||
-- 豆包语音合成模型2.0配置说明文档
|
||||
UPDATE `ai_model_config` SET
|
||||
`doc_link` = 'https://www.volcengine.com/docs/6561/1329505',
|
||||
`remark` = '豆包语音合成模型2.0配置说明(基于火山引擎seed-tts-2.0):
|
||||
1. 访问 https://www.volcengine.com/ 注册并开通火山引擎账号
|
||||
2. 访问 https://console.volcengine.com/speech/service/10035 开通语音合成大模型,购买音色
|
||||
3. 在页面底部获取appid和access_token
|
||||
4. 资源ID固定为:seed-tts-2.0(豆包语音合成模型2.0)
|
||||
5. 链接复用:开启WebSocket连接复用,默认true减少链接损耗(注意:复用后设备处于聆听状态时空闲链接会占并发数)
|
||||
|
||||
详细参数文档:https://www.volcengine.com/docs/6561/1329505
|
||||
【audio_params】音频输出配置 - 用户可自定义添加火山引擎支持的任何音频参数
|
||||
- speech_rate: 语速(-50~100),默认0
|
||||
- loudness_rate: 音量(-50~100),默认0
|
||||
示例:{"speech_rate": 10, "loudness_rate": 5}
|
||||
|
||||
【additions】高级文本处理配置 - 用户可自定义添加火山引擎支持的任何高级参数
|
||||
- post_process.pitch: 音高(-12~12),默认0
|
||||
- aigc_metadata: AIGC元数据配置
|
||||
- cache_config: 缓存配置
|
||||
示例:{"post_process": {"pitch": 2}, "aigc_metadata": {}, "cache_config": {}}
|
||||
|
||||
注意:
|
||||
- 豆包语音合成模型2.0使用seed-tts-2.0资源ID,与火山双流式TTS(volc.service_type.10029)不同
|
||||
- 相关音色列表:https://www.volcengine.com/docs/6561/1257544
|
||||
- 用户可根据火山引擎API文档自行添加更多参数
|
||||
' WHERE `id` = 'TTS_HSDSTTS_V2';
|
||||
|
||||
-- 添加豆包语音合成模型2.0音色(与火山双流式TTS音色相同)
|
||||
delete from `ai_tts_voice` where tts_model_id = 'TTS_HSDSTTS_V2';
|
||||
INSERT INTO `ai_tts_voice` VALUES ('TTS_HSDSTTS_V2_0001', 'TTS_HSDSTTS_V2', 'Vivi', 'zh_female_vv_uranus_bigtts', '普通话、日语、印尼语、墨西哥西班牙语', 'https://lf3-static.bytednsdoc.com/obj/eden-cn/lm_hz_ihsph/ljhwZthlaukjlkulzlp/portal/bigtts/zh_female_vv_uranus_bigtts.wav', NULL, NULL, NULL, 1, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO `ai_tts_voice` VALUES ('TTS_HSDSTTS_V2_0002', 'TTS_HSDSTTS_V2', '小何', 'zh_female_xiaohe_uranus_bigtts', '普通话、英语', 'https://lf3-static.bytednsdoc.com/obj/eden-cn/lm_hz_ihsph/ljhwZthlaukjlkulzlp/portal/bigtts/zh_female_xiaohe_uranus_bigtts.mp3', NULL, NULL, NULL, 2, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO `ai_tts_voice` VALUES ('TTS_HSDSTTS_V2_0003', 'TTS_HSDSTTS_V2', '云舟', 'zh_male_m191_uranus_bigtts', '普通话、英语', 'https://lf3-static.bytednsdoc.com/obj/eden-cn/lm_hz_ihsph/ljhwZthlaukjlkulzlp/portal/bigtts/zh_male_m191_uranus_bigtts.mp3', NULL, NULL, NULL, 3, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO `ai_tts_voice` VALUES ('TTS_HSDSTTS_V2_0004', 'TTS_HSDSTTS_V2', '小天', 'zh_male_taocheng_uranus_bigtts', '普通话、英语', 'https://lf3-static.bytednsdoc.com/obj/eden-cn/lm_hz_ihsph/ljhwZthlaukjlkulzlp/portal/bigtts/zh_male_taocheng_uranus_bigtts.mp3', NULL, NULL, NULL, 4, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO `ai_tts_voice` VALUES ('TTS_HSDSTTS_V2_0005', 'TTS_HSDSTTS_V2', '刘飞', 'zh_male_liufei_uranus_bigtts', '普通话、英语', 'https://lf3-static.bytednsdoc.com/obj/eden-cn/lm_hz_ihsph/ljhwZthlaukjlkulzlp/portal/bigtts/zh_male_liufei_uranus_bigtts.mp3', NULL, NULL, NULL, 5, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO `ai_tts_voice` VALUES ('TTS_HSDSTTS_V2_0006', 'TTS_HSDSTTS_V2', '魅力苏菲', 'zh_female_sophie_uranus_bigtts', '普通话、英语', 'https://lf3-static.bytednsdoc.com/obj/eden-cn/lm_hz_ihsph/ljhwZthlaukjlkulzlp/portal/bigtts/zh_male_sophie_uranus_bigtts.mp3', NULL, NULL, NULL, 6, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO `ai_tts_voice` VALUES ('TTS_HSDSTTS_V2_0007', 'TTS_HSDSTTS_V2', '清新女声', 'zh_female_qingxinnvsheng_uranus_bigtts', '普通话、英语', 'https://lf3-static.bytednsdoc.com/obj/eden-cn/lm_hz_ihsph/ljhwZthlaukjlkulzlp/portal/bigtts/zh_female_qingxinnvsheng_uranus_bigtts.mp3', NULL, NULL, NULL, 7, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO `ai_tts_voice` VALUES ('TTS_HSDSTTS_V2_0008', 'TTS_HSDSTTS_V2', '知性灿灿', 'zh_female_cancan_uranus_bigtts', '普通话、英语', 'https://lf3-static.bytednsdoc.com/obj/eden-cn/lm_hz_ihsph/ljhwZthlaukjlkulzlp/portal/bigtts/zh_female_cancan_uranus_bigtts.mp3', NULL, NULL, NULL, 8, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO `ai_tts_voice` VALUES ('TTS_HSDSTTS_V2_0009', 'TTS_HSDSTTS_V2', '撒娇学妹', 'zh_female_sajiaoxuemei_uranus_bigtts', '普通话、英语', 'https://lf3-static.bytednsdoc.com/obj/eden-cn/lm_hz_ihsph/ljhwZthlaukjlkulzlp/portal/bigtts/zh_female_sajiaoxuemei_uranus_bigtts.mp3', NULL, NULL, NULL, 9, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO `ai_tts_voice` VALUES ('TTS_HSDSTTS_V2_0010', 'TTS_HSDSTTS_V2', '甜美小源', 'zh_female_tianmeixiaoyuan_uranus_bigtts', '普通话、英语', 'https://lf3-static.bytednsdoc.com/obj/eden-cn/lm_hz_ihsph/ljhwZthlaukjlkulzlp/portal/bigtts/zh_female_tianmeixiaoyuan_uranus_bigtts.mp3', NULL, NULL, NULL, 10, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO `ai_tts_voice` VALUES ('TTS_HSDSTTS_V2_0011', 'TTS_HSDSTTS_V2', '甜美桃子', 'zh_female_tianmeitaozi_uranus_bigtts', '普通话、英语', 'https://lf3-static.bytednsdoc.com/obj/eden-cn/lm_hz_ihsph/ljhwZthlaukjlkulzlp/portal/bigtts/zh_female_tianmeitaozi_uranus_bigtts.mp3', NULL, NULL, NULL, 11, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO `ai_tts_voice` VALUES ('TTS_HSDSTTS_V2_0012', 'TTS_HSDSTTS_V2', '爽快思思', 'zh_female_shuangkuaisisi_uranus_bigtts', '普通话、英语', 'https://lf3-static.bytednsdoc.com/obj/eden-cn/lm_hz_ihsph/ljhwZthlaukjlkulzlp/portal/bigtts/zh_female_shuangkuaisisi_uranus_bigtts.mp3', NULL, NULL, NULL, 12, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO `ai_tts_voice` VALUES ('TTS_HSDSTTS_V2_0013', 'TTS_HSDSTTS_V2', '佩奇猪', 'zh_female_peiqi_uranus_bigtts', '普通话、英语', 'https://lf3-static.bytednsdoc.com/obj/eden-cn/lm_hz_ihsph/ljhwZthlaukjlkulzlp/portal/bigtts/zh_female_peiqi_uranus_bigtts.mp3', NULL, NULL, NULL, 13, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO `ai_tts_voice` VALUES ('TTS_HSDSTTS_V2_0014', 'TTS_HSDSTTS_V2', '邻家女孩', 'zh_female_linjianvhai_uranus_bigtts', '普通话、英语', 'https://lf3-static.bytednsdoc.com/obj/eden-cn/lm_hz_ihsph/ljhwZthlaukjlkulzlp/portal/bigtts/zh_female_linjianvhai_uranus_bigtts.mp3', NULL, NULL, NULL, 14, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO `ai_tts_voice` VALUES ('TTS_HSDSTTS_V2_0015', 'TTS_HSDSTTS_V2', '少年梓辛/Brayan', 'zh_male_shaonianzixin_uranus_bigtts', '普通话、英语', 'https://lf3-static.bytednsdoc.com/obj/eden-cn/lm_hz_ihsph/ljhwZthlaukjlkulzlp/portal/bigtts/zh_male_shaonianzixin_uranus_bigtts.mp3', NULL, NULL, NULL, 15, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO `ai_tts_voice` VALUES ('TTS_HSDSTTS_V2_0016', 'TTS_HSDSTTS_V2', '猴哥', 'zh_male_sunwukong_uranus_bigtts', '普通话、英语', 'https://lf3-static.bytednsdoc.com/obj/eden-cn/lm_hz_ihsph/ljhwZthlaukjlkulzlp/portal/bigtts/zh_male_sunwukong_uranus_bigtts.mp3', NULL, NULL, NULL, 16, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO `ai_tts_voice` VALUES ('TTS_HSDSTTS_V2_0017', 'TTS_HSDSTTS_V2', '魅力女友', 'zh_female_meilinvyou_uranus_bigtts', '普通话、英语', 'https://lf3-static.bytednsdoc.com/obj/eden-cn/lm_hz_ihsph/ljhwZthlaukjlkulzlp/portal/bigtts/zh_female_meilinvyou_uranus_bigtts.mp3', NULL, NULL, NULL, 17, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO `ai_tts_voice` VALUES ('TTS_HSDSTTS_V2_0018', 'TTS_HSDSTTS_V2', 'Tim', 'en_male_tim_uranus_bigtts', '英语', 'https://lf3-static.bytednsdoc.com/obj/eden-cn/lm_hz_ihsph/ljhwZthlaukjlkulzlp/portal/bigtts/en_male_tim_uranus_bigtts.mp3', NULL, NULL, NULL, 18, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO `ai_tts_voice` VALUES ('TTS_HSDSTTS_V2_0019', 'TTS_HSDSTTS_V2', 'Dacey', 'en_female_dacey_uranus_bigtts', '英语', 'https://lf3-static.bytednsdoc.com/obj/eden-cn/lm_hz_ihsph/ljhwZthlaukjlkulzlp/portal/bigtts/en_female_dacey_uranus_bigtts.mp3', NULL, NULL, NULL, 19, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO `ai_tts_voice` VALUES ('TTS_HSDSTTS_V2_0020', 'TTS_HSDSTTS_V2', 'Stokie', 'en_female_stokie_uranus_bigtts', '英语', 'https://lf3-static.bytednsdoc.com/obj/eden-cn/lm_hz_ihsph/ljhwZthlaukjlkulzlp/portal/bigtts/en_female_stokie_uranus_bigtts.mp3', NULL, NULL, NULL, 20, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO `ai_tts_voice` VALUES ('TTS_HSDSTTS_V2_0021', 'TTS_HSDSTTS_V2', '温暖阿虎/Alvin', 'zh_male_wennuanahu_uranus_bigtts', '普通话、英语', 'https://lf3-static.bytednsdoc.com/obj/eden-cn/lm_hz_ihsph/ljhwZthlaukjlkulzlp/portal/bigtts/zh_male_wennuanahu_uranus_bigtts.mp3', NULL, NULL, NULL, 21, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO `ai_tts_voice` VALUES ('TTS_HSDSTTS_V2_0022', 'TTS_HSDSTTS_V2', '奶气萌娃', 'zh_male_naiqimengwa_uranus_bigtts', '普通话、英语', 'https://lf3-static.bytednsdoc.com/obj/eden-cn/lm_hz_ihsph/ljhwZthlaukjlkulzlp/portal/bigtts/zh_male_naiqimengwa_uranus_bigtts.mp3', NULL, NULL, NULL, 22, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO `ai_tts_voice` VALUES ('TTS_HSDSTTS_V2_0023', 'TTS_HSDSTTS_V2', '婆婆', 'zh_female_popo_uranus_bigtts', '普通话、英语', 'https://lf3-static.bytednsdoc.com/obj/eden-cn/lm_hz_ihsph/ljhwZthlaukjlkulzlp/portal/bigtts/zh_female_popo_uranus_bigtts.mp3', NULL, NULL, NULL, 23, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO `ai_tts_voice` VALUES ('TTS_HSDSTTS_V2_0024', 'TTS_HSDSTTS_V2', '开朗姐姐', 'zh_female_kailangjiejie_uranus_bigtts', '普通话、英语', 'https://lf3-static.bytednsdoc.com/obj/eden-cn/lm_hz_ihsph/ljhwZthlaukjlkulzlp/portal/bigtts/zh_female_kailangjiejie_uranus_bigtts.mp3', NULL, NULL, NULL, 24, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO `ai_tts_voice` VALUES ('TTS_HSDSTTS_V2_0025', 'TTS_HSDSTTS_V2', '轻盈朵朵', 'saturn_zh_female_qingyingduoduo_cs_tob', '普通话、英语', 'https://lf3-static.bytednsdoc.com/obj/eden-cn/lm_hz_ihsph/ljhwZthlaukjlkulzlp/portal/bigtts/ICL_zh_female_qingyingduoduo_cs_tob.mp3', NULL, NULL, NULL, 25, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO `ai_tts_voice` VALUES ('TTS_HSDSTTS_V2_0026', 'TTS_HSDSTTS_V2', '温婉珊珊', 'saturn_zh_female_wenwanshanshan_cs_tob', '普通话、英语', 'https://lf3-static.bytednsdoc.com/obj/eden-cn/lm_hz_ihsph/ljhwZthlaukjlkulzlp/portal/bigtts/ICL_zh_female_wenwanshanshan_cs_tob.mp3', NULL, NULL, NULL, 26, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO `ai_tts_voice` VALUES ('TTS_HSDSTTS_V2_0027', 'TTS_HSDSTTS_V2', '霸气青叔', 'zh_male_baqiqingshu_uranus_bigtts', '普通话、英语', 'https://lf3-static.bytednsdoc.com/obj/eden-cn/lm_hz_ihsph/ljhwZthlaukjlkulzlp/portal/bigtts/zh_male_baqiqingshu_uranus_bigtts.mp3', NULL, NULL, NULL, 27, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO `ai_tts_voice` VALUES ('TTS_HSDSTTS_V2_0028', 'TTS_HSDSTTS_V2', '悬疑解说', 'zh_male_xuanyijieshuo_uranus_bigtts', '普通话、英语', 'https://lf3-static.bytednsdoc.com/obj/eden-cn/lm_hz_ihsph/ljhwZthlaukjlkulzlp/portal/bigtts/zh_male_xuanyijieshuo_uranus_bigtts.mp3', NULL, NULL, NULL, 28, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO `ai_tts_voice` VALUES ('TTS_HSDSTTS_V2_0029', 'TTS_HSDSTTS_V2', '古风少御', 'zh_female_gufengshaoyu_uranus_bigtts', '普通话、英语', 'https://lf3-static.bytednsdoc.com/obj/eden-cn/lm_hz_ihsph/ljhwZthlaukjlkulzlp/portal/bigtts/zh_female_gufengshaoyu_uranus_bigtts.mp3', NULL, NULL, NULL, 29, NULL, NULL, NULL, NULL);
|
||||
INSERT INTO `ai_tts_voice` VALUES ('TTS_HSDSTTS_V2_0030', 'TTS_HSDSTTS_V2', '唐僧', 'zh_male_tangseng_uranus_bigtts', '普通话、英语', 'https://lf3-static.bytednsdoc.com/obj/eden-cn/lm_hz_ihsph/ljhwZthlaukjlkulzlp/portal/bigtts/zh_male_tangseng_uranus_bigtts.mp3', NULL, NULL, NULL, 30, NULL, NULL, NULL, NULL);
|
||||
@@ -0,0 +1,45 @@
|
||||
-- 修复豆包语音合成模型2.0 provider_code 重复问题,添加 ASR 2.0 支持
|
||||
|
||||
-- ==================== 豆包语音合成模型2.0 ====================
|
||||
-- 删除 TTS 2.0 供应器(不再需要单独的供应器)
|
||||
delete from `ai_model_provider` where id = 'SYSTEM_TTS_HSDSTTS_V2';
|
||||
|
||||
-- ==================== 豆包语音识别(流式) ====================
|
||||
-- 修正原有豆包语音识别(流式)供应器,移除cluster字段,添加resource_id字段
|
||||
UPDATE `ai_model_provider` SET `fields` = '[{"key":"appid","type":"string","label":"应用ID"},{"key":"access_token","type":"string","label":"访问令牌"},{"key":"boosting_table_name","type":"string","label":"热词文件名称"},{"key":"correct_table_name","type":"string","label":"替换词文件名称"},{"key":"output_dir","type":"string","label":"输出目录"},{"key":"end_window_size","type":"number","label":"静音判定时长(ms)"},{"key":"enable_multilingual","type":"boolean","label":"是否开启多语种识别模式"},{"key":"language","type":"string","label":"指定语言编码"},{"key":"resource_id","type":"string","label":"资源ID"}]' WHERE `id` = 'SYSTEM_ASR_DoubaoStreamASR';
|
||||
|
||||
-- 修正原有豆包语音识别(流式)配置,移除cluster字段,添加resource_id默认值
|
||||
UPDATE `ai_model_config` SET `config_json` = JSON_REMOVE(JSON_SET(`config_json`, '$.resource_id', 'volc.bigasr.sauc.duration'), '$.cluster') WHERE `id` = 'ASR_DoubaoStreamASR';
|
||||
|
||||
-- ==================== 豆包语音识别模型2.0 ====================
|
||||
|
||||
-- 插入豆包语音识别模型2.0配置
|
||||
delete from `ai_model_config` where id = 'ASR_DoubaoStreamASRV2';
|
||||
INSERT INTO `ai_model_config` VALUES ('ASR_DoubaoStreamASRV2', 'ASR', 'DoubaoStreamASRV2', '豆包语音识别模型2.0', 0, 1, '{
|
||||
"type": "doubao_stream",
|
||||
"appid": "",
|
||||
"access_token": "",
|
||||
"resource_id": "volc.seedasr.sauc.duration",
|
||||
"end_window_size": 200,
|
||||
"enable_multilingual": false,
|
||||
"language": "zh-CN",
|
||||
"output_dir": "tmp/"
|
||||
}', NULL, NULL, 6, NULL, NULL, NULL, NULL);
|
||||
|
||||
-- 豆包语音识别模型2.0配置说明文档
|
||||
UPDATE `ai_model_config` SET
|
||||
`doc_link` = 'https://www.volcengine.com/docs/6561/109979',
|
||||
`remark` = '豆包语音识别模型2.0配置说明(基于火山引擎seed-asr):
|
||||
1. 访问 https://www.volcengine.com/ 注册并开通火山引擎账号
|
||||
2. 访问 https://console.volcengine.com/speech/service/10038 开通豆包流式语音识别模型2.0
|
||||
3. 在页面底部获取appid和access_token
|
||||
4. 资源ID有两种:小时版(volc.seedasr.sauc.duration)和并发版(volc.seedasr.sauc.concurrent)
|
||||
- 小时版:固定为:volc.seedasr.sauc.duration(豆包语音识别模型2.0)
|
||||
- 并发版:固定为:volc.seedasr.sauc.concurrent(豆包语音识别模型2.0)
|
||||
|
||||
详细参数文档:https://www.volcengine.com/docs/6561/109979
|
||||
|
||||
注意:
|
||||
- 豆包语音识别模型2.0使用volc.seedasr.sauc.duration资源ID,与豆包语音识别(流式)(volc.bigasr.sauc.duration)不同
|
||||
- 语音识别模型2.0价格更为便宜,建议在高并发场景下使用并发版资源ID
|
||||
' WHERE `id` = 'ASR_DoubaoStreamASRV2';
|
||||
@@ -571,3 +571,59 @@ databaseChangeLog:
|
||||
- sqlFile:
|
||||
encoding: utf8
|
||||
path: classpath:db/changelog/202603111131.sql
|
||||
- changeSet:
|
||||
id: 202603231037
|
||||
author: rainv123
|
||||
changes:
|
||||
- sqlFile:
|
||||
encoding: utf8
|
||||
path: classpath:db/changelog/202603231037.sql
|
||||
- changeSet:
|
||||
id: 202603311200
|
||||
author: cgd
|
||||
changes:
|
||||
- sqlFile:
|
||||
encoding: utf8
|
||||
path: classpath:db/changelog/202603311200.sql
|
||||
- changeSet:
|
||||
id: 202604010930
|
||||
author: rainv123
|
||||
changes:
|
||||
- sqlFile:
|
||||
encoding: utf8
|
||||
path: classpath:db/changelog/202604010930.sql
|
||||
- changeSet:
|
||||
id: 202604011035
|
||||
author: RanChen
|
||||
changes:
|
||||
- sqlFile:
|
||||
encoding: utf8
|
||||
path: classpath:db/changelog/202604011035.sql
|
||||
- changeSet:
|
||||
id: 202604011545
|
||||
author: rainv123
|
||||
changes:
|
||||
- sqlFile:
|
||||
encoding: utf8
|
||||
path: classpath:db/changelog/202604011545.sql
|
||||
- changeSet:
|
||||
id: 202604161357
|
||||
author: hrz
|
||||
changes:
|
||||
- sqlFile:
|
||||
encoding: utf8
|
||||
path: classpath:db/changelog/202604161357.sql
|
||||
- changeSet:
|
||||
id: 202604201719
|
||||
author: RanChen
|
||||
changes:
|
||||
- sqlFile:
|
||||
encoding: utf8
|
||||
path: classpath:db/changelog/202604201719.sql
|
||||
- changeSet:
|
||||
id: 202604211700
|
||||
author: RanChen
|
||||
changes:
|
||||
- sqlFile:
|
||||
encoding: utf8
|
||||
path: classpath:db/changelog/202604211700.sql
|
||||
|
||||
@@ -206,4 +206,7 @@
|
||||
10197=\u6807\u7B7E\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A
|
||||
10198=\u6807\u7B7E\u4E0D\u5B58\u5728
|
||||
10199=\u89E3\u6790\u4E2D\u6587\u4EF6\u6682\u4E0D\u652F\u6301\u6B64\u64CD\u4F5C
|
||||
10200=\u6CA1\u6709\u6743\u9650\u67E5\u770B\u8BE5\u667A\u80FD\u4F53\u7684MCP\u63A5\u5165\u70B9\u5730\u5740
|
||||
10201=\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u8FDB\u5165\u53C2\u6570\u7BA1\u7406\u914D\u7F6Emcp\u63A5\u5165\u70B9\u5730\u5740
|
||||
10202=\u6CA1\u6709\u6743\u9650\u67E5\u770B\u8BE5\u667A\u80FD\u4F53\u7684MCP\u5DE5\u5177\u5217\u8868
|
||||
|
||||
|
||||
@@ -206,4 +206,7 @@
|
||||
10197=Tag-Name darf nicht leer sein
|
||||
10198=Tag nicht gefunden
|
||||
10199=Dateianalyse l\u00E4uft, dieser Vorgang wird nicht unterst\u00FCtzt
|
||||
10200=Keine Berechtigung, die MCP-Endpunktadresse dieses Agenten anzuzeigen
|
||||
10201=Bitte kontaktieren Sie den Administrator, um die MCP-Endpunktadresse in der Parameterverwaltung zu konfigurieren
|
||||
10202=Keine Berechtigung, die MCP-Tool-Liste dieses Agenten anzuzeigen
|
||||
|
||||
|
||||
@@ -206,4 +206,7 @@
|
||||
10197=Tag name cannot be empty
|
||||
10198=Tag not found
|
||||
10199=Parsing in progress, this operation is not supported
|
||||
10200=No permission to view the MCP endpoint address of this agent
|
||||
10201=Please contact the administrator to configure the MCP endpoint address in parameter management
|
||||
10202=No permission to view the MCP tool list of this agent
|
||||
|
||||
|
||||
@@ -205,4 +205,7 @@
|
||||
10196=Nome da etiqueta j\u00E1 existe
|
||||
10197=Nome da etiqueta n\u00E3o pode ser vazio
|
||||
10198=Etiqueta n\u00E3o existe
|
||||
10199=Esta opera\u00E7\u00E3o n\u00E3o � suportada na an\u00E1lise de arquivos chineses
|
||||
10199=Esta opera\u00E7\u00E3o n\u00E3o � suportada na an\u00E1lise de arquivos chineses
|
||||
10200=Sem permiss\u00e3o para visualizar o endere\u00e7o do endpoint MCP deste agente
|
||||
10201=Por favor, contate o administrador para configurar o endere\u00e7o do endpoint MCP no gerenciamento de par\u00e2metros
|
||||
10202=Sem permiss\u00e3o para visualizar a lista de ferramentas MCP deste agente
|
||||
@@ -206,4 +206,6 @@
|
||||
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
|
||||
|
||||
10200=Kh\u00f4ng c\u00f3 quy\u1ec1n xem \u0111\u1ecba ch\u1ec9 \u0111i\u1ec3m cu\u1ed1i MCP c\u1ee7a \u0111\u1ea1i l\u00fd n\u00e0y
|
||||
10201=Vui l\u00f2ng li\u00ean h\u1ec7 qu\u1ea3n tr\u1ecb vi\u00ean \u0111\u1ec3 c\u1ea5u h\u00ecnh \u0111\u1ecba ch\u1ec9 \u0111i\u1ec3m cu\u1ed1i MCP trong qu\u1ea3n l\u00fd tham s\u1ed1
|
||||
10202=Kh\u00f4ng c\u00f3 quy\u1ec1n xem danh s\u00e1ch c\u00f4ng c\u1ee5 MCP c\u1ee7a \u0111\u1ea1i l\u00fd n\u00e0y
|
||||
@@ -206,3 +206,6 @@
|
||||
10197=\u6807\u7B7E\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A
|
||||
10198=\u6807\u7B7E\u4E0D\u5B58\u5728
|
||||
10199=\u89E3\u6790\u4E2D\u6587\u4EF6\u6682\u4E0D\u652F\u6301\u6B64\u64CD\u4F5C
|
||||
10200=\u6CA1\u6709\u6743\u9650\u67E5\u770B\u8BE5\u667A\u80FD\u4F53\u7684MCP\u63A5\u5165\u70B9\u5730\u5740
|
||||
10201=\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u8FDB\u5165\u53C2\u6570\u7BA1\u7406\u914D\u7F6Emcp\u63A5\u5165\u70B9\u5730\u5740
|
||||
10202=\u6CA1\u6709\u6743\u9650\u67E5\u770B\u8BE5\u667A\u80FD\u4F53\u7684MCP\u5DE5\u5177\u5217\u8868
|
||||
|
||||
@@ -206,4 +206,6 @@
|
||||
10197=\u6A19\u7C64\u540D\u7A31\u4E0D\u80FD\u4E3A\u7A7A
|
||||
10198=\u6A19\u7C64\u4E0D\u5B58\u5728
|
||||
10199=\u89E3\u6790\u4E2D\u6587\u4EF6\u66AB\u4E0D\u652F\u6301\u6B64\u64CD\u4F5C
|
||||
|
||||
10200=\u6C92\u6709\u6B0A\u9650\u67E5\u770B\u8A72\u667A\u80FD\u9AD4\u7684MCP\u63A5\u5165\u9EDE\u5730\u5740
|
||||
10201=\u8ACB\u806F\u7E6B\u7BA1\u7406\u54E1\u9032\u5165\u53C3\u6578\u7BA1\u7406\u914D\u7F6Emcp\u63A5\u5165\u9EDE\u5730\u5740
|
||||
10202=\u6C92\u6709\u6B0A\u9650\u67E5\u770B\u8A72\u667A\u80FD\u9AD4\u7684MCP\u5DE5\u5177\u5217\u8868
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
<result column="asrModelId" property="asrModelId"/>
|
||||
<result column="vadModelId" property="vadModelId"/>
|
||||
<result column="llmModelId" property="llmModelId"/>
|
||||
<result column="slmModelId" property="slmModelId"/>
|
||||
<result column="vllmModelId" property="vllmModelId"/>
|
||||
<result column="ttsModelId" property="ttsModelId"/>
|
||||
<result column="ttsVoiceId" property="ttsVoiceId"/>
|
||||
<result column="ttsLanguage" property="ttsLanguage"/>
|
||||
@@ -46,6 +48,7 @@
|
||||
a.asr_model_id AS asrModelId,
|
||||
a.vad_model_id AS vadModelId,
|
||||
a.llm_model_id AS llmModelId,
|
||||
a.slm_model_id AS slmModelId,
|
||||
a.vllm_model_id AS vllmModelId,
|
||||
a.tts_model_id AS ttsModelId,
|
||||
a.tts_voice_id AS ttsVoiceId,
|
||||
|
||||
@@ -131,6 +131,7 @@ export function getMcpAddress(agentId: string) {
|
||||
meta: {
|
||||
ignoreAuth: false,
|
||||
toast: false,
|
||||
isExposeError: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ export interface AgentDetail {
|
||||
asrModelId: string
|
||||
vadModelId: string
|
||||
llmModelId: string
|
||||
slmModelId: string
|
||||
vllmModelId: string
|
||||
ttsModelId: string
|
||||
ttsVoiceId: string
|
||||
|
||||
@@ -34,6 +34,9 @@ export function getChatHistory(agentId: string, sessionId: string) {
|
||||
ignoreAuth: false,
|
||||
toast: false,
|
||||
},
|
||||
cacheFor: {
|
||||
expire: -1,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ export interface ChatSession {
|
||||
sessionId: string
|
||||
createdAt: string
|
||||
chatCount: number
|
||||
title: string
|
||||
}
|
||||
|
||||
// 聊天会话列表响应
|
||||
@@ -14,7 +15,7 @@ export interface ChatSessionsResponse {
|
||||
// 聊天消息
|
||||
export interface ChatMessage {
|
||||
createdAt: string
|
||||
chatType: 1 | 2 // 1是用户,2是AI
|
||||
chatType: 1 | 2 | 3 // 1是用户,2是AI,3是参数说明
|
||||
content: string
|
||||
audioId: string | null
|
||||
macAddress: string
|
||||
|
||||
@@ -60,3 +60,13 @@ export function updateVoicePrint(data: VoicePrint) {
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// 获取音频下载ID
|
||||
export function getAudioDownloadId(audioId: string) {
|
||||
return http.Post<string>(`/agent/audio/${audioId}`, {}, {
|
||||
meta: {
|
||||
ignoreAuth: false,
|
||||
toast: false,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { uniappRequestAdapter } from '@alova/adapter-uniapp'
|
||||
import type { IResponse } from './types'
|
||||
import type { Language } from '@/store/lang'
|
||||
import AdapterUniapp from '@alova/adapter-uniapp'
|
||||
import { createAlova } from 'alova'
|
||||
import { createServerTokenAuthentication } from 'alova/client'
|
||||
@@ -8,6 +9,16 @@ import { getEnvBaseUrl } from '@/utils'
|
||||
import { toast } from '@/utils/toast'
|
||||
import { ContentTypeEnum, ResultEnum, ShowMessage } from './enum'
|
||||
|
||||
// 语言映射, 用于设置 Accept-language 头
|
||||
const langMap: Record<Language, string> = {
|
||||
zh_CN: 'zh-CN',
|
||||
en: 'en-US',
|
||||
zh_TW: 'zh-TW',
|
||||
de: 'de',
|
||||
vi: 'vi',
|
||||
pt_BR: 'pt-BR',
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建请求实例
|
||||
*/
|
||||
@@ -51,6 +62,7 @@ const alovaInstance = createAlova({
|
||||
// 检查混合内容错误(HTTPS页面请求HTTP接口)
|
||||
const currentProtocol = typeof window !== 'undefined' && window.location.protocol
|
||||
const requestProtocol = method.baseURL?.split(':')[0]
|
||||
const currentLang = langMap[uni.getStorageSync('app_language') as Language || 'zh_CN']
|
||||
if (currentProtocol === 'https:' && requestProtocol === 'http') {
|
||||
const errorMessage = '无法配置http协议地址,请检查接口地址'
|
||||
throw new Error(errorMessage)
|
||||
@@ -60,6 +72,7 @@ const alovaInstance = createAlova({
|
||||
method.config.headers = {
|
||||
'Content-Type': ContentTypeEnum.JSON,
|
||||
'Accept': 'application/json, text/plain, */*',
|
||||
'Accept-language': currentLang,
|
||||
...method.config.headers,
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ export default {
|
||||
'login.navigationTitle': 'Anmelden',
|
||||
'login.fetchConfigError': 'Konfiguration konnte nicht abgerufen werden:',
|
||||
'login.selectLanguage': 'Sprache auswählen',
|
||||
'login.selectLanguageTip': 'Vi',
|
||||
'login.selectLanguageTip': 'De',
|
||||
'login.welcomeBack': 'Willkommen zurück',
|
||||
'login.pleaseLogin': 'Bitte melden Sie sich an',
|
||||
'login.enterUsername': 'Bitte Benutzernamen eingeben',
|
||||
@@ -69,7 +69,7 @@ export default {
|
||||
'home.createFirstAgent': 'Klicken Sie auf die + Schaltfläche unten rechts, um Ihren ersten Agenten zu erstellen',
|
||||
'home.dialogTitle': 'Agent erstellen',
|
||||
'home.inputPlaceholder': 'z.B. Kundenservice-Assistent, Sprachassistent, Wissens-F&A',
|
||||
'home.createError': 'Bitte Agenten-Namen eingeben',
|
||||
'home.createError': 'Der Name muss zwischen 1 und 64 Zeichen lang sein.',
|
||||
'home.createNow': 'Jetzt erstellen',
|
||||
'home.justNow': 'Gerade eben',
|
||||
'home.minutesAgo': 'Minuten her',
|
||||
@@ -85,7 +85,7 @@ export default {
|
||||
'agent.chatHistory': 'Chat-Verlauf',
|
||||
'agent.voiceprintManagement': 'Stimmabdruckverwaltung',
|
||||
'agent.editTitle': 'Agent bearbeiten',
|
||||
'agent.toolsTitle': 'Funktionen bearbeiten',
|
||||
'agent.toolsTitle': 'Bearbeiten',
|
||||
'agent.voiceActivityDetection': 'Sprachaktivitätserkennung',
|
||||
'agent.speechRecognition': 'Spracherkennung',
|
||||
'agent.largeLanguageModel': 'Großes Sprachmodell',
|
||||
@@ -108,7 +108,8 @@ export default {
|
||||
'agent.modelConfig': 'Modellkonfiguration',
|
||||
'agent.vad': 'Sprachaktivitätserkennung',
|
||||
'agent.asr': 'Spracherkennung',
|
||||
'agent.llm': 'Großes Sprachmodell',
|
||||
'agent.llm': 'Hauptsprachenmodell',
|
||||
'agent.slm': 'Kleine Parametermodelle',
|
||||
'agent.vllm': 'Vision-Sprachmodell',
|
||||
'agent.intent': 'Absichtserkennung',
|
||||
'agent.memory': 'Speicher',
|
||||
@@ -116,7 +117,7 @@ export default {
|
||||
'agent.tts': 'Text-zu-Sprache',
|
||||
'agent.voiceprint': 'Agenten-Stimme',
|
||||
'agent.plugins': 'Plugins',
|
||||
'agent.editFunctions': 'Funktionen bearbeiten',
|
||||
'agent.editFunctions': 'Bearbeiten',
|
||||
'agent.historyMemory': 'Verlaufsspeicher',
|
||||
'agent.memoryContent': 'Speicherinhalt',
|
||||
'agent.saving': 'Wird gespeichert...',
|
||||
@@ -371,7 +372,7 @@ export default {
|
||||
'agent.tools.mcpAccessPoint': 'MCP-Zugangspunkt',
|
||||
'agent.tools.copy': 'Kopieren',
|
||||
'agent.tools.noTools': 'Keine Werkzeuge verfügbar',
|
||||
'agent.tools.parameterConfig': 'Parameterkonfiguration',
|
||||
'agent.tools.parameterConfig': 'Konfig',
|
||||
'agent.tools.noParamsNeeded': 'Keine Parameter benötigt',
|
||||
'agent.tools.pleaseInput': 'Bitte eingeben',
|
||||
'agent.tools.inputOneItemPerLine': 'Ein Element pro Zeile eingeben',
|
||||
@@ -471,6 +472,7 @@ export default {
|
||||
'deviceConfig.reCheck': 'Erneut prüfen',
|
||||
'deviceConfig.connectedXiaozhiHotspot': 'Mit xiaozhi Hotspot verbunden',
|
||||
'deviceConfig.wifiNetwork': 'WiFi-Netzwerk',
|
||||
'deviceConfig.wifiPassword': 'WiFi-Passwort',
|
||||
'deviceConfig.scanning': 'Wird gescannt',
|
||||
'deviceConfig.cancel': 'Abbrechen',
|
||||
'deviceConfig.clickRefreshScan': 'Bitte Scan aktualisieren klicken',
|
||||
@@ -484,6 +486,7 @@ export default {
|
||||
'deviceConfig.wifiConfigFailed': 'WiFi-Konfiguration fehlgeschlagen',
|
||||
'deviceConfig.pleaseCheckNetworkConnection': 'Bitte Netzwerkverbindung prüfen',
|
||||
'deviceConfig.startWifiConfigButton': 'Konfiguration starten',
|
||||
'deviceConfig.configuring': 'Wird konfiguriert...',
|
||||
'deviceConfig.wifiConfigInstructions': 'WiFi-Konfigurationsanleitung',
|
||||
'deviceConfig.phoneConnectXiaozhiHotspot': 'Telefon mit xiaozhi Hotspot verbinden',
|
||||
'deviceConfig.selectTargetWifiNetwork': 'Ziel-WiFi-Netzwerk auswählen',
|
||||
@@ -492,4 +495,9 @@ export default {
|
||||
'deviceConfig.afterConfigSuccessDeviceWillRestart': 'Nach erfolgreicher Konfiguration startet Gerät automatisch neu',
|
||||
'deviceConfig.audioPlaybackError': 'Audio-Wiedergabe-Fehler',
|
||||
'deviceConfig.playbackFailed': 'Wiedergabe fehlgeschlagen',
|
||||
|
||||
// Voiceprint page
|
||||
'voiceprint.audioNotExist': 'Audio existiert nicht',
|
||||
'voiceprint.getAudioFailed': 'Audio konnte nicht abgerufen werden',
|
||||
'voiceprint.audioPlayFailed': 'Audio-Wiedergabe fehlgeschlagen',
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ export default {
|
||||
'login.navigationTitle': 'Login',
|
||||
'login.fetchConfigError': 'Failed to fetch configuration:',
|
||||
'login.selectLanguage': 'Select Language',
|
||||
'login.selectLanguageTip': '中文',
|
||||
'login.selectLanguageTip': 'En',
|
||||
'login.welcomeBack': 'Welcome Back',
|
||||
'login.pleaseLogin': 'Please log in to your account',
|
||||
'login.enterUsername': 'Please enter username',
|
||||
@@ -69,7 +69,7 @@ export default {
|
||||
'home.createFirstAgent': 'Click the + button in the lower right corner to create your first agent',
|
||||
'home.dialogTitle': 'Create Agent',
|
||||
'home.inputPlaceholder': 'e.g. Customer Service Assistant, Voice Assistant, Knowledge Q&A',
|
||||
'home.createError': 'Please input agent name',
|
||||
'home.createError': 'The name length must be between 1 and 64 characters',
|
||||
'home.createNow': 'Create Now',
|
||||
'home.justNow': 'Just now',
|
||||
'home.minutesAgo': 'minutes ago',
|
||||
@@ -108,7 +108,8 @@ export default {
|
||||
'agent.modelConfig': 'Model Configuration',
|
||||
'agent.vad': 'Voice Activity Detection',
|
||||
'agent.asr': 'Speech Recognition',
|
||||
'agent.llm': 'Large Language Model',
|
||||
'agent.llm': 'Main language model',
|
||||
'agent.slm': 'Small parameter model',
|
||||
'agent.vllm': 'Vision Language Model',
|
||||
'agent.intent': 'Intent Recognition',
|
||||
'agent.memory': 'Memory',
|
||||
@@ -371,7 +372,7 @@ export default {
|
||||
'agent.tools.mcpAccessPoint': 'MCP Access Point',
|
||||
'agent.tools.copy': 'Copy',
|
||||
'agent.tools.noTools': 'No tools available',
|
||||
'agent.tools.parameterConfig': 'Parameter Configuration',
|
||||
'agent.tools.parameterConfig': 'Param Config',
|
||||
'agent.tools.noParamsNeeded': 'No parameters needed',
|
||||
'agent.tools.pleaseInput': 'Please input',
|
||||
'agent.tools.inputOneItemPerLine': 'Input one item per line',
|
||||
@@ -471,6 +472,7 @@ export default {
|
||||
'deviceConfig.reCheck': 'Re-check',
|
||||
'deviceConfig.connectedXiaozhiHotspot': 'Connected to xiaozhi hotspot',
|
||||
'deviceConfig.wifiNetwork': 'WiFi Network',
|
||||
'deviceConfig.wifiPassword': 'WiFi Password',
|
||||
'deviceConfig.scanning': 'Scanning',
|
||||
'deviceConfig.cancel': 'Cancel',
|
||||
'deviceConfig.clickRefreshScan': 'Please click Refresh Scan',
|
||||
@@ -484,6 +486,7 @@ export default {
|
||||
'deviceConfig.wifiConfigFailed': 'WiFi configuration failed',
|
||||
'deviceConfig.pleaseCheckNetworkConnection': 'Please check network connection',
|
||||
'deviceConfig.startWifiConfigButton': 'Start Configuration',
|
||||
'deviceConfig.configuring': 'Configuring...',
|
||||
'deviceConfig.wifiConfigInstructions': 'WiFi Configuration Instructions',
|
||||
'deviceConfig.phoneConnectXiaozhiHotspot': 'Phone connect to xiaozhi hotspot',
|
||||
'deviceConfig.selectTargetWifiNetwork': 'Select target WiFi network',
|
||||
@@ -492,4 +495,9 @@ export default {
|
||||
'deviceConfig.afterConfigSuccessDeviceWillRestart': 'After successful configuration, device will automatically restart',
|
||||
'deviceConfig.audioPlaybackError': 'Audio playback error',
|
||||
'deviceConfig.playbackFailed': 'Playback failed',
|
||||
|
||||
// Voiceprint page
|
||||
'voiceprint.audioNotExist': 'Audio does not exist',
|
||||
'voiceprint.getAudioFailed': 'Failed to get audio',
|
||||
'voiceprint.audioPlayFailed': 'Audio playback failed',
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ export default {
|
||||
'login.navigationTitle': 'Entrar',
|
||||
'login.fetchConfigError': 'Falha ao buscar configuração:',
|
||||
'login.selectLanguage': 'Selecionar Idioma',
|
||||
'login.selectLanguageTip': '中文',
|
||||
'login.selectLanguageTip': 'Pt',
|
||||
'login.welcomeBack': 'Bem-vindo de Volta',
|
||||
'login.pleaseLogin': 'Por favor, entre na sua conta',
|
||||
'login.enterUsername': 'Por favor, insira o nome de usuário',
|
||||
@@ -69,7 +69,7 @@ export default {
|
||||
'home.createFirstAgent': 'Clique no botão + no canto inferior direito para criar seu primeiro agente',
|
||||
'home.dialogTitle': 'Criar Agente',
|
||||
'home.inputPlaceholder': 'ex: Assistente de Atendimento, Assistente de Voz, Perguntas e Respostas',
|
||||
'home.createError': 'Por favor, insira o nome do agente',
|
||||
'home.createError': 'O comprimento do nome deve estar entre 1 e 64 caracteres',
|
||||
'home.createNow': 'Criar Agora',
|
||||
'home.justNow': 'Agora mesmo',
|
||||
'home.minutesAgo': 'minutos atrás',
|
||||
@@ -108,7 +108,8 @@ export default {
|
||||
'agent.modelConfig': 'Configuração do Modelo',
|
||||
'agent.vad': 'Detecção de Atividade de Voz',
|
||||
'agent.asr': 'Reconhecimento de Fala',
|
||||
'agent.llm': 'Modelo de Linguagem Grande',
|
||||
'agent.llm': 'Modelo de Linguagem Principal',
|
||||
'agent.slm': 'Modelo de pequenos parâmetros',
|
||||
'agent.vllm': 'Modelo de Linguagem Visual',
|
||||
'agent.intent': 'Reconhecimento de Intenção',
|
||||
'agent.memory': 'Memória',
|
||||
@@ -371,7 +372,7 @@ export default {
|
||||
'agent.tools.mcpAccessPoint': 'Ponto de Acesso MCP',
|
||||
'agent.tools.copy': 'Copiar',
|
||||
'agent.tools.noTools': 'Nenhuma ferramenta disponível',
|
||||
'agent.tools.parameterConfig': 'Configuração de Parâmetros',
|
||||
'agent.tools.parameterConfig': 'Configuração',
|
||||
'agent.tools.noParamsNeeded': 'Nenhum parâmetro necessário',
|
||||
'agent.tools.pleaseInput': 'Por favor, insira',
|
||||
'agent.tools.inputOneItemPerLine': 'Insira um item por linha',
|
||||
@@ -471,6 +472,7 @@ export default {
|
||||
'deviceConfig.reCheck': 'Verificar Novamente',
|
||||
'deviceConfig.connectedXiaozhiHotspot': 'Conectado ao hotspot xiaozhi',
|
||||
'deviceConfig.wifiNetwork': 'Rede WiFi',
|
||||
'deviceConfig.wifiPassword': 'Senha do WiFi',
|
||||
'deviceConfig.scanning': 'Buscando',
|
||||
'deviceConfig.cancel': 'Cancelar',
|
||||
'deviceConfig.clickRefreshScan': 'Por favor, clique em Atualizar Busca',
|
||||
@@ -484,6 +486,7 @@ export default {
|
||||
'deviceConfig.wifiConfigFailed': 'Falha na configuração WiFi',
|
||||
'deviceConfig.pleaseCheckNetworkConnection': 'Por favor, verifique a conexão de rede',
|
||||
'deviceConfig.startWifiConfigButton': 'Iniciar Configuração',
|
||||
'deviceConfig.configuring': 'Configurando...',
|
||||
'deviceConfig.wifiConfigInstructions': 'Instruções de Configuração WiFi',
|
||||
'deviceConfig.phoneConnectXiaozhiHotspot': 'Conecte o celular ao hotspot xiaozhi',
|
||||
'deviceConfig.selectTargetWifiNetwork': 'Selecione a rede WiFi de destino',
|
||||
@@ -492,4 +495,9 @@ export default {
|
||||
'deviceConfig.afterConfigSuccessDeviceWillRestart': 'Após a configuração bem-sucedida, o dispositivo reiniciará automaticamente',
|
||||
'deviceConfig.audioPlaybackError': 'Erro na reprodução de áudio',
|
||||
'deviceConfig.playbackFailed': 'Falha na reprodução',
|
||||
|
||||
// Voiceprint page
|
||||
'voiceprint.audioNotExist': 'O áudio não existe',
|
||||
'voiceprint.getAudioFailed': 'Falha ao obter áudio',
|
||||
'voiceprint.audioPlayFailed': 'Falha na reprodução de áudio',
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ export default {
|
||||
'login.navigationTitle': 'Đăng nhập',
|
||||
'login.fetchConfigError': 'Không thể tải cấu hình:',
|
||||
'login.selectLanguage': 'Chọn ngôn ngữ',
|
||||
'login.selectLanguageTip': 'de',
|
||||
'login.selectLanguageTip': 'Vi',
|
||||
'login.welcomeBack': 'Chào mừng trở lại',
|
||||
'login.pleaseLogin': 'Vui lòng đăng nhập vào tài khoản của bạn',
|
||||
'login.enterUsername': 'Vui lòng nhập tên đăng nhập',
|
||||
@@ -69,7 +69,7 @@ export default {
|
||||
'home.createFirstAgent': 'Nhấp vào nút + ở góc dưới bên phải để tạo đại lý đầu tiên của bạn',
|
||||
'home.dialogTitle': 'Tạo đại lý',
|
||||
'home.inputPlaceholder': 'ví dụ: Trợ lý chăm sóc khách hàng, Trợ lý giọng nói, Hỏi đáp kiến thức',
|
||||
'home.createError': 'Vui lòng nhập tên đại lý',
|
||||
'home.createError': 'Độ dài tên phải từ 1 đến 64 ký tự',
|
||||
'home.createNow': 'Tạo ngay',
|
||||
'home.justNow': 'Vừa xong',
|
||||
'home.minutesAgo': 'phút trước',
|
||||
@@ -108,7 +108,8 @@ export default {
|
||||
'agent.modelConfig': 'Cấu hình mô hình',
|
||||
'agent.vad': 'Phát hiện hoạt động giọng nói',
|
||||
'agent.asr': 'Nhận dạng giọng nói',
|
||||
'agent.llm': 'Mô hình ngôn ngữ lớn',
|
||||
'agent.llm': 'Mô hình ngôn ngữ chính',
|
||||
'agent.slm': 'Mô hình tham số nhỏ',
|
||||
'agent.vllm': 'Mô hình ngôn ngữ thị giác',
|
||||
'agent.intent': 'Nhận dạng ý định',
|
||||
'agent.memory': 'Bộ nhớ',
|
||||
@@ -371,7 +372,7 @@ export default {
|
||||
'agent.tools.mcpAccessPoint': 'Điểm truy cập MCP',
|
||||
'agent.tools.copy': 'Sao chép',
|
||||
'agent.tools.noTools': 'Không có công cụ nào',
|
||||
'agent.tools.parameterConfig': 'Cấu hình tham số',
|
||||
'agent.tools.parameterConfig': 'Cấu hình',
|
||||
'agent.tools.noParamsNeeded': 'Không cần tham số',
|
||||
'agent.tools.pleaseInput': 'Vui lòng nhập',
|
||||
'agent.tools.inputOneItemPerLine': 'Nhập một mục mỗi dòng',
|
||||
@@ -471,6 +472,7 @@ export default {
|
||||
'deviceConfig.reCheck': 'Kiểm tra lại',
|
||||
'deviceConfig.connectedXiaozhiHotspot': 'Đã kết nối với điểm phát sóng xiaozhi',
|
||||
'deviceConfig.wifiNetwork': 'Mạng WiFi',
|
||||
'deviceConfig.wifiPassword': 'Mật khẩu WiFi',
|
||||
'deviceConfig.scanning': 'Đang quét',
|
||||
'deviceConfig.cancel': 'Hủy',
|
||||
'deviceConfig.clickRefreshScan': 'Vui lòng nhấp Làm mới quét',
|
||||
@@ -484,6 +486,7 @@ export default {
|
||||
'deviceConfig.wifiConfigFailed': 'Cấu hình WiFi thất bại',
|
||||
'deviceConfig.pleaseCheckNetworkConnection': 'Vui lòng kiểm tra kết nối mạng',
|
||||
'deviceConfig.startWifiConfigButton': 'Bắt đầu cấu hình',
|
||||
'deviceConfig.configuring': 'Đang cấu hình...',
|
||||
'deviceConfig.wifiConfigInstructions': 'Hướng dẫn cấu hình WiFi',
|
||||
'deviceConfig.phoneConnectXiaozhiHotspot': 'Điện thoại kết nối với điểm phát sóng xiaozhi',
|
||||
'deviceConfig.selectTargetWifiNetwork': 'Chọn mạng WiFi mục tiêu',
|
||||
@@ -492,4 +495,9 @@ export default {
|
||||
'deviceConfig.afterConfigSuccessDeviceWillRestart': 'Sau khi cấu hình thành công, thiết bị sẽ tự động khởi động lại',
|
||||
'deviceConfig.audioPlaybackError': 'Lỗi phát âm thanh',
|
||||
'deviceConfig.playbackFailed': 'Phát thất bại',
|
||||
|
||||
// Voiceprint page
|
||||
'voiceprint.audioNotExist': 'Âm thanh không tồn tại',
|
||||
'voiceprint.getAudioFailed': 'Không thể lấy âm thanh',
|
||||
'voiceprint.audioPlayFailed': 'Phát âm thanh thất bại',
|
||||
}
|
||||
@@ -11,7 +11,7 @@ export default {
|
||||
'login.navigationTitle': '登录',
|
||||
'login.fetchConfigError': '获取配置失败:',
|
||||
'login.selectLanguage': '选择语言',
|
||||
'login.selectLanguageTip': 'En',
|
||||
'login.selectLanguageTip': '中文',
|
||||
'login.welcomeBack': '欢迎回来',
|
||||
'login.pleaseLogin': '请登录您的账户',
|
||||
'login.enterUsername': '请输入用户名',
|
||||
@@ -69,7 +69,7 @@ export default {
|
||||
'home.createFirstAgent': '点击右下角 + 号创建您的第一个智能体',
|
||||
'home.dialogTitle': '创建智能体',
|
||||
'home.inputPlaceholder': '例如:客服助手、语音助理、知识问答',
|
||||
'home.createError': '请输入智能体名称',
|
||||
'home.createError': '名称长度必须在 1 到 64 个字符之间',
|
||||
'home.createNow': '立即创建',
|
||||
'home.justNow': '刚刚',
|
||||
'home.minutesAgo': '分钟前',
|
||||
@@ -108,7 +108,8 @@ export default {
|
||||
'agent.modelConfig': '模型配置',
|
||||
'agent.vad': '语音活动检测',
|
||||
'agent.asr': '语音识别',
|
||||
'agent.llm': '大语言模型',
|
||||
'agent.llm': '主语言模型',
|
||||
'agent.slm': '小参数模型',
|
||||
'agent.vllm': '视觉大模型',
|
||||
'agent.intent': '意图识别',
|
||||
'agent.memory': '记忆',
|
||||
@@ -471,6 +472,7 @@ export default {
|
||||
'deviceConfig.reCheck': '重新检查',
|
||||
'deviceConfig.connectedXiaozhiHotspot': '已连接xiaozhi热点',
|
||||
'deviceConfig.wifiNetwork': 'WiFi网络',
|
||||
'deviceConfig.wifiPassword': 'WiFi密码',
|
||||
'deviceConfig.scanning': '扫描中',
|
||||
'deviceConfig.cancel': '取消',
|
||||
'deviceConfig.clickRefreshScan': '请点击刷新扫描',
|
||||
@@ -484,6 +486,7 @@ export default {
|
||||
'deviceConfig.wifiConfigFailed': 'WiFi配网失败',
|
||||
'deviceConfig.pleaseCheckNetworkConnection': '请检查网络连接',
|
||||
'deviceConfig.startWifiConfigButton': '开始配网',
|
||||
'deviceConfig.configuring': '配网中...',
|
||||
'deviceConfig.wifiConfigInstructions': 'WiFi配网说明',
|
||||
'deviceConfig.phoneConnectXiaozhiHotspot': '手机连接xiaozhi热点',
|
||||
'deviceConfig.selectTargetWifiNetwork': '选择目标WiFi网络',
|
||||
@@ -492,4 +495,9 @@ export default {
|
||||
'deviceConfig.afterConfigSuccessDeviceWillRestart': '配网成功后设备将自动重启',
|
||||
'deviceConfig.audioPlaybackError': '音频播放错误',
|
||||
'deviceConfig.playbackFailed': '播放失败',
|
||||
|
||||
// Voiceprint page
|
||||
'voiceprint.audioNotExist': '该音频不存在',
|
||||
'voiceprint.getAudioFailed': '获取音频失败',
|
||||
'voiceprint.audioPlayFailed': '音频播放失败',
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ export default {
|
||||
'login.navigationTitle': '登錄',
|
||||
'login.fetchConfigError': '獲取配置失敗:',
|
||||
'login.selectLanguage': '選擇語言',
|
||||
'login.selectLanguageTip': '简体',
|
||||
'login.selectLanguageTip': '繁體',
|
||||
'login.welcomeBack': '歡迎回來',
|
||||
'login.pleaseLogin': '請登錄您的賬戶',
|
||||
'login.enterUsername': '請輸入用戶名',
|
||||
@@ -90,7 +90,7 @@ export default {
|
||||
'home.createFirstAgent': '點擊右下角 + 號創建您的第一個智能體',
|
||||
'home.dialogTitle': '創建智能體',
|
||||
'home.inputPlaceholder': '例如:客服助手、語音助理、知識問答',
|
||||
'home.createError': '請輸入智能體暱稱',
|
||||
'home.createError': '暱稱長度必須在 1 到 64 個字元之間。',
|
||||
'home.createNow': '立即創建',
|
||||
'home.justNow': '剛剛',
|
||||
'home.minutesAgo': '分鐘前',
|
||||
@@ -129,7 +129,8 @@ export default {
|
||||
'agent.modelConfig': '模型配置',
|
||||
'agent.vad': '語音活動檢測',
|
||||
'agent.asr': '語音識別',
|
||||
'agent.llm': '大語言模型',
|
||||
'agent.llm': '主語言模型',
|
||||
'agent.slm': '小參數模型',
|
||||
'agent.vllm': '視覺大模型',
|
||||
'agent.intent': '意圖識別',
|
||||
'agent.memory': '記憶',
|
||||
@@ -471,6 +472,7 @@ export default {
|
||||
'deviceConfig.reCheck': '重新檢查',
|
||||
'deviceConfig.connectedXiaozhiHotspot': '已連接xiaozhi熱點',
|
||||
'deviceConfig.wifiNetwork': 'WiFi網絡',
|
||||
'deviceConfig.wifiPassword': 'WiFi密碼',
|
||||
'deviceConfig.scanning': '掃描中',
|
||||
'deviceConfig.cancel': '取消',
|
||||
'deviceConfig.clickRefreshScan': '請點擊刷新掃描',
|
||||
@@ -484,6 +486,7 @@ export default {
|
||||
'deviceConfig.wifiConfigFailed': 'WiFi配網失敗',
|
||||
'deviceConfig.pleaseCheckNetworkConnection': '請檢查網絡連接',
|
||||
'deviceConfig.startWifiConfigButton': '開始配網',
|
||||
'deviceConfig.configuring': '配網中...',
|
||||
'deviceConfig.wifiConfigInstructions': 'WiFi配網說明',
|
||||
'deviceConfig.phoneConnectXiaozhiHotspot': '手機連接xiaozhi熱點',
|
||||
'deviceConfig.selectTargetWifiNetwork': '選擇目標WiFi網絡',
|
||||
@@ -492,4 +495,9 @@ export default {
|
||||
'deviceConfig.afterConfigSuccessDeviceWillRestart': '配網成功後設備將自動重啟',
|
||||
'deviceConfig.audioPlaybackError': '音頻播放錯誤',
|
||||
'deviceConfig.playbackFailed': '播放失敗',
|
||||
|
||||
// Voiceprint page
|
||||
'voiceprint.audioNotExist': '該音頻不存在',
|
||||
'voiceprint.getAudioFailed': '獲取音頻失敗',
|
||||
'voiceprint.audioPlayFailed': '音頻播放失敗',
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ const formData = ref<Partial<AgentDetail>>({
|
||||
vadModelId: '',
|
||||
asrModelId: '',
|
||||
llmModelId: '',
|
||||
slmModelId: '',
|
||||
vllmModelId: '',
|
||||
intentModelId: '',
|
||||
memModelId: '',
|
||||
@@ -46,6 +47,7 @@ const displayNames = ref({
|
||||
vad: t('agent.pleaseSelect'),
|
||||
asr: t('agent.pleaseSelect'),
|
||||
llm: t('agent.pleaseSelect'),
|
||||
slm: t('agent.pleaseSelect'),
|
||||
vllm: t('agent.pleaseSelect'),
|
||||
intent: t('agent.pleaseSelect'),
|
||||
memory: t('agent.pleaseSelect'),
|
||||
@@ -94,6 +96,7 @@ const pickerShow = ref<{
|
||||
vad: false,
|
||||
asr: false,
|
||||
llm: false,
|
||||
slm: false,
|
||||
vllm: false,
|
||||
intent: false,
|
||||
memory: false,
|
||||
@@ -109,6 +112,11 @@ const inputValue = ref('')
|
||||
const inputVisible = ref(false)
|
||||
const languageOptions = ref([])
|
||||
const isVisibleReport = ref(false)
|
||||
const tempSummaryMemory = ref('')
|
||||
|
||||
// 音频播放相关
|
||||
const audioRef = ref<UniApp.InnerAudioContext | null>(null)
|
||||
const playingVoiceId = ref<string>('')
|
||||
|
||||
// 使用插件store
|
||||
const pluginStore = usePluginStore()
|
||||
@@ -158,6 +166,9 @@ function handleInputConfirm() {
|
||||
inputVisible.value = false
|
||||
}
|
||||
|
||||
// 是否禁用历史记忆输入框
|
||||
const isMemoryDisabled = computed(() => formData.value.memModelId !== 'Memory_mem_local_short')
|
||||
|
||||
// 打开上下文源编辑弹窗
|
||||
function openContextProviderDialog() {
|
||||
uni.navigateTo({
|
||||
@@ -178,6 +189,7 @@ async function loadAgentDetail() {
|
||||
|
||||
try {
|
||||
loading.value = true
|
||||
tempSummaryMemory.value = ''
|
||||
const detail = await getAgentDetail(agentId.value)
|
||||
formData.value = { ...detail }
|
||||
|
||||
@@ -263,6 +275,7 @@ function updateDisplayNames() {
|
||||
displayNames.value.vad = getModelDisplayName('VAD', formData.value.vadModelId)
|
||||
displayNames.value.asr = getModelDisplayName('ASR', formData.value.asrModelId)
|
||||
displayNames.value.llm = getModelDisplayName('LLM', formData.value.llmModelId)
|
||||
displayNames.value.slm = getModelDisplayName('LLM', formData.value.slmModelId)
|
||||
displayNames.value.vllm = getModelDisplayName('VLLM', formData.value.vllmModelId)
|
||||
displayNames.value.intent = getModelDisplayName('Intent', formData.value.intentModelId)
|
||||
displayNames.value.memory = getModelDisplayName('Memory', formData.value.memModelId)
|
||||
@@ -270,7 +283,6 @@ function updateDisplayNames() {
|
||||
|
||||
// 角色音色特殊处理
|
||||
displayNames.value.report = reportOptions.find(item => item.value === formData.value.chatHistoryConf)?.name
|
||||
displayNames.value.language = formData.value.ttsLanguage
|
||||
|
||||
isVisibleReport.value = formData.value.memModelId !== 'Memory_nomem'
|
||||
|
||||
@@ -422,15 +434,25 @@ function selectRoleTemplate(templateId: string) {
|
||||
selectedTemplateId.value = templateId
|
||||
const template = roleTemplates.value.find(t => t.id === templateId)
|
||||
if (template) {
|
||||
formData.value.systemPrompt = template.systemPrompt
|
||||
formData.value.vadModelId = template.vadModelId
|
||||
formData.value.asrModelId = template.asrModelId
|
||||
formData.value.llmModelId = template.llmModelId
|
||||
formData.value.vllmModelId = template.vllmModelId
|
||||
formData.value.intentModelId = template.intentModelId
|
||||
formData.value.memModelId = template.memModelId
|
||||
formData.value.ttsModelId = template.ttsModelId
|
||||
formData.value.ttsVoiceId = template.ttsVoiceId
|
||||
formData.value = {
|
||||
...formData.value,
|
||||
systemPrompt: template.systemPrompt || formData.value.systemPrompt,
|
||||
vadModelId: template.vadModelId || formData.value.vadModelId,
|
||||
asrModelId: template.asrModelId || formData.value.asrModelId,
|
||||
llmModelId: template.llmModelId || formData.value.llmModelId,
|
||||
slmModelId: template.llmModelId || formData.value.slmModelId,
|
||||
vllmModelId: template.vllmModelId || formData.value.vllmModelId,
|
||||
intentModelId: template.intentModelId || formData.value.intentModelId,
|
||||
memModelId: template.memModelId || formData.value.memModelId,
|
||||
ttsModelId: template.ttsModelId || formData.value.ttsModelId,
|
||||
ttsVoiceId: template.ttsVoiceId || formData.value.ttsVoiceId,
|
||||
agentName: template.agentName || formData.value.agentName,
|
||||
chatHistoryConf: template.chatHistoryConf || formData.value.chatHistoryConf,
|
||||
summaryMemory: template.summaryMemory || formData.value.summaryMemory,
|
||||
langCode: template.langCode || formData.value.langCode,
|
||||
}
|
||||
fetchAllLanguag(template.ttsModelId || formData.value.ttsModelId)
|
||||
updateDisplayNames()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -456,6 +478,9 @@ async function onPickerConfirm(type: string, value: any, name: string) {
|
||||
case 'llm':
|
||||
formData.value.llmModelId = value
|
||||
break
|
||||
case 'slm':
|
||||
formData.value.slmModelId = value
|
||||
break
|
||||
case 'vllm':
|
||||
formData.value.vllmModelId = value
|
||||
break
|
||||
@@ -469,6 +494,14 @@ async function onPickerConfirm(type: string, value: any, name: string) {
|
||||
displayNames.value.memory = name // 确保显示名称正确更新
|
||||
displayNames.value.report = reportOptions[1].name
|
||||
isVisibleReport.value = value !== 'Memory_nomem'
|
||||
if (value === 'Memory_nomem' || value === 'Memory_mem_report_only') {
|
||||
tempSummaryMemory.value = formData.value.summaryMemory
|
||||
formData.value.summaryMemory = ''
|
||||
}
|
||||
else if (tempSummaryMemory.value !== '' && formData.value.summaryMemory === '') {
|
||||
formData.value.summaryMemory = tempSummaryMemory.value
|
||||
tempSummaryMemory.value = ''
|
||||
}
|
||||
break
|
||||
case 'tts':
|
||||
formData.value.ttsModelId = value
|
||||
@@ -493,6 +526,57 @@ async function onPickerConfirm(type: string, value: any, name: string) {
|
||||
// 选择器取消
|
||||
function onPickerCancel(type: string) {
|
||||
pickerShow.value[type] = false
|
||||
// 关闭时停止播放
|
||||
if (type === 'voiceprint') {
|
||||
stopAudio()
|
||||
}
|
||||
}
|
||||
|
||||
// 播放音频
|
||||
function playAudio(voiceDemo: string, voiceId: string, event: Event) {
|
||||
event.stopPropagation() // 阻止事件冒泡,防止关闭下拉框
|
||||
|
||||
if (!voiceDemo) {
|
||||
return
|
||||
}
|
||||
|
||||
// 如果正在播放同一个音频,则停止
|
||||
if (playingVoiceId.value === voiceId) {
|
||||
stopAudio()
|
||||
return
|
||||
}
|
||||
|
||||
// 停止之前的音频
|
||||
stopAudio()
|
||||
|
||||
// 创建新的音频实例
|
||||
audioRef.value = uni.createInnerAudioContext()
|
||||
audioRef.value.src = voiceDemo
|
||||
playingVoiceId.value = voiceId
|
||||
|
||||
// 监听播放结束
|
||||
audioRef.value.onEnded(() => {
|
||||
playingVoiceId.value = ''
|
||||
})
|
||||
|
||||
// 监听播放错误
|
||||
audioRef.value.onError(() => {
|
||||
toast.error('音频播放失败')
|
||||
playingVoiceId.value = ''
|
||||
})
|
||||
|
||||
// 播放音频
|
||||
audioRef.value.play()
|
||||
}
|
||||
|
||||
// 停止音频
|
||||
function stopAudio() {
|
||||
if (audioRef.value) {
|
||||
audioRef.value.stop()
|
||||
audioRef.value.destroy()
|
||||
audioRef.value = null
|
||||
}
|
||||
playingVoiceId.value = ''
|
||||
}
|
||||
|
||||
// 获取模型显示名称
|
||||
@@ -763,6 +847,16 @@ onMounted(async () => {
|
||||
<wd-icon name="arrow-right" custom-class="text-[20rpx] text-[#9d9ea3]" />
|
||||
</view>
|
||||
|
||||
<view class="flex cursor-pointer items-center justify-between border border-[#eeeeee] rounded-[12rpx] bg-[#f5f7fb] p-[20rpx] transition-all duration-300 active:bg-[#eef3ff]" @click="openPicker('slm')">
|
||||
<text class="text-[28rpx] text-[#232338] font-medium">
|
||||
{{ t('agent.slm') }}
|
||||
</text>
|
||||
<text class="mx-[16rpx] flex-1 text-right text-[26rpx] text-[#65686f]">
|
||||
{{ displayNames.slm }}
|
||||
</text>
|
||||
<wd-icon name="arrow-right" custom-class="text-[20rpx] text-[#9d9ea3]" />
|
||||
</view>
|
||||
|
||||
<view class="flex cursor-pointer items-center justify-between border border-[#eeeeee] rounded-[12rpx] bg-[#f5f7fb] p-[20rpx] transition-all duration-300 active:bg-[#eef3ff]" @click="openPicker('vllm')">
|
||||
<text class="text-[28rpx] text-[#232338] font-medium">
|
||||
{{ t('agent.vllm') }}
|
||||
@@ -878,8 +972,9 @@ onMounted(async () => {
|
||||
<textarea
|
||||
v-model="formData.summaryMemory"
|
||||
:placeholder="t('agent.memoryContent')"
|
||||
disabled
|
||||
class="box-border h-[500rpx] w-full resize-none break-words break-all border border-[#eeeeee] rounded-[12rpx] bg-[#f0f0f0] p-[20rpx] text-[26rpx] text-[#65686f] leading-[1.6] opacity-80 outline-none"
|
||||
:disabled="isMemoryDisabled"
|
||||
:style="isMemoryDisabled ? 'background: #f0f0f0' : ''"
|
||||
class="box-border h-[500rpx] w-full resize-none break-words break-all border border-[#eeeeee] rounded-[12rpx] p-[20rpx] text-[26rpx] leading-[1.6] opacity-80 outline-none"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
@@ -918,6 +1013,13 @@ onMounted(async () => {
|
||||
@select="({ item }) => onPickerConfirm('llm', item.value, item.name)"
|
||||
/>
|
||||
|
||||
<wd-action-sheet
|
||||
v-model="pickerShow.slm"
|
||||
:actions="modelOptions.LLM && modelOptions.LLM.map(item => ({ name: item.modelName, value: item.id }))"
|
||||
@close="onPickerCancel('slm')"
|
||||
@select="({ item }) => onPickerConfirm('slm', item.value, item.name)"
|
||||
/>
|
||||
|
||||
<wd-action-sheet
|
||||
v-model="pickerShow.vllm"
|
||||
:actions="modelOptions.VLLM && modelOptions.VLLM.map(item => ({ name: item.modelName, value: item.id }))"
|
||||
@@ -942,16 +1044,35 @@ onMounted(async () => {
|
||||
<wd-action-sheet
|
||||
v-model="pickerShow.tts"
|
||||
:actions="modelOptions.TTS && modelOptions.TTS.map(item => ({ name: item.modelName, value: item.id }))"
|
||||
class="custom-sheet-tts"
|
||||
@close="onPickerCancel('tts')"
|
||||
@select="({ item }) => onPickerConfirm('tts', item.value, item.name)"
|
||||
/>
|
||||
|
||||
<wd-action-sheet
|
||||
v-model="pickerShow.voiceprint"
|
||||
:actions="voiceOptions"
|
||||
@close="onPickerCancel('voiceprint')"
|
||||
@select="({ item }) => onPickerConfirm('voiceprint', item.value, item.name)"
|
||||
/>
|
||||
<!-- 自定义语音选择弹出层 -->
|
||||
<wd-popup v-model="pickerShow.voiceprint" class="custom-popup" position="bottom" @close="onPickerCancel('voiceprint')">
|
||||
<view class="overflow-hidden rounded-[20rpx] bg-white pb-[20rpx] pt-[20rpx]">
|
||||
<view class="max-h-[600rpx] overflow-y-auto">
|
||||
<view
|
||||
v-for="voice in voiceOptions"
|
||||
:key="voice.value"
|
||||
class="flex items-center justify-between border-b border-[#f5f5f5] p-[32rpx] transition-all active:bg-[#f5f7fb]"
|
||||
@click="onPickerConfirm('voiceprint', voice.value, voice.name)"
|
||||
>
|
||||
<text :class="`flex-1 text-[28rpx] text-[#232338] ${(voice.voiceDemo || voice.voice_demo) ? '' : 'text-center'}`">
|
||||
{{ voice.name }}
|
||||
</text>
|
||||
<view v-if="voice.voiceDemo || voice.voice_demo" class="ml-[20rpx]" @click.stop="playAudio(voice.voiceDemo || voice.voice_demo, voice.value, $event)">
|
||||
<wd-icon
|
||||
:name="playingVoiceId === voice.value ? 'pause-circle' : 'play-circle'"
|
||||
size="24px"
|
||||
:custom-class="playingVoiceId === voice.value ? 'text-[#336cff]' : 'text-[#9d9ea3]'"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</wd-popup>
|
||||
<wd-action-sheet
|
||||
v-model="pickerShow.language"
|
||||
:actions="languageOptions"
|
||||
@@ -971,4 +1092,19 @@ onMounted(async () => {
|
||||
::v-deep .wd-tag__close {
|
||||
color: #336cff !important;
|
||||
}
|
||||
::v-deep .custom-popup {
|
||||
.wd-popup {
|
||||
padding: 20rpx !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
}
|
||||
::v-deep .custom-sheet-tts {
|
||||
.wd-action-sheet {
|
||||
padding: 8px 0 !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
.wd-action-sheet__actions {
|
||||
padding: 0 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -51,17 +51,18 @@ const currentTab = ref('agent-config')
|
||||
|
||||
// 刷新和加载状态
|
||||
const refreshing = ref(false)
|
||||
|
||||
// 计算是否启用下拉刷新(角色编辑页面不启用)
|
||||
const refresherEnabled = computed(() => {
|
||||
return currentTab.value !== 'agent-config'
|
||||
})
|
||||
const refresherEnabled = ref(false)
|
||||
|
||||
// 子组件引用
|
||||
const deviceRef = ref()
|
||||
const chatRef = ref()
|
||||
const voiceprintRef = ref()
|
||||
|
||||
// 更新刷新器状态
|
||||
function updateRefresherEnabled(value: boolean) {
|
||||
refresherEnabled.value = value
|
||||
}
|
||||
|
||||
// Tab 配置
|
||||
const tabList = [
|
||||
{
|
||||
@@ -144,6 +145,10 @@ async function onLoadMore() {
|
||||
}
|
||||
}
|
||||
|
||||
watch(() => currentTab.value, (newTab) => {
|
||||
updateRefresherEnabled(newTab !== 'agent-config')
|
||||
})
|
||||
|
||||
// 接收页面参数
|
||||
onLoad((options) => {
|
||||
if (options?.agentId) {
|
||||
@@ -204,6 +209,7 @@ onMounted(async () => {
|
||||
v-else-if="currentTab === 'voiceprint-management'"
|
||||
ref="voiceprintRef"
|
||||
:agent-id="currentAgentId"
|
||||
@update-refresher-enabled="updateRefresherEnabled"
|
||||
/>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
@@ -70,6 +70,7 @@ async function mergeFunctions() {
|
||||
uni.setStorageSync(`cachedMcpAddress_${agentId.value}`, address)
|
||||
}
|
||||
catch (error) {
|
||||
mcpAddress.value = error
|
||||
console.error('获取MCP地址失败:', error)
|
||||
}
|
||||
|
||||
@@ -310,7 +311,7 @@ onMounted(async () => {
|
||||
v-if="notSelectedList.length === 0"
|
||||
class="h-[400rpx] flex items-center justify-center"
|
||||
>
|
||||
<wd-status-tip image="content" tip="{{ t('agent.tools.noMorePlugins') }}" />
|
||||
<wd-status-tip image="content" :tip="t('agent.tools.noMorePlugins')" />
|
||||
</view>
|
||||
<view v-else class="p-[20rpx] space-y-[20rpx]">
|
||||
<view
|
||||
@@ -346,7 +347,7 @@ onMounted(async () => {
|
||||
v-if="selectedList.length === 0"
|
||||
class="h-[400rpx] flex items-center justify-center"
|
||||
>
|
||||
<wd-status-tip image="content" tip="{{ t('agent.tools.pleaseSelectPlugin') }}" />
|
||||
<wd-status-tip image="content" :tip="t('agent.tools.pleaseSelectPlugin')" />
|
||||
</view>
|
||||
<view v-else class="p-[20rpx] space-y-[20rpx]">
|
||||
<view
|
||||
@@ -444,7 +445,7 @@ onMounted(async () => {
|
||||
<!-- 参数编辑弹窗 -->
|
||||
<wd-action-sheet
|
||||
v-model="showParamDialog"
|
||||
:title="`${t('agent.tools.paramConfiguration')} - ${currentFunction?.name || ''}`"
|
||||
:title="`${t('agent.tools.parameterConfig')} - ${currentFunction?.name || ''}`"
|
||||
custom-header-class="h-[75vh]"
|
||||
@close="closeParamEdit"
|
||||
>
|
||||
@@ -585,3 +586,9 @@ onMounted(async () => {
|
||||
</wd-action-sheet>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
::v-deep .wd-action-sheet__header {
|
||||
padding-right: 30rpx;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -13,9 +13,9 @@ import type { ChatMessage, UserMessageContent } from '@/api/chat-history/types'
|
||||
import { onLoad, onUnload } from '@dcloudio/uni-app'
|
||||
import { computed, ref } from 'vue'
|
||||
import { getAudioId, getChatHistory } from '@/api/chat-history/chat-history'
|
||||
import { getEnvBaseUrl } from '@/utils'
|
||||
import { toast } from '@/utils/toast'
|
||||
import { t } from '@/i18n'
|
||||
import { debounce, getEnvBaseUrl } from '@/utils'
|
||||
import { toast } from '@/utils/toast'
|
||||
|
||||
defineOptions({
|
||||
name: 'ChatDetail',
|
||||
@@ -61,6 +61,7 @@ const loading = ref(false)
|
||||
// 音频播放相关
|
||||
const audioContext = ref<UniApp.InnerAudioContext | null>(null)
|
||||
const playingAudioId = ref<string | null>(null)
|
||||
const expandedToolResults = ref({})
|
||||
|
||||
// 返回上一页
|
||||
function goBack() {
|
||||
@@ -124,12 +125,54 @@ function getSpeakerName(message: ChatMessage): string {
|
||||
|
||||
// 格式化时间
|
||||
function formatTime(timeStr: string) {
|
||||
const date = new Date(timeStr)
|
||||
return `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`
|
||||
if (!timeStr)
|
||||
return t('chatHistory.unknownTime')
|
||||
|
||||
// 处理时间字符串,确保格式正确
|
||||
const date = new Date(timeStr.replace(' ', 'T')) // 转换为ISO格式
|
||||
const now = new Date()
|
||||
|
||||
// 检查日期是否有效
|
||||
if (Number.isNaN(date.getTime())) {
|
||||
return timeStr // 如果解析失败,直接返回原字符串
|
||||
}
|
||||
|
||||
const diff = now.getTime() - date.getTime()
|
||||
|
||||
// 小于1分钟
|
||||
if (diff < 60000)
|
||||
return t('chatHistory.justNow')
|
||||
|
||||
// 小于1小时
|
||||
if (diff < 3600000)
|
||||
return t('chatHistory.minutesAgo', { minutes: Math.floor(diff / 60000) })
|
||||
|
||||
// 小于1天(24小时)
|
||||
if (diff < 86400000)
|
||||
return t('chatHistory.hoursAgo', { hours: Math.floor(diff / 3600000) })
|
||||
|
||||
// 小于7天
|
||||
if (diff < 604800000) {
|
||||
const days = Math.floor(diff / 86400000)
|
||||
return t('chatHistory.daysAgo', { days })
|
||||
}
|
||||
|
||||
// 超过7天,显示具体日期
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const currentYear = now.getFullYear()
|
||||
|
||||
// 如果是当前年份,不显示年份
|
||||
if (year === currentYear) {
|
||||
return `${month}-${day}`
|
||||
}
|
||||
|
||||
return `${year}-${month}-${day}`
|
||||
}
|
||||
|
||||
// 播放音频
|
||||
async function playAudio(audioId: string) {
|
||||
const playAudio = debounce(async (audioId: string) => {
|
||||
if (!audioId) {
|
||||
toast.error(t('chatHistory.invalidAudioId'))
|
||||
return
|
||||
@@ -139,8 +182,11 @@ async function playAudio(audioId: string) {
|
||||
// 如果正在播放其他音频,先停止
|
||||
if (audioContext.value) {
|
||||
audioContext.value.stop()
|
||||
audioContext.value.destroy()
|
||||
audioContext.value = null
|
||||
}
|
||||
// 如果当前音频ID与请求ID相同暂停播放
|
||||
if (playingAudioId.value === audioId) {
|
||||
playingAudioId.value = null
|
||||
return
|
||||
}
|
||||
|
||||
// 获取音频下载ID
|
||||
@@ -151,7 +197,9 @@ async function playAudio(audioId: string) {
|
||||
const audioUrl = `${baseUrl}/agent/play/${downloadId}`
|
||||
|
||||
// 创建音频上下文
|
||||
audioContext.value = uni.createInnerAudioContext()
|
||||
if (!audioContext.value) {
|
||||
audioContext.value = uni.createInnerAudioContext()
|
||||
}
|
||||
audioContext.value.src = audioUrl
|
||||
|
||||
// 设置播放状态
|
||||
@@ -185,13 +233,58 @@ async function playAudio(audioId: string) {
|
||||
toast.error(t('chatHistory.playAudioFailed'))
|
||||
playingAudioId.value = null
|
||||
}
|
||||
}, 400)
|
||||
|
||||
function extractContentFromString(content: string) {
|
||||
if (!content || content.trim() === '') {
|
||||
return content
|
||||
}
|
||||
|
||||
// 尝试解析为 JSON
|
||||
try {
|
||||
const jsonObj = JSON.parse(content)
|
||||
|
||||
// 如果是数组格式(包含 text 和 tool)
|
||||
if (Array.isArray(jsonObj)) {
|
||||
return jsonObj
|
||||
}
|
||||
|
||||
// 如果是对象且有 content 字段
|
||||
if (jsonObj && typeof jsonObj === 'object' && jsonObj.content) {
|
||||
return jsonObj.content
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
// 如果不是有效的 JSON,直接返回原内容
|
||||
}
|
||||
|
||||
// 如果不是 JSON 格式或没有 content 字段,直接返回原内容
|
||||
return content
|
||||
}
|
||||
|
||||
function toggleToolResult(messageIndex, itemIndex) {
|
||||
const key = `${messageIndex}-${itemIndex}`
|
||||
expandedToolResults.value[key] = !expandedToolResults.value[key]
|
||||
}
|
||||
|
||||
function isToolResultCollapsed(messageIndex, itemIndex) {
|
||||
const key = `${messageIndex}-${itemIndex}`
|
||||
// 默认折叠(true表示折叠)
|
||||
return !expandedToolResults.value[key]
|
||||
}
|
||||
|
||||
function getFirstLineText(text: string) {
|
||||
if (!text) {
|
||||
return ''
|
||||
}
|
||||
const firstLine = text.split('\n')[0]
|
||||
return firstLine.length < text.length ? `${firstLine}...` : text
|
||||
}
|
||||
|
||||
onLoad((options) => {
|
||||
if (options?.sessionId && options?.agentId) {
|
||||
sessionId.value = options.sessionId
|
||||
agentId.value = options.agentId
|
||||
loadChatHistory()
|
||||
}
|
||||
else {
|
||||
console.error('缺少必要参数')
|
||||
@@ -199,6 +292,10 @@ onLoad((options) => {
|
||||
}
|
||||
})
|
||||
|
||||
onShow(() => {
|
||||
loadChatHistory()
|
||||
})
|
||||
|
||||
// 页面销毁时清理音频资源
|
||||
onUnload(() => {
|
||||
if (audioContext.value) {
|
||||
@@ -251,6 +348,7 @@ onUnload(() => {
|
||||
:class="{
|
||||
'items-end': message.chatType === 1,
|
||||
'items-start': message.chatType === 2,
|
||||
'tool-message': message.chatType === 3,
|
||||
}"
|
||||
>
|
||||
<!-- 消息气泡 -->
|
||||
@@ -258,11 +356,39 @@ onUnload(() => {
|
||||
class="shadow-message break-words rounded-[20rpx] p-[24rpx] leading-[1.4]"
|
||||
:class="{
|
||||
'bg-[#336cff] text-white': message.chatType === 1,
|
||||
'bg-white text-[#232338] border border-[#eeeeee]': message.chatType === 2,
|
||||
'bg-white text-[#232338] border border-[#eeeeee]': [2, 3].includes(message.chatType),
|
||||
}"
|
||||
>
|
||||
<template v-if="Array.isArray(extractContentFromString(message.content))">
|
||||
<div class="content-wrapper">
|
||||
<div v-for="(item, idx) in extractContentFromString(message.content)" :key="idx">
|
||||
<div v-if="item.type === 'text'" class="text-content">
|
||||
{{ item.text }}
|
||||
</div>
|
||||
<div v-else-if="item.type === 'tool'" class="tool-call-text">
|
||||
{{ item.text }}
|
||||
</div>
|
||||
<div v-else-if="item.type === 'tool_result'" class="tool-call-text">
|
||||
<div v-if="item.text && item.text.length > 80" class="tool-result-wrapper">
|
||||
<div v-if="isToolResultCollapsed(index, idx)" class="tool-result-collapsed">
|
||||
{{ getFirstLineText(item.text) }}
|
||||
</div>
|
||||
<div v-else class="tool-result-expanded">
|
||||
{{ item.text }}
|
||||
</div>
|
||||
<span class="tool-toggle-btn" @click="toggleToolResult(index, idx)">
|
||||
<wd-icon :name="isToolResultCollapsed(index, idx) ? 'arrow-down' : 'arrow-up'" size="12" />
|
||||
</span>
|
||||
</div>
|
||||
<div v-else>
|
||||
{{ item.text }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 内容区域 - 使用flex布局让图标和文本对齐 -->
|
||||
<view class="flex items-center gap-[12rpx]">
|
||||
<view v-else class="flex items-center gap-[12rpx]">
|
||||
<!-- 音频播放图标 -->
|
||||
<view
|
||||
v-if="message.audioId"
|
||||
@@ -329,4 +455,46 @@ onUnload(() => {
|
||||
.animate-pulse-audio {
|
||||
animation: pulse-audio 1.5s infinite;
|
||||
}
|
||||
|
||||
.text-content {
|
||||
display: block;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.tool-call-text {
|
||||
color: #1890ff;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-weight: 500;
|
||||
font-size: 24rpx;
|
||||
display: block;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
.user-message .tool-call-text {
|
||||
color: #e6f7ff;
|
||||
}
|
||||
|
||||
.tool-message .message-content {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.tool-result-wrapper {
|
||||
position: relative;
|
||||
padding-right: 40rpx;
|
||||
}
|
||||
|
||||
.tool-result-collapsed {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tool-toggle-btn {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
cursor: pointer;
|
||||
color: #1890ff;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -3,20 +3,21 @@ import type { ChatSession } from '@/api/chat-history/types'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { getChatSessions } from '@/api/chat-history/chat-history'
|
||||
import { t } from '@/i18n'
|
||||
import { deepClone } from '@/utils'
|
||||
|
||||
defineOptions({
|
||||
name: 'ChatHistory',
|
||||
})
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
agentId: 'default',
|
||||
})
|
||||
|
||||
// 接收props
|
||||
interface Props {
|
||||
agentId?: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
agentId: 'default'
|
||||
})
|
||||
|
||||
// 获取屏幕边界到安全区域距离
|
||||
let safeAreaInsets: any
|
||||
let systemInfo: any
|
||||
@@ -43,7 +44,7 @@ const sessionList = ref<ChatSession[]>([])
|
||||
const loading = ref(false)
|
||||
const loadingMore = ref(false)
|
||||
const hasMore = ref(true)
|
||||
const currentPage = ref(1)
|
||||
const currentPage = ref(0)
|
||||
const pageSize = 10
|
||||
|
||||
// 使用传入的智能体ID
|
||||
@@ -52,10 +53,8 @@ const currentAgentId = computed(() => {
|
||||
})
|
||||
|
||||
// 加载聊天会话列表
|
||||
async function loadChatSessions(page = 1, isRefresh = false) {
|
||||
async function loadChatSessions(page = 1, isUpdate = false) {
|
||||
try {
|
||||
console.log(t('chatHistory.getChatSessions'), { page, isRefresh })
|
||||
|
||||
// 检查是否有当前选中的智能体
|
||||
if (!currentAgentId.value) {
|
||||
console.warn(t('chatHistory.noSelectedAgent'))
|
||||
@@ -76,7 +75,10 @@ async function loadChatSessions(page = 1, isRefresh = false) {
|
||||
})
|
||||
|
||||
if (page === 1) {
|
||||
sessionList.value = response.list || []
|
||||
const oldSessionList = deepClone(sessionList.value)
|
||||
oldSessionList.splice(0, 10)
|
||||
oldSessionList.unshift(...(response.list || []))
|
||||
sessionList.value = isUpdate ? oldSessionList : response.list || []
|
||||
}
|
||||
else {
|
||||
sessionList.value.push(...(response.list || []))
|
||||
@@ -170,10 +172,15 @@ function goToChatDetail(session: ChatSession) {
|
||||
|
||||
onMounted(async () => {
|
||||
// 智能体已简化为默认
|
||||
|
||||
loadChatSessions(1)
|
||||
})
|
||||
|
||||
onShow(() => {
|
||||
if (currentPage.value !== 0) {
|
||||
loadChatSessions(1, true)
|
||||
}
|
||||
})
|
||||
|
||||
// 暴露方法给父组件
|
||||
defineExpose({
|
||||
refresh,
|
||||
@@ -187,8 +194,8 @@ defineExpose({
|
||||
<view v-if="loading && sessionList.length === 0" class="loading-container">
|
||||
<wd-loading color="#336cff" />
|
||||
<text class="loading-text">
|
||||
{{ t('chatHistory.loading') }}
|
||||
</text>
|
||||
{{ t('chatHistory.loading') }}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<!-- 会话列表 -->
|
||||
@@ -205,7 +212,7 @@ defineExpose({
|
||||
<view class="session-info">
|
||||
<view class="session-header">
|
||||
<text class="session-title">
|
||||
{{ t('chatHistory.conversationRecord') }} {{ session.sessionId.substring(0, 8) }}...
|
||||
{{ session.title || `${t('chatHistory.conversationRecord')} ${session.sessionId.substring(0, 8)}...` }}
|
||||
</text>
|
||||
<text class="session-time">
|
||||
{{ formatTime(session.createdAt) }}
|
||||
@@ -242,11 +249,11 @@ defineExpose({
|
||||
<view v-else-if="!loading" class="empty-state">
|
||||
<wd-icon name="chat" custom-class="empty-icon" />
|
||||
<text class="empty-text">
|
||||
{{ t('chatHistory.noChatRecords') }}
|
||||
</text>
|
||||
<text class="empty-desc">
|
||||
{{ t('chatHistory.chatRecordsDescription') }}
|
||||
</text>
|
||||
{{ t('chatHistory.noChatRecords') }}
|
||||
</text>
|
||||
<text class="empty-desc">
|
||||
{{ t('chatHistory.chatRecordsDescription') }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
@@ -333,7 +340,7 @@ defineExpose({
|
||||
padding: 32rpx;
|
||||
|
||||
.session-info {
|
||||
flex: 1;
|
||||
width: 94%;
|
||||
|
||||
.session-header {
|
||||
display: flex;
|
||||
@@ -345,8 +352,11 @@ defineExpose({
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #232338;
|
||||
max-width: 70%;
|
||||
width: 70%;
|
||||
word-break: break-all;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.session-time {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { useToast } from 'wot-design-uni'
|
||||
import { t } from '@/i18n'
|
||||
import { toast } from '@/utils/toast'
|
||||
|
||||
// 类型定义
|
||||
interface WiFiNetwork {
|
||||
@@ -19,9 +19,6 @@ interface Props {
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
// Toast 实例
|
||||
const toast = useToast()
|
||||
|
||||
// 响应式数据
|
||||
const configuring = ref(false)
|
||||
|
||||
@@ -45,7 +42,7 @@ async function checkESP32Connection() {
|
||||
return response.statusCode === 200
|
||||
}
|
||||
catch (error) {
|
||||
console.log(t('deviceConfig.esp32ConnectionCheckFailed') + ':', error)
|
||||
console.log(`${t('deviceConfig.esp32ConnectionCheckFailed')}:`, error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -58,12 +55,12 @@ async function submitConfig() {
|
||||
// 检查ESP32连接
|
||||
const connected = await checkESP32Connection()
|
||||
if (!connected) {
|
||||
toast.error(t('deviceConfig.connectXiaozhiHotspot'))
|
||||
return
|
||||
}
|
||||
toast.error(t('deviceConfig.connectXiaozhiHotspot'))
|
||||
return
|
||||
}
|
||||
|
||||
configuring.value = true
|
||||
console.log(t('deviceConfig.startWifiConfig') + ':', props.selectedNetwork.ssid)
|
||||
console.log(`${t('deviceConfig.startWifiConfig')}:`, props.selectedNetwork.ssid)
|
||||
|
||||
try {
|
||||
const response = await uni.request({
|
||||
@@ -83,6 +80,14 @@ async function submitConfig() {
|
||||
|
||||
if (response.statusCode === 200 && (response.data as any)?.success) {
|
||||
toast.success(`${t('deviceConfig.configSuccess')}!${t('deviceConfig.deviceWillConnectTo')} ${props.selectedNetwork.ssid},${t('deviceConfig.deviceWillRestart')}。${t('deviceConfig.pleaseDisconnectXiaozhiHotspot')}`)
|
||||
// 设备退出配网模式
|
||||
setTimeout(() => {
|
||||
uni.request({
|
||||
url: 'http://192.168.4.1/exit',
|
||||
method: 'POST',
|
||||
timeout: 15000,
|
||||
})
|
||||
}, 1500)
|
||||
}
|
||||
else {
|
||||
const errorMsg = (response.data as any)?.error || t('deviceConfig.configFailed')
|
||||
@@ -90,8 +95,8 @@ async function submitConfig() {
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error(t('deviceConfig.wifiConfigFailed') + ':', error)
|
||||
toast.error(`${t('deviceConfig.configFailed')},${t('deviceConfig.pleaseCheckNetworkConnection')}`)
|
||||
console.error(`${t('deviceConfig.wifiConfigFailed')}:`, error)
|
||||
toast.error(`${t('deviceConfig.configFailed')},${t('deviceConfig.pleaseCheckNetworkConnection')}`)
|
||||
}
|
||||
finally {
|
||||
configuring.value = false
|
||||
@@ -134,9 +139,9 @@ async function submitConfig() {
|
||||
|
||||
<!-- 使用说明 -->
|
||||
<view class="help-section">
|
||||
<view class="help-title">
|
||||
{{ t('deviceConfig.wifiConfigInstructions') }}
|
||||
</view>
|
||||
<view class="help-title">
|
||||
{{ t('deviceConfig.wifiConfigInstructions') }}
|
||||
</view>
|
||||
<view class="help-content">
|
||||
<text class="help-item">
|
||||
1. {{ t('deviceConfig.phoneConnectXiaozhiHotspot') }} (xiaozhi-XXXXXX)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, defineEmits, defineExpose, onMounted, ref } from 'vue'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useToast } from 'wot-design-uni'
|
||||
import { t } from '@/i18n'
|
||||
|
||||
@@ -71,9 +71,9 @@ async function checkESP32Connection() {
|
||||
// 扫描WiFi网络
|
||||
async function scanWifi() {
|
||||
if (!isConnectedToESP32.value) {
|
||||
toast.error(t('deviceConfig.connectXiaozhiHotspot'))
|
||||
return
|
||||
}
|
||||
toast.error(t('deviceConfig.connectXiaozhiHotspot'))
|
||||
return
|
||||
}
|
||||
|
||||
scanning.value = true
|
||||
console.log('开始扫描WiFi网络')
|
||||
@@ -85,7 +85,7 @@ async function scanWifi() {
|
||||
timeout: 10000,
|
||||
})
|
||||
|
||||
console.log(t('deviceConfig.wifiScanResponse') + ':', response)
|
||||
console.log(`${t('deviceConfig.wifiScanResponse')}:`, response)
|
||||
|
||||
if (response.statusCode === 200 && response.data) {
|
||||
const data = response.data as any
|
||||
@@ -93,6 +93,16 @@ async function scanWifi() {
|
||||
wifiNetworks.value = data.networks
|
||||
console.log(`${t('deviceConfig.scanSuccess')},发现 ${data.networks.length} ${t('deviceConfig.networks')}`)
|
||||
}
|
||||
else if (data.aps && Array.isArray(data.aps)) {
|
||||
// 兼容 { data: { support_5g, aps } } 格式
|
||||
wifiNetworks.value = data.aps.map((item: any) => ({
|
||||
ssid: item.ssid,
|
||||
rssi: item.rssi,
|
||||
authmode: item.authmode,
|
||||
channel: item.channel || 0,
|
||||
}))
|
||||
console.log(`${t('deviceConfig.scanSuccess')},发现 ${data.aps.length} ${t('deviceConfig.networks')}`)
|
||||
}
|
||||
else if (Array.isArray(response.data)) {
|
||||
// 兼容旧格式
|
||||
wifiNetworks.value = response.data.map((item: any) => ({
|
||||
@@ -111,8 +121,8 @@ async function scanWifi() {
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error(t('deviceConfig.wifiScanFailed') + ':', error)
|
||||
toast.error(t('deviceConfig.scanFailedCheckConnection'))
|
||||
console.error(`${t('deviceConfig.wifiScanFailed')}:`, error)
|
||||
toast.error(t('deviceConfig.scanFailedCheckConnection'))
|
||||
}
|
||||
finally {
|
||||
scanning.value = false
|
||||
@@ -125,9 +135,9 @@ async function showNetworkSelector() {
|
||||
await checkESP32Connection()
|
||||
|
||||
if (!isConnectedToESP32.value) {
|
||||
toast.error(t('deviceConfig.connectXiaozhiHotspot'))
|
||||
return
|
||||
}
|
||||
toast.error(t('deviceConfig.connectXiaozhiHotspot'))
|
||||
return
|
||||
}
|
||||
|
||||
selectorExpanded.value = true
|
||||
|
||||
@@ -213,73 +223,73 @@ onMounted(() => {
|
||||
<!-- Xiaozhi连接状态 -->
|
||||
<view v-if="props.autoConnect" class="connection-status">
|
||||
<view v-if="!isConnectedToESP32" class="status-warning">
|
||||
<view class="status-content">
|
||||
<text class="warning-text">
|
||||
{{ t('deviceConfig.connectXiaozhiHotspot') }} (xiaozhi-XXXXXX)
|
||||
</text>
|
||||
<wd-button
|
||||
size="small"
|
||||
type="primary"
|
||||
:loading="checkingConnection"
|
||||
@click="checkESP32Connection"
|
||||
>
|
||||
{{ checkingConnection ? t('deviceConfig.checking') : t('deviceConfig.reCheck') }}
|
||||
</wd-button>
|
||||
</view>
|
||||
<view class="status-content">
|
||||
<text class="warning-text">
|
||||
{{ t('deviceConfig.connectXiaozhiHotspot') }} (xiaozhi-XXXXXX)
|
||||
</text>
|
||||
<wd-button
|
||||
size="small"
|
||||
type="primary"
|
||||
:loading="checkingConnection"
|
||||
@click="checkESP32Connection"
|
||||
>
|
||||
{{ checkingConnection ? t('deviceConfig.checking') : t('deviceConfig.reCheck') }}
|
||||
</wd-button>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="status-success">
|
||||
<view class="status-content">
|
||||
<text class="success-text">
|
||||
{{ t('deviceConfig.connectedXiaozhiHotspot') }}
|
||||
</text>
|
||||
<wd-button
|
||||
size="small"
|
||||
:loading="checkingConnection"
|
||||
@click="checkESP32Connection"
|
||||
>
|
||||
{{ checkingConnection ? t('deviceConfig.checking') : t('deviceConfig.refreshStatus') }}
|
||||
</wd-button>
|
||||
</view>
|
||||
<view class="status-content">
|
||||
<text class="success-text">
|
||||
{{ t('deviceConfig.connectedXiaozhiHotspot') }}
|
||||
</text>
|
||||
<wd-button
|
||||
size="small"
|
||||
:loading="checkingConnection"
|
||||
@click="checkESP32Connection"
|
||||
>
|
||||
{{ checkingConnection ? t('deviceConfig.checking') : t('deviceConfig.refreshStatus') }}
|
||||
</wd-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- WiFi网络选择器 -->
|
||||
<view class="network-selector">
|
||||
<view class="selector-item" @click="showNetworkSelector">
|
||||
<text class="selector-label">
|
||||
{{ t('deviceConfig.wifiNetwork') }}
|
||||
</text>
|
||||
<text class="selector-value">
|
||||
{{ networkDisplayText }}
|
||||
</text>
|
||||
<wd-icon name="arrow-right" custom-class="arrow-icon" />
|
||||
</view>
|
||||
<view class="selector-item" @click="showNetworkSelector">
|
||||
<text class="selector-label">
|
||||
{{ t('deviceConfig.wifiNetwork') }}
|
||||
</text>
|
||||
<text class="selector-value">
|
||||
{{ networkDisplayText }}
|
||||
</text>
|
||||
<wd-icon name="arrow-right" custom-class="arrow-icon" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 展开的网络列表 -->
|
||||
<view v-if="selectorExpanded" class="network-list-overlay">
|
||||
<view class="network-list-container">
|
||||
<view class="list-header">
|
||||
<text class="list-title">
|
||||
{{ t('deviceConfig.selectWifiNetwork') }}
|
||||
</text>
|
||||
<view class="list-actions">
|
||||
<wd-button
|
||||
type="primary"
|
||||
size="small"
|
||||
:loading="scanning"
|
||||
@click="scanWifi"
|
||||
>
|
||||
{{ scanning ? t('deviceConfig.scanning') : t('deviceConfig.refreshScan') }}
|
||||
</wd-button>
|
||||
<wd-button
|
||||
size="small"
|
||||
@click="selectorExpanded = false"
|
||||
>
|
||||
{{ t('deviceConfig.cancel') }}
|
||||
</wd-button>
|
||||
</view>
|
||||
<text class="list-title">
|
||||
{{ t('deviceConfig.selectWifiNetwork') }}
|
||||
</text>
|
||||
<view class="list-actions">
|
||||
<wd-button
|
||||
type="primary"
|
||||
size="small"
|
||||
:loading="scanning"
|
||||
@click="scanWifi"
|
||||
>
|
||||
{{ scanning ? t('deviceConfig.scanning') : t('deviceConfig.refreshScan') }}
|
||||
</wd-button>
|
||||
<wd-button
|
||||
size="small"
|
||||
@click="selectorExpanded = false"
|
||||
>
|
||||
{{ t('deviceConfig.cancel') }}
|
||||
</wd-button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="network-list">
|
||||
<view v-if="wifiNetworks.length === 0 && !scanning" class="empty-state">
|
||||
@@ -313,7 +323,7 @@ onMounted(() => {
|
||||
</view>
|
||||
<view class="wifi-security">
|
||||
<text class="security-icon">
|
||||
{{ network.authmode === 0 ? t('deviceConfig.open') : t('deviceConfig.encrypted') }}
|
||||
{{ network.authmode === 0 ? t('deviceConfig.open') : t('deviceConfig.encryptedNetwork') }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
@@ -325,17 +335,17 @@ onMounted(() => {
|
||||
<!-- 密码输入 -->
|
||||
<view v-if="selectedNetwork && selectedNetwork.authmode > 0" class="password-section">
|
||||
<view class="password-item">
|
||||
<text class="password-label">
|
||||
{{ t('deviceConfig.networkPassword') }}
|
||||
</text>
|
||||
<wd-input
|
||||
v-model="password"
|
||||
:placeholder="t('deviceConfig.enterWifiPassword')"
|
||||
show-password
|
||||
clearable
|
||||
@input="onPasswordChange"
|
||||
/>
|
||||
</view>
|
||||
<text class="password-label">
|
||||
{{ t('deviceConfig.wifiPassword') }}
|
||||
</text>
|
||||
<wd-input
|
||||
v-model="password"
|
||||
:placeholder="t('deviceConfig.enterWifiPassword')"
|
||||
show-password
|
||||
clearable
|
||||
@input="onPasswordChange"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -125,7 +125,7 @@ function openCreateDialog() {
|
||||
msg: '',
|
||||
inputPlaceholder: t('home.inputPlaceholder'),
|
||||
inputValue: '',
|
||||
inputPattern: /^[\u4E00-\u9FA5a-z0-9\s]{1,50}$/i,
|
||||
inputPattern: /^.{1,64}$/i,
|
||||
inputError: t('home.createError'),
|
||||
confirmButtonText: t('home.createNow'),
|
||||
cancelButtonText: t('common.cancel'),
|
||||
@@ -159,7 +159,7 @@ function formatTime(timeStr: string) {
|
||||
onShow(() => {
|
||||
console.log('首页 onShow,刷新智能体列表')
|
||||
if (pagingRef.value) {
|
||||
pagingRef.value.reload()
|
||||
pagingRef.value.refresh()
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import { computed, onMounted, ref } from 'vue'
|
||||
import { login } from '@/api/auth'
|
||||
// 导入国际化相关功能
|
||||
import { changeLanguage, getCurrentLanguage, getSupportedLanguages, initI18n, t } from '@/i18n'
|
||||
import { useConfigStore } from '@/store'
|
||||
import { useConfigStore, useUserStore } from '@/store'
|
||||
// 导入SM2加密工具
|
||||
import { getEnvBaseUrl, sm2Encrypt } from '@/utils'
|
||||
import { toast } from '@/utils/toast'
|
||||
@@ -61,6 +61,7 @@ const loginType = ref<'username' | 'mobile'>('username')
|
||||
|
||||
// 获取配置store
|
||||
const configStore = useConfigStore()
|
||||
const userStore = useUserStore()
|
||||
|
||||
// 区号选择相关
|
||||
const showAreaCodeSheet = ref(false)
|
||||
@@ -227,6 +228,7 @@ async function handleLogin() {
|
||||
const response = await login(loginData)
|
||||
// 存储token
|
||||
uni.setStorageSync('token', JSON.stringify(response))
|
||||
await userStore.getUserInfo()
|
||||
|
||||
toast.success(t('message.loginSuccess'))
|
||||
|
||||
@@ -581,7 +583,6 @@ onMounted(async () => {
|
||||
|
||||
.input-wrapper {
|
||||
position: relative;
|
||||
background: #f8f9fa;
|
||||
border-radius: 16rpx;
|
||||
padding: 20rpx 16rpx;
|
||||
border: 2rpx solid #e9ecef;
|
||||
|
||||
@@ -127,10 +127,10 @@ async function saveServerBaseUrl() {
|
||||
uni.request({
|
||||
url: `${getEnvBaseUrl()}/user/pub-config`,
|
||||
method: 'GET',
|
||||
success: (res) => {
|
||||
success: (res: any) => {
|
||||
if (res.statusCode === 200) {
|
||||
configStore.setConfig(res.data.data)
|
||||
uni.setStorageSync('config', res.data.data.sm2PubKey)
|
||||
uni.setStorageSync('config', res.data.data)
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
@@ -165,6 +165,7 @@ const showLanguageSheet = ref(false)
|
||||
function handleLanguageChange(lang: Language) {
|
||||
changeLanguage(lang)
|
||||
showLanguageSheet.value = false
|
||||
currentLanguage.value = lang
|
||||
toast.success(t('settings.languageChanged'))
|
||||
}
|
||||
|
||||
@@ -225,7 +226,8 @@ function clearAllCacheAfterUrlChange() {
|
||||
|
||||
// 重新获取缓存信息
|
||||
getCacheInfo()
|
||||
} catch (error) {
|
||||
}
|
||||
catch (error) {
|
||||
console.error('清除缓存失败:', error)
|
||||
}
|
||||
}
|
||||
@@ -250,7 +252,8 @@ async function clearCache() {
|
||||
}
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
}
|
||||
catch (error) {
|
||||
console.error('清除缓存失败:', error)
|
||||
toast.error(t('settings.clearCacheFailed'))
|
||||
}
|
||||
@@ -262,7 +265,7 @@ function showAbout() {
|
||||
title: t('settings.aboutApp', { appName: import.meta.env.VITE_APP_TITLE }),
|
||||
content: t('settings.aboutContent', {
|
||||
appName: import.meta.env.VITE_APP_TITLE,
|
||||
version: '0.9.2'
|
||||
version: '0.9.3'
|
||||
}),
|
||||
showCancel: false,
|
||||
confirmText: t('common.confirm'),
|
||||
@@ -278,7 +281,7 @@ onMounted(async () => {
|
||||
|
||||
// 动态设置导航栏标题为国际化文本
|
||||
uni.setNavigationBarTitle({
|
||||
title: t('settings.title')
|
||||
title: t('settings.title'),
|
||||
})
|
||||
})
|
||||
</script>
|
||||
@@ -296,8 +299,10 @@ onMounted(async () => {
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="border border-[#eeeeee] rounded-[24rpx] bg-[#fbfbfb] p-[32rpx] overflow-hidden"
|
||||
style="box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);">
|
||||
<view
|
||||
class="overflow-hidden border border-[#eeeeee] rounded-[24rpx] bg-[#fbfbfb] p-[32rpx]"
|
||||
style="box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);"
|
||||
>
|
||||
<view class="mb-[24rpx]">
|
||||
<text class="text-[28rpx] text-[#232338] font-semibold">
|
||||
{{ t('settings.serverApiUrl') }}
|
||||
@@ -308,11 +313,13 @@ onMounted(async () => {
|
||||
</view>
|
||||
|
||||
<view class="mb-[24rpx]">
|
||||
<view class="w-full rounded-[16rpx] border border-[#eeeeee] bg-[#f5f7fb] overflow-hidden">
|
||||
<wd-input v-model="baseUrlInput" type="text" clearable :maxlength="200"
|
||||
<view class="w-full overflow-hidden border border-[#eeeeee] rounded-[16rpx] bg-[#f5f7fb]">
|
||||
<wd-input
|
||||
v-model="baseUrlInput" type="text" clearable :maxlength="200"
|
||||
:placeholder="t('settings.enterServerUrl')"
|
||||
custom-class="!border-none !bg-transparent h-[64rpx] px-[24rpx] items-center"
|
||||
input-class="text-[28rpx] text-[#232338]" @input="validateUrl" @blur="validateUrl" />
|
||||
input-class="text-[28rpx] text-[#232338]" @input="validateUrl" @blur="validateUrl"
|
||||
/>
|
||||
</view>
|
||||
<text v-if="urlError" class="mt-[8rpx] block text-[24rpx] text-[#ff4d4f]">
|
||||
{{ urlError }}
|
||||
@@ -320,14 +327,18 @@ onMounted(async () => {
|
||||
</view>
|
||||
|
||||
<view class="flex gap-[16rpx]">
|
||||
<wd-button type="primary"
|
||||
<wd-button
|
||||
type="primary"
|
||||
custom-class="flex-1 h-[88rpx] rounded-[20rpx] text-[28rpx] font-semibold bg-[#336cff] border-none shadow-[0_4rpx_16rpx_rgba(51,108,255,0.3)] active:shadow-[0_2rpx_8rpx_rgba(51,108,255,0.4)] active:scale-98"
|
||||
@click="saveServerBaseUrl">
|
||||
@click="saveServerBaseUrl"
|
||||
>
|
||||
{{ t('settings.saveSettings') }}
|
||||
</wd-button>
|
||||
<wd-button type="default"
|
||||
<wd-button
|
||||
type="default"
|
||||
custom-class="flex-1 h-[88rpx] rounded-[20rpx] text-[28rpx] font-semibold bg-white border-[#eeeeee] text-[#65686f] active:bg-[#f5f7fb]"
|
||||
@click="resetServerBaseUrl">
|
||||
@click="resetServerBaseUrl"
|
||||
>
|
||||
{{ t('settings.resetDefault') }}
|
||||
</wd-button>
|
||||
</view>
|
||||
@@ -342,12 +353,15 @@ onMounted(async () => {
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="border border-[#eeeeee] rounded-[24rpx] bg-[#fbfbfb] p-[32rpx]"
|
||||
style="box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);">
|
||||
<view
|
||||
class="border border-[#eeeeee] rounded-[24rpx] bg-[#fbfbfb] p-[32rpx]"
|
||||
style="box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);"
|
||||
>
|
||||
<view class="space-y-[16rpx]">
|
||||
<!-- 缓存信息展示,参考插件样式 -->
|
||||
<view
|
||||
class="flex items-center justify-between border border-[#eeeeee] rounded-[16rpx] bg-[#f5f7fb] p-[24rpx] transition-all active:bg-[#eef3ff]">
|
||||
class="flex items-center justify-between border border-[#eeeeee] rounded-[16rpx] bg-[#f5f7fb] p-[24rpx] transition-all active:bg-[#eef3ff]"
|
||||
>
|
||||
<view>
|
||||
<text class="text-[28rpx] text-[#232338] font-medium">
|
||||
{{ t('settings.totalCacheSize') }}
|
||||
@@ -363,7 +377,8 @@ onMounted(async () => {
|
||||
|
||||
<!-- 清除缓存按钮,参考插件编辑按钮样式 -->
|
||||
<view
|
||||
class="flex items-center justify-between border border-[#eeeeee] rounded-[16rpx] bg-[#f5f7fb] p-[24rpx]">
|
||||
class="flex items-center justify-between border border-[#eeeeee] rounded-[16rpx] bg-[#f5f7fb] p-[24rpx]"
|
||||
>
|
||||
<view>
|
||||
<text class="text-[28rpx] text-[#232338] font-medium">
|
||||
{{ t('settings.cacheClear') }}
|
||||
@@ -374,7 +389,8 @@ onMounted(async () => {
|
||||
</view>
|
||||
<view
|
||||
class="cursor-pointer rounded-[24rpx] bg-[rgba(255,107,107,0.1)] px-[28rpx] py-[16rpx] text-[24rpx] text-[#ff6b6b] font-semibold transition-all duration-300 active:scale-95 active:bg-[#ff6b6b] active:text-white"
|
||||
@click="clearCache">
|
||||
@click="clearCache"
|
||||
>
|
||||
{{ t('settings.clearCache') }}
|
||||
</view>
|
||||
</view>
|
||||
@@ -390,11 +406,14 @@ onMounted(async () => {
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="border border-[#eeeeee] rounded-[24rpx] bg-[#fbfbfb] p-[32rpx]"
|
||||
style="box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);">
|
||||
<view
|
||||
class="border border-[#eeeeee] rounded-[24rpx] bg-[#fbfbfb] p-[32rpx]"
|
||||
style="box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);"
|
||||
>
|
||||
<view
|
||||
class="flex cursor-pointer items-center justify-between border border-[#eeeeee] rounded-[16rpx] bg-[#f5f7fb] p-[24rpx] transition-all active:bg-[#eef3ff]"
|
||||
@click="showAbout">
|
||||
@click="showAbout"
|
||||
>
|
||||
<view>
|
||||
<text class="text-[28rpx] text-[#232338] font-medium">
|
||||
{{ t('settings.aboutUs') }}
|
||||
@@ -416,11 +435,14 @@ onMounted(async () => {
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="border border-[#eeeeee] rounded-[24rpx] bg-[#fbfbfb] p-[32rpx]"
|
||||
style="box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);">
|
||||
<view
|
||||
class="border border-[#eeeeee] rounded-[24rpx] bg-[#fbfbfb] p-[32rpx]"
|
||||
style="box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);"
|
||||
>
|
||||
<view
|
||||
class="flex cursor-pointer items-center justify-between border border-[#eeeeee] rounded-[16rpx] bg-[#f5f7fb] p-[24rpx] transition-all active:bg-[#eef3ff]"
|
||||
@click="showLanguageSheet = true">
|
||||
@click="showLanguageSheet = true"
|
||||
>
|
||||
<view>
|
||||
<text class="text-[32rpx] text-[#232338] font-medium">
|
||||
{{ t('settings.language') }}
|
||||
@@ -430,8 +452,8 @@ onMounted(async () => {
|
||||
</text>
|
||||
</view>
|
||||
<view class="flex items-center">
|
||||
<text class="text-[32rpx] text-[#9d9ea3] font-semibold mr-[16rpx]">
|
||||
{{supportedLanguages.find(lang => lang.code === currentLanguage)?.name}}
|
||||
<text class="mr-[16rpx] text-[32rpx] text-[#9d9ea3] font-semibold">
|
||||
{{ supportedLanguages.find(lang => lang.code === currentLanguage)?.name }}
|
||||
</text>
|
||||
<wd-icon name="arrow-right" custom-class="text-[32rpx] text-[#9d9ea3]" />
|
||||
</view>
|
||||
@@ -443,8 +465,10 @@ onMounted(async () => {
|
||||
<wd-action-sheet v-model="showLanguageSheet" :title="t('settings.selectLanguage')" :close-on-click-modal="true">
|
||||
<view class="language-sheet">
|
||||
<scroll-view scroll-y class="language-list">
|
||||
<view v-for="lang in supportedLanguages" :key="lang.code" class="language-item"
|
||||
@click="handleLanguageChange(lang.code)">
|
||||
<view
|
||||
v-for="lang in supportedLanguages" :key="lang.code" class="language-item"
|
||||
@click="handleLanguageChange(lang.code)"
|
||||
>
|
||||
<text class="language-name">
|
||||
{{ lang.name }}
|
||||
</text>
|
||||
|
||||
@@ -3,22 +3,25 @@ import type { ChatHistory, CreateSpeakerData, VoicePrint } from '@/api/voiceprin
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useMessage } from 'wot-design-uni'
|
||||
import { useToast } from 'wot-design-uni/components/wd-toast'
|
||||
import { createVoicePrint, deleteVoicePrint, getChatHistory, getVoicePrintList, updateVoicePrint } from '@/api/voiceprint'
|
||||
import { createVoicePrint, deleteVoicePrint, getAudioDownloadId, getChatHistory, getVoicePrintList, updateVoicePrint } from '@/api/voiceprint'
|
||||
import { t } from '@/i18n'
|
||||
import { getEnvBaseUrl } from '@/utils'
|
||||
|
||||
defineOptions({
|
||||
name: 'VoicePrintManage',
|
||||
})
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
agentId: 'default',
|
||||
})
|
||||
|
||||
const emits = defineEmits(['update-refresher-enabled'])
|
||||
|
||||
// 接收props
|
||||
interface Props {
|
||||
agentId?: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
agentId: 'default'
|
||||
})
|
||||
|
||||
// 获取屏幕边界到安全区域距离
|
||||
let safeAreaInsets: any
|
||||
let systemInfo: any
|
||||
@@ -50,6 +53,10 @@ const chatHistoryActions = ref<any[]>([])
|
||||
const swipeStates = ref<Record<string, 'left' | 'close' | 'right'>>({})
|
||||
const loading = ref(false)
|
||||
|
||||
// 音频播放相关
|
||||
const audioRef = ref<UniApp.InnerAudioContext | null>(null)
|
||||
const playingAudioId = ref<string>('')
|
||||
|
||||
// 使用传入的智能体ID
|
||||
const currentAgentId = computed(() => {
|
||||
return props.agentId
|
||||
@@ -130,7 +137,6 @@ async function loadChatHistory() {
|
||||
audioId: item.audioId,
|
||||
index,
|
||||
}))
|
||||
showChatHistoryDialog.value = true
|
||||
}
|
||||
catch (error) {
|
||||
console.error('获取对话记录失败:', error)
|
||||
@@ -157,11 +163,13 @@ function openAddDialog() {
|
||||
introduce: '',
|
||||
}
|
||||
showAddDialog.value = true
|
||||
} catch (error: any) {
|
||||
}
|
||||
catch (error: any) {
|
||||
// 捕捉声纹接口未配置错误
|
||||
if (error.message && error.message.includes('请求错误[10054]')) {
|
||||
toast.error(t('voiceprint.voiceprintInterfaceNotConfigured'))
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
// 其他错误,继续打开弹窗
|
||||
addForm.value = {
|
||||
agentId: currentAgentId.value,
|
||||
@@ -202,6 +210,11 @@ function selectAudioId({ item }: { item: any }) {
|
||||
showChatHistoryDialog.value = false
|
||||
}
|
||||
|
||||
// 点击选择
|
||||
function handleItemClick(item: any) {
|
||||
selectAudioId({ item })
|
||||
}
|
||||
|
||||
// 提交添加说话人
|
||||
async function submitAdd() {
|
||||
if (!addForm.value.sourceName.trim()) {
|
||||
@@ -274,14 +287,93 @@ async function handleDelete(id: string) {
|
||||
})
|
||||
}
|
||||
|
||||
// 播放音频
|
||||
async function playAudio(audioId: string, event: Event) {
|
||||
event.stopPropagation() // 阻止事件冒泡,防止关闭下拉框
|
||||
|
||||
if (!audioId) {
|
||||
toast.warning(t('voiceprint.audioNotExist'))
|
||||
return
|
||||
}
|
||||
|
||||
// 如果正在播放同一个音频,则停止
|
||||
if (playingAudioId.value === audioId) {
|
||||
stopAudio()
|
||||
return
|
||||
}
|
||||
|
||||
// 停止之前的音频
|
||||
stopAudio()
|
||||
|
||||
try {
|
||||
// 先获取音频下载ID
|
||||
playingAudioId.value = audioId
|
||||
const downloadId = await getAudioDownloadId(audioId)
|
||||
|
||||
if (!downloadId) {
|
||||
toast.error(t('voiceprint.getAudioFailed'))
|
||||
playingAudioId.value = ''
|
||||
return
|
||||
}
|
||||
|
||||
// 获取baseURL
|
||||
const baseURL = getEnvBaseUrl()
|
||||
const audioUrl = `${baseURL}/agent/play/${downloadId}`
|
||||
|
||||
// 创建新的音频实例
|
||||
audioRef.value = uni.createInnerAudioContext()
|
||||
audioRef.value.src = audioUrl
|
||||
audioRef.value.autoplay = true
|
||||
|
||||
// 监听播放结束
|
||||
audioRef.value.onEnded(() => {
|
||||
playingAudioId.value = ''
|
||||
})
|
||||
|
||||
// 监听播放错误
|
||||
audioRef.value.onError((error) => {
|
||||
console.error('音频播放错误:', error)
|
||||
toast.error(t('voiceprint.audioPlayFailed'))
|
||||
playingAudioId.value = ''
|
||||
})
|
||||
}
|
||||
catch (error) {
|
||||
console.error('播放音频失败:', error)
|
||||
toast.error(t('voiceprint.audioPlayFailed'))
|
||||
playingAudioId.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
// 停止音频
|
||||
function stopAudio() {
|
||||
if (audioRef.value) {
|
||||
audioRef.value.stop()
|
||||
audioRef.value.destroy()
|
||||
audioRef.value = null
|
||||
}
|
||||
playingAudioId.value = ''
|
||||
}
|
||||
|
||||
watch(() => [showAddDialog.value, showEditDialog.value], (newValues) => {
|
||||
if (newValues.some((value: boolean) => value)) {
|
||||
emits('update-refresher-enabled', false)
|
||||
}
|
||||
else {
|
||||
emits('update-refresher-enabled', true)
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
// 智能体已简化为默认
|
||||
|
||||
loadVoicePrintList()
|
||||
loadChatHistory()
|
||||
})
|
||||
|
||||
// 暴露方法给父组件
|
||||
defineExpose({
|
||||
showAddDialog,
|
||||
showEditDialog,
|
||||
refresh,
|
||||
})
|
||||
</script>
|
||||
@@ -349,7 +441,7 @@ defineExpose({
|
||||
</view>
|
||||
|
||||
<!-- 浮动操作按钮 -->
|
||||
<wd-fab type="primary" size="small" :draggable="true" :expandable="false" @click="openAddDialog">
|
||||
<wd-fab custom-style="z-index:10" type="primary" size="small" :draggable="true" :expandable="false" @click="openAddDialog">
|
||||
<wd-icon name="add" />
|
||||
</wd-fab>
|
||||
|
||||
@@ -359,25 +451,24 @@ defineExpose({
|
||||
|
||||
<!-- 添加说话人弹窗 -->
|
||||
<wd-popup
|
||||
v-model="showAddDialog" position="center" custom-style="width: 90%; max-width: 400px; border-radius: 16px;"
|
||||
v-model="showAddDialog"
|
||||
position="center"
|
||||
custom-style="width: 90%; max-width: 400px; border-radius: 16px;"
|
||||
safe-area-inset-bottom
|
||||
>
|
||||
<view>
|
||||
<view class="w-full flex items-center justify-between border-b-[2rpx] border-[#eeeeee] p-[32rpx_32rpx_24rpx]">
|
||||
<text class="w-full text-center text-[32rpx] text-[#232338] font-semibold">
|
||||
{{ t('voiceprint.addSpeaker') }}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="p-[32rpx]">
|
||||
<!-- 声纹向量选择 -->
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="mb-[16rpx] block text-[28rpx] text-[#232338] font-medium">
|
||||
* {{ t('voiceprint.voiceVector') }}
|
||||
<text class="text-red">
|
||||
*
|
||||
</text>
|
||||
{{ t('voiceprint.voiceVector') }}
|
||||
</text>
|
||||
<view
|
||||
class="flex cursor-pointer items-center justify-between border-[1rpx] border-[#eeeeee] rounded-[12rpx] bg-[#f5f7fb] p-[20rpx] transition-all duration-300 active:bg-[#eef3ff]"
|
||||
@click="loadChatHistory"
|
||||
@click="showChatHistoryDialog = true"
|
||||
>
|
||||
<text
|
||||
class="m-r-[16rpx] flex-1 text-left text-[26rpx] text-[#232338]"
|
||||
@@ -392,7 +483,10 @@ defineExpose({
|
||||
<!-- 姓名 -->
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="mb-[16rpx] block text-[28rpx] text-[#232338] font-medium">
|
||||
* {{ t('voiceprint.name') }}
|
||||
<text class="text-red">
|
||||
*
|
||||
</text>
|
||||
{{ t('voiceprint.name') }}
|
||||
</text>
|
||||
<input
|
||||
v-model="addForm.sourceName"
|
||||
@@ -404,7 +498,10 @@ defineExpose({
|
||||
<!-- 描述 -->
|
||||
<view>
|
||||
<text class="mb-[16rpx] block text-[28rpx] text-[#232338] font-medium">
|
||||
* {{ t('voiceprint.description') }}
|
||||
<text class="text-red">
|
||||
*
|
||||
</text>
|
||||
{{ t('voiceprint.description') }}
|
||||
</text>
|
||||
<textarea
|
||||
v-model="addForm.introduce" :maxlength="100" :placeholder="t('voiceprint.pleaseInputDescription')"
|
||||
@@ -418,11 +515,11 @@ defineExpose({
|
||||
|
||||
<view class="flex gap-[16rpx] border-t-[2rpx] border-[#eeeeee] p-[24rpx_32rpx_32rpx]">
|
||||
<wd-button type="info" custom-class="flex-1" @click="showAddDialog = false">
|
||||
{{ t('voiceprint.cancel') }}
|
||||
</wd-button>
|
||||
<wd-button type="primary" custom-class="flex-1" @click="submitAdd">
|
||||
{{ t('voiceprint.save') }}
|
||||
</wd-button>
|
||||
{{ t('voiceprint.cancel') }}
|
||||
</wd-button>
|
||||
<wd-button type="primary" custom-class="flex-1" @click="submitAdd">
|
||||
{{ t('voiceprint.save') }}
|
||||
</wd-button>
|
||||
</view>
|
||||
</view>
|
||||
</wd-popup>
|
||||
@@ -433,7 +530,7 @@ defineExpose({
|
||||
safe-area-inset-bottom
|
||||
>
|
||||
<view>
|
||||
<view class="w-full flex items-center justify-between border-b-[2rpx] border-[#eeeeee] p-[32rpx_32rpx_24rpx]">
|
||||
<view class="box-border w-full flex items-center justify-between border-b-[2rpx] border-[#eeeeee] p-[32rpx_32rpx_24rpx]">
|
||||
<text class="w-full text-center text-[32rpx] text-[#232338] font-semibold">
|
||||
{{ t('voiceprint.editSpeaker') }}
|
||||
</text>
|
||||
@@ -443,11 +540,14 @@ defineExpose({
|
||||
<!-- 声纹向量选择 -->
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="mb-[16rpx] block text-[28rpx] text-[#232338] font-medium">
|
||||
* {{ t('voiceprint.voiceVector') }}
|
||||
<text class="text-red">
|
||||
*
|
||||
</text>
|
||||
{{ t('voiceprint.voiceVector') }}
|
||||
</text>
|
||||
<view
|
||||
class="flex cursor-pointer items-center justify-between border-[1rpx] border-[#eeeeee] rounded-[12rpx] bg-[#f5f7fb] p-[20rpx] transition-all duration-300 active:bg-[#eef3ff]"
|
||||
@click="loadChatHistory"
|
||||
@click="showChatHistoryDialog = true"
|
||||
>
|
||||
<text
|
||||
class="m-r-[16rpx] flex-1 text-left text-[26rpx] text-[#232338]"
|
||||
@@ -462,7 +562,10 @@ defineExpose({
|
||||
<!-- 姓名 -->
|
||||
<view class="mb-[32rpx]">
|
||||
<text class="mb-[16rpx] block text-[28rpx] text-[#232338] font-medium">
|
||||
* {{ t('voiceprint.name') }}
|
||||
<text class="text-red">
|
||||
*
|
||||
</text>
|
||||
{{ t('voiceprint.name') }}
|
||||
</text>
|
||||
<input
|
||||
v-model="editForm.sourceName"
|
||||
@@ -474,7 +577,10 @@ defineExpose({
|
||||
<!-- 描述 -->
|
||||
<view>
|
||||
<text class="mb-[16rpx] block text-[28rpx] text-[#232338] font-medium">
|
||||
* {{ t('voiceprint.description') }}
|
||||
<text class="text-red">
|
||||
*
|
||||
</text>
|
||||
{{ t('voiceprint.description') }}
|
||||
</text>
|
||||
<textarea
|
||||
v-model="editForm.introduce" :maxlength="100" :placeholder="t('voiceprint.pleaseInputDescription')"
|
||||
@@ -488,23 +594,42 @@ defineExpose({
|
||||
|
||||
<view class="flex gap-[16rpx] border-t-[2rpx] border-[#eeeeee] p-[24rpx_32rpx_32rpx]">
|
||||
<wd-button type="info" custom-class="flex-1" @click="showEditDialog = false">
|
||||
{{ t('voiceprint.cancel') }}
|
||||
</wd-button>
|
||||
<wd-button type="primary" custom-class="flex-1" @click="submitEdit">
|
||||
{{ t('voiceprint.save') }}
|
||||
</wd-button>
|
||||
{{ t('voiceprint.cancel') }}
|
||||
</wd-button>
|
||||
<wd-button type="primary" custom-class="flex-1" @click="submitEdit">
|
||||
{{ t('voiceprint.save') }}
|
||||
</wd-button>
|
||||
</view>
|
||||
</view>
|
||||
</wd-popup>
|
||||
|
||||
<!-- 语音对话记录选择动作面板 -->
|
||||
<wd-action-sheet
|
||||
v-model="showChatHistoryDialog" :actions="chatHistoryActions" :title="t('voiceprint.selectVector')"
|
||||
@select="selectAudioId"
|
||||
/>
|
||||
<!-- 自定义语音对话记录选择弹出层 -->
|
||||
<wd-popup v-model="showChatHistoryDialog" class="custom-popup" position="bottom" @close="stopAudio">
|
||||
<view class="rounded-[20rpx] bg-white pb-[20rpx] pt-[20rpx]">
|
||||
<view class="max-h-[600rpx] overflow-y-auto rounded-[20rpx]">
|
||||
<view
|
||||
v-for="item in chatHistoryActions"
|
||||
:key="item.audioId"
|
||||
class="flex items-center justify-between border-b border-[#f5f5f5] p-[32rpx] transition-all active:bg-[#f5f7fb]"
|
||||
@click="handleItemClick(item)"
|
||||
>
|
||||
<text class="flex-1 text-[28rpx] text-[#232338]">
|
||||
{{ item.name }}
|
||||
</text>
|
||||
<view class="ml-[20rpx]" @click.stop="playAudio(item.audioId, $event)">
|
||||
<wd-icon
|
||||
:name="playingAudioId === item.audioId ? 'pause-circle' : 'play-circle'"
|
||||
size="24px"
|
||||
:custom-class="playingAudioId === item.audioId ? 'text-[#336cff]' : 'text-[#9d9ea3]'"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</wd-popup>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
<style lang="scss" scoped>
|
||||
.voiceprint-container {
|
||||
position: relative;
|
||||
}
|
||||
@@ -523,14 +648,10 @@ defineExpose({
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
:deep(.wd-swipe-action) {
|
||||
border-radius: 20rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
||||
border: 1rpx solid #eeeeee;
|
||||
}
|
||||
|
||||
:deep(.flex-1) {
|
||||
flex: 1;
|
||||
::v-deep .custom-popup {
|
||||
.wd-popup {
|
||||
padding: 20rpx !important;
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -19,7 +19,7 @@ const userInfoState: UserInfo & { avatar?: string, token?: string } = {
|
||||
}
|
||||
|
||||
export const useUserStore = defineStore(
|
||||
'user',
|
||||
'userInfo',
|
||||
() => {
|
||||
// 定义用户信息
|
||||
const userInfo = ref<UserInfo & { avatar?: string, token?: string }>({ ...userInfoState })
|
||||
@@ -51,16 +51,8 @@ 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: authInfo.token || '',
|
||||
}
|
||||
setUserInfo(userInfoWithExtras)
|
||||
uni.setStorageSync('userInfo', userInfoWithExtras)
|
||||
// TODO 这里可以增加获取用户路由的方法 根据用户的角色动态生成路由
|
||||
return userInfoWithExtras
|
||||
setUserInfo(userData)
|
||||
return userData
|
||||
}
|
||||
/**
|
||||
* 退出登录 并 删除用户信息
|
||||
@@ -79,6 +71,12 @@ export const useUserStore = defineStore(
|
||||
}
|
||||
},
|
||||
{
|
||||
persist: true,
|
||||
persist: {
|
||||
key: 'userInfo',
|
||||
serializer: {
|
||||
serialize: state => JSON.stringify(state.userInfo),
|
||||
deserialize: value => ({ userInfo: JSON.parse(value) }),
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import smCrypto from 'sm-crypto'
|
||||
import { pages, subPackages } from '@/pages.json'
|
||||
|
||||
import { isMpWeixin } from './platform'
|
||||
|
||||
/**
|
||||
@@ -197,23 +199,21 @@ export function getEnvBaseUploadUrl() {
|
||||
return baseUploadUrl
|
||||
}
|
||||
|
||||
import smCrypto from 'sm-crypto'
|
||||
|
||||
/**
|
||||
* 生成SM2密钥对(十六进制格式)
|
||||
* @returns {Object} 包含公钥和私钥的对象
|
||||
*/
|
||||
export function generateSm2KeyPairHex() {
|
||||
// 使用sm-crypto库生成SM2密钥对
|
||||
const sm2 = smCrypto.sm2;
|
||||
const keypair = sm2.generateKeyPairHex();
|
||||
|
||||
return {
|
||||
publicKey: keypair.publicKey,
|
||||
privateKey: keypair.privateKey,
|
||||
clientPublicKey: keypair.publicKey, // 客户端公钥
|
||||
clientPrivateKey: keypair.privateKey // 客户端私钥
|
||||
};
|
||||
// 使用sm-crypto库生成SM2密钥对
|
||||
const sm2 = smCrypto.sm2
|
||||
const keypair = sm2.generateKeyPairHex()
|
||||
|
||||
return {
|
||||
publicKey: keypair.publicKey,
|
||||
privateKey: keypair.privateKey,
|
||||
clientPublicKey: keypair.publicKey, // 客户端公钥
|
||||
clientPrivateKey: keypair.privateKey, // 客户端私钥
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -223,21 +223,21 @@ export function generateSm2KeyPairHex() {
|
||||
* @returns {string} 加密后的密文(十六进制格式)
|
||||
*/
|
||||
export function sm2Encrypt(publicKey: string, plainText: string): string {
|
||||
if (!publicKey) {
|
||||
throw new Error('公钥不能为null或undefined');
|
||||
}
|
||||
|
||||
if (!plainText) {
|
||||
throw new Error('明文不能为空');
|
||||
}
|
||||
|
||||
const sm2 = smCrypto.sm2;
|
||||
// SM2加密,添加04前缀表示未压缩公钥
|
||||
const encrypted = sm2.doEncrypt(plainText, publicKey, 1);
|
||||
// 转换为十六进制格式(与后端保持一致,添加04前缀)
|
||||
const result = "04" + encrypted;
|
||||
|
||||
return result;
|
||||
if (!publicKey) {
|
||||
throw new Error('公钥不能为null或undefined')
|
||||
}
|
||||
|
||||
if (!plainText) {
|
||||
throw new Error('明文不能为空')
|
||||
}
|
||||
|
||||
const sm2 = smCrypto.sm2
|
||||
// SM2加密,添加04前缀表示未压缩公钥
|
||||
const encrypted = sm2.doEncrypt(plainText, publicKey, 1)
|
||||
// 转换为十六进制格式(与后端保持一致,添加04前缀)
|
||||
const result = `04${encrypted}`
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -247,9 +247,89 @@ export function sm2Encrypt(publicKey: string, plainText: string): string {
|
||||
* @returns {string} 解密后的明文
|
||||
*/
|
||||
export function sm2Decrypt(privateKey: string, cipherText: string): string {
|
||||
const sm2 = smCrypto.sm2;
|
||||
// 移除04前缀(与后端保持一致)
|
||||
const dataWithoutPrefix = cipherText.startsWith("04") ? cipherText.substring(2) : cipherText;
|
||||
// SM2解密
|
||||
return sm2.doDecrypt(dataWithoutPrefix, privateKey, 1);
|
||||
const sm2 = smCrypto.sm2
|
||||
// 移除04前缀(与后端保持一致)
|
||||
const dataWithoutPrefix = cipherText.startsWith('04') ? cipherText.substring(2) : cipherText
|
||||
// SM2解密
|
||||
return sm2.doDecrypt(dataWithoutPrefix, privateKey, 1)
|
||||
}
|
||||
|
||||
type AnyFunction = (...args: any[]) => any
|
||||
|
||||
interface DebouncedFunction extends AnyFunction {
|
||||
cancel: () => void
|
||||
}
|
||||
|
||||
/**
|
||||
* 防抖函数
|
||||
* @param fn 要防抖的函数
|
||||
* @param delay 延迟时间(毫秒),默认500ms
|
||||
* @param immediate 是否立即执行,默认false
|
||||
* @returns 防抖处理后的函数
|
||||
*/
|
||||
export function debounce<T extends AnyFunction>(
|
||||
fn: T,
|
||||
delay = 500,
|
||||
immediate = false,
|
||||
): DebouncedFunction {
|
||||
let timer: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
const debounced = function (this: any, ...args: Parameters<T>) {
|
||||
if (timer) {
|
||||
clearTimeout(timer)
|
||||
}
|
||||
|
||||
if (immediate && !timer) {
|
||||
fn.apply(this, args)
|
||||
}
|
||||
|
||||
timer = setTimeout(() => {
|
||||
if (!immediate) {
|
||||
fn.apply(this, args)
|
||||
}
|
||||
timer = null
|
||||
}, delay)
|
||||
} as DebouncedFunction
|
||||
|
||||
debounced.cancel = () => {
|
||||
if (timer) {
|
||||
clearTimeout(timer)
|
||||
timer = null
|
||||
}
|
||||
}
|
||||
|
||||
return debounced
|
||||
}
|
||||
|
||||
type DeepCloneTarget = string | number | boolean | null | undefined | object
|
||||
|
||||
/**
|
||||
* 深拷贝方法
|
||||
* @param target 要拷贝的目标
|
||||
* @returns 拷贝后的新对象
|
||||
*/
|
||||
export function deepClone<T extends DeepCloneTarget>(target: T): T {
|
||||
if (target === null || typeof target !== 'object') {
|
||||
return target
|
||||
}
|
||||
|
||||
if (target instanceof Date) {
|
||||
return new Date(target.getTime()) as any
|
||||
}
|
||||
|
||||
if (Array.isArray(target)) {
|
||||
return target.map(item => deepClone(item)) as any
|
||||
}
|
||||
|
||||
if (target instanceof Object) {
|
||||
const clonedObj = {} as T
|
||||
for (const key in target) {
|
||||
if (Object.prototype.hasOwnProperty.call(target, key)) {
|
||||
(clonedObj as any)[key] = deepClone((target as any)[key])
|
||||
}
|
||||
}
|
||||
return clonedObj
|
||||
}
|
||||
|
||||
return target
|
||||
}
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
VUE_APP_TITLE=智控台
|
||||
VUE_APP_TITLE=智控台
|
||||
VUE_APP_DESCRIPTION=小智后端服务(xiaozhi-server)是由华南理工大学刘思源教授团队主导研发的智能终端软硬件体系后端服务系统,专为xiaozhi-esp32开源硬件打造,具备多协议兼容、声纹识别、知识库管理等核心能力。
|
||||
VUE_APP_KEYWORDS=xiaozhi-server,小智服务端,智控台,AI硬件,智能硬件,AI玩具,情感陪伴,聊天机器人,智能家居,车载机器人
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -2,12 +2,11 @@
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="icon" href="./favicon.ico">
|
||||
<title>Xiaozhi AI Customization</title>
|
||||
<script type="module" crossorigin src="./assets/index-B8r0c7xg.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="./assets/index-CrIJdTCK.css">
|
||||
<script type="module" crossorigin src="./assets/index-Guo1hQ-y.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="./assets/index-B3ns2K1c.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 154 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 173 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 326 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 207 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user