feat(ai): 优化 AI 提示词并完善建议交互功能

- 优化后端提示词:描述生成、子任务拆分、负责人推荐,新增栏目信息,去掉无效的 similar_count
- 优化前端提示词:去掉硬性字数限制,即时消息改为简短输出
- 新增 :::ai-action{...}::: 语法处理,支持单独采纳/忽略 assignee 和 similar
- 采纳/忽略后更新消息状态显示
- 负责人改为追加模式,保留现有负责人
- 新增任务关联功能,similar 采纳时自动创建双向关联
- 相似度阈值从 0.7 调整为 0.5,搜索结果增加到 200

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-21 12:09:45 +00:00
parent c253044f61
commit 29be29b9cf
9 changed files with 428 additions and 235 deletions

View File

@@ -3838,6 +3838,8 @@ class ProjectController extends AbstractController
* @apiParam {Number} task_id 任务ID
* @apiParam {Number} msg_id 消息ID
* @apiParam {String} type 建议类型description/subtasks/assignee/similar
* @apiParam {Number} [userid] 用户IDassignee类型时用于指定采纳哪个推荐
* @apiParam {Number} [related] 关联任务IDsimilar类型时用于指定采纳哪个相似任务
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
@@ -3853,6 +3855,8 @@ class ProjectController extends AbstractController
$taskId = intval(Request::input('task_id'));
$msgId = intval(Request::input('msg_id'));
$type = trim(Request::input('type'));
$userid = intval(Request::input('userid'));
$related = intval(Request::input('related'));
// 验证建议类型
if (!in_array($type, ProjectTaskAiEvent::getEventTypes())) {
@@ -3871,8 +3875,8 @@ class ProjectController extends AbstractController
->where('msg_id', $msgId)
->first();
if (!$event || $event->status !== ProjectTaskAiEvent::STATUS_COMPLETED) {
return Base::retError('建议不存在或已处理');
if (!$event) {
return Base::retError('建议不存在');
}
$result = $event->result;
@@ -3883,19 +3887,36 @@ class ProjectController extends AbstractController
// 标记事件为已采纳
$event->markApplied();
// 记录日志
$task->addLog('AI建议采纳' . $type . '建议');
// 更新消息状态
if ($msgId > 0 && $task->dialog_id) {
AiTaskSuggestion::updateMessageStatus($msgId, $task->dialog_id, $type, 'applied');
// similar 类型:创建任务关联
if ($type === 'similar' && $related > 0) {
ProjectTaskRelation::createRelation(
$taskId,
$related,
$task->dialog_id,
$msgId,
User::userid()
);
}
// 返回建议数据,由前端调用相应接口处理
// 记录日志
if ($type === 'assignee' && $userid > 0) {
$user = User::find($userid);
$task->addLog('AI建议指派给 ' . ($user ? $user->nickname : $userid));
} elseif ($type === 'similar' && $related > 0) {
$task->addLog('AI建议关联任务 #' . $related);
} else {
$task->addLog('AI建议采纳' . $type . '建议');
}
// 更新消息状态
$msgResult = AiTaskSuggestion::updateMessageStatus($msgId, $task->dialog_id, $type, 'applied', $userid, $related);
// 返回建议数据和消息内容
return Base::retSuccess('已采纳', [
'type' => $type,
'task_id' => $taskId,
'result' => $result,
'msg' => $msgResult['data'] ?? null,
]);
}
@@ -3909,6 +3930,8 @@ class ProjectController extends AbstractController
* @apiParam {Number} task_id 任务ID
* @apiParam {Number} msg_id 消息ID
* @apiParam {String} type 建议类型
* @apiParam {Number} [userid] 用户IDassignee类型时用于忽略单个推荐
* @apiParam {Number} [related] 关联任务IDsimilar类型时用于忽略单个推荐
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
@@ -3921,6 +3944,8 @@ class ProjectController extends AbstractController
$taskId = intval(Request::input('task_id'));
$msgId = intval(Request::input('msg_id'));
$type = trim(Request::input('type'));
$userid = intval(Request::input('userid'));
$related = intval(Request::input('related'));
// 验证建议类型
if (!in_array($type, ProjectTaskAiEvent::getEventTypes())) {
@@ -3939,18 +3964,19 @@ class ProjectController extends AbstractController
->where('msg_id', $msgId)
->first();
if (!$event || $event->status !== ProjectTaskAiEvent::STATUS_COMPLETED) {
return Base::retError('建议不存在或已处理');
if (!$event) {
return Base::retError('建议不存在');
}
// 标记事件为已忽略
$event->markDismissed();
// 更新消息状态
if ($msgId > 0 && $task->dialog_id) {
AiTaskSuggestion::updateMessageStatus($msgId, $task->dialog_id, $type, 'dismissed');
}
$msgResult = AiTaskSuggestion::updateMessageStatus($msgId, $task->dialog_id, $type, 'dismissed', $userid, $related);
return Base::retSuccess('已忽略');
// 返回消息内容
return Base::retSuccess('已忽略', [
'msg' => $msgResult['data'] ?? null,
]);
}
}