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;
/**
* 会话管理服务
* 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
*/
@ -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) {
return listConversations(username, null);
}
/**
* 获取用户的会话列表(按工作区过滤)
* Workspace-scoped variant of {@link #listConversations(String)}.
*
* <p>获取用户的会话列表(按工作区过滤)。
*/
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>()
.in(ConversationEntity::getUsername, username, SYSTEM_USER)
.isNull(ConversationEntity::getParentConversationId)
@ -105,7 +120,8 @@ public class ConversationService {
return List.of();
}
// 批量查询关联的 Agent 信息,避免 N+1 查询
// Batch-load associated Agent rows to avoid N+1 queries.
// 批量查询关联的 Agent 信息,避免 N+1 查询。
List<Long> agentIds = entities.stream()
.filter(e -> e.getAgentId() != null)
.map(ConversationEntity::getAgentId)
@ -117,7 +133,8 @@ public class ConversationService {
: agentMapper.selectBatchIds(agentIds).stream()
.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()
.map(entity -> {
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
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
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
public ConversationEntity createChildConversation(String childConversationId, Long agentId,
@ -177,11 +201,19 @@ public class ConversationService {
}
/**
* 获取或创建共享渠道会话。
* <p>
* IM 渠道(飞书/钉钉/企微等)的会话需要在控制台中对登录用户可见,
* 因此统一使用 system 作为 owner。对于历史上已写成发送者昵称/open_id 的会话,
* 这里会自动修正为 system,避免控制台列表和消息接口因权限校验而不可见。
* Get-or-create a shared channel conversation.
*
* <p>IM-channel (Feishu / DingTalk / WeCom / …) conversations must be
* visible to every logged-in user in the admin console, so the owner is
* 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
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 —
* preserves the legacy behavior for any caller that doesn't have an
* agent-level model to inherit from.
*
* <p>获取或创建共享渠道会话(workspace 感知)。委托到 5 参重载,model 默认值传
* {@code null},保留对不需要继承 agent 模型的调用方的旧行为。
*/
@Transactional
public ConversationEntity getOrCreateSharedConversation(String conversationId, Long agentId, Long workspaceId) {
@ -245,13 +280,14 @@ public class ConversationService {
try {
conversationMapper.insert(conv);
} 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>()
.eq(ConversationEntity::getConversationId, conversationId));
if (conv == null) {
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
public MessageEntity saveMessage(String conversationId, String role, String content) {
@ -327,21 +366,26 @@ public class ConversationService {
message.setCompletionTokens(completionTokens);
message.setRuntimeModel(runtimeModel);
message.setRuntimeProvider(runtimeProvider);
message.setMetadata(metadata != null ? metadata : "{}"); // 初始化为空对象
message.setMetadata(metadata != null ? metadata : "{}"); // Initialize as empty JSON object / 初始化为空对象
messageMapper.insert(message);
// 更新会话信息
// Update aggregate counters on the parent conversation row.
// 更新会话信息(消息计数、最后活跃时间、最后一条摘要)。
ConversationEntity conv = conversationMapper.selectOne(new LambdaQueryWrapper<ConversationEntity>()
.eq(ConversationEntity::getConversationId, conversationId));
if (conv != null) {
conv.setMessageCount(conv.getMessageCount() + 1);
conv.setLastActiveTime(LocalDateTime.now());
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())) {
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)) {
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
public void updateMessageMetadata(Long messageId, String metadata) {
@ -362,7 +408,9 @@ public class ConversationService {
}
/**
* 重命名会话
* Rename a conversation.
*
* <p>重命名会话。
*/
@Transactional
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
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) {
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) {
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) {
return messageMapper.selectList(new LambdaQueryWrapper<MessageEntity>()
@ -503,8 +560,12 @@ public class ConversationService {
}
/**
* 加载最近 N 条消息(倒序取出后翻转为正序)。
* 利用复合索引 (conversation_id, create_time) 高效分页。
* Load the most recent N messages — pulled DESC then reversed to ASC.
* 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) {
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。
*/
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) {
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
public void clearMessages(String conversationId) {
@ -898,9 +968,13 @@ public class ConversationService {
}
/**
* 删除指定会话中所有审批占位 assistant 消息
* <p>
* 在 replay 前调用,确保 LLM 上下文中不包含任何审批相关文本。
* Remove all approval-placeholder assistant messages from a conversation.
*
* <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
@ -1124,7 +1198,9 @@ public class ConversationService {
}
/**
* 检查会话是否存在
* Check whether a conversation row exists.
*
* <p>检查会话是否存在。
*/
public boolean conversationExists(String conversationId) {
return conversationMapper.selectCount(
@ -1133,8 +1209,12 @@ public class ConversationService {
}
/**
* 校验用户是否拥有该会话。
* 定时任务产生的会话(username=system)对所有登录用户可见。
* Check whether a user owns the conversation, treating system-owned
* rows (e.g. from scheduled jobs / IM channels) as visible to every
* authenticated user.
*
* <p>校验用户是否拥有该会话。定时任务产生的会话(username = system)
* 对所有登录用户可见。
*/
public boolean isConversationOwner(String conversationId, String username) {
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) {
ConversationEntity conv = conversationMapper.selectOne(