docs(conversation): bilingualize ConversationService comments (en/zh)

This commit is contained in:
matevip 2026-05-20 20:57:53 +08:00
parent 86c5d871bf
commit 82540a5fcb

View File

@ -45,7 +45,13 @@ import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
/** /**
* 会话管理服务 * Conversation management service (会话管理服务).
*
* <p>Owns the full lifecycle of {@link ConversationEntity} and
* {@link MessageEntity} rows list / get-or-create / save / rename /
* pin / delete / compress / approval-state reconciliation and the
* cascade of side-tables that hang off a conversation (approvals,
* async tasks, channel sessions, attachment files, tool-result spill).
* *
* @author MateClaw Team * @author MateClaw Team
*/ */
@ -79,18 +85,27 @@ public class ConversationService {
} }
/** /**
* 获取用户的会话列表返回 VO包含 agentName/agentIcon/status * List conversations for a user, returned as VOs that include
* {@code agentName} / {@code agentIcon} / {@code status}.
*
* <p>获取用户的会话列表返回 VO包含 agentName / agentIcon / status
*/ */
public List<ConversationVO> listConversations(String username) { public List<ConversationVO> listConversations(String username) {
return listConversations(username, null); return listConversations(username, null);
} }
/** /**
* 获取用户的会话列表按工作区过滤 * Workspace-scoped variant of {@link #listConversations(String)}.
*
* <p>获取用户的会话列表按工作区过滤
*/ */
public List<ConversationVO> listConversations(String username, Long workspaceId) { public List<ConversationVO> listConversations(String username, Long workspaceId) {
// 同时返回当前用户的会话 定时任务system产生的会话 // Return both the current user's conversations AND those created by
// 排除子会话委派产生的子会话不在侧边栏显示 // scheduled jobs (owner=system). Child conversations spawned by
// delegation are excluded they don't belong in the sidebar.
//
// 同时返回当前用户的会话和定时任务system产生的会话
// 排除子会话委派产生的子会话不在侧边栏显示
LambdaQueryWrapper<ConversationEntity> wrapper = new LambdaQueryWrapper<ConversationEntity>() LambdaQueryWrapper<ConversationEntity> wrapper = new LambdaQueryWrapper<ConversationEntity>()
.in(ConversationEntity::getUsername, username, SYSTEM_USER) .in(ConversationEntity::getUsername, username, SYSTEM_USER)
.isNull(ConversationEntity::getParentConversationId) .isNull(ConversationEntity::getParentConversationId)
@ -105,7 +120,8 @@ public class ConversationService {
return List.of(); return List.of();
} }
// 批量查询关联的 Agent 信息避免 N+1 查询 // Batch-load associated Agent rows to avoid N+1 queries.
// 批量查询关联的 Agent 信息避免 N+1 查询
List<Long> agentIds = entities.stream() List<Long> agentIds = entities.stream()
.filter(e -> e.getAgentId() != null) .filter(e -> e.getAgentId() != null)
.map(ConversationEntity::getAgentId) .map(ConversationEntity::getAgentId)
@ -117,7 +133,8 @@ public class ConversationService {
: agentMapper.selectBatchIds(agentIds).stream() : agentMapper.selectBatchIds(agentIds).stream()
.collect(Collectors.toMap(AgentEntity::getId, a -> a)); .collect(Collectors.toMap(AgentEntity::getId, a -> a));
// 转换为 VO补充 agentName/agentIcon/status // Map entities to VOs and enrich with agentName / agentIcon / status.
// 转换为 VO补充 agentName / agentIcon / status
return entities.stream() return entities.stream()
.map(entity -> { .map(entity -> {
AgentEntity agent = entity.getAgentId() != null AgentEntity agent = entity.getAgentId() != null
@ -131,7 +148,9 @@ public class ConversationService {
} }
/** /**
* 获取或创建会话向后兼容默认 workspace 1 * Get-or-create conversation (backward-compat overload, defaults to workspace 1).
*
* <p>获取或创建会话向后兼容默认 workspace 1
*/ */
@Transactional @Transactional
public ConversationEntity getOrCreateConversation(String conversationId, Long agentId, String username) { public ConversationEntity getOrCreateConversation(String conversationId, Long agentId, String username) {
@ -139,7 +158,9 @@ public class ConversationService {
} }
/** /**
* 获取或创建会话workspace 感知 * Workspace-aware get-or-create.
*
* <p>获取或创建会话workspace 感知
*/ */
@Transactional @Transactional
public ConversationEntity getOrCreateConversation(String conversationId, Long agentId, public ConversationEntity getOrCreateConversation(String conversationId, Long agentId,
@ -163,7 +184,10 @@ public class ConversationService {
} }
/** /**
* 创建子会话委派场景关联父会话 ID * Create a child conversation (delegation scenario), linking it back to
* its parent via {@code parentConversationId}.
*
* <p>创建子会话委派场景关联父会话 ID
*/ */
@Transactional @Transactional
public ConversationEntity createChildConversation(String childConversationId, Long agentId, public ConversationEntity createChildConversation(String childConversationId, Long agentId,
@ -177,11 +201,19 @@ public class ConversationService {
} }
/** /**
* 获取或创建共享渠道会话 * Get-or-create a shared channel conversation.
* <p> *
* IM 渠道飞书/钉钉/企微等的会话需要在控制台中对登录用户可见 * <p>IM-channel (Feishu / DingTalk / WeCom / ) conversations must be
* 因此统一使用 system 作为 owner对于历史上已写成发送者昵称/open_id 的会话 * visible to every logged-in user in the admin console, so the owner is
* 这里会自动修正为 system避免控制台列表和消息接口因权限校验而不可见 * uniformly set to {@code system}. For legacy rows whose owner was
* historically written as a sender nickname / {@code open_id}, this
* method silently rewrites it to {@code system} on read otherwise the
* console list and message endpoints would 403 those rows.
*
* <p>获取或创建共享渠道会话IM 渠道飞书 / 钉钉 / 企微等的会话需要在控制台中
* 对登录用户可见因此统一使用 {@code system} 作为 owner对于历史上已写成发送者
* 昵称 / open_id 的会话这里会自动修正为 {@code system}避免控制台列表和
* 消息接口因权限校验而不可见
*/ */
@Transactional @Transactional
public ConversationEntity getOrCreateSharedConversation(String conversationId, Long agentId) { public ConversationEntity getOrCreateSharedConversation(String conversationId, Long agentId) {
@ -189,11 +221,14 @@ public class ConversationService {
} }
/** /**
* 获取或创建共享渠道会话workspace 感知 * Workspace-aware get-or-create for shared channel conversations.
* *
* <p>Delegates to the 5-arg overload with {@code null} model defaults * <p>Delegates to the 5-arg overload with {@code null} model defaults
* preserves the legacy behavior for any caller that doesn't have an * preserves the legacy behavior for any caller that doesn't have an
* agent-level model to inherit from. * agent-level model to inherit from.
*
* <p>获取或创建共享渠道会话workspace 感知委托到 5 参重载model 默认值传
* {@code null}保留对不需要继承 agent 模型的调用方的旧行为
*/ */
@Transactional @Transactional
public ConversationEntity getOrCreateSharedConversation(String conversationId, Long agentId, Long workspaceId) { public ConversationEntity getOrCreateSharedConversation(String conversationId, Long agentId, Long workspaceId) {
@ -245,13 +280,14 @@ public class ConversationService {
try { try {
conversationMapper.insert(conv); conversationMapper.insert(conv);
} catch (org.springframework.dao.DuplicateKeyException e) { } catch (org.springframework.dao.DuplicateKeyException e) {
// 并发插入另一个线程已创建回退到查询 // Concurrent insert: another thread won the race re-query
// and fall through to the owner-correction block below.
// 并发插入另一个线程已创建回退到查询继续走下面的 owner 修正逻辑
conv = conversationMapper.selectOne(new LambdaQueryWrapper<ConversationEntity>() conv = conversationMapper.selectOne(new LambdaQueryWrapper<ConversationEntity>()
.eq(ConversationEntity::getConversationId, conversationId)); .eq(ConversationEntity::getConversationId, conversationId));
if (conv == null) { if (conv == null) {
throw new IllegalStateException("Conversation vanished after duplicate key: " + conversationId, e); throw new IllegalStateException("Conversation vanished after duplicate key: " + conversationId, e);
} }
// 继续走下面的 owner 修正逻辑
} }
} }
@ -284,7 +320,10 @@ public class ConversationService {
} }
/** /**
* 保存消息并更新会话统计 * Persist a message and update the conversation's aggregate counters
* ({@code messageCount}, {@code lastActiveTime}, {@code lastMessage}).
*
* <p>保存消息并更新会话统计
*/ */
@Transactional @Transactional
public MessageEntity saveMessage(String conversationId, String role, String content) { public MessageEntity saveMessage(String conversationId, String role, String content) {
@ -327,21 +366,26 @@ public class ConversationService {
message.setCompletionTokens(completionTokens); message.setCompletionTokens(completionTokens);
message.setRuntimeModel(runtimeModel); message.setRuntimeModel(runtimeModel);
message.setRuntimeProvider(runtimeProvider); message.setRuntimeProvider(runtimeProvider);
message.setMetadata(metadata != null ? metadata : "{}"); // 初始化为空对象 message.setMetadata(metadata != null ? metadata : "{}"); // Initialize as empty JSON object / 初始化为空对象
messageMapper.insert(message); messageMapper.insert(message);
// 更新会话信息 // Update aggregate counters on the parent conversation row.
// 更新会话信息消息计数最后活跃时间最后一条摘要
ConversationEntity conv = conversationMapper.selectOne(new LambdaQueryWrapper<ConversationEntity>() ConversationEntity conv = conversationMapper.selectOne(new LambdaQueryWrapper<ConversationEntity>()
.eq(ConversationEntity::getConversationId, conversationId)); .eq(ConversationEntity::getConversationId, conversationId));
if (conv != null) { if (conv != null) {
conv.setMessageCount(conv.getMessageCount() + 1); conv.setMessageCount(conv.getMessageCount() + 1);
conv.setLastActiveTime(LocalDateTime.now()); conv.setLastActiveTime(LocalDateTime.now());
String summary = summarizeMessage(content, parts); String summary = summarizeMessage(content, parts);
// 用第一条用户消息作为会话标题 // Derive the conversation title from the first user message
// (only when the title is still the default "新对话").
// 用第一条用户消息作为会话标题
if ("user".equals(role) && "新对话".equals(conv.getTitle())) { if ("user".equals(role) && "新对话".equals(conv.getTitle())) {
conv.setTitle(summary.length() > 20 ? summary.substring(0, 20) + "..." : summary); conv.setTitle(summary.length() > 20 ? summary.substring(0, 20) + "..." : summary);
} }
// 保存最后一条 AI 回复摘要 // Keep a short preview of the latest assistant reply for the
// sidebar / list view (last_message column).
// 保存最后一条 AI 回复摘要
if ("assistant".equals(role)) { if ("assistant".equals(role)) {
conv.setLastMessage(summary.length() > 50 ? summary.substring(0, 50) + "..." : summary); conv.setLastMessage(summary.length() > 50 ? summary.substring(0, 50) + "..." : summary);
} }
@ -351,7 +395,9 @@ public class ConversationService {
} }
/** /**
* 更新消息的元数据toolCalls, plan, currentPhase * Update a message's metadata JSON (toolCalls, plan, currentPhase, ).
*
* <p>更新消息的元数据toolCalls / plan / currentPhase
*/ */
@Transactional @Transactional
public void updateMessageMetadata(Long messageId, String metadata) { public void updateMessageMetadata(Long messageId, String metadata) {
@ -362,7 +408,9 @@ public class ConversationService {
} }
/** /**
* 重命名会话 * Rename a conversation.
*
* <p>重命名会话
*/ */
@Transactional @Transactional
public void renameConversation(String conversationId, String title) { public void renameConversation(String conversationId, String title) {
@ -388,7 +436,9 @@ public class ConversationService {
} }
/** /**
* 更新会话的流状态running / idle * Update a conversation's stream status ({@code running} / {@code idle}).
*
* <p>更新会话的流状态running / idle
*/ */
@Transactional @Transactional
public void updateStreamStatus(String conversationId, String streamStatus) { public void updateStreamStatus(String conversationId, String streamStatus) {
@ -451,7 +501,10 @@ public class ConversationService {
} }
/** /**
* 获取会话最后一条消息内容用于 rate limit 防护等场景 * Get the latest message preview text for a conversation used by
* rate-limit guards and similar duplicate-detection paths.
*
* <p>获取会话最后一条消息内容用于 rate limit 防护等场景
*/ */
public String getLastMessage(String conversationId) { public String getLastMessage(String conversationId) {
ConversationEntity conv = conversationMapper.selectOne(new LambdaQueryWrapper<ConversationEntity>() ConversationEntity conv = conversationMapper.selectOne(new LambdaQueryWrapper<ConversationEntity>()
@ -460,7 +513,9 @@ public class ConversationService {
} }
/** /**
* 获取会话的消息数量 * Get a conversation's message count.
*
* <p>获取会话的消息数量
*/ */
public int getMessageCount(String conversationId) { public int getMessageCount(String conversationId) {
ConversationEntity conv = conversationMapper.selectOne(new LambdaQueryWrapper<ConversationEntity>() ConversationEntity conv = conversationMapper.selectOne(new LambdaQueryWrapper<ConversationEntity>()
@ -469,7 +524,9 @@ public class ConversationService {
} }
/** /**
* 获取会话的消息历史 * Load the full message history for a conversation in chronological order.
*
* <p>获取会话的消息历史
*/ */
public List<MessageEntity> listMessages(String conversationId) { public List<MessageEntity> listMessages(String conversationId) {
return messageMapper.selectList(new LambdaQueryWrapper<MessageEntity>() return messageMapper.selectList(new LambdaQueryWrapper<MessageEntity>()
@ -503,8 +560,12 @@ public class ConversationService {
} }
/** /**
* 加载最近 N 条消息倒序取出后翻转为正序 * Load the most recent N messages pulled DESC then reversed to ASC.
* 利用复合索引 (conversation_id, create_time) 高效分页 * Uses the composite index {@code (conversation_id, create_time)} for
* efficient tail pagination.
*
* <p>加载最近 N 条消息倒序取出后翻转为正序利用复合索引
* {@code (conversation_id, create_time)} 高效分页
*/ */
public List<MessageEntity> listRecentMessages(String conversationId, int lastN) { public List<MessageEntity> listRecentMessages(String conversationId, int lastN) {
List<MessageEntity> recent = messageMapper.selectList( List<MessageEntity> recent = messageMapper.selectList(
@ -518,7 +579,11 @@ public class ConversationService {
} }
/** /**
* 分页加载指定 ID 之前的消息用于前端上拉加载更早消息 * Load a page of messages older than a given id used by the frontend
* pull-up infinite-scroll. Results are returned DESC; the caller is
* responsible for reversing if it needs ASC.
*
* <p>分页加载指定 ID 之前的消息用于前端上拉加载更早消息
* 返回倒序结果调用方需自行 reverse * 返回倒序结果调用方需自行 reverse
*/ */
public List<MessageEntity> listMessagesBefore(String conversationId, Long beforeId, int limit) { public List<MessageEntity> listMessagesBefore(String conversationId, Long beforeId, int limit) {
@ -534,7 +599,9 @@ public class ConversationService {
} }
/** /**
* 查询会话消息总数 * Count the total number of messages in a conversation.
*
* <p>查询会话消息总数
*/ */
public long countMessages(String conversationId) { public long countMessages(String conversationId) {
return messageMapper.selectCount( return messageMapper.selectCount(
@ -748,7 +815,10 @@ public class ConversationService {
} }
/** /**
* 清空会话消息同时清理附件文件 * Wipe all messages in a conversation and reset its aggregate counters,
* also cleaning any attachment files those messages produced.
*
* <p>清空会话消息同时清理附件文件
*/ */
@Transactional @Transactional
public void clearMessages(String conversationId) { public void clearMessages(String conversationId) {
@ -898,9 +968,13 @@ public class ConversationService {
} }
/** /**
* 删除指定会话中所有审批占位 assistant 消息 * Remove all approval-placeholder assistant messages from a conversation.
* <p> *
* replay 前调用确保 LLM 上下文中不包含任何审批相关文本 * <p>Called before replay so the LLM context contains no approval-related
* stub text that could confuse the next turn.
*
* <p>删除指定会话中所有审批占位 assistant 消息 replay 前调用
* 确保 LLM 上下文中不包含任何审批相关文本
*/ */
/** /**
* Reconcile persisted assistant-message state when one or more pending approvals * Reconcile persisted assistant-message state when one or more pending approvals
@ -1124,7 +1198,9 @@ public class ConversationService {
} }
/** /**
* 检查会话是否存在 * Check whether a conversation row exists.
*
* <p>检查会话是否存在
*/ */
public boolean conversationExists(String conversationId) { public boolean conversationExists(String conversationId) {
return conversationMapper.selectCount( return conversationMapper.selectCount(
@ -1133,8 +1209,12 @@ public class ConversationService {
} }
/** /**
* 校验用户是否拥有该会话 * Check whether a user owns the conversation, treating system-owned
* 定时任务产生的会话username=system对所有登录用户可见 * rows (e.g. from scheduled jobs / IM channels) as visible to every
* authenticated user.
*
* <p>校验用户是否拥有该会话定时任务产生的会话username = system
* 对所有登录用户可见
*/ */
public boolean isConversationOwner(String conversationId, String username) { public boolean isConversationOwner(String conversationId, String username) {
ConversationEntity conv = conversationMapper.selectOne( ConversationEntity conv = conversationMapper.selectOne(
@ -1147,7 +1227,9 @@ public class ConversationService {
} }
/** /**
* 获取会话的持久化流状态 * Get the persisted stream status for a conversation.
*
* <p>获取会话的持久化流状态
*/ */
public String getStreamStatus(String conversationId) { public String getStreamStatus(String conversationId) {
ConversationEntity conv = conversationMapper.selectOne( ConversationEntity conv = conversationMapper.selectOne(