diff --git a/mateclaw-server/src/main/java/vip/mate/workspace/conversation/ConversationService.java b/mateclaw-server/src/main/java/vip/mate/workspace/conversation/ConversationService.java index 1790e4c5..0b311e21 100644 --- a/mateclaw-server/src/main/java/vip/mate/workspace/conversation/ConversationService.java +++ b/mateclaw-server/src/main/java/vip/mate/workspace/conversation/ConversationService.java @@ -147,6 +147,76 @@ public class ConversationService { .collect(Collectors.toList()); } + /** + * Paginated variant used by the Sessions admin page. + * + *

Mirrors {@link #listConversations(String, Long)}'s filtering (current + * user + system rows, top-level only, optional workspace) and adds a + * {@code keyword} match against title / conversationId. The keyword is + * case-insensitive and treated as a substring. + * + *

会话管理页使用的分页查询。在 {@link #listConversations(String, Long)} + * 的基础上增加 title / conversationId 模糊匹配。 + */ + public com.baomidou.mybatisplus.core.metadata.IPage pageConversations( + String username, Long workspaceId, int page, int size, String keyword) { + if (page < 1) page = 1; + if (size < 1 || size > 200) size = 20; + + com.baomidou.mybatisplus.extension.plugins.pagination.Page pager = + new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(page, size); + + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .in(ConversationEntity::getUsername, username, SYSTEM_USER) + .isNull(ConversationEntity::getParentConversationId) + .orderByDesc(ConversationEntity::getPinned) + .orderByDesc(ConversationEntity::getLastActiveTime); + if (workspaceId != null) { + wrapper.eq(ConversationEntity::getWorkspaceId, workspaceId); + } + if (keyword != null && !keyword.isBlank()) { + String kw = keyword.trim(); + wrapper.and(w -> w + .like(ConversationEntity::getTitle, kw) + .or() + .like(ConversationEntity::getConversationId, kw)); + } + + com.baomidou.mybatisplus.core.metadata.IPage entityPage = + conversationMapper.selectPage(pager, wrapper); + + List entities = entityPage.getRecords(); + Map agentMap; + if (entities.isEmpty()) { + agentMap = Map.of(); + } else { + List agentIds = entities.stream() + .filter(e -> e.getAgentId() != null) + .map(ConversationEntity::getAgentId) + .distinct() + .collect(Collectors.toList()); + agentMap = agentIds.isEmpty() + ? Map.of() + : agentMapper.selectBatchIds(agentIds).stream() + .collect(Collectors.toMap(AgentEntity::getId, a -> a)); + } + + com.baomidou.mybatisplus.core.metadata.IPage voPage = + new com.baomidou.mybatisplus.extension.plugins.pagination.Page( + entityPage.getCurrent(), entityPage.getSize(), entityPage.getTotal()); + voPage.setRecords(entities.stream() + .map(entity -> { + AgentEntity agent = entity.getAgentId() != null + ? agentMap.get(entity.getAgentId()) + : null; + String agentName = agent != null ? agent.getName() : null; + String agentIcon = agent != null ? agent.getIcon() : null; + return ConversationVO.from(entity, agentName, agentIcon); + }) + .collect(Collectors.toList())); + return voPage; + } + /** * Get-or-create conversation (backward-compat overload, defaults to workspace 1). * diff --git a/mateclaw-server/src/main/java/vip/mate/workspace/conversation/controller/ConversationController.java b/mateclaw-server/src/main/java/vip/mate/workspace/conversation/controller/ConversationController.java index 87373543..ab9a283c 100644 --- a/mateclaw-server/src/main/java/vip/mate/workspace/conversation/controller/ConversationController.java +++ b/mateclaw-server/src/main/java/vip/mate/workspace/conversation/controller/ConversationController.java @@ -41,6 +41,22 @@ public class ConversationController { return R.ok(conversationService.listConversations(username, workspaceId)); } + /** + * 分页查询会话列表(用于会话管理页)。 + *

会话管理页可能跨多个 IM 渠道,单页全量返回会拖慢首屏。 + */ + @Operation(summary = "分页查询会话列表") + @GetMapping("/page") + public R> page( + Authentication auth, + @RequestHeader(value = "X-Workspace-Id", required = false) Long workspaceId, + @RequestParam(defaultValue = "1") int page, + @RequestParam(defaultValue = "20") int size, + @RequestParam(required = false) String keyword) { + String username = auth != null ? auth.getName() : "anonymous"; + return R.ok(conversationService.pageConversations(username, workspaceId, page, size, keyword)); + } + /** * 获取指定会话的消息历史(支持分页)。 *

diff --git a/mateclaw-ui/src/api/index.ts b/mateclaw-ui/src/api/index.ts index 0ab2ae5f..def69533 100644 --- a/mateclaw-ui/src/api/index.ts +++ b/mateclaw-ui/src/api/index.ts @@ -154,6 +154,13 @@ export const chatApi = { // ==================== Conversation ==================== export const conversationApi = { list: () => http.get('/conversations'), + /** + * Paginated list used by the Sessions admin page. Keyword matches title + * or conversationId server-side; ChatConsole's left panel still uses the + * non-paginated list() because it shows a per-agent rolling history. + */ + page: (params: { page?: number; size?: number; keyword?: string }) => + http.get('/conversations/page', { params }), listMessages: (conversationId: string, params?: { beforeId?: number; limit?: number }) => http.get(`/conversations/${conversationId}/messages`, { params }), getStatus: (conversationId: string) => diff --git a/mateclaw-ui/src/i18n/locales/en-US.ts b/mateclaw-ui/src/i18n/locales/en-US.ts index cd1e304a..08cbca47 100644 --- a/mateclaw-ui/src/i18n/locales/en-US.ts +++ b/mateclaw-ui/src/i18n/locales/en-US.ts @@ -1736,6 +1736,7 @@ export default { sessions: { title: 'Sessions', desc: 'View and manage all conversation sessions', + back: 'Back', search: 'Search sessions...', columns: { session: 'Session', @@ -1752,6 +1753,9 @@ export default { closed: 'Closed', }, empty: 'No sessions found', + emptyHeading: 'No sessions yet', + emptyDesc: 'Head over to Chat to start a conversation — every session you have will show up here.', + emptyCta: 'Start chatting', loadFailed: 'Failed to load sessions', deleteConfirm: 'Are you sure you want to delete this session?', deleteTitle: 'Confirm Delete', diff --git a/mateclaw-ui/src/i18n/locales/zh-CN.ts b/mateclaw-ui/src/i18n/locales/zh-CN.ts index b46f577c..5a1f4319 100644 --- a/mateclaw-ui/src/i18n/locales/zh-CN.ts +++ b/mateclaw-ui/src/i18n/locales/zh-CN.ts @@ -1628,6 +1628,7 @@ export default { sessions: { title: '会话管理', desc: '查看和管理所有会话', + back: '返回', search: '搜索会话...', columns: { session: '会话', @@ -1644,6 +1645,9 @@ export default { closed: '已关闭', }, empty: '暂无会话', + emptyHeading: '还没有任何会话', + emptyDesc: '去对话页开始一次交流,所有的会话都会在这里汇总', + emptyCta: '开始对话', loadFailed: '加载会话列表失败', deleteConfirm: '确定要删除这个会话吗?', deleteTitle: '确认删除', diff --git a/mateclaw-ui/src/views/Sessions.vue b/mateclaw-ui/src/views/Sessions.vue index ddada5d5..1dea7d16 100644 --- a/mateclaw-ui/src/views/Sessions.vue +++ b/mateclaw-ui/src/views/Sessions.vue @@ -1,9 +1,16 @@