mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 03:13:41 +08:00
docs(conversation): bilingualize ConversationService comments (en/zh)
This commit is contained in:
parent
86c5d871bf
commit
82540a5fcb
@ -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(
|
||||
|
||||
Loading…
Reference in New Issue
Block a user