feat: 完成管理端聊天工作台收口
- 新增管理端聊天工作台与会话级额外知识库持久化 - 补齐发布态聊天、历史会话只读判断与答案版本切换 - 新增 chat_round 热数据与主线消息读取支撑
This commit is contained in:
@@ -13,13 +13,17 @@ import com.mybatisflex.core.query.QueryWrapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
import tech.easyflow.admin.controller.ai.support.AiResourceCreatorNameSupport;
|
||||
import tech.easyflow.admin.service.ai.ChatWorkspaceService;
|
||||
import tech.easyflow.ai.chattime.availability.ChatTimeToolAvailabilityContext;
|
||||
import tech.easyflow.ai.easyagents.listener.PromptChoreChatStreamListener;
|
||||
import tech.easyflow.ai.entity.*;
|
||||
import tech.easyflow.ai.enums.PublishStatus;
|
||||
import tech.easyflow.ai.publish.BotPublishAppService;
|
||||
import tech.easyflow.approval.entity.vo.ApprovalActionResult;
|
||||
import tech.easyflow.ai.service.*;
|
||||
@@ -31,9 +35,11 @@ import tech.easyflow.common.satoken.util.SaTokenUtil;
|
||||
import tech.easyflow.common.web.controller.BaseCurdController;
|
||||
import tech.easyflow.common.web.exceptions.BusinessException;
|
||||
import tech.easyflow.common.web.jsonbody.JsonBody;
|
||||
import tech.easyflow.chatlog.service.ChatRoundOperateService;
|
||||
import tech.easyflow.core.chat.protocol.sse.ChatSseEmitter;
|
||||
import tech.easyflow.core.chat.protocol.sse.ChatSseUtil;
|
||||
import tech.easyflow.core.runtime.ChatChannel;
|
||||
import tech.easyflow.core.runtime.ChatRuntimeExtKeys;
|
||||
import tech.easyflow.core.runtime.ChatRuntimeContext;
|
||||
import tech.easyflow.system.entity.vo.RoleCategoryAccessSnapshot;
|
||||
import tech.easyflow.system.service.CategoryPermissionService;
|
||||
@@ -74,9 +80,13 @@ public class BotController extends BaseCurdController<BotService, Bot> {
|
||||
@Resource
|
||||
private BotPublishAppService botPublishAppService;
|
||||
@Resource
|
||||
private ChatRoundOperateService chatRoundOperateService;
|
||||
@Resource
|
||||
private AiResourceApprovalStateService aiResourceApprovalStateService;
|
||||
@Resource
|
||||
private AiResourceCreatorNameSupport aiResourceCreatorNameSupport;
|
||||
@Resource
|
||||
private ChatWorkspaceService chatWorkspaceService;
|
||||
|
||||
public BotController(BotService service, ModelService modelService, BotWorkflowService botWorkflowService,
|
||||
BotDocumentCollectionService botDocumentCollectionService, BotMessageService botMessageService) {
|
||||
@@ -162,13 +172,30 @@ public class BotController extends BaseCurdController<BotService, Bot> {
|
||||
@JsonBody(value = "botId", required = true) BigInteger botId,
|
||||
@JsonBody(value = "conversationId", required = true) BigInteger conversationId,
|
||||
@JsonBody(value = "messages") List<Map<String, String>> messages,
|
||||
@JsonBody(value = "attachments") List<String> attachments
|
||||
@JsonBody(value = "attachments") List<String> attachments,
|
||||
@JsonBody(value = "publishedOnly") Boolean publishedOnly,
|
||||
@JsonBody(value = "extraKnowledgeIds") List<BigInteger> extraKnowledgeIds,
|
||||
@JsonBody(value = "regenerateRoundId") BigInteger regenerateRoundId
|
||||
|
||||
) {
|
||||
boolean usePublishedOnly = Boolean.TRUE.equals(publishedOnly);
|
||||
BotServiceImpl.ChatCheckResult chatCheckResult = new BotServiceImpl.ChatCheckResult();
|
||||
if (usePublishedOnly) {
|
||||
chatWorkspaceService.assertSessionContinuable(requireCurrentLoginAccount(), conversationId, botId);
|
||||
}
|
||||
if (regenerateRoundId != null) {
|
||||
chatRoundOperateService.requireRegeneratableRound(conversationId, regenerateRoundId);
|
||||
}
|
||||
|
||||
// 前置校验:失败则直接返回错误SseEmitter
|
||||
SseEmitter errorEmitter = botService.checkChatBeforeStart(botId, prompt, conversationId.toString(), chatCheckResult);
|
||||
SseEmitter errorEmitter = botService.checkChatBeforeStart(
|
||||
botId,
|
||||
prompt,
|
||||
conversationId.toString(),
|
||||
chatCheckResult,
|
||||
usePublishedOnly,
|
||||
regenerateRoundId
|
||||
);
|
||||
if (errorEmitter != null) {
|
||||
return errorEmitter;
|
||||
}
|
||||
@@ -179,7 +206,7 @@ public class BotController extends BaseCurdController<BotService, Bot> {
|
||||
messages,
|
||||
chatCheckResult,
|
||||
attachments,
|
||||
buildRuntimeContext(chatCheckResult.getAiBot(), conversationId, prompt, attachments)
|
||||
buildRuntimeContext(chatCheckResult.getAiBot(), conversationId, prompt, attachments, extraKnowledgeIds, regenerateRoundId)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -194,16 +221,24 @@ public class BotController extends BaseCurdController<BotService, Bot> {
|
||||
@GetMapping("getDetail")
|
||||
@SaIgnore
|
||||
public Result<Bot> getDetail(String id) {
|
||||
Bot bot = StpUtil.isLogin() ? botService.getDetail(id) : botService.getPublishedDetail(id);
|
||||
if (bot != null && StpUtil.isLogin()) {
|
||||
categoryPermissionService.assertCategoryResourceVisible("BOT", bot.getCreatedBy(), bot.getCategoryId(), "无权限访问聊天助手");
|
||||
boolean publishedOnly = isPublishedOnlyRequest();
|
||||
Bot rawBot = StpUtil.isLogin() ? botService.getDetail(id) : botService.getPublishedDetail(id);
|
||||
if (rawBot != null && StpUtil.isLogin()) {
|
||||
categoryPermissionService.assertCategoryResourceVisible("BOT", rawBot.getCreatedBy(), rawBot.getCategoryId(), "无权限访问聊天助手");
|
||||
}
|
||||
if (bot == null) {
|
||||
if (rawBot == null) {
|
||||
return Result.ok(null);
|
||||
}
|
||||
if (!StpUtil.isLogin() && !tech.easyflow.ai.enums.PublishStatus.from(bot.getPublishStatus()).isExternallyVisible()) {
|
||||
if (!StpUtil.isLogin() && !PublishStatus.from(rawBot.getPublishStatus()).isExternallyVisible()) {
|
||||
throw new BusinessException("聊天助手尚未发布");
|
||||
}
|
||||
Bot bot = rawBot;
|
||||
if (publishedOnly && StpUtil.isLogin()) {
|
||||
if (PublishStatus.from(rawBot.getPublishStatus()) != PublishStatus.PUBLISHED) {
|
||||
throw new BusinessException("聊天助手尚未发布");
|
||||
}
|
||||
bot = botService.toPublishedView(rawBot);
|
||||
}
|
||||
if (StpUtil.isLogin()) {
|
||||
aiResourceApprovalStateService.fillBotApprovalState(bot);
|
||||
}
|
||||
@@ -213,17 +248,25 @@ public class BotController extends BaseCurdController<BotService, Bot> {
|
||||
@Override
|
||||
@SaIgnore
|
||||
public Result<Bot> detail(String id) {
|
||||
Bot data = StpUtil.isLogin() ? botService.getDetail(id) : botService.getPublishedDetail(id);
|
||||
if (data == null) {
|
||||
return Result.ok(data);
|
||||
boolean publishedOnly = isPublishedOnlyRequest();
|
||||
Bot rawData = StpUtil.isLogin() ? botService.getDetail(id) : botService.getPublishedDetail(id);
|
||||
if (rawData == null) {
|
||||
return Result.ok(rawData);
|
||||
}
|
||||
if (StpUtil.isLogin()) {
|
||||
categoryPermissionService.assertCategoryResourceVisible("BOT", data.getCreatedBy(), data.getCategoryId(), "无权限访问聊天助手");
|
||||
categoryPermissionService.assertCategoryResourceVisible("BOT", rawData.getCreatedBy(), rawData.getCategoryId(), "无权限访问聊天助手");
|
||||
}
|
||||
|
||||
if (!StpUtil.isLogin() && !tech.easyflow.ai.enums.PublishStatus.from(data.getPublishStatus()).isExternallyVisible()) {
|
||||
if (!StpUtil.isLogin() && !PublishStatus.from(rawData.getPublishStatus()).isExternallyVisible()) {
|
||||
throw new BusinessException("聊天助手尚未发布");
|
||||
}
|
||||
Bot data = rawData;
|
||||
if (publishedOnly && StpUtil.isLogin()) {
|
||||
if (PublishStatus.from(rawData.getPublishStatus()) != PublishStatus.PUBLISHED) {
|
||||
throw new BusinessException("聊天助手尚未发布");
|
||||
}
|
||||
data = botService.toPublishedView(rawData);
|
||||
}
|
||||
|
||||
Map<String, Object> llmOptions = data.getModelOptions();
|
||||
if (llmOptions == null) {
|
||||
@@ -298,8 +341,12 @@ public class BotController extends BaseCurdController<BotService, Bot> {
|
||||
public Result<List<Bot>> list(Bot entity, Boolean asTree, String sortKey, String sortType) {
|
||||
QueryWrapper queryWrapper = QueryWrapper.create(entity, buildOperators(entity));
|
||||
applyCategoryPermission(queryWrapper);
|
||||
applyPublishedOnlyFilter(queryWrapper);
|
||||
queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy()));
|
||||
List<Bot> bots = service.list(queryWrapper);
|
||||
if (isPublishedOnlyRequest()) {
|
||||
bots = bots.stream().map(botService::toPublishedView).toList();
|
||||
}
|
||||
aiResourceApprovalStateService.fillBotApprovalState(bots);
|
||||
return Result.ok(bots);
|
||||
}
|
||||
@@ -307,7 +354,11 @@ public class BotController extends BaseCurdController<BotService, Bot> {
|
||||
@Override
|
||||
protected Page<Bot> queryPage(Page<Bot> page, QueryWrapper queryWrapper) {
|
||||
applyCategoryPermission(queryWrapper);
|
||||
applyPublishedOnlyFilter(queryWrapper);
|
||||
Page<Bot> result = super.queryPage(page, queryWrapper);
|
||||
if (isPublishedOnlyRequest()) {
|
||||
result.setRecords(result.getRecords().stream().map(botService::toPublishedView).toList());
|
||||
}
|
||||
aiResourceApprovalStateService.fillBotApprovalState(result.getRecords());
|
||||
aiResourceCreatorNameSupport.fillBotCreatorNames(result.getRecords());
|
||||
return result;
|
||||
@@ -407,7 +458,9 @@ public class BotController extends BaseCurdController<BotService, Bot> {
|
||||
return result;
|
||||
}
|
||||
|
||||
private ChatRuntimeContext buildRuntimeContext(Bot bot, BigInteger conversationId, String prompt, List<String> attachments) {
|
||||
private ChatRuntimeContext buildRuntimeContext(Bot bot, BigInteger conversationId, String prompt, List<String> attachments,
|
||||
List<BigInteger> extraKnowledgeIds,
|
||||
BigInteger regenerateRoundId) {
|
||||
LoginAccount account = requireCurrentLoginAccount();
|
||||
ChatRuntimeContext context = new ChatRuntimeContext();
|
||||
context.setChannel(ChatChannel.ADMIN);
|
||||
@@ -422,10 +475,30 @@ public class BotController extends BaseCurdController<BotService, Bot> {
|
||||
context.setAssistantName(bot == null ? null : bot.getTitle());
|
||||
context.setSessionTitle(prompt.length() > 200 ? prompt.substring(0, 200) : prompt);
|
||||
context.setAttachments(attachments);
|
||||
if (extraKnowledgeIds != null) {
|
||||
context.getExt().put(ChatRuntimeExtKeys.EXTRA_KNOWLEDGE_IDS, extraKnowledgeIds);
|
||||
}
|
||||
if (regenerateRoundId != null) {
|
||||
context.getExt().put(ChatRuntimeExtKeys.REGENERATE_ROUND_ID, regenerateRoundId);
|
||||
}
|
||||
ChatTimeToolAvailabilityContext.bindLoggedInSnapshot(context, account, bot);
|
||||
return context;
|
||||
}
|
||||
|
||||
private void applyPublishedOnlyFilter(QueryWrapper queryWrapper) {
|
||||
if (isPublishedOnlyRequest()) {
|
||||
queryWrapper.eq("publish_status", PublishStatus.PUBLISHED.getCode());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isPublishedOnlyRequest() {
|
||||
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||
if (attributes == null) {
|
||||
return false;
|
||||
}
|
||||
return "true".equalsIgnoreCase(attributes.getRequest().getParameter("publishedOnly"));
|
||||
}
|
||||
|
||||
private LoginAccount requireCurrentLoginAccount() {
|
||||
try {
|
||||
return SaTokenUtil.getLoginAccount();
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
package tech.easyflow.admin.controller.ai;
|
||||
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
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.ai.ChatWorkspaceService;
|
||||
import tech.easyflow.chatlog.domain.dto.ChatHistoryPage;
|
||||
import tech.easyflow.chatlog.domain.dto.ChatMessageRecord;
|
||||
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;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 管理端聊天工作台控制器。
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/chatWorkspace")
|
||||
public class ChatWorkspaceController {
|
||||
|
||||
private final ChatWorkspaceService chatWorkspaceService;
|
||||
|
||||
public ChatWorkspaceController(ChatWorkspaceService chatWorkspaceService) {
|
||||
this.chatWorkspaceService = chatWorkspaceService;
|
||||
}
|
||||
|
||||
@GetMapping("/sessions")
|
||||
public Result<ChatWorkspaceSessionPage> listSessions(BigInteger assistantId, ChatPageQuery query) {
|
||||
return Result.ok(chatWorkspaceService.queryCurrentUserSessions(currentAccount(), assistantId, query));
|
||||
}
|
||||
|
||||
@GetMapping("/sessions/{sessionId}")
|
||||
public Result<ChatWorkspaceSessionDetailView> getSession(@PathVariable BigInteger sessionId) {
|
||||
return Result.ok(chatWorkspaceService.getCurrentUserSession(currentAccount(), sessionId));
|
||||
}
|
||||
|
||||
@GetMapping("/sessions/{sessionId}/messages")
|
||||
public Result<ChatHistoryPage> queryMessages(@PathVariable BigInteger sessionId, ChatPageQuery query) {
|
||||
return Result.ok(chatWorkspaceService.queryCurrentUserMessages(currentAccount(), sessionId, query));
|
||||
}
|
||||
|
||||
@GetMapping("/sessions/{sessionId}/conversation")
|
||||
public Result<ChatWorkspaceConversationView> getConversation(@PathVariable BigInteger sessionId) {
|
||||
return Result.ok(chatWorkspaceService.getCurrentUserConversation(currentAccount(), sessionId));
|
||||
}
|
||||
|
||||
@PostMapping("/sessions/{sessionId}/rename")
|
||||
public Result<Void> renameSession(@PathVariable BigInteger sessionId,
|
||||
@JsonBody(value = "title", required = true) String title) {
|
||||
chatWorkspaceService.renameCurrentUserSession(currentAccount(), sessionId, title);
|
||||
return Result.ok();
|
||||
}
|
||||
|
||||
@PostMapping("/sessions/{sessionId}/delete")
|
||||
public Result<Void> deleteSession(@PathVariable BigInteger sessionId) {
|
||||
chatWorkspaceService.deleteCurrentUserSession(currentAccount(), sessionId);
|
||||
return Result.ok();
|
||||
}
|
||||
|
||||
@GetMapping("/sessions/{sessionId}/rounds/{roundId}/variants")
|
||||
public Result<List<ChatMessageRecord>> listRoundVariants(@PathVariable BigInteger sessionId,
|
||||
@PathVariable BigInteger roundId) {
|
||||
return Result.ok(chatWorkspaceService.listCurrentUserRoundVariants(currentAccount(), sessionId, roundId));
|
||||
}
|
||||
|
||||
@PostMapping("/sessions/{sessionId}/rounds/{roundId}/selectVariant")
|
||||
public Result<ChatMessageRecord> selectRoundVariant(@PathVariable BigInteger sessionId,
|
||||
@PathVariable BigInteger roundId,
|
||||
@JsonBody(value = "variantIndex", required = true) Integer variantIndex) {
|
||||
return Result.ok(chatWorkspaceService.selectCurrentUserRoundVariant(currentAccount(), sessionId, roundId, variantIndex));
|
||||
}
|
||||
|
||||
private LoginAccount currentAccount() {
|
||||
return SaTokenUtil.getLoginAccount();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package tech.easyflow.admin.dto.chatworkspace;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* 工作台助手展示快照。
|
||||
*/
|
||||
public class ChatWorkspaceAssistantView implements Serializable {
|
||||
|
||||
private BigInteger id;
|
||||
private String alias;
|
||||
private String title;
|
||||
private String description;
|
||||
private String icon;
|
||||
|
||||
public BigInteger getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(BigInteger id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getAlias() {
|
||||
return alias;
|
||||
}
|
||||
|
||||
public void setAlias(String alias) {
|
||||
this.alias = alias;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getIcon() {
|
||||
return icon;
|
||||
}
|
||||
|
||||
public void setIcon(String icon) {
|
||||
this.icon = icon;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package tech.easyflow.admin.dto.chatworkspace;
|
||||
|
||||
import tech.easyflow.chatlog.domain.dto.ChatMessageRecord;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 管理端聊天工作台完整会话视图。
|
||||
*/
|
||||
public class ChatWorkspaceConversationView implements Serializable {
|
||||
|
||||
private long total;
|
||||
private List<ChatMessageRecord> records = new ArrayList<>();
|
||||
private Map<String, List<ChatMessageRecord>> variantsByRound = new LinkedHashMap<>();
|
||||
|
||||
/**
|
||||
* 获取当前主线可见消息数量。
|
||||
*
|
||||
* @return 主线消息数量
|
||||
*/
|
||||
public long getTotal() {
|
||||
return total;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前主线可见消息数量。
|
||||
*
|
||||
* @param total 主线消息数量
|
||||
*/
|
||||
public void setTotal(long total) {
|
||||
this.total = total;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前主线可见消息。
|
||||
*
|
||||
* @return 当前主线可见消息
|
||||
*/
|
||||
public List<ChatMessageRecord> getRecords() {
|
||||
return records;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前主线可见消息。
|
||||
*
|
||||
* @param records 当前主线可见消息
|
||||
*/
|
||||
public void setRecords(List<ChatMessageRecord> records) {
|
||||
this.records = records == null ? new ArrayList<>() : records;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取按轮次分组的全部答案版本。
|
||||
*
|
||||
* @return roundId 到答案版本列表的映射
|
||||
*/
|
||||
public Map<String, List<ChatMessageRecord>> getVariantsByRound() {
|
||||
return variantsByRound;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置按轮次分组的全部答案版本。
|
||||
*
|
||||
* @param variantsByRound roundId 到答案版本列表的映射
|
||||
*/
|
||||
public void setVariantsByRound(Map<String, List<ChatMessageRecord>> variantsByRound) {
|
||||
this.variantsByRound = variantsByRound == null ? new LinkedHashMap<>() : variantsByRound;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package tech.easyflow.admin.dto.chatworkspace;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* 工作台知识库展示对象。
|
||||
*/
|
||||
public class ChatWorkspaceKnowledgeView implements Serializable {
|
||||
|
||||
private BigInteger id;
|
||||
private String alias;
|
||||
private String title;
|
||||
private String description;
|
||||
private String icon;
|
||||
|
||||
public BigInteger getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(BigInteger id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getAlias() {
|
||||
return alias;
|
||||
}
|
||||
|
||||
public void setAlias(String alias) {
|
||||
this.alias = alias;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getIcon() {
|
||||
return icon;
|
||||
}
|
||||
|
||||
public void setIcon(String icon) {
|
||||
this.icon = icon;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package tech.easyflow.admin.dto.chatworkspace;
|
||||
|
||||
/**
|
||||
* 管理端聊天工作台只读原因。
|
||||
*/
|
||||
public enum ChatWorkspaceReadOnlyReason {
|
||||
ASSISTANT_OFFLINE,
|
||||
ASSISTANT_DELETED,
|
||||
NO_PERMISSION
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package tech.easyflow.admin.dto.chatworkspace;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 工作台会话详情。
|
||||
*/
|
||||
public class ChatWorkspaceSessionDetailView extends ChatWorkspaceSessionView implements Serializable {
|
||||
|
||||
private ChatWorkspaceAssistantView assistant;
|
||||
private List<ChatWorkspaceKnowledgeView> boundKnowledges = new ArrayList<>();
|
||||
private List<ChatWorkspaceKnowledgeView> extraKnowledges = new ArrayList<>();
|
||||
private List<String> removedExtraKnowledgeNames = new ArrayList<>();
|
||||
|
||||
public ChatWorkspaceAssistantView getAssistant() {
|
||||
return assistant;
|
||||
}
|
||||
|
||||
public void setAssistant(ChatWorkspaceAssistantView assistant) {
|
||||
this.assistant = assistant;
|
||||
}
|
||||
|
||||
public List<ChatWorkspaceKnowledgeView> getBoundKnowledges() {
|
||||
return boundKnowledges;
|
||||
}
|
||||
|
||||
public void setBoundKnowledges(List<ChatWorkspaceKnowledgeView> boundKnowledges) {
|
||||
this.boundKnowledges = boundKnowledges == null ? new ArrayList<>() : boundKnowledges;
|
||||
}
|
||||
|
||||
public List<ChatWorkspaceKnowledgeView> getExtraKnowledges() {
|
||||
return extraKnowledges;
|
||||
}
|
||||
|
||||
public void setExtraKnowledges(List<ChatWorkspaceKnowledgeView> extraKnowledges) {
|
||||
this.extraKnowledges = extraKnowledges == null ? new ArrayList<>() : extraKnowledges;
|
||||
}
|
||||
|
||||
public List<String> getRemovedExtraKnowledgeNames() {
|
||||
return removedExtraKnowledgeNames;
|
||||
}
|
||||
|
||||
public void setRemovedExtraKnowledgeNames(List<String> removedExtraKnowledgeNames) {
|
||||
this.removedExtraKnowledgeNames = removedExtraKnowledgeNames == null ? new ArrayList<>() : removedExtraKnowledgeNames;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package tech.easyflow.admin.dto.chatworkspace;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 工作台会话分页结果。
|
||||
*/
|
||||
public class ChatWorkspaceSessionPage implements Serializable {
|
||||
|
||||
private Long total;
|
||||
private Long pageNumber;
|
||||
private Long pageSize;
|
||||
private List<ChatWorkspaceSessionView> records = new ArrayList<>();
|
||||
|
||||
public Long getTotal() {
|
||||
return total;
|
||||
}
|
||||
|
||||
public void setTotal(Long total) {
|
||||
this.total = total;
|
||||
}
|
||||
|
||||
public Long getPageNumber() {
|
||||
return pageNumber;
|
||||
}
|
||||
|
||||
public void setPageNumber(Long pageNumber) {
|
||||
this.pageNumber = pageNumber;
|
||||
}
|
||||
|
||||
public Long getPageSize() {
|
||||
return pageSize;
|
||||
}
|
||||
|
||||
public void setPageSize(Long pageSize) {
|
||||
this.pageSize = pageSize;
|
||||
}
|
||||
|
||||
public List<ChatWorkspaceSessionView> getRecords() {
|
||||
return records;
|
||||
}
|
||||
|
||||
public void setRecords(List<ChatWorkspaceSessionView> records) {
|
||||
this.records = records == null ? new ArrayList<>() : records;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package tech.easyflow.admin.dto.chatworkspace;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigInteger;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 工作台会话摘要。
|
||||
*/
|
||||
public class ChatWorkspaceSessionView implements Serializable {
|
||||
|
||||
private BigInteger sessionId;
|
||||
private BigInteger assistantId;
|
||||
private String assistantCode;
|
||||
private String assistantName;
|
||||
private String title;
|
||||
private String lastMessagePreview;
|
||||
private Integer messageCount;
|
||||
private Date accessAt;
|
||||
private Date lastMessageAt;
|
||||
private Boolean continuable;
|
||||
private ChatWorkspaceReadOnlyReason readOnlyReason;
|
||||
|
||||
public BigInteger getSessionId() {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
public void setSessionId(BigInteger sessionId) {
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
|
||||
public BigInteger getAssistantId() {
|
||||
return assistantId;
|
||||
}
|
||||
|
||||
public void setAssistantId(BigInteger assistantId) {
|
||||
this.assistantId = assistantId;
|
||||
}
|
||||
|
||||
public String getAssistantCode() {
|
||||
return assistantCode;
|
||||
}
|
||||
|
||||
public void setAssistantCode(String assistantCode) {
|
||||
this.assistantCode = assistantCode;
|
||||
}
|
||||
|
||||
public String getAssistantName() {
|
||||
return assistantName;
|
||||
}
|
||||
|
||||
public void setAssistantName(String assistantName) {
|
||||
this.assistantName = assistantName;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public String getLastMessagePreview() {
|
||||
return lastMessagePreview;
|
||||
}
|
||||
|
||||
public void setLastMessagePreview(String lastMessagePreview) {
|
||||
this.lastMessagePreview = lastMessagePreview;
|
||||
}
|
||||
|
||||
public Integer getMessageCount() {
|
||||
return messageCount;
|
||||
}
|
||||
|
||||
public void setMessageCount(Integer messageCount) {
|
||||
this.messageCount = messageCount;
|
||||
}
|
||||
|
||||
public Date getAccessAt() {
|
||||
return accessAt;
|
||||
}
|
||||
|
||||
public void setAccessAt(Date accessAt) {
|
||||
this.accessAt = accessAt;
|
||||
}
|
||||
|
||||
public Date getLastMessageAt() {
|
||||
return lastMessageAt;
|
||||
}
|
||||
|
||||
public void setLastMessageAt(Date lastMessageAt) {
|
||||
this.lastMessageAt = lastMessageAt;
|
||||
}
|
||||
|
||||
public Boolean getContinuable() {
|
||||
return continuable;
|
||||
}
|
||||
|
||||
public void setContinuable(Boolean continuable) {
|
||||
this.continuable = continuable;
|
||||
}
|
||||
|
||||
public ChatWorkspaceReadOnlyReason getReadOnlyReason() {
|
||||
return readOnlyReason;
|
||||
}
|
||||
|
||||
public void setReadOnlyReason(ChatWorkspaceReadOnlyReason readOnlyReason) {
|
||||
this.readOnlyReason = readOnlyReason;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,515 @@
|
||||
package tech.easyflow.admin.service.ai;
|
||||
|
||||
import com.mybatisflex.core.query.QueryWrapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
import tech.easyflow.admin.dto.chatworkspace.ChatWorkspaceAssistantView;
|
||||
import tech.easyflow.admin.dto.chatworkspace.ChatWorkspaceConversationView;
|
||||
import tech.easyflow.admin.dto.chatworkspace.ChatWorkspaceKnowledgeView;
|
||||
import tech.easyflow.admin.dto.chatworkspace.ChatWorkspaceReadOnlyReason;
|
||||
import tech.easyflow.admin.dto.chatworkspace.ChatWorkspaceSessionDetailView;
|
||||
import tech.easyflow.admin.dto.chatworkspace.ChatWorkspaceSessionPage;
|
||||
import tech.easyflow.admin.dto.chatworkspace.ChatWorkspaceSessionView;
|
||||
import tech.easyflow.ai.entity.Bot;
|
||||
import tech.easyflow.ai.entity.DocumentCollection;
|
||||
import tech.easyflow.ai.enums.PublishStatus;
|
||||
import tech.easyflow.ai.permission.KnowledgeReadAccessSnapshot;
|
||||
import tech.easyflow.ai.permission.KnowledgeVisibilityQueryHelper;
|
||||
import tech.easyflow.ai.service.BotService;
|
||||
import tech.easyflow.ai.service.DocumentCollectionService;
|
||||
import tech.easyflow.chatlog.domain.command.ChatSessionUpsertCommand;
|
||||
import tech.easyflow.chatlog.domain.dto.ChatHistoryPage;
|
||||
import tech.easyflow.chatlog.domain.dto.ChatMessageRecord;
|
||||
import tech.easyflow.chatlog.domain.dto.ChatSessionExtPayload;
|
||||
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.ChatRoundOperateService;
|
||||
import tech.easyflow.chatlog.service.ChatSessionCommandService;
|
||||
import tech.easyflow.chatlog.service.ChatSessionQueryService;
|
||||
import tech.easyflow.chatlog.support.ChatJsonSupport;
|
||||
import tech.easyflow.common.entity.LoginAccount;
|
||||
import tech.easyflow.common.web.exceptions.BusinessException;
|
||||
import tech.easyflow.system.entity.vo.RoleCategoryAccessSnapshot;
|
||||
import tech.easyflow.system.service.CategoryPermissionService;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import static tech.easyflow.ai.entity.table.BotTableDef.BOT;
|
||||
|
||||
/**
|
||||
* 管理端聊天工作台服务。
|
||||
*/
|
||||
@Service
|
||||
public class ChatWorkspaceService {
|
||||
|
||||
private final ChatSessionQueryService chatSessionQueryService;
|
||||
private final ChatSessionCommandService chatSessionCommandService;
|
||||
private final ChatRoundOperateService chatRoundOperateService;
|
||||
private final BotService botService;
|
||||
private final DocumentCollectionService documentCollectionService;
|
||||
private final CategoryPermissionService categoryPermissionService;
|
||||
private final KnowledgeVisibilityQueryHelper knowledgeVisibilityQueryHelper;
|
||||
private final ChatJsonSupport chatJsonSupport;
|
||||
|
||||
public ChatWorkspaceService(ChatSessionQueryService chatSessionQueryService,
|
||||
ChatSessionCommandService chatSessionCommandService,
|
||||
ChatRoundOperateService chatRoundOperateService,
|
||||
BotService botService,
|
||||
DocumentCollectionService documentCollectionService,
|
||||
CategoryPermissionService categoryPermissionService,
|
||||
KnowledgeVisibilityQueryHelper knowledgeVisibilityQueryHelper,
|
||||
ChatJsonSupport chatJsonSupport) {
|
||||
this.chatSessionQueryService = chatSessionQueryService;
|
||||
this.chatSessionCommandService = chatSessionCommandService;
|
||||
this.chatRoundOperateService = chatRoundOperateService;
|
||||
this.botService = botService;
|
||||
this.documentCollectionService = documentCollectionService;
|
||||
this.categoryPermissionService = categoryPermissionService;
|
||||
this.knowledgeVisibilityQueryHelper = knowledgeVisibilityQueryHelper;
|
||||
this.chatJsonSupport = chatJsonSupport;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询当前用户会话分页。
|
||||
*
|
||||
* @param account 当前登录用户
|
||||
* @param assistantId 助手过滤条件
|
||||
* @param query 分页参数
|
||||
* @return 工作台会话分页
|
||||
*/
|
||||
public ChatWorkspaceSessionPage queryCurrentUserSessions(LoginAccount account, BigInteger assistantId, ChatPageQuery query) {
|
||||
ChatSessionPage page = chatSessionQueryService.pageSessions(account.getId(), assistantId, query);
|
||||
Map<BigInteger, AssistantAvailability> availabilityMap = resolveAssistantAvailability(account, 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询当前用户会话详情。
|
||||
*
|
||||
* @param account 当前登录用户
|
||||
* @param sessionId 会话 ID
|
||||
* @return 工作台会话详情
|
||||
*/
|
||||
public ChatWorkspaceSessionDetailView getCurrentUserSession(LoginAccount account, BigInteger sessionId) {
|
||||
ChatSessionSummary summary = requireUserSession(account, sessionId);
|
||||
AssistantAvailability availability = resolveAssistantAvailability(account, List.of(summary)).get(summary.getAssistantId());
|
||||
ChatWorkspaceSessionDetailView detail = new ChatWorkspaceSessionDetailView();
|
||||
fillSessionView(detail, summary, availability);
|
||||
if (availability != null && availability.displayBot() != null) {
|
||||
detail.setAssistant(toAssistantView(availability.displayBot(), summary));
|
||||
detail.setBoundKnowledges(resolveBoundKnowledges(availability.displayBot()));
|
||||
} else {
|
||||
detail.setAssistant(toAssistantView(null, summary));
|
||||
}
|
||||
ExtraKnowledgeResolution extraKnowledgeResolution = resolveExtraKnowledges(summary);
|
||||
detail.setExtraKnowledges(extraKnowledgeResolution.validKnowledges());
|
||||
detail.setRemovedExtraKnowledgeNames(extraKnowledgeResolution.removedNames());
|
||||
if (extraKnowledgeResolution.shouldSync()) {
|
||||
syncSessionExtraKnowledges(summary, extraKnowledgeResolution.validKnowledgeIds(), account.getId());
|
||||
}
|
||||
return detail;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询当前用户会话消息。
|
||||
*
|
||||
* @param account 当前登录用户
|
||||
* @param sessionId 会话 ID
|
||||
* @param query 分页参数
|
||||
* @return 历史消息分页
|
||||
*/
|
||||
public ChatHistoryPage queryCurrentUserMessages(LoginAccount account, BigInteger sessionId, ChatPageQuery query) {
|
||||
ChatSessionSummary summary = requireUserSession(account, sessionId);
|
||||
ChatHistoryPage firstPage = restoreRecentMessages(summary, query);
|
||||
if (firstPage != null) {
|
||||
return firstPage;
|
||||
}
|
||||
return chatSessionQueryService.pageMainlineMessages(sessionId, query);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询当前用户完整工作台会话。
|
||||
*
|
||||
* @param account 当前登录用户
|
||||
* @param sessionId 会话 ID
|
||||
* @return 完整会话视图
|
||||
*/
|
||||
public ChatWorkspaceConversationView getCurrentUserConversation(LoginAccount account, BigInteger sessionId) {
|
||||
requireUserSession(account, sessionId);
|
||||
List<ChatMessageRecord> records = chatSessionQueryService.listMainlineMessages(sessionId);
|
||||
Map<String, List<ChatMessageRecord>> variantsByRound = new LinkedHashMap<>();
|
||||
Set<BigInteger> roundIds = new LinkedHashSet<>();
|
||||
for (ChatMessageRecord record : records) {
|
||||
if (record == null || record.getRoundId() == null) {
|
||||
continue;
|
||||
}
|
||||
Integer variantCount = record.getVariantCount();
|
||||
if (variantCount != null && variantCount > 1) {
|
||||
roundIds.add(record.getRoundId());
|
||||
}
|
||||
}
|
||||
for (BigInteger roundId : roundIds) {
|
||||
variantsByRound.put(roundId.toString(), chatRoundOperateService.listVariants(sessionId, roundId));
|
||||
}
|
||||
ChatWorkspaceConversationView view = new ChatWorkspaceConversationView();
|
||||
view.setRecords(records);
|
||||
view.setVariantsByRound(variantsByRound);
|
||||
view.setTotal(records.size());
|
||||
return view;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重命名当前用户会话。
|
||||
*
|
||||
* @param account 当前登录用户
|
||||
* @param sessionId 会话 ID
|
||||
* @param title 新标题
|
||||
*/
|
||||
public void renameCurrentUserSession(LoginAccount account, BigInteger sessionId, String title) {
|
||||
if (!StringUtils.hasText(title)) {
|
||||
throw new BusinessException("标题不能为空");
|
||||
}
|
||||
requireUserSession(account, sessionId);
|
||||
chatSessionCommandService.renameSession(sessionId, account.getId(), title.trim(), account.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除当前用户会话。
|
||||
*
|
||||
* @param account 当前登录用户
|
||||
* @param sessionId 会话 ID
|
||||
*/
|
||||
public void deleteCurrentUserSession(LoginAccount account, BigInteger sessionId) {
|
||||
requireUserSession(account, sessionId);
|
||||
chatSessionCommandService.deleteSession(sessionId, account.getId(), account.getId());
|
||||
}
|
||||
|
||||
public List<ChatMessageRecord> listCurrentUserRoundVariants(LoginAccount account, BigInteger sessionId, BigInteger roundId) {
|
||||
requireUserSession(account, sessionId);
|
||||
return chatRoundOperateService.listVariants(sessionId, roundId);
|
||||
}
|
||||
|
||||
public ChatMessageRecord selectCurrentUserRoundVariant(LoginAccount account, BigInteger sessionId, BigInteger roundId, Integer variantIndex) {
|
||||
requireUserSession(account, sessionId);
|
||||
return chatRoundOperateService.selectVariant(sessionId, roundId, variantIndex, account.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送前校验会话是否仍可继续聊天。
|
||||
*
|
||||
* @param account 当前登录用户
|
||||
* @param sessionId 会话 ID
|
||||
* @param requestBotId 本次请求助手 ID
|
||||
*/
|
||||
public void assertSessionContinuable(LoginAccount account, BigInteger sessionId, BigInteger requestBotId) {
|
||||
ChatSessionSummary summary = chatSessionQueryService.getSessionSummary(sessionId);
|
||||
if (summary == null || Integer.valueOf(1).equals(summary.getIsDeleted())) {
|
||||
return;
|
||||
}
|
||||
if (!Objects.equals(summary.getUserId(), account.getId())) {
|
||||
throw new BusinessException("无权访问该会话");
|
||||
}
|
||||
if (requestBotId != null && summary.getAssistantId() != null && !Objects.equals(summary.getAssistantId(), requestBotId)) {
|
||||
throw new BusinessException("当前会话与所选聊天助手不匹配");
|
||||
}
|
||||
AssistantAvailability availability = resolveAssistantAvailability(account, List.of(summary)).get(summary.getAssistantId());
|
||||
if (availability == null || !availability.continuable()) {
|
||||
throw new BusinessException(buildReadOnlyMessage(availability == null ? ChatWorkspaceReadOnlyReason.ASSISTANT_DELETED : availability.reason()));
|
||||
}
|
||||
}
|
||||
|
||||
private ChatSessionSummary requireUserSession(LoginAccount account, BigInteger sessionId) {
|
||||
ChatSessionSummary summary = chatSessionQueryService.getSessionSummary(sessionId);
|
||||
if (summary == null || Integer.valueOf(1).equals(summary.getIsDeleted())) {
|
||||
throw new BusinessException("会话不存在");
|
||||
}
|
||||
if (!Objects.equals(summary.getUserId(), account.getId())) {
|
||||
throw new BusinessException("无权访问该会话");
|
||||
}
|
||||
return summary;
|
||||
}
|
||||
|
||||
/**
|
||||
* 首屏优先从热态恢复最近消息,避免分析库延迟导致刚完成的回复不可见。
|
||||
*
|
||||
* @param summary 会话摘要
|
||||
* @param query 分页参数
|
||||
* @return 命中热态时返回恢复结果,否则返回 null 继续走历史库
|
||||
*/
|
||||
private ChatHistoryPage restoreRecentMessages(ChatSessionSummary summary, ChatPageQuery query) {
|
||||
if (summary == null || query == null || query.getPageNumber() != 1) {
|
||||
return null;
|
||||
}
|
||||
List<tech.easyflow.chatlog.domain.dto.ChatMessageRecord> records =
|
||||
chatSessionQueryService.getRecentTail(summary.getId(), Math.toIntExact(query.getPageSize()));
|
||||
if (records == null || records.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
if (!isRestoredTailReliable(records)) {
|
||||
return null;
|
||||
}
|
||||
ChatHistoryPage page = new ChatHistoryPage();
|
||||
page.setPageNumber(query.getPageNumber());
|
||||
page.setPageSize(query.getPageSize());
|
||||
page.setRecords(records);
|
||||
long total = summary.getMessageCount() == null ? 0L : summary.getMessageCount();
|
||||
page.setTotal(Math.max(total, records.size()));
|
||||
return page;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验 Redis tail 是否仍符合当前主线版本语义。
|
||||
*
|
||||
* @param records Redis tail 消息
|
||||
* @return true 表示可直接用于首屏恢复
|
||||
*/
|
||||
private boolean isRestoredTailReliable(List<ChatMessageRecord> records) {
|
||||
Map<BigInteger, Integer> selectedVariantByRound = new LinkedHashMap<>();
|
||||
Map<BigInteger, Set<Integer>> assistantVariantsByRound = new LinkedHashMap<>();
|
||||
for (ChatMessageRecord record : records) {
|
||||
if (record == null || record.getRoundId() == null) {
|
||||
continue;
|
||||
}
|
||||
Integer selectedVariantIndex = record.getSelectedVariantIndex();
|
||||
if (selectedVariantIndex != null && selectedVariantIndex > 0) {
|
||||
Integer previous = selectedVariantByRound.putIfAbsent(record.getRoundId(), selectedVariantIndex);
|
||||
if (previous != null && !Objects.equals(previous, selectedVariantIndex)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if ("assistant".equalsIgnoreCase(record.getSenderRole())
|
||||
&& record.getVariantIndex() != null
|
||||
&& record.getVariantIndex() > 0) {
|
||||
assistantVariantsByRound
|
||||
.computeIfAbsent(record.getRoundId(), key -> new LinkedHashSet<>())
|
||||
.add(record.getVariantIndex());
|
||||
}
|
||||
}
|
||||
for (Map.Entry<BigInteger, Integer> entry : selectedVariantByRound.entrySet()) {
|
||||
Set<Integer> visibleVariants = assistantVariantsByRound.get(entry.getKey());
|
||||
if (visibleVariants != null && !visibleVariants.isEmpty() && !visibleVariants.contains(entry.getValue())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private Map<BigInteger, AssistantAvailability> resolveAssistantAvailability(LoginAccount account, List<ChatSessionSummary> sessions) {
|
||||
Map<BigInteger, AssistantAvailability> result = new LinkedHashMap<>();
|
||||
if (sessions == null || sessions.isEmpty()) {
|
||||
return result;
|
||||
}
|
||||
Set<BigInteger> assistantIds = new LinkedHashSet<>();
|
||||
for (ChatSessionSummary session : sessions) {
|
||||
if (session != null && session.getAssistantId() != null) {
|
||||
assistantIds.add(session.getAssistantId());
|
||||
}
|
||||
}
|
||||
if (assistantIds.isEmpty()) {
|
||||
return result;
|
||||
}
|
||||
List<Bot> bots = botService.list(QueryWrapper.create().where(BOT.ID.in(assistantIds)));
|
||||
Map<BigInteger, Bot> botMap = new LinkedHashMap<>();
|
||||
for (Bot bot : bots) {
|
||||
botMap.put(bot.getId(), bot);
|
||||
}
|
||||
RoleCategoryAccessSnapshot accessSnapshot = categoryPermissionService.getAccess("BOT", account);
|
||||
for (BigInteger assistantId : assistantIds) {
|
||||
Bot currentBot = botMap.get(assistantId);
|
||||
if (currentBot == null) {
|
||||
result.put(assistantId, new AssistantAvailability(false, ChatWorkspaceReadOnlyReason.ASSISTANT_DELETED, null));
|
||||
continue;
|
||||
}
|
||||
if (!accessSnapshot.canAccess(currentBot.getCreatedBy(), currentBot.getCategoryId())) {
|
||||
result.put(assistantId, new AssistantAvailability(false, ChatWorkspaceReadOnlyReason.NO_PERMISSION, null));
|
||||
continue;
|
||||
}
|
||||
Bot displayBot = botService.toPublishedView(currentBot);
|
||||
boolean online = Integer.valueOf(1).equals(currentBot.getStatus())
|
||||
&& PublishStatus.from(currentBot.getPublishStatus()) == PublishStatus.PUBLISHED;
|
||||
result.put(assistantId, new AssistantAvailability(
|
||||
online,
|
||||
online ? null : ChatWorkspaceReadOnlyReason.ASSISTANT_OFFLINE,
|
||||
displayBot
|
||||
));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private ChatWorkspaceSessionView toSessionView(ChatSessionSummary summary, AssistantAvailability availability) {
|
||||
ChatWorkspaceSessionView view = new ChatWorkspaceSessionView();
|
||||
fillSessionView(view, summary, availability);
|
||||
return view;
|
||||
}
|
||||
|
||||
private void fillSessionView(ChatWorkspaceSessionView view, ChatSessionSummary summary, AssistantAvailability 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(Bot bot, ChatSessionSummary summary) {
|
||||
ChatWorkspaceAssistantView view = new ChatWorkspaceAssistantView();
|
||||
if (bot != null) {
|
||||
view.setId(bot.getId());
|
||||
view.setAlias(bot.getAlias());
|
||||
view.setTitle(bot.getTitle());
|
||||
view.setDescription(bot.getDescription());
|
||||
view.setIcon(bot.getIcon());
|
||||
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;
|
||||
}
|
||||
|
||||
private List<ChatWorkspaceKnowledgeView> resolveBoundKnowledges(Bot displayBot) {
|
||||
if (displayBot == null || displayBot.getPublishedSnapshotJson() == null) {
|
||||
return List.of();
|
||||
}
|
||||
Object rawBindings = displayBot.getPublishedSnapshotJson().get("knowledgeBindings");
|
||||
if (!(rawBindings instanceof List<?> bindings) || bindings.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
List<BigInteger> knowledgeIds = new ArrayList<>();
|
||||
for (Object binding : bindings) {
|
||||
if (!(binding instanceof Map<?, ?> bindingMap) || bindingMap.get("knowledgeId") == null) {
|
||||
continue;
|
||||
}
|
||||
knowledgeIds.add(new BigInteger(String.valueOf(bindingMap.get("knowledgeId"))));
|
||||
}
|
||||
return resolveVisibleKnowledgeViews(knowledgeIds).validKnowledges();
|
||||
}
|
||||
|
||||
private ExtraKnowledgeResolution resolveExtraKnowledges(ChatSessionSummary summary) {
|
||||
ChatSessionExtPayload payload = chatJsonSupport.fromJson(summary.getExtJson(), ChatSessionExtPayload.class);
|
||||
List<BigInteger> extraKnowledgeIds = payload == null ? List.of() : payload.getExtraKnowledgeIds();
|
||||
return resolveVisibleKnowledgeViews(extraKnowledgeIds);
|
||||
}
|
||||
|
||||
private ExtraKnowledgeResolution resolveVisibleKnowledgeViews(List<BigInteger> knowledgeIds) {
|
||||
if (knowledgeIds == null || knowledgeIds.isEmpty()) {
|
||||
return new ExtraKnowledgeResolution(List.of(), List.of(), List.of(), false);
|
||||
}
|
||||
List<BigInteger> normalizedIds = new ArrayList<>();
|
||||
for (BigInteger knowledgeId : knowledgeIds) {
|
||||
if (knowledgeId != null && !normalizedIds.contains(knowledgeId)) {
|
||||
normalizedIds.add(knowledgeId);
|
||||
}
|
||||
}
|
||||
if (normalizedIds.isEmpty()) {
|
||||
return new ExtraKnowledgeResolution(List.of(), List.of(), List.of(), false);
|
||||
}
|
||||
List<DocumentCollection> collections = documentCollectionService.listByIds(normalizedIds);
|
||||
Map<BigInteger, DocumentCollection> collectionMap = new LinkedHashMap<>();
|
||||
for (DocumentCollection collection : collections) {
|
||||
collectionMap.put(collection.getId(), collection);
|
||||
}
|
||||
KnowledgeReadAccessSnapshot accessSnapshot = knowledgeVisibilityQueryHelper.getCurrentReadSnapshot();
|
||||
List<ChatWorkspaceKnowledgeView> validKnowledges = new ArrayList<>();
|
||||
List<BigInteger> validKnowledgeIds = new ArrayList<>();
|
||||
List<String> removedNames = new ArrayList<>();
|
||||
boolean changed = false;
|
||||
for (BigInteger knowledgeId : normalizedIds) {
|
||||
DocumentCollection current = collectionMap.get(knowledgeId);
|
||||
if (current == null) {
|
||||
removedNames.add("知识库#" + knowledgeId);
|
||||
changed = true;
|
||||
continue;
|
||||
}
|
||||
if (PublishStatus.from(current.getPublishStatus()) != PublishStatus.PUBLISHED) {
|
||||
removedNames.add(current.getTitle());
|
||||
changed = true;
|
||||
continue;
|
||||
}
|
||||
if (!knowledgeVisibilityQueryHelper.canRead(current, accessSnapshot)) {
|
||||
removedNames.add(current.getTitle());
|
||||
changed = true;
|
||||
continue;
|
||||
}
|
||||
validKnowledges.add(toKnowledgeView(documentCollectionService.toPublishedView(current)));
|
||||
validKnowledgeIds.add(current.getId());
|
||||
}
|
||||
if (!Objects.equals(normalizedIds, validKnowledgeIds)) {
|
||||
changed = true;
|
||||
}
|
||||
return new ExtraKnowledgeResolution(validKnowledges, validKnowledgeIds, removedNames, changed);
|
||||
}
|
||||
|
||||
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 void syncSessionExtraKnowledges(ChatSessionSummary summary, List<BigInteger> validKnowledgeIds, BigInteger operatorId) {
|
||||
ChatSessionExtPayload payload = new ChatSessionExtPayload();
|
||||
payload.setExtraKnowledgeIds(validKnowledgeIds);
|
||||
ChatSessionUpsertCommand command = new ChatSessionUpsertCommand();
|
||||
command.setSessionId(summary.getId());
|
||||
command.setTenantId(summary.getTenantId());
|
||||
command.setDeptId(summary.getDeptId());
|
||||
command.setUserId(summary.getUserId());
|
||||
command.setUserAccount(summary.getUserAccount());
|
||||
command.setAssistantId(summary.getAssistantId());
|
||||
command.setAssistantCode(summary.getAssistantCode());
|
||||
command.setAssistantName(summary.getAssistantName());
|
||||
command.setTitle(summary.getTitle());
|
||||
command.setExtJson(chatJsonSupport.toJson(payload));
|
||||
command.setOperatorId(operatorId);
|
||||
command.setOperateAt(new Date());
|
||||
chatSessionCommandService.createOrTouchSession(command);
|
||||
}
|
||||
|
||||
private String buildReadOnlyMessage(ChatWorkspaceReadOnlyReason reason) {
|
||||
if (reason == ChatWorkspaceReadOnlyReason.NO_PERMISSION) {
|
||||
return "当前会话对应的聊天助手已无权限访问,仅支持查看历史记录";
|
||||
}
|
||||
if (reason == ChatWorkspaceReadOnlyReason.ASSISTANT_OFFLINE) {
|
||||
return "当前会话对应的聊天助手已下架,无法继续聊天";
|
||||
}
|
||||
return "当前会话对应的聊天助手已删除,无法继续聊天";
|
||||
}
|
||||
|
||||
private record AssistantAvailability(boolean continuable,
|
||||
ChatWorkspaceReadOnlyReason reason,
|
||||
Bot displayBot) {
|
||||
}
|
||||
|
||||
private record ExtraKnowledgeResolution(List<ChatWorkspaceKnowledgeView> validKnowledges,
|
||||
List<BigInteger> validKnowledgeIds,
|
||||
List<String> removedNames,
|
||||
boolean shouldSync) {
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user