feat: 全新智能体功能

- 基于先进智能体框架,增加智能体编排功能
- 增加智能体聊天,并对接持久化
This commit is contained in:
2026-05-25 11:42:48 +08:00
parent 6c3d98eaac
commit 72df00f25b
168 changed files with 22045 additions and 400 deletions

View File

@@ -20,6 +20,10 @@
<groupId>tech.easyflow</groupId>
<artifactId>easyflow-module-ai</artifactId>
</dependency>
<dependency>
<groupId>tech.easyflow</groupId>
<artifactId>easyflow-module-agent</artifactId>
</dependency>
<dependency>
<groupId>tech.easyflow</groupId>
<artifactId>easyflow-module-chatlog</artifactId>

View File

@@ -0,0 +1,87 @@
package tech.easyflow.admin.controller.agent;
import com.mybatisflex.core.query.QueryWrapper;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import tech.easyflow.agent.entity.Agent;
import tech.easyflow.agent.entity.AgentCategory;
import tech.easyflow.agent.mapper.AgentMapper;
import tech.easyflow.agent.service.AgentCategoryService;
import tech.easyflow.common.annotation.UsePermission;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.web.controller.BaseCurdController;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.system.entity.vo.RoleCategoryAccessSnapshot;
import tech.easyflow.system.enums.CategoryResourceType;
import tech.easyflow.system.service.CategoryPermissionService;
import javax.annotation.Resource;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* Agent 分类管理控制器。
*/
@RestController
@RequestMapping("/api/v1/agentCategory")
@UsePermission(moduleName = "/api/v1/agent")
public class AgentCategoryController extends BaseCurdController<AgentCategoryService, AgentCategory> {
@Resource
private AgentMapper agentMapper;
@Resource
private CategoryPermissionService categoryPermissionService;
/**
* 创建 Agent 分类管理控制器。
*
* @param service Agent 分类服务
*/
public AgentCategoryController(AgentCategoryService service) {
super(service);
}
/**
* 查询当前用户可见的 Agent 分类。
*
* @param entity 查询条件
* @param asTree 是否转树
* @param sortKey 排序字段
* @param sortType 排序方式
* @return 可见分类列表
*/
@GetMapping("visibleList")
public Result<List<AgentCategory>> visibleList(AgentCategory entity, Boolean asTree, String sortKey, String sortType) {
QueryWrapper queryWrapper = QueryWrapper.create(entity, buildOperators(entity));
RoleCategoryAccessSnapshot access = categoryPermissionService.getCurrentAccess(CategoryResourceType.AGENT.getCode());
if (access.isRestricted()) {
if (access.getCategoryIds().isEmpty()) {
return Result.ok(Collections.emptyList());
}
queryWrapper.in("id", access.getCategoryIds());
}
queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy()));
return Result.ok(service.list(queryWrapper));
}
/**
* 删除分类前校验是否仍被 Agent 使用。
*
* @param ids 分类 ID 集合
* @return 校验结果
*/
@Override
protected Result<?> onRemoveBefore(Collection<Serializable> ids) {
for (Serializable id : ids) {
QueryWrapper queryWrapper = QueryWrapper.create().eq(Agent::getCategoryId, id);
List<Agent> agents = agentMapper.selectListByQuery(queryWrapper);
if (!agents.isEmpty()) {
throw new BusinessException("请先删除该分类下的所有 Agent");
}
}
return super.onRemoveBefore(ids);
}
}

View File

@@ -0,0 +1,351 @@
package tech.easyflow.admin.controller.agent;
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.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import tech.easyflow.admin.controller.ai.support.AiResourceCreatorNameSupport;
import tech.easyflow.agent.entity.Agent;
import tech.easyflow.agent.entity.AgentKnowledgeBinding;
import tech.easyflow.agent.entity.AgentToolBinding;
import tech.easyflow.agent.publish.AgentPublishAppService;
import tech.easyflow.agent.runtime.AgentChatRequest;
import tech.easyflow.agent.runtime.AgentDraftChatRequest;
import tech.easyflow.agent.runtime.AgentRunService;
import tech.easyflow.agent.service.AgentApprovalStateService;
import tech.easyflow.agent.service.AgentKnowledgeBindingService;
import tech.easyflow.agent.service.AgentService;
import tech.easyflow.agent.service.AgentToolBindingService;
import tech.easyflow.ai.enums.PublishStatus;
import tech.easyflow.approval.entity.vo.ApprovalActionResult;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.web.controller.BaseCurdController;
import tech.easyflow.common.web.jsonbody.JsonBody;
import tech.easyflow.system.entity.vo.RoleCategoryAccessSnapshot;
import tech.easyflow.system.enums.CategoryResourceType;
import tech.easyflow.system.enums.ResourceAction;
import tech.easyflow.system.service.CategoryPermissionService;
import tech.easyflow.system.service.ResourceAccessService;
import javax.annotation.Resource;
import java.io.Serializable;
import java.math.BigInteger;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import static tech.easyflow.agent.entity.table.AgentTableDef.AGENT;
/**
* Agent 管理端控制器。
*/
@RestController
@RequestMapping("/api/v1/agent")
public class AgentController extends BaseCurdController<AgentService, Agent> {
@Resource
private AgentToolBindingService agentToolBindingService;
@Resource
private AgentKnowledgeBindingService agentKnowledgeBindingService;
@Resource
private AgentRunService agentRunService;
@Resource
private AgentPublishAppService agentPublishAppService;
@Resource
private ResourceAccessService resourceAccessService;
@Resource
private CategoryPermissionService categoryPermissionService;
@Resource
private AgentApprovalStateService agentApprovalStateService;
@Resource
private AiResourceCreatorNameSupport aiResourceCreatorNameSupport;
/**
* 创建 Agent 控制器。
*
* @param service Agent 服务
*/
public AgentController(AgentService service) {
super(service);
}
/**
* 获取 Agent 详情。
*
* @param id Agent ID
* @return Agent 详情
*/
@GetMapping("/getDetail")
public Result<Agent> getDetail(BigInteger id) {
Agent agent = service.getDetail(id);
agentApprovalStateService.fillAgentApprovalState(agent);
return Result.ok(agent);
}
/**
* 保存 Agent 草稿。
*
* @param agent Agent 草稿
* @return Agent 详情
*/
@Override
@PostMapping("save")
public Result<?> save(@JsonBody Agent agent) {
return Result.ok(service.saveDraft(agent));
}
/**
* 更新 Agent 草稿。
*
* @param agent Agent 草稿
* @return Agent 详情
*/
@Override
@PostMapping("update")
public Result<?> update(@JsonBody Agent agent) {
return Result.ok(service.updateDraft(agent));
}
/**
* 查询 Agent 列表。
*
* @param entity 查询条件
* @param asTree 是否转树
* @param sortKey 排序字段
* @param sortType 排序方式
* @return Agent 列表
*/
@Override
public Result<List<Agent>> list(Agent entity, Boolean asTree, String sortKey, String sortType) {
HttpServletRequest request = currentRequest();
QueryWrapper queryWrapper = request == null ? QueryWrapper.create() : buildQueryWrapper(request);
if (!applyCategoryPermission(queryWrapper)) {
return Result.ok(Collections.emptyList());
}
applyPublishedOnlyFilter(queryWrapper);
queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy()));
List<Agent> agents = service.list(queryWrapper);
if (isPublishedOnlyRequest()) {
agents = agents.stream().map(agent -> service.fromSnapshot(agent.getPublishedSnapshotJson())).toList();
}
agentApprovalStateService.fillAgentApprovalState(agents);
aiResourceCreatorNameSupport.fillAgentCreatorNames(agents);
return Result.ok(agents);
}
/**
* 运行 Agent 纯文本聊天。
*
* @param request 聊天请求
* @return SSE Emitter
*/
@PostMapping("chat")
public SseEmitter chat(@JsonBody AgentChatRequest request) {
return agentRunService.chat(request);
}
/**
* 运行 Agent 草稿态纯文本试用。
*
* @param request 草稿试用请求
* @return SSE Emitter
*/
@PostMapping("/chat/draft")
public SseEmitter chatDraft(@JsonBody AgentDraftChatRequest request) {
return agentRunService.chatDraft(request);
}
/**
* 清理 Agent 草稿试运行会话。
*
* @param sessionId 草稿试运行会话 ID
* @return 操作结果
*/
@PostMapping("/chat/draft/clear")
public Result<Void> clearDraftSession(@JsonBody(value = "sessionId", required = true) String sessionId) {
agentRunService.clearDraftSession(sessionId);
return Result.ok();
}
/**
* 批准工具执行。
*
* @param requestId 请求 ID
* @param resumeToken 恢复令牌
* @return 操作结果
*/
@PostMapping("/run/approve")
public Result<Void> approve(@JsonBody("requestId") String requestId,
@JsonBody(value = "resumeToken", required = true) String resumeToken) {
agentRunService.approve(requestId, resumeToken);
return Result.ok();
}
/**
* 拒绝工具执行。
*
* @param requestId 请求 ID
* @param resumeToken 恢复令牌
* @param reason 拒绝原因
* @return 操作结果
*/
@PostMapping("/run/reject")
public Result<Void> reject(@JsonBody("requestId") String requestId,
@JsonBody(value = "resumeToken", required = true) String resumeToken,
@JsonBody("reason") String reason) {
agentRunService.reject(requestId, resumeToken, reason);
return Result.ok();
}
/**
* 更新 Agent 工具绑定。
*
* @param agentId Agent ID
* @param bindings 工具绑定
* @return 保存后的启用绑定
*/
@PostMapping("/toolBinding/update")
@SaCheckPermission("/api/v1/agent/save")
public Result<List<AgentToolBinding>> updateToolBinding(@JsonBody(value = "agentId", required = true) BigInteger agentId,
@JsonBody("bindings") List<AgentToolBinding> bindings) {
return Result.ok(agentToolBindingService.replaceBindings(agentId, bindings));
}
/**
* 更新 Agent 知识库绑定。
*
* @param agentId Agent ID
* @param bindings 知识库绑定
* @return 保存后的启用绑定
*/
@PostMapping("/knowledgeBinding/update")
@SaCheckPermission("/api/v1/agent/save")
public Result<List<AgentKnowledgeBinding>> updateKnowledgeBinding(@JsonBody(value = "agentId", required = true) BigInteger agentId,
@JsonBody("bindings") List<AgentKnowledgeBinding> bindings) {
return Result.ok(agentKnowledgeBindingService.replaceBindings(agentId, bindings));
}
/**
* 提交发布审批。
*
* @param id Agent ID
* @return 审批实例 ID
*/
@PostMapping("/submitPublishApproval")
@SaCheckPermission("/api/v1/agent/save")
public Result<BigInteger> submitPublishApproval(@JsonBody("id") BigInteger id) {
return buildApprovalActionResult(agentPublishAppService.submitPublishApproval(id), "已提交发布审批", "已直接发布");
}
/**
* 提交下线审批。
*
* @param id Agent ID
* @return 审批实例 ID
*/
@PostMapping("/submitOfflineApproval")
@SaCheckPermission("/api/v1/agent/save")
public Result<BigInteger> submitOfflineApproval(@JsonBody("id") BigInteger id) {
return buildApprovalActionResult(agentPublishAppService.submitOfflineApproval(id), "已提交下线审批", "已直接下线");
}
/**
* 提交删除审批。
*
* @param id Agent ID
* @return 审批实例 ID
*/
@PostMapping("/submitDeleteApproval")
@SaCheckPermission("/api/v1/agent/remove")
public Result<BigInteger> submitDeleteApproval(@JsonBody("id") BigInteger id) {
return buildApprovalActionResult(agentPublishAppService.submitDeleteApproval(id), "已提交删除审批", "已直接删除");
}
@Override
protected Result<?> onRemoveBefore(Collection<Serializable> ids) {
for (Serializable id : ids) {
Agent agent = service.getById(String.valueOf(id));
if (agent != null) {
resourceAccessService.assertAccess(CategoryResourceType.AGENT, agent, ResourceAction.MANAGE, "无权限删除该 Agent");
}
}
agentToolBindingService.remove(QueryWrapper.create().in("agent_id", ids));
agentKnowledgeBindingService.remove(QueryWrapper.create().in("agent_id", ids));
return super.onRemoveBefore(ids);
}
/**
* 查询 Agent 分页。
*
* @param page 分页参数
* @param queryWrapper 查询条件
* @return Agent 分页
*/
@Override
protected Page<Agent> queryPage(Page<Agent> page, QueryWrapper queryWrapper) {
if (!applyCategoryPermission(queryWrapper)) {
return new Page<>(Collections.emptyList(), page.getPageNumber(), page.getPageSize(), 0L);
}
applyPublishedOnlyFilter(queryWrapper);
Page<Agent> result = super.queryPage(page, queryWrapper);
if (isPublishedOnlyRequest()) {
result.setRecords(result.getRecords().stream().map(agent -> service.fromSnapshot(agent.getPublishedSnapshotJson())).toList());
}
agentApprovalStateService.fillAgentApprovalState(result.getRecords());
aiResourceCreatorNameSupport.fillAgentCreatorNames(result.getRecords());
return result;
}
private boolean applyCategoryPermission(QueryWrapper queryWrapper) {
RoleCategoryAccessSnapshot access = categoryPermissionService.getCurrentAccess(CategoryResourceType.AGENT.getCode());
if (!access.isRestricted()) {
return true;
}
if (access.getCategoryIds().isEmpty()) {
queryWrapper.eq(Agent::getCreatedBy, access.getAccountId());
return true;
}
queryWrapper.and(AGENT.CREATED_BY.eq(access.getAccountId()).or(AGENT.CATEGORY_ID.in(access.getCategoryIds())));
return true;
}
private void applyPublishedOnlyFilter(QueryWrapper queryWrapper) {
if (isPublishedOnlyRequest()) {
queryWrapper.eq("publish_status", PublishStatus.PUBLISHED.getCode());
}
}
private boolean isPublishedOnlyRequest() {
HttpServletRequest request = currentRequest();
if (request == null) {
return false;
}
return "true".equalsIgnoreCase(request.getParameter("publishedOnly"));
}
/**
* 获取当前 HTTP 请求。
*
* @return 当前请求,不在 Web 请求上下文中时返回 null
*/
private HttpServletRequest currentRequest() {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes == null) {
return null;
}
return attributes.getRequest();
}
private Result<BigInteger> buildApprovalActionResult(ApprovalActionResult actionResult,
String approvalMessage,
String directMessage) {
return Result.ok(actionResult.isApprovalRequired() ? approvalMessage : directMessage, actionResult.getInstanceId());
}
}

View File

@@ -0,0 +1,122 @@
package tech.easyflow.admin.controller.agent;
import com.mybatisflex.core.keygen.impl.SnowFlakeIDKeyGenerator;
import org.springframework.web.bind.annotation.*;
import tech.easyflow.admin.dto.chatworkspace.ChatWorkspaceConversationView;
import tech.easyflow.admin.dto.chatworkspace.ChatWorkspaceSessionDetailView;
import tech.easyflow.admin.dto.chatworkspace.ChatWorkspaceSessionPage;
import tech.easyflow.admin.service.agent.AgentSessionService;
import tech.easyflow.chatlog.domain.dto.ChatHistoryPage;
import tech.easyflow.chatlog.domain.query.ChatPageQuery;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.satoken.util.SaTokenUtil;
import tech.easyflow.common.web.jsonbody.JsonBody;
import java.math.BigInteger;
/**
* Agent 管理端会话控制器。
*/
@RestController
@RequestMapping("/api/v1/agent/session")
public class AgentSessionController {
private final AgentSessionService agentSessionService;
/**
* 创建 Agent 管理端会话控制器。
*
* @param agentSessionService Agent 会话服务
*/
public AgentSessionController(AgentSessionService agentSessionService) {
this.agentSessionService = agentSessionService;
}
/**
* 生成 Agent 会话 ID。
*
* @return 会话 ID 字符串
*/
@GetMapping("/generateId")
public Result<String> generateId() {
long nextId = new SnowFlakeIDKeyGenerator().nextId();
return Result.ok(String.valueOf(nextId));
}
/**
* 查询 Agent 会话分页。
*
* @param agentId Agent ID可为空
* @param query 分页参数
* @return 会话分页
*/
@GetMapping("/list")
public Result<ChatWorkspaceSessionPage> list(BigInteger agentId, ChatPageQuery query) {
return Result.ok(agentSessionService.queryCurrentUserSessions(currentAccount(), agentId, query));
}
/**
* 查询 Agent 会话详情。
*
* @param sessionId 会话 ID
* @return 会话详情
*/
@GetMapping("/{sessionId}")
public Result<ChatWorkspaceSessionDetailView> detail(@PathVariable BigInteger sessionId) {
return Result.ok(agentSessionService.getCurrentUserSession(currentAccount(), sessionId));
}
/**
* 查询 Agent 会话消息。
*
* @param sessionId 会话 ID
* @param query 分页参数
* @return 消息分页
*/
@GetMapping("/{sessionId}/messages")
public Result<ChatHistoryPage> messages(@PathVariable BigInteger sessionId, ChatPageQuery query) {
return Result.ok(agentSessionService.queryCurrentUserMessages(currentAccount(), sessionId, query));
}
/**
* 查询 Agent 完整会话。
*
* @param sessionId 会话 ID
* @return 完整会话
*/
@GetMapping("/{sessionId}/conversation")
public Result<ChatWorkspaceConversationView> conversation(@PathVariable BigInteger sessionId) {
return Result.ok(agentSessionService.getCurrentUserConversation(currentAccount(), sessionId));
}
/**
* 重命名 Agent 会话。
*
* @param sessionId 会话 ID
* @param title 新标题
* @return 操作结果
*/
@PostMapping("/{sessionId}/rename")
public Result<Void> rename(@PathVariable BigInteger sessionId,
@JsonBody(value = "title", required = true) String title) {
agentSessionService.renameCurrentUserSession(currentAccount(), sessionId, title);
return Result.ok();
}
/**
* 删除 Agent 会话。
*
* @param sessionId 会话 ID
* @return 操作结果
*/
@PostMapping("/{sessionId}/delete")
public Result<Void> delete(@PathVariable BigInteger sessionId) {
agentSessionService.deleteCurrentUserSession(currentAccount(), sessionId);
return Result.ok();
}
private LoginAccount currentAccount() {
return SaTokenUtil.getLoginAccount();
}
}

View File

@@ -1,6 +1,7 @@
package tech.easyflow.admin.controller.ai.support;
import org.springframework.stereotype.Component;
import tech.easyflow.agent.entity.Agent;
import tech.easyflow.ai.entity.Bot;
import tech.easyflow.ai.entity.DocumentCollection;
import tech.easyflow.ai.entity.Plugin;
@@ -66,6 +67,15 @@ public class AiResourceCreatorNameSupport {
fillCreatorNames(plugins, Plugin::getCreatedBy, Plugin::setCreatedByName);
}
/**
* 批量填充 Agent 创建人名称。
*
* @param agents Agent 集合
*/
public void fillAgentCreatorNames(Collection<Agent> agents) {
fillCreatorNames(agents, Agent::getCreatedBy, Agent::setCreatedByName);
}
/**
* 通用的创建人名称填充逻辑。
*

View File

@@ -0,0 +1,302 @@
package tech.easyflow.admin.service.agent;
import com.mybatisflex.core.query.QueryWrapper;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import tech.easyflow.admin.dto.chatworkspace.*;
import tech.easyflow.agent.entity.Agent;
import tech.easyflow.agent.runtime.AgentRuntimeStateCleanupService;
import tech.easyflow.agent.service.AgentService;
import tech.easyflow.ai.entity.DocumentCollection;
import tech.easyflow.ai.enums.PublishStatus;
import tech.easyflow.ai.service.DocumentCollectionService;
import tech.easyflow.chatlog.domain.dto.ChatHistoryPage;
import tech.easyflow.chatlog.domain.dto.ChatMessageRecord;
import tech.easyflow.chatlog.domain.dto.ChatSessionPage;
import tech.easyflow.chatlog.domain.dto.ChatSessionSummary;
import tech.easyflow.chatlog.domain.query.ChatPageQuery;
import tech.easyflow.chatlog.service.ChatSessionCommandService;
import tech.easyflow.chatlog.service.ChatSessionQueryService;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.system.enums.CategoryResourceType;
import tech.easyflow.system.enums.ResourceAction;
import tech.easyflow.system.service.ResourceAccessService;
import java.math.BigInteger;
import java.util.*;
/**
* Agent 管理端会话服务。
*/
@Service
public class AgentSessionService {
private static final String ASSISTANT_CODE = "AGENT";
private final ChatSessionQueryService chatSessionQueryService;
private final ChatSessionCommandService chatSessionCommandService;
private final AgentService agentService;
private final DocumentCollectionService documentCollectionService;
private final ResourceAccessService resourceAccessService;
private final AgentRuntimeStateCleanupService agentRuntimeStateCleanupService;
/**
* 创建 Agent 管理端会话服务。
*
* @param chatSessionQueryService 聊天会话查询服务
* @param chatSessionCommandService 聊天会话命令服务
* @param agentService Agent 服务
* @param documentCollectionService 知识库服务
* @param resourceAccessService 资源访问服务
* @param agentRuntimeStateCleanupService Agent 运行态清理服务
*/
public AgentSessionService(ChatSessionQueryService chatSessionQueryService,
ChatSessionCommandService chatSessionCommandService,
AgentService agentService,
DocumentCollectionService documentCollectionService,
ResourceAccessService resourceAccessService,
AgentRuntimeStateCleanupService agentRuntimeStateCleanupService) {
this.chatSessionQueryService = chatSessionQueryService;
this.chatSessionCommandService = chatSessionCommandService;
this.agentService = agentService;
this.documentCollectionService = documentCollectionService;
this.resourceAccessService = resourceAccessService;
this.agentRuntimeStateCleanupService = agentRuntimeStateCleanupService;
}
/**
* 查询当前用户的 Agent 会话分页。
*
* @param account 当前登录账号
* @param agentId Agent ID
* @param query 分页参数
* @return Agent 会话分页
*/
public ChatWorkspaceSessionPage queryCurrentUserSessions(LoginAccount account, BigInteger agentId, ChatPageQuery query) {
ChatSessionPage page = chatSessionQueryService.pageSessions(account.getId(), agentId, ASSISTANT_CODE, query);
Map<BigInteger, AgentAvailability> availabilityMap = resolveAgentAvailability(page.getRecords());
ChatWorkspaceSessionPage result = new ChatWorkspaceSessionPage();
result.setTotal(page.getTotal());
result.setPageNumber(page.getPageNumber());
result.setPageSize(page.getPageSize());
List<ChatWorkspaceSessionView> records = new ArrayList<>();
for (ChatSessionSummary summary : page.getRecords()) {
records.add(toSessionView(summary, availabilityMap.get(summary.getAssistantId())));
}
result.setRecords(records);
return result;
}
/**
* 查询当前用户的 Agent 会话详情。
*
* @param account 当前登录账号
* @param sessionId 会话 ID
* @return Agent 会话详情
*/
public ChatWorkspaceSessionDetailView getCurrentUserSession(LoginAccount account, BigInteger sessionId) {
ChatSessionSummary summary = requireUserAgentSession(account, sessionId);
AgentAvailability availability = resolveAgentAvailability(List.of(summary)).get(summary.getAssistantId());
ChatWorkspaceSessionDetailView detail = new ChatWorkspaceSessionDetailView();
fillSessionView(detail, summary, availability);
Agent displayAgent = availability == null ? null : availability.displayAgent();
detail.setAssistant(toAssistantView(displayAgent, summary));
detail.setBoundKnowledges(resolveBoundKnowledges(displayAgent));
return detail;
}
/**
* 查询当前用户的 Agent 会话消息。
*
* @param account 当前登录账号
* @param sessionId 会话 ID
* @param query 分页参数
* @return 消息分页
*/
public ChatHistoryPage queryCurrentUserMessages(LoginAccount account, BigInteger sessionId, ChatPageQuery query) {
requireUserAgentSession(account, sessionId);
return chatSessionQueryService.pageMainlineMessages(sessionId, query);
}
/**
* 查询当前用户的 Agent 完整会话。
*
* @param account 当前登录账号
* @param sessionId 会话 ID
* @return 完整会话
*/
public ChatWorkspaceConversationView getCurrentUserConversation(LoginAccount account, BigInteger sessionId) {
requireUserAgentSession(account, sessionId);
List<ChatMessageRecord> records = chatSessionQueryService.listMainlineMessages(sessionId);
ChatWorkspaceConversationView view = new ChatWorkspaceConversationView();
view.setRecords(records);
view.setTotal(records.size());
return view;
}
/**
* 重命名当前用户的 Agent 会话。
*
* @param account 当前登录账号
* @param sessionId 会话 ID
* @param title 新标题
*/
public void renameCurrentUserSession(LoginAccount account, BigInteger sessionId, String title) {
if (!StringUtils.hasText(title)) {
throw new BusinessException("标题不能为空");
}
requireUserAgentSession(account, sessionId);
chatSessionCommandService.renameSession(sessionId, account.getId(), title.trim(), account.getId());
}
/**
* 删除当前用户的 Agent 会话。
*
* @param account 当前登录账号
* @param sessionId 会话 ID
*/
public void deleteCurrentUserSession(LoginAccount account, BigInteger sessionId) {
requireUserAgentSession(account, sessionId);
agentRuntimeStateCleanupService.clearChatSession(sessionId, account.getId());
chatSessionCommandService.deleteSession(sessionId, account.getId(), account.getId());
}
private ChatSessionSummary requireUserAgentSession(LoginAccount account, BigInteger sessionId) {
ChatSessionSummary summary = chatSessionQueryService.getSessionSummary(sessionId);
if (summary == null || Integer.valueOf(1).equals(summary.getIsDeleted())
|| !ASSISTANT_CODE.equals(summary.getAssistantCode())) {
throw new BusinessException("Agent 会话不存在");
}
if (!Objects.equals(summary.getUserId(), account.getId())) {
throw new BusinessException("无权访问该 Agent 会话");
}
return summary;
}
private Map<BigInteger, AgentAvailability> resolveAgentAvailability(List<ChatSessionSummary> sessions) {
Map<BigInteger, AgentAvailability> result = new LinkedHashMap<>();
if (sessions == null || sessions.isEmpty()) {
return result;
}
Set<BigInteger> agentIds = new LinkedHashSet<>();
for (ChatSessionSummary session : sessions) {
if (session != null && session.getAssistantId() != null) {
agentIds.add(session.getAssistantId());
}
}
if (agentIds.isEmpty()) {
return result;
}
List<Agent> agents = agentService.list(QueryWrapper.create().in("id", agentIds));
Map<BigInteger, Agent> agentMap = new LinkedHashMap<>();
for (Agent agent : agents) {
agentMap.put(agent.getId(), agent);
}
for (BigInteger agentId : agentIds) {
Agent currentAgent = agentMap.get(agentId);
if (currentAgent == null) {
result.put(agentId, new AgentAvailability(false, ChatWorkspaceReadOnlyReason.ASSISTANT_DELETED, null));
continue;
}
if (!resourceAccessService.canAccess(CategoryResourceType.AGENT, currentAgent, ResourceAction.USE)) {
result.put(agentId, new AgentAvailability(false, ChatWorkspaceReadOnlyReason.NO_PERMISSION, null));
continue;
}
boolean online = Integer.valueOf(1).equals(currentAgent.getStatus())
&& PublishStatus.from(currentAgent.getPublishStatus()) == PublishStatus.PUBLISHED;
result.put(agentId, new AgentAvailability(
online,
online ? null : ChatWorkspaceReadOnlyReason.ASSISTANT_OFFLINE,
toDisplayAgent(currentAgent)
));
}
return result;
}
private Agent toDisplayAgent(Agent currentAgent) {
if (currentAgent.getPublishedSnapshotJson() != null && !currentAgent.getPublishedSnapshotJson().isEmpty()) {
return agentService.fromSnapshot(currentAgent.getPublishedSnapshotJson());
}
return currentAgent;
}
private ChatWorkspaceSessionView toSessionView(ChatSessionSummary summary, AgentAvailability availability) {
ChatWorkspaceSessionView view = new ChatWorkspaceSessionView();
fillSessionView(view, summary, availability);
return view;
}
private void fillSessionView(ChatWorkspaceSessionView view, ChatSessionSummary summary, AgentAvailability availability) {
view.setSessionId(summary.getId());
view.setAssistantId(summary.getAssistantId());
view.setAssistantCode(summary.getAssistantCode());
view.setAssistantName(summary.getAssistantName());
view.setTitle(summary.getTitle());
view.setLastMessagePreview(summary.getLastMessagePreview());
view.setMessageCount(summary.getMessageCount());
view.setAccessAt(summary.getAccessAt());
view.setLastMessageAt(summary.getLastMessageAt());
view.setContinuable(availability != null && availability.continuable());
view.setReadOnlyReason(availability == null ? ChatWorkspaceReadOnlyReason.ASSISTANT_DELETED : availability.reason());
}
private ChatWorkspaceAssistantView toAssistantView(Agent agent, ChatSessionSummary summary) {
ChatWorkspaceAssistantView view = new ChatWorkspaceAssistantView();
if (agent != null) {
view.setId(agent.getId());
view.setAlias(agent.getId() == null ? null : agent.getId().toString());
view.setTitle(agent.getName());
view.setDescription(agent.getDescription());
view.setIcon(agent.getAvatar());
return view;
}
view.setId(summary == null ? null : summary.getAssistantId());
view.setAlias(summary == null ? null : summary.getAssistantCode());
view.setTitle(summary == null ? null : summary.getAssistantName());
return view;
}
@SuppressWarnings("unchecked")
private List<ChatWorkspaceKnowledgeView> resolveBoundKnowledges(Agent displayAgent) {
if (displayAgent == null || displayAgent.getKnowledgeBindings() == null || displayAgent.getKnowledgeBindings().isEmpty()) {
return List.of();
}
List<BigInteger> knowledgeIds = displayAgent.getKnowledgeBindings().stream()
.map(binding -> binding.getKnowledgeId())
.filter(Objects::nonNull)
.toList();
if (knowledgeIds.isEmpty()) {
return List.of();
}
List<DocumentCollection> collections = documentCollectionService.listByIds(knowledgeIds);
Map<BigInteger, DocumentCollection> collectionMap = new LinkedHashMap<>();
for (DocumentCollection collection : collections) {
collectionMap.put(collection.getId(), collection);
}
List<ChatWorkspaceKnowledgeView> views = new ArrayList<>();
for (BigInteger knowledgeId : knowledgeIds) {
DocumentCollection collection = collectionMap.get(knowledgeId);
if (collection == null || PublishStatus.from(collection.getPublishStatus()) != PublishStatus.PUBLISHED) {
continue;
}
views.add(toKnowledgeView(documentCollectionService.toPublishedView(collection)));
}
return views;
}
private ChatWorkspaceKnowledgeView toKnowledgeView(DocumentCollection collection) {
ChatWorkspaceKnowledgeView view = new ChatWorkspaceKnowledgeView();
view.setId(collection.getId());
view.setAlias(collection.getAlias());
view.setTitle(collection.getTitle());
view.setDescription(collection.getDescription());
view.setIcon(collection.getIcon());
return view;
}
private record AgentAvailability(boolean continuable,
ChatWorkspaceReadOnlyReason reason,
Agent displayAgent) {
}
}