refactor(ai): 简化 AI 命令消息处理为单条 text 消息更新

- 将"正在处理"消息从 notice 类型改为 text 类型
- 命令完成后直接更新原消息内容,而非发送额外消息
- 移除 sendMessage 方法,统一使用 updatePendingMessage
- 重命名 notifyMsgId 为 pendingMsgId 以更准确表达用途

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
This commit is contained in:
kuaifan 2026-01-25 10:52:38 +00:00
parent 5ee5f253ec
commit 28504e4a4e
2 changed files with 57 additions and 88 deletions

View File

@ -3610,22 +3610,22 @@ class DialogController extends AbstractController
// 设置锁,有效期 3 分钟AI 任务超时时间为 120 秒) // 设置锁,有效期 3 分钟AI 任务超时时间为 120 秒)
Cache::put($lockKey, true, Carbon::now()->addMinutes(3)); Cache::put($lockKey, true, Carbon::now()->addMinutes(3));
// 发送"正在处理"提示消息notice 类型,前端自动翻译 // 发送"正在处理"消息text 类型,完成后会更新此消息
$noticeKey = $command === 'analyze' ? '正在分析,请稍候...' : '正在总结,请稍候...'; $pendingText = $command === 'analyze' ? '正在分析,请稍候...' : '正在总结,请稍候...';
$result = WebSocketDialogMsg::sendMsg( $result = WebSocketDialogMsg::sendMsg(
null, null,
$dialogId, $dialogId,
'notice', 'text',
['notice' => $noticeKey], ['text' => $pendingText],
\App\Module\AiDialogCommand::AI_ASSISTANT_USERID, \App\Module\AiDialogCommand::AI_ASSISTANT_USERID,
true, // push_self true, // push_self
false, // push_retry false, // push_retry
true // push_silence true // push_silence
); );
$notifyMsgId = $result['data']->id ?? 0; $pendingMsgId = $result['data']->id ?? 0;
// 投递异步任务 // 投递异步任务
Task::deliver(new AiDialogCommandTask($dialogId, $command, $user->userid, $notifyMsgId)); Task::deliver(new AiDialogCommandTask($dialogId, $command, $user->userid, $pendingMsgId));
return Base::retSuccess('命令已接受'); return Base::retSuccess('命令已接受');
} }

View File

@ -28,19 +28,19 @@ class AiDialogCommand
/** /**
* 执行 /analyze 命令 * 执行 /analyze 命令
*/ */
public static function analyze(WebSocketDialog $dialog, int $userId, int $notifyMsgId = 0): void public static function analyze(WebSocketDialog $dialog, int $userId, int $pendingMsgId = 0): void
{ {
$lang = self::getUserLanguage($userId); $lang = self::getUserLanguage($userId);
switch ($dialog->group_type) { switch ($dialog->group_type) {
case 'task': case 'task':
self::analyzeTask($dialog, $userId, $lang, $notifyMsgId); self::analyzeTask($dialog, $userId, $lang, $pendingMsgId);
break; break;
case 'project': case 'project':
self::analyzeProject($dialog, $userId, $lang, $notifyMsgId); self::analyzeProject($dialog, $userId, $lang, $pendingMsgId);
break; break;
default: default:
self::updateNotifyMessage($dialog, $notifyMsgId, '/analyze 命令仅在任务和项目对话中可用', 'error'); self::updatePendingMessage($dialog, $pendingMsgId, '❌ /analyze 命令仅在任务和项目对话中可用');
break; break;
} }
} }
@ -48,19 +48,19 @@ class AiDialogCommand
/** /**
* 执行 /summarize 命令 * 执行 /summarize 命令
*/ */
public static function summarize(WebSocketDialog $dialog, int $userId, int $notifyMsgId = 0): void public static function summarize(WebSocketDialog $dialog, int $userId, int $pendingMsgId = 0): void
{ {
$lang = self::getUserLanguage($userId); $lang = self::getUserLanguage($userId);
switch ($dialog->group_type) { switch ($dialog->group_type) {
case 'task': case 'task':
self::summarizeTask($dialog, $lang, $notifyMsgId); self::summarizeTask($dialog, $lang, $pendingMsgId);
break; break;
case 'project': case 'project':
self::summarizeProject($dialog, $lang, $notifyMsgId); self::summarizeProject($dialog, $lang, $pendingMsgId);
break; break;
default: default:
self::summarizeGeneral($dialog, $lang, $notifyMsgId); self::summarizeGeneral($dialog, $lang, $pendingMsgId);
break; break;
} }
} }
@ -77,11 +77,11 @@ class AiDialogCommand
/** /**
* 分析任务对话 - 复用 AiTaskSuggestion 逻辑 * 分析任务对话 - 复用 AiTaskSuggestion 逻辑
*/ */
private static function analyzeTask(WebSocketDialog $dialog, int $userId, string $lang, int $notifyMsgId): void private static function analyzeTask(WebSocketDialog $dialog, int $userId, string $lang, int $pendingMsgId): void
{ {
$task = ProjectTask::with(['project', 'projectColumn'])->whereDialogId($dialog->id)->first(); $task = ProjectTask::with(['project', 'projectColumn'])->whereDialogId($dialog->id)->first();
if (!$task) { if (!$task) {
self::updateNotifyMessage($dialog, $notifyMsgId, '未找到关联的任务', 'error'); self::updatePendingMessage($dialog, $pendingMsgId, '❌ 未找到关联的任务');
return; return;
} }
@ -109,23 +109,22 @@ class AiDialogCommand
} }
if (empty($suggestions)) { if (empty($suggestions)) {
self::updateNotifyMessage($dialog, $notifyMsgId, '当前任务状态良好,暂无建议', 'success'); self::updatePendingMessage($dialog, $pendingMsgId, '✅ 当前任务状态良好,暂无建议');
} else { } else {
// 更新提示消息为成功 // 构建建议消息内容并更新待处理消息
self::updateNotifyMessage($dialog, $notifyMsgId, '分析完成', 'success'); $content = AiTaskSuggestion::buildMarkdownMessage($task->id, $suggestions, $pendingMsgId, $lang);
// 复用 AiTaskSuggestion 的消息构建和发送逻辑 self::updatePendingMessage($dialog, $pendingMsgId, $content, true);
AiTaskSuggestion::sendSuggestionMessage($task, $suggestions);
} }
} }
/** /**
* 分析项目对话 - 项目健康度分析 * 分析项目对话 - 项目健康度分析
*/ */
private static function analyzeProject(WebSocketDialog $dialog, int $userId, string $lang, int $notifyMsgId): void private static function analyzeProject(WebSocketDialog $dialog, int $userId, string $lang, int $pendingMsgId): void
{ {
$project = Project::whereDialogId($dialog->id)->first(); $project = Project::whereDialogId($dialog->id)->first();
if (!$project) { if (!$project) {
self::updateNotifyMessage($dialog, $notifyMsgId, '未找到关联的项目', 'error'); self::updatePendingMessage($dialog, $pendingMsgId, '❌ 未找到关联的项目');
return; return;
} }
@ -142,20 +141,18 @@ class AiDialogCommand
], 120); ], 120);
if (Base::isError($result)) { if (Base::isError($result)) {
self::updateNotifyMessage($dialog, $notifyMsgId, $result['msg'] ?? 'AI 分析失败', 'error'); self::updatePendingMessage($dialog, $pendingMsgId, '❌ ' . ($result['msg'] ?? 'AI 分析失败'));
return; return;
} }
$content = $result['data']['content'] ?? ''; $content = $result['data']['content'] ?? '';
if (empty($content)) { if (empty($content)) {
self::updateNotifyMessage($dialog, $notifyMsgId, 'AI 未返回有效内容', 'error'); self::updatePendingMessage($dialog, $pendingMsgId, '❌ AI 未返回有效内容');
return; return;
} }
// 更新提示消息为成功 // 直接用分析结果更新待处理消息
self::updateNotifyMessage($dialog, $notifyMsgId, '分析完成', 'success'); self::updatePendingMessage($dialog, $pendingMsgId, $content, true);
// 发送分析结果
self::sendMessage($dialog, $content);
} }
/** /**
@ -280,11 +277,11 @@ PROMPT;
/** /**
* 总结任务对话 * 总结任务对话
*/ */
private static function summarizeTask(WebSocketDialog $dialog, string $lang, int $notifyMsgId): void private static function summarizeTask(WebSocketDialog $dialog, string $lang, int $pendingMsgId): void
{ {
$task = ProjectTask::whereDialogId($dialog->id)->first(); $task = ProjectTask::whereDialogId($dialog->id)->first();
if (!$task) { if (!$task) {
self::updateNotifyMessage($dialog, $notifyMsgId, '未找到关联的任务', 'error'); self::updatePendingMessage($dialog, $pendingMsgId, '❌ 未找到关联的任务');
return; return;
} }
@ -292,7 +289,7 @@ PROMPT;
$messages = self::getRecentMessages($dialog->id, 50); $messages = self::getRecentMessages($dialog->id, 50);
if (empty($messages)) { if (empty($messages)) {
self::updateNotifyMessage($dialog, $notifyMsgId, '暂无可供总结的消息记录', 'success'); self::updatePendingMessage($dialog, $pendingMsgId, '✅ 暂无可供总结的消息记录');
return; return;
} }
@ -306,30 +303,28 @@ PROMPT;
], 120); ], 120);
if (Base::isError($result)) { if (Base::isError($result)) {
self::updateNotifyMessage($dialog, $notifyMsgId, $result['msg'] ?? 'AI 总结失败', 'error'); self::updatePendingMessage($dialog, $pendingMsgId, '❌ ' . ($result['msg'] ?? 'AI 总结失败'));
return; return;
} }
$content = $result['data']['content'] ?? ''; $content = $result['data']['content'] ?? '';
if (empty($content)) { if (empty($content)) {
self::updateNotifyMessage($dialog, $notifyMsgId, 'AI 未返回有效内容', 'error'); self::updatePendingMessage($dialog, $pendingMsgId, '❌ AI 未返回有效内容');
return; return;
} }
// 更新提示消息为成功 // 直接用总结结果更新待处理消息
self::updateNotifyMessage($dialog, $notifyMsgId, '总结完成', 'success'); self::updatePendingMessage($dialog, $pendingMsgId, $content, true);
// 发送总结结果
self::sendMessage($dialog, $content);
} }
/** /**
* 总结项目对话 * 总结项目对话
*/ */
private static function summarizeProject(WebSocketDialog $dialog, string $lang, int $notifyMsgId): void private static function summarizeProject(WebSocketDialog $dialog, string $lang, int $pendingMsgId): void
{ {
$project = Project::whereDialogId($dialog->id)->first(); $project = Project::whereDialogId($dialog->id)->first();
if (!$project) { if (!$project) {
self::updateNotifyMessage($dialog, $notifyMsgId, '未找到关联的项目', 'error'); self::updatePendingMessage($dialog, $pendingMsgId, '❌ 未找到关联的项目');
return; return;
} }
@ -337,7 +332,7 @@ PROMPT;
$messages = self::getRecentMessages($dialog->id, 50); $messages = self::getRecentMessages($dialog->id, 50);
if (empty($messages)) { if (empty($messages)) {
self::updateNotifyMessage($dialog, $notifyMsgId, '暂无可供总结的消息记录', 'success'); self::updatePendingMessage($dialog, $pendingMsgId, '✅ 暂无可供总结的消息记录');
return; return;
} }
@ -351,32 +346,30 @@ PROMPT;
], 120); ], 120);
if (Base::isError($result)) { if (Base::isError($result)) {
self::updateNotifyMessage($dialog, $notifyMsgId, $result['msg'] ?? 'AI 总结失败', 'error'); self::updatePendingMessage($dialog, $pendingMsgId, '❌ ' . ($result['msg'] ?? 'AI 总结失败'));
return; return;
} }
$content = $result['data']['content'] ?? ''; $content = $result['data']['content'] ?? '';
if (empty($content)) { if (empty($content)) {
self::updateNotifyMessage($dialog, $notifyMsgId, 'AI 未返回有效内容', 'error'); self::updatePendingMessage($dialog, $pendingMsgId, '❌ AI 未返回有效内容');
return; return;
} }
// 更新提示消息为成功 // 直接用总结结果更新待处理消息
self::updateNotifyMessage($dialog, $notifyMsgId, '总结完成', 'success'); self::updatePendingMessage($dialog, $pendingMsgId, $content, true);
// 发送总结结果
self::sendMessage($dialog, $content);
} }
/** /**
* 总结普通对话 * 总结普通对话
*/ */
private static function summarizeGeneral(WebSocketDialog $dialog, string $lang, int $notifyMsgId): void private static function summarizeGeneral(WebSocketDialog $dialog, string $lang, int $pendingMsgId): void
{ {
// 获取最近消息 // 获取最近消息
$messages = self::getRecentMessages($dialog->id, 50); $messages = self::getRecentMessages($dialog->id, 50);
if (empty($messages)) { if (empty($messages)) {
self::updateNotifyMessage($dialog, $notifyMsgId, '暂无可供总结的消息记录', 'success'); self::updatePendingMessage($dialog, $pendingMsgId, '✅ 暂无可供总结的消息记录');
return; return;
} }
@ -390,20 +383,18 @@ PROMPT;
], 120); ], 120);
if (Base::isError($result)) { if (Base::isError($result)) {
self::updateNotifyMessage($dialog, $notifyMsgId, $result['msg'] ?? 'AI 总结失败', 'error'); self::updatePendingMessage($dialog, $pendingMsgId, '❌ ' . ($result['msg'] ?? 'AI 总结失败'));
return; return;
} }
$content = $result['data']['content'] ?? ''; $content = $result['data']['content'] ?? '';
if (empty($content)) { if (empty($content)) {
self::updateNotifyMessage($dialog, $notifyMsgId, 'AI 未返回有效内容', 'error'); self::updatePendingMessage($dialog, $pendingMsgId, '❌ AI 未返回有效内容');
return; return;
} }
// 更新提示消息为成功 // 直接用总结结果更新待处理消息
self::updateNotifyMessage($dialog, $notifyMsgId, '总结完成', 'success'); self::updatePendingMessage($dialog, $pendingMsgId, $content, true);
// 发送总结结果
self::sendMessage($dialog, $content);
} }
/** /**
@ -536,50 +527,28 @@ PROMPT;
} }
/** /**
* 发送消息到对话 * 更新待处理消息
*/
private static function sendMessage(WebSocketDialog $dialog, string $content): void
{
WebSocketDialogMsg::sendMsg(
null,
$dialog->id,
'text',
['text' => $content, 'type' => 'md'],
self::AI_ASSISTANT_USERID,
true, // push_self
false, // push_retry
true // push_silence
);
}
/**
* 更新提示消息
* @param WebSocketDialog $dialog 对话 * @param WebSocketDialog $dialog 对话
* @param int $notifyMsgId 提示消息ID * @param int $pendingMsgId 待处理消息ID
* @param string $content 新内容 * @param string $content 新内容
* @param string $status 状态: success/error * @param bool $isMarkdown 是否为 Markdown 格式
*/ */
private static function updateNotifyMessage(WebSocketDialog $dialog, int $notifyMsgId, string $content, string $status): void private static function updatePendingMessage(WebSocketDialog $dialog, int $pendingMsgId, string $content, bool $isMarkdown = false): void
{ {
// 清除并发锁 // 清除并发锁
self::releaseLock($dialog->id); self::releaseLock($dialog->id);
if ($notifyMsgId <= 0) { $msg = ['text' => $content];
// 没有提示消息ID直接发送新消息 if ($isMarkdown) {
self::sendMessage($dialog, $content); $msg['type'] = 'md';
return;
} }
// 根据状态添加前缀图标 // 更新或发送新消息
$prefix = $status === 'error' ? '❌ ' : '✅ ';
$noticeContent = $prefix . $content;
// 更新消息(不带 source=api让前端自动翻译
WebSocketDialogMsg::sendMsg( WebSocketDialogMsg::sendMsg(
'update-' . $notifyMsgId, $pendingMsgId > 0 ? 'update-' . $pendingMsgId : null,
$dialog->id, $dialog->id,
'notice', 'text',
['notice' => $noticeContent], $msg,
self::AI_ASSISTANT_USERID, self::AI_ASSISTANT_USERID,
true, // push_self true, // push_self
false, // push_retry false, // push_retry