feat: 支持智能体文档附件与轻量读取
- 建立文档上传、异步读取、对象存储、补偿与聊天绑定闭环 - 按智能体 20K 上下文预算选择文档片段并保留稳定引用 - 统一聊天文件卡片、类型图标、草稿恢复与可靠下载
This commit is contained in:
@@ -4,6 +4,7 @@ import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import com.mybatisflex.core.paginate.Page;
|
||||
import com.mybatisflex.core.query.QueryWrapper;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.springframework.http.ContentDisposition;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@@ -16,6 +17,7 @@ import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
|
||||
import tech.easyflow.admin.controller.ai.support.AiResourceCreatorNameSupport;
|
||||
import tech.easyflow.agent.entity.Agent;
|
||||
import tech.easyflow.agent.entity.AgentKnowledgeBinding;
|
||||
@@ -27,6 +29,9 @@ import tech.easyflow.agent.runtime.AgentRunService;
|
||||
import tech.easyflow.agent.runtime.composer.AgentComposerDraft;
|
||||
import tech.easyflow.agent.runtime.composer.AgentComposerDraftService;
|
||||
import tech.easyflow.agent.runtime.composer.AgentComposerSession;
|
||||
import tech.easyflow.agent.runtime.document.AgentDocumentResource;
|
||||
import tech.easyflow.agent.runtime.document.AgentDocumentService;
|
||||
import tech.easyflow.agent.runtime.document.AgentDocumentUploadView;
|
||||
import tech.easyflow.agent.runtime.media.AgentMediaService;
|
||||
import tech.easyflow.agent.runtime.media.AgentMediaUploadView;
|
||||
import com.easyagents.agent.runtime.media.AgentMediaResource;
|
||||
@@ -50,6 +55,7 @@ import tech.easyflow.system.service.ResourceAccessService;
|
||||
import javax.annotation.Resource;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -82,6 +88,8 @@ public class AgentController extends BaseCurdController<AgentService, Agent> {
|
||||
@Resource
|
||||
private AgentMediaService agentMediaService;
|
||||
@Resource
|
||||
private AgentDocumentService agentDocumentService;
|
||||
@Resource
|
||||
private AgentComposerDraftService agentComposerDraftService;
|
||||
|
||||
/**
|
||||
@@ -226,6 +234,91 @@ public class AgentController extends BaseCurdController<AgentService, Agent> {
|
||||
.body(resource.bytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传一份 Agent 聊天文档并异步触发轻量读取。
|
||||
*
|
||||
* @param file 文档文件
|
||||
* @param mode 聊天模式
|
||||
* @param agentId Agent ID
|
||||
* @param sessionId 会话 ID
|
||||
* @param uploadId 客户端生成的幂等上传 ID
|
||||
* @return 上传与读取状态
|
||||
*/
|
||||
@PostMapping(value = "/media/document/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
public Result<AgentDocumentUploadView> uploadDocument(@RequestParam("file") MultipartFile file,
|
||||
@RequestParam("mode") String mode,
|
||||
@RequestParam("agentId") String agentId,
|
||||
@RequestParam("sessionId") String sessionId,
|
||||
@RequestParam(value = "uploadId", required = false)
|
||||
String uploadId) {
|
||||
return Result.ok(agentDocumentService.upload(
|
||||
file, mode, agentId, sessionId, uploadId, SaTokenUtil.getLoginAccount()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询当前账号一个上传文档的读取状态。
|
||||
*
|
||||
* @param uploadId 上传 ID
|
||||
* @return 最新状态
|
||||
*/
|
||||
@GetMapping("/media/document/status")
|
||||
public Result<AgentDocumentUploadView> documentStatus(@RequestParam("uploadId") String uploadId) {
|
||||
return Result.ok(agentDocumentService.status(uploadId, SaTokenUtil.getLoginAccount()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 重试一次明确失败的文档读取。
|
||||
*
|
||||
* @param uploadId 上传 ID
|
||||
* @return 重试后的状态
|
||||
*/
|
||||
@PostMapping("/media/document/retry")
|
||||
public Result<AgentDocumentUploadView> retryDocument(
|
||||
@JsonBody(value = "uploadId", required = true) String uploadId) {
|
||||
return Result.ok(agentDocumentService.retry(uploadId, SaTokenUtil.getLoginAccount()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除当前账号尚未发送的临时文档。
|
||||
*
|
||||
* @param uploadId 上传 ID
|
||||
* @return 操作结果
|
||||
*/
|
||||
@PostMapping("/media/document/delete")
|
||||
public Result<Void> deleteDocument(
|
||||
@JsonBody(value = "uploadId", required = true) String uploadId) {
|
||||
agentDocumentService.deleteUpload(uploadId, SaTokenUtil.getLoginAccount());
|
||||
return Result.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过鉴权代理流式下载 Agent 私有聊天文档。
|
||||
*
|
||||
* @param reference 稳定文档引用
|
||||
* @return 文档流
|
||||
*/
|
||||
@GetMapping("/media/document/content")
|
||||
@LogReporterDisabled
|
||||
public ResponseEntity<StreamingResponseBody> documentContent(
|
||||
@RequestParam("reference") String reference) {
|
||||
AgentDocumentResource resource = agentDocumentService.load(
|
||||
reference, SaTokenUtil.getLoginAccount());
|
||||
StreamingResponseBody body = output -> {
|
||||
try (var input = resource.inputStream()) {
|
||||
input.transferTo(output);
|
||||
}
|
||||
};
|
||||
ContentDisposition disposition = ContentDisposition.attachment()
|
||||
.filename(resource.name(), StandardCharsets.UTF_8)
|
||||
.build();
|
||||
return ResponseEntity.ok()
|
||||
.header(HttpHeaders.CACHE_CONTROL, "private, no-store")
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, disposition.toString())
|
||||
.contentType(MediaType.parseMediaType(resource.mimeType()))
|
||||
.contentLength(resource.size())
|
||||
.body(body);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为输入框预分配稳定会话 ID。
|
||||
*
|
||||
@@ -272,7 +365,8 @@ public class AgentController extends BaseCurdController<AgentService, Agent> {
|
||||
* @param agentId Agent ID
|
||||
* @param sessionId 会话 ID
|
||||
* @param imageUploadIds 调用方仍持有的上传 ID
|
||||
* @param deleteUploads 是否同时删除临时图片
|
||||
* @param documentUploadIds 调用方仍持有的文档上传 ID
|
||||
* @param deleteUploads 是否同时删除临时附件
|
||||
* @return 操作结果
|
||||
*/
|
||||
@PostMapping("/composer/draft/delete")
|
||||
@@ -280,8 +374,9 @@ public class AgentController extends BaseCurdController<AgentService, Agent> {
|
||||
@JsonBody(value = "agentId", required = true) String agentId,
|
||||
@JsonBody(value = "sessionId", required = true) String sessionId,
|
||||
@JsonBody(value = "imageUploadIds") List<String> imageUploadIds,
|
||||
@JsonBody(value = "documentUploadIds") List<String> documentUploadIds,
|
||||
@JsonBody(value = "deleteUploads") Boolean deleteUploads) {
|
||||
agentComposerDraftService.delete(mode, agentId, sessionId, imageUploadIds,
|
||||
agentComposerDraftService.delete(mode, agentId, sessionId, imageUploadIds, documentUploadIds,
|
||||
!Boolean.FALSE.equals(deleteUploads), SaTokenUtil.getLoginAccount());
|
||||
return Result.ok();
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import tech.easyflow.admin.dto.chatworkspace.*;
|
||||
import tech.easyflow.agent.entity.Agent;
|
||||
import tech.easyflow.agent.runtime.AgentRuntimeStateCleanupService;
|
||||
import tech.easyflow.agent.runtime.composer.AgentComposerDraftService;
|
||||
import tech.easyflow.agent.runtime.document.AgentDocumentService;
|
||||
import tech.easyflow.agent.runtime.media.AgentMediaService;
|
||||
import tech.easyflow.agent.service.AgentService;
|
||||
import tech.easyflow.ai.entity.DocumentCollection;
|
||||
@@ -24,6 +25,7 @@ import tech.easyflow.system.enums.CategoryResourceType;
|
||||
import tech.easyflow.system.enums.ResourceAction;
|
||||
import tech.easyflow.system.service.ResourceAccessService;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.math.BigInteger;
|
||||
import java.util.*;
|
||||
|
||||
@@ -44,6 +46,8 @@ public class AgentSessionService {
|
||||
private final AgentMediaService agentMediaService;
|
||||
private final AgentComposerDraftService agentComposerDraftService;
|
||||
private final ChatJsonSupport chatJsonSupport;
|
||||
@Resource
|
||||
private AgentDocumentService agentDocumentService;
|
||||
|
||||
/**
|
||||
* 创建 Agent 管理端会话服务。
|
||||
@@ -201,6 +205,7 @@ public class AgentSessionService {
|
||||
// 上一次删除可能已写入删除标记但媒体清理失败,重试时继续清理当前用户目录。
|
||||
deleteComposerDraft(summary, account, sessionId);
|
||||
agentMediaService.deleteFormalSession(sessionId.toString(), account);
|
||||
deleteFormalDocuments(sessionId, account);
|
||||
return;
|
||||
}
|
||||
requireUserAgentSession(account, summary);
|
||||
@@ -208,6 +213,19 @@ public class AgentSessionService {
|
||||
chatSessionCommandService.deleteSession(sessionId, account.getId(), account.getId());
|
||||
deleteComposerDraft(summary, account, sessionId);
|
||||
agentMediaService.deleteFormalSession(sessionId.toString(), account);
|
||||
deleteFormalDocuments(sessionId, account);
|
||||
}
|
||||
|
||||
/**
|
||||
* 幂等清理正式会话绑定的文档对象与快照。
|
||||
*
|
||||
* @param sessionId 会话 ID
|
||||
* @param account 当前账号
|
||||
*/
|
||||
private void deleteFormalDocuments(BigInteger sessionId, LoginAccount account) {
|
||||
if (agentDocumentService != null) {
|
||||
agentDocumentService.deleteFormalSession(sessionId.toString(), account);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user