feat: 切换管理端聊天记录至智能体会话
- 管理端固定筛选 Agent 会话并拒绝旧 Bot 历史访问 - 筛选候选及页面语义切换为智能体并补充前后端回归测试
This commit is contained in:
@@ -5,9 +5,13 @@ import org.springframework.format.annotation.DateTimeFormat;
|
|||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 管理端聊天会话筛选条件。
|
||||||
|
*/
|
||||||
public class ChatSessionFilterQuery extends ChatPageQuery {
|
public class ChatSessionFilterQuery extends ChatPageQuery {
|
||||||
|
|
||||||
private BigInteger assistantId;
|
private BigInteger assistantId;
|
||||||
|
private String assistantCode;
|
||||||
private BigInteger userId;
|
private BigInteger userId;
|
||||||
private String userAccount;
|
private String userAccount;
|
||||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
@@ -15,42 +19,110 @@ public class ChatSessionFilterQuery extends ChatPageQuery {
|
|||||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
private Date endTime;
|
private Date endTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取助手 ID。
|
||||||
|
*
|
||||||
|
* @return 助手 ID
|
||||||
|
*/
|
||||||
public BigInteger getAssistantId() {
|
public BigInteger getAssistantId() {
|
||||||
return assistantId;
|
return assistantId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置助手 ID。
|
||||||
|
*
|
||||||
|
* @param assistantId 助手 ID
|
||||||
|
*/
|
||||||
public void setAssistantId(BigInteger assistantId) {
|
public void setAssistantId(BigInteger assistantId) {
|
||||||
this.assistantId = assistantId;
|
this.assistantId = assistantId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取助手类型编码。
|
||||||
|
*
|
||||||
|
* @return 助手类型编码
|
||||||
|
*/
|
||||||
|
public String getAssistantCode() {
|
||||||
|
return assistantCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置助手类型编码。
|
||||||
|
*
|
||||||
|
* @param assistantCode 助手类型编码
|
||||||
|
*/
|
||||||
|
public void setAssistantCode(String assistantCode) {
|
||||||
|
this.assistantCode = assistantCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户 ID。
|
||||||
|
*
|
||||||
|
* @return 用户 ID
|
||||||
|
*/
|
||||||
public BigInteger getUserId() {
|
public BigInteger getUserId() {
|
||||||
return userId;
|
return userId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置用户 ID。
|
||||||
|
*
|
||||||
|
* @param userId 用户 ID
|
||||||
|
*/
|
||||||
public void setUserId(BigInteger userId) {
|
public void setUserId(BigInteger userId) {
|
||||||
this.userId = userId;
|
this.userId = userId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户账号筛选值。
|
||||||
|
*
|
||||||
|
* @return 用户账号筛选值
|
||||||
|
*/
|
||||||
public String getUserAccount() {
|
public String getUserAccount() {
|
||||||
return userAccount;
|
return userAccount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置用户账号筛选值。
|
||||||
|
*
|
||||||
|
* @param userAccount 用户账号筛选值
|
||||||
|
*/
|
||||||
public void setUserAccount(String userAccount) {
|
public void setUserAccount(String userAccount) {
|
||||||
this.userAccount = userAccount;
|
this.userAccount = userAccount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取开始时间。
|
||||||
|
*
|
||||||
|
* @return 开始时间
|
||||||
|
*/
|
||||||
public Date getStartTime() {
|
public Date getStartTime() {
|
||||||
return startTime;
|
return startTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置开始时间。
|
||||||
|
*
|
||||||
|
* @param startTime 开始时间
|
||||||
|
*/
|
||||||
public void setStartTime(Date startTime) {
|
public void setStartTime(Date startTime) {
|
||||||
this.startTime = startTime;
|
this.startTime = startTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取结束时间。
|
||||||
|
*
|
||||||
|
* @return 结束时间
|
||||||
|
*/
|
||||||
public Date getEndTime() {
|
public Date getEndTime() {
|
||||||
return endTime;
|
return endTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置结束时间。
|
||||||
|
*
|
||||||
|
* @param endTime 结束时间
|
||||||
|
*/
|
||||||
public void setEndTime(Date endTime) {
|
public void setEndTime(Date endTime) {
|
||||||
this.endTime = endTime;
|
this.endTime = endTime;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -732,6 +732,10 @@ public class ChatAnalyticalDBRepository {
|
|||||||
if (query == null) {
|
if (query == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (StringUtils.hasText(query.getAssistantCode())) {
|
||||||
|
sql.append(" AND assistant_code=?");
|
||||||
|
args.add(query.getAssistantCode().trim());
|
||||||
|
}
|
||||||
if (query.getAssistantId() != null) {
|
if (query.getAssistantId() != null) {
|
||||||
sql.append(" AND assistant_id=?");
|
sql.append(" AND assistant_id=?");
|
||||||
args.add(query.getAssistantId());
|
args.add(query.getAssistantId());
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ import java.math.BigInteger;
|
|||||||
@Service
|
@Service
|
||||||
public class ChatHistoryManageServiceImpl implements ChatHistoryManageService {
|
public class ChatHistoryManageServiceImpl implements ChatHistoryManageService {
|
||||||
|
|
||||||
|
private static final String ADMIN_ASSISTANT_CODE = "AGENT";
|
||||||
|
|
||||||
private final ChatSessionQueryService chatSessionQueryService;
|
private final ChatSessionQueryService chatSessionQueryService;
|
||||||
private final ChatSessionCommandService chatSessionCommandService;
|
private final ChatSessionCommandService chatSessionCommandService;
|
||||||
private final ChatHistoryQueryService chatHistoryQueryService;
|
private final ChatHistoryQueryService chatHistoryQueryService;
|
||||||
@@ -46,7 +48,10 @@ public class ChatHistoryManageServiceImpl implements ChatHistoryManageService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ChatSessionPage queryAdminSessions(ChatSessionFilterQuery query) {
|
public ChatSessionPage queryAdminSessions(ChatSessionFilterQuery query) {
|
||||||
return chatAnalyticalDBRepository.pageSessions(query);
|
ChatSessionFilterQuery effectiveQuery = query == null ? new ChatSessionFilterQuery() : query;
|
||||||
|
// 管理端聊天历史已经切换为 Agent 专属入口,类型由服务端固定,避免客户端绕过。
|
||||||
|
effectiveQuery.setAssistantCode(ADMIN_ASSISTANT_CODE);
|
||||||
|
return chatAnalyticalDBRepository.pageSessions(effectiveQuery);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -64,8 +69,10 @@ public class ChatHistoryManageServiceImpl implements ChatHistoryManageService {
|
|||||||
@Override
|
@Override
|
||||||
public ChatSessionSummary getAdminSession(BigInteger sessionId) {
|
public ChatSessionSummary getAdminSession(BigInteger sessionId) {
|
||||||
ChatSessionSummary summary = chatAnalyticalDBRepository.getSession(sessionId);
|
ChatSessionSummary summary = chatAnalyticalDBRepository.getSession(sessionId);
|
||||||
if (summary == null || summary.getIsDeleted() != null && summary.getIsDeleted() == 1) {
|
if (summary == null
|
||||||
throw new BusinessException("会话不存在");
|
|| Integer.valueOf(1).equals(summary.getIsDeleted())
|
||||||
|
|| !ADMIN_ASSISTANT_CODE.equals(summary.getAssistantCode())) {
|
||||||
|
throw new BusinessException("Agent 会话不存在");
|
||||||
}
|
}
|
||||||
return summary;
|
return summary;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import org.springframework.beans.factory.support.StaticListableBeanFactory;
|
|||||||
import org.springframework.jdbc.core.ParameterizedPreparedStatementSetter;
|
import org.springframework.jdbc.core.ParameterizedPreparedStatementSetter;
|
||||||
import org.springframework.jdbc.core.RowMapper;
|
import org.springframework.jdbc.core.RowMapper;
|
||||||
import tech.easyflow.chatlog.domain.dto.ChatDashboardSummary;
|
import tech.easyflow.chatlog.domain.dto.ChatDashboardSummary;
|
||||||
|
import tech.easyflow.chatlog.domain.query.ChatSessionFilterQuery;
|
||||||
import tech.easyflow.chatlog.support.ChatJsonSupport;
|
import tech.easyflow.chatlog.support.ChatJsonSupport;
|
||||||
import tech.easyflow.common.analyticaldb.config.AnalyticalDBFlywayProperties;
|
import tech.easyflow.common.analyticaldb.config.AnalyticalDBFlywayProperties;
|
||||||
import tech.easyflow.common.analyticaldb.core.AnalyticalDBOperations;
|
import tech.easyflow.common.analyticaldb.core.AnalyticalDBOperations;
|
||||||
@@ -26,6 +27,27 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public class ChatAnalyticalDBRepositoryTest {
|
public class ChatAnalyticalDBRepositoryTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证会话分页的统计 SQL 与数据 SQL 使用相同的 Agent 类型和 ID 条件。
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void shouldApplyAssistantCodeAndIdToSessionPageQueries() {
|
||||||
|
RecordingAnalyticalDBOperations operations = new RecordingAnalyticalDBOperations();
|
||||||
|
ChatSessionFilterQuery query = new ChatSessionFilterQuery();
|
||||||
|
query.setAssistantCode("AGENT");
|
||||||
|
query.setAssistantId(BigInteger.TEN);
|
||||||
|
|
||||||
|
ChatAnalyticalDBRepository repository = newRepository(operations);
|
||||||
|
repository.pageSessions(query);
|
||||||
|
|
||||||
|
Assert.assertTrue(operations.lastPageCountSql.contains("assistant_code=?"));
|
||||||
|
Assert.assertTrue(operations.lastPageCountSql.contains("assistant_id=?"));
|
||||||
|
Assert.assertTrue(operations.lastPageDataSql.contains("assistant_code=?"));
|
||||||
|
Assert.assertTrue(operations.lastPageDataSql.contains("assistant_id=?"));
|
||||||
|
Assert.assertArrayEquals(new Object[]{"AGENT", BigInteger.TEN}, operations.lastPageCountArgs);
|
||||||
|
Assert.assertArrayEquals(new Object[]{"AGENT", BigInteger.TEN}, operations.lastPageDataArgs);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 验证工作台汇总使用跨天去重的 session 口径。
|
* 验证工作台汇总使用跨天去重的 session 口径。
|
||||||
*/
|
*/
|
||||||
@@ -149,6 +171,10 @@ public class ChatAnalyticalDBRepositoryTest {
|
|||||||
|
|
||||||
private String lastQueryOneSql;
|
private String lastQueryOneSql;
|
||||||
private String lastQuerySql;
|
private String lastQuerySql;
|
||||||
|
private String lastPageCountSql;
|
||||||
|
private String lastPageDataSql;
|
||||||
|
private Object[] lastPageCountArgs;
|
||||||
|
private Object[] lastPageDataArgs;
|
||||||
private ChatDashboardSummary queryOneResult;
|
private ChatDashboardSummary queryOneResult;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -204,7 +230,11 @@ public class ChatAnalyticalDBRepositoryTest {
|
|||||||
Object[] dataArgs,
|
Object[] dataArgs,
|
||||||
AnalyticalDBPageRequest pageRequest,
|
AnalyticalDBPageRequest pageRequest,
|
||||||
RowMapper<T> rowMapper) {
|
RowMapper<T> rowMapper) {
|
||||||
return null;
|
this.lastPageCountSql = countSql;
|
||||||
|
this.lastPageCountArgs = countArgs;
|
||||||
|
this.lastPageDataSql = dataSql;
|
||||||
|
this.lastPageDataArgs = dataArgs;
|
||||||
|
return new AnalyticalDBPageResult<>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,242 @@
|
|||||||
|
package tech.easyflow.chatlog.service.impl;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.springframework.beans.factory.support.StaticListableBeanFactory;
|
||||||
|
import tech.easyflow.chatlog.domain.dto.ChatHistoryPage;
|
||||||
|
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.domain.query.ChatSessionFilterQuery;
|
||||||
|
import tech.easyflow.chatlog.repository.analyticaldb.ChatAnalyticalDBRepository;
|
||||||
|
import tech.easyflow.chatlog.service.ChatHistoryQueryService;
|
||||||
|
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.analyticaldb.core.AnalyticalDBOperations;
|
||||||
|
import tech.easyflow.common.web.exceptions.BusinessException;
|
||||||
|
|
||||||
|
import java.lang.reflect.Proxy;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link ChatHistoryManageServiceImpl} 管理端 Agent 会话边界测试。
|
||||||
|
*/
|
||||||
|
public class ChatHistoryManageServiceImplTest {
|
||||||
|
|
||||||
|
private StubChatAnalyticalDBRepository chatAnalyticalDBRepository;
|
||||||
|
private ChatHistoryManageServiceImpl service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化管理端聊天历史服务及其依赖。
|
||||||
|
*/
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
ChatSessionQueryService chatSessionQueryService = unusedDependency(ChatSessionQueryService.class);
|
||||||
|
ChatSessionCommandService chatSessionCommandService = unusedDependency(ChatSessionCommandService.class);
|
||||||
|
ChatHistoryQueryService chatHistoryQueryService = unusedDependency(ChatHistoryQueryService.class);
|
||||||
|
ChatRoundOperateService chatRoundOperateService = unusedDependency(ChatRoundOperateService.class);
|
||||||
|
chatAnalyticalDBRepository = new StubChatAnalyticalDBRepository();
|
||||||
|
service = new ChatHistoryManageServiceImpl(
|
||||||
|
chatSessionQueryService,
|
||||||
|
chatSessionCommandService,
|
||||||
|
chatHistoryQueryService,
|
||||||
|
chatRoundOperateService,
|
||||||
|
chatAnalyticalDBRepository
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证管理端列表始终覆盖客户端传入的会话类型为 Agent。
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void queryAdminSessionsShouldForceAgentAssistantCode() {
|
||||||
|
ChatSessionFilterQuery query = new ChatSessionFilterQuery();
|
||||||
|
query.setAssistantCode("BOT");
|
||||||
|
|
||||||
|
service.queryAdminSessions(query);
|
||||||
|
|
||||||
|
Assert.assertEquals("AGENT", query.getAssistantCode());
|
||||||
|
Assert.assertSame(query, chatAnalyticalDBRepository.lastPageQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证管理端可以读取正式 Agent 会话。
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void getAdminSessionShouldReturnAgentSession() {
|
||||||
|
BigInteger sessionId = BigInteger.valueOf(1001);
|
||||||
|
ChatSessionSummary summary = session(sessionId, "AGENT", 0);
|
||||||
|
chatAnalyticalDBRepository.sessionResult = summary;
|
||||||
|
|
||||||
|
ChatSessionSummary result = service.getAdminSession(sessionId);
|
||||||
|
|
||||||
|
Assert.assertSame(summary, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证正式 Agent 会话可以继续读取历史消息。
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void queryAdminMessagesShouldReturnAgentHistory() {
|
||||||
|
BigInteger sessionId = BigInteger.valueOf(1004);
|
||||||
|
ChatHistoryPage expectedPage = new ChatHistoryPage();
|
||||||
|
chatAnalyticalDBRepository.sessionResult = session(sessionId, "AGENT", 0);
|
||||||
|
service = new ChatHistoryManageServiceImpl(
|
||||||
|
unusedDependency(ChatSessionQueryService.class),
|
||||||
|
unusedDependency(ChatSessionCommandService.class),
|
||||||
|
(requestedSessionId, query) -> expectedPage,
|
||||||
|
unusedDependency(ChatRoundOperateService.class),
|
||||||
|
chatAnalyticalDBRepository
|
||||||
|
);
|
||||||
|
ChatPageQuery query = new ChatPageQuery();
|
||||||
|
query.setPageNumber(2);
|
||||||
|
|
||||||
|
ChatHistoryPage result = service.queryAdminMessages(sessionId, query);
|
||||||
|
|
||||||
|
Assert.assertSame(expectedPage, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证管理端拒绝旧 Bot 会话详情。
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void getAdminSessionShouldRejectBotSession() {
|
||||||
|
BigInteger sessionId = BigInteger.valueOf(1002);
|
||||||
|
chatAnalyticalDBRepository.sessionResult = session(sessionId, "BOT", 0);
|
||||||
|
|
||||||
|
BusinessException exception = Assert.assertThrows(
|
||||||
|
BusinessException.class,
|
||||||
|
() -> service.getAdminSession(sessionId)
|
||||||
|
);
|
||||||
|
|
||||||
|
Assert.assertEquals("Agent 会话不存在", exception.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证旧 Bot 会话在消息查询前即被拒绝,不会继续读取历史消息。
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void queryAdminMessagesShouldRejectBotBeforeHistoryLookup() {
|
||||||
|
BigInteger sessionId = BigInteger.valueOf(1003);
|
||||||
|
chatAnalyticalDBRepository.sessionResult = session(sessionId, "BOT", 0);
|
||||||
|
|
||||||
|
Assert.assertThrows(
|
||||||
|
BusinessException.class,
|
||||||
|
() -> service.queryAdminMessages(sessionId, new ChatPageQuery())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证旧 Bot 会话在答案版本列表查询前即被拒绝。
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void listAdminRoundVariantsShouldRejectBotBeforeRoundLookup() {
|
||||||
|
BigInteger sessionId = BigInteger.valueOf(1005);
|
||||||
|
chatAnalyticalDBRepository.sessionResult = session(sessionId, "BOT", 0);
|
||||||
|
|
||||||
|
Assert.assertThrows(
|
||||||
|
BusinessException.class,
|
||||||
|
() -> service.listAdminRoundVariants(sessionId, BigInteger.ONE)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证旧 Bot 会话在答案版本选择前即被拒绝。
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void selectAdminRoundVariantShouldRejectBotBeforeRoundUpdate() {
|
||||||
|
BigInteger sessionId = BigInteger.valueOf(1006);
|
||||||
|
chatAnalyticalDBRepository.sessionResult = session(sessionId, "BOT", 0);
|
||||||
|
|
||||||
|
Assert.assertThrows(
|
||||||
|
BusinessException.class,
|
||||||
|
() -> service.selectAdminRoundVariant(
|
||||||
|
sessionId,
|
||||||
|
BigInteger.ONE,
|
||||||
|
1,
|
||||||
|
BigInteger.TEN
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建不应在当前测试路径中被调用的接口依赖。
|
||||||
|
*
|
||||||
|
* @param dependencyType 依赖接口类型
|
||||||
|
* @param <T> 依赖接口类型
|
||||||
|
* @return 调用任意方法即失败的代理对象
|
||||||
|
*/
|
||||||
|
private <T> T unusedDependency(Class<T> dependencyType) {
|
||||||
|
Object proxy = Proxy.newProxyInstance(
|
||||||
|
dependencyType.getClassLoader(),
|
||||||
|
new Class<?>[]{dependencyType},
|
||||||
|
(instance, method, args) -> {
|
||||||
|
throw new AssertionError("测试路径不应调用依赖方法: " + method.getName());
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return dependencyType.cast(proxy);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造会话摘要。
|
||||||
|
*
|
||||||
|
* @param sessionId 会话 ID
|
||||||
|
* @param assistantCode 助手类型编码
|
||||||
|
* @param isDeleted 删除标识
|
||||||
|
* @return 会话摘要
|
||||||
|
*/
|
||||||
|
private ChatSessionSummary session(BigInteger sessionId, String assistantCode, int isDeleted) {
|
||||||
|
ChatSessionSummary summary = new ChatSessionSummary();
|
||||||
|
summary.setId(sessionId);
|
||||||
|
summary.setAssistantCode(assistantCode);
|
||||||
|
summary.setIsDeleted(isDeleted);
|
||||||
|
return summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 仅记录管理端会话查询参数和返回值的分析库仓储桩。
|
||||||
|
*/
|
||||||
|
private static class StubChatAnalyticalDBRepository extends ChatAnalyticalDBRepository {
|
||||||
|
|
||||||
|
private ChatSessionFilterQuery lastPageQuery;
|
||||||
|
private ChatSessionSummary sessionResult;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建不连接真实分析库的仓储桩。
|
||||||
|
*/
|
||||||
|
private StubChatAnalyticalDBRepository() {
|
||||||
|
super(
|
||||||
|
new StaticListableBeanFactory().getBeanProvider(AnalyticalDBOperations.class),
|
||||||
|
null,
|
||||||
|
new ChatJsonSupport(new ObjectMapper())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录分页筛选参数。
|
||||||
|
*
|
||||||
|
* @param query 会话筛选条件
|
||||||
|
* @return 空分页结果
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public ChatSessionPage pageSessions(ChatSessionFilterQuery query) {
|
||||||
|
lastPageQuery = query;
|
||||||
|
return new ChatSessionPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回预设的会话摘要。
|
||||||
|
*
|
||||||
|
* @param sessionId 会话 ID
|
||||||
|
* @return 预设会话摘要
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public ChatSessionSummary getSession(BigInteger sessionId) {
|
||||||
|
return sessionResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -60,7 +60,7 @@ function resolveSenderName(item: any) {
|
|||||||
if (item?.role === 'tool') {
|
if (item?.role === 'tool') {
|
||||||
return '工具调用';
|
return '工具调用';
|
||||||
}
|
}
|
||||||
return item?.role === 'assistant' ? '聊天助手' : '聊天用户';
|
return item?.role === 'assistant' ? '智能体' : '聊天用户';
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleLoadMore() {
|
async function handleLoadMore() {
|
||||||
@@ -152,7 +152,7 @@ async function handleCopyMessage(item: ChatTimeTimelineItem) {
|
|||||||
{{ session?.title || '未命名会话' }}
|
{{ session?.title || '未命名会话' }}
|
||||||
</h2>
|
</h2>
|
||||||
<span class="chat-history-detail__assistant-tag">
|
<span class="chat-history-detail__assistant-tag">
|
||||||
{{ session?.assistantName || '聊天助手' }}
|
{{ session?.assistantName || '智能体' }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="chat-history-detail__meta-row">
|
<div class="chat-history-detail__meta-row">
|
||||||
|
|||||||
28
easyflow-ui-admin/app/src/views/ai/chatHistory/index.test.ts
Normal file
28
easyflow-ui-admin/app/src/views/ai/chatHistory/index.test.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
|
import detailDrawerSource from '#/components/chat-history/ChatHistoryDetailDrawer.vue?raw';
|
||||||
|
|
||||||
|
import pageSource from './index.vue?raw';
|
||||||
|
|
||||||
|
describe('管理端智能体聊天历史契约', () => {
|
||||||
|
it('使用智能体候选接口并保留 assistantId 查询参数', () => {
|
||||||
|
expect(pageSource).toContain("'/api/v1/agent/list'");
|
||||||
|
expect(pageSource).not.toContain('/api/v1/bot/list');
|
||||||
|
expect(pageSource).toContain('label: item.name');
|
||||||
|
expect(pageSource).toContain('assistantId: query.value.assistantId');
|
||||||
|
expect(pageSource).not.toContain('publishedOnly');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('统一页面和详情抽屉的智能体文案', () => {
|
||||||
|
expect(pageSource).toContain('placeholder="筛选智能体"');
|
||||||
|
expect(pageSource).toContain('label="智能体"');
|
||||||
|
expect(pageSource).toContain('暂无智能体聊天记录');
|
||||||
|
expect(pageSource).not.toContain('聊天助手');
|
||||||
|
expect(detailDrawerSource).not.toContain('聊天助手');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('为智能体筛选提供独立加载反馈', () => {
|
||||||
|
expect(pageSource).toContain(':loading="agentLoading"');
|
||||||
|
expect(pageSource).toContain('智能体列表加载失败');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
ElDatePicker,
|
ElDatePicker,
|
||||||
ElEmpty,
|
ElEmpty,
|
||||||
ElInput,
|
ElInput,
|
||||||
|
ElMessage,
|
||||||
ElPagination,
|
ElPagination,
|
||||||
ElSelect,
|
ElSelect,
|
||||||
ElTable,
|
ElTable,
|
||||||
@@ -27,7 +28,8 @@ import { api } from '#/api/request';
|
|||||||
import ChatHistoryDetailDrawer from '#/components/chat-history/ChatHistoryDetailDrawer.vue';
|
import ChatHistoryDetailDrawer from '#/components/chat-history/ChatHistoryDetailDrawer.vue';
|
||||||
import ListPageShell from '#/components/page/ListPageShell.vue';
|
import ListPageShell from '#/components/page/ListPageShell.vue';
|
||||||
|
|
||||||
const assistantList = ref<any[]>([]);
|
const agentOptions = ref<any[]>([]);
|
||||||
|
const agentLoading = ref(false);
|
||||||
const sessions = ref<any[]>([]);
|
const sessions = ref<any[]>([]);
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const query = ref({
|
const query = ref({
|
||||||
@@ -88,19 +90,25 @@ const selectedSessionId = computed(() =>
|
|||||||
);
|
);
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await Promise.all([fetchAssistants(), fetchSessions()]);
|
await Promise.all([fetchAgents(), fetchSessions()]);
|
||||||
});
|
});
|
||||||
|
|
||||||
async function fetchAssistants() {
|
async function fetchAgents() {
|
||||||
const [, res] = await tryit(api.get)('/api/v1/bot/list', {
|
agentLoading.value = true;
|
||||||
params: { status: 1 },
|
const [error, res] = await tryit(api.get)('/api/v1/agent/list');
|
||||||
});
|
agentLoading.value = false;
|
||||||
if (res?.errorCode === 0) {
|
if (error || res?.errorCode !== 0) {
|
||||||
assistantList.value = (res.data || []).map((item: any) => ({
|
agentOptions.value = [];
|
||||||
label: item.title,
|
ElMessage.error(res?.message || '智能体列表加载失败');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const agents = Array.isArray(res.data) ? res.data : [];
|
||||||
|
agentOptions.value = agents
|
||||||
|
.filter((item: any) => item?.id !== undefined && item?.id !== null)
|
||||||
|
.map((item: any) => ({
|
||||||
|
label: item.name || `智能体 #${item.id}`,
|
||||||
value: item.id,
|
value: item.id,
|
||||||
}));
|
}));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchSessions() {
|
async function fetchSessions() {
|
||||||
@@ -372,8 +380,10 @@ function closeDetail() {
|
|||||||
<ElSelect
|
<ElSelect
|
||||||
v-model="query.assistantId"
|
v-model="query.assistantId"
|
||||||
clearable
|
clearable
|
||||||
placeholder="筛选聊天助手"
|
filterable
|
||||||
:options="assistantList"
|
placeholder="筛选智能体"
|
||||||
|
:loading="agentLoading"
|
||||||
|
:options="agentOptions"
|
||||||
class="chat-history-page__filter-control is-select"
|
class="chat-history-page__filter-control is-select"
|
||||||
@change="handleSearch"
|
@change="handleSearch"
|
||||||
/>
|
/>
|
||||||
@@ -436,10 +446,10 @@ function closeDetail() {
|
|||||||
</template>
|
</template>
|
||||||
</ElTableColumn>
|
</ElTableColumn>
|
||||||
|
|
||||||
<ElTableColumn label="聊天助手" min-width="160">
|
<ElTableColumn label="智能体" min-width="160">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<span class="chat-history-page__assistant-chip">
|
<span class="chat-history-page__assistant-chip">
|
||||||
{{ row.assistantName || '聊天助手' }}
|
{{ row.assistantName || '智能体' }}
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</ElTableColumn>
|
</ElTableColumn>
|
||||||
@@ -484,7 +494,7 @@ function closeDetail() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else class="chat-history-page__empty">
|
<div v-else class="chat-history-page__empty">
|
||||||
<ElEmpty description="暂无聊天历史" />
|
<ElEmpty description="暂无智能体聊天记录" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="pageState.total > 0" class="chat-history-page__pagination">
|
<div v-if="pageState.total > 0" class="chat-history-page__pagination">
|
||||||
|
|||||||
Reference in New Issue
Block a user