perf: 优化AI创建任务

This commit is contained in:
kuaifan 2024-12-06 19:49:45 +08:00
parent 13222fbe9a
commit cbd9e8a33c
5 changed files with 180 additions and 158 deletions

View File

@ -2893,8 +2893,6 @@ class DialogController extends AbstractController
* @apiGroup dialog
* @apiName msg__applied
*
* @apiParam {String} type 类型
* - CreateTask: 创建任务
* @apiParam {Number} index 索引
* @apiParam {Number} msg_id 消息ID
*
@ -2907,7 +2905,6 @@ class DialogController extends AbstractController
User::auth();
//
$msg_id = intval(Request::input('msg_id'));
$type = trim(Request::input('type'));
$index = intval(Request::input('index'));
//
$msg = WebSocketDialogMsg::whereId($msg_id)->first();
@ -2917,17 +2914,17 @@ class DialogController extends AbstractController
WebSocketDialog::checkDialog($msg->dialog_id);
//
$originalMsg = $msg->getRawOriginal('msg');
$pattern = '/```\s*' . preg_quote($type, '/') . '\s*(applying|applied)?\s*(\n|\\\\n)/';
$pattern = '/:::\s*(create-task-list|create-subtask-list)(?:\s+(\S+))?/';
$count = -1;
$updatedMsg = preg_replace_callback($pattern, function($matches) use (&$count, $index, $type) {
$updatedMsg = preg_replace_callback($pattern, function($matches) use (&$count, $index) {
$count++;
if ($count === $index || ($index === 0 && $count === 1)) {
return "```{$type} applied{$matches[2]}";
return "::: {$matches[1]} applied";
}
return $matches[0];
}, $originalMsg);
if ($count === 0) {
if ($count === -1) {
return Base::retError("未找到可应用的规则");
}

View File

@ -593,13 +593,13 @@ class BotReceiveMsgTask extends AbstractTask
}
$before_text[] = <<<EOF
如果你判断我想要添加任务,请将任务标题和描述添加到回复中,例如:
```CreateTask
::: create-task-list
title: 任务标题1
desc: 任务描述1
title: 任务标题2
desc: 任务描述2
```
:::
EOF;
}
break;
@ -621,10 +621,10 @@ class BotReceiveMsgTask extends AbstractTask
}
$before_text[] = <<<EOF
如果你判断我想要添加子任务,请将子任务标题添加到回复中,例如:
```CreateSubTask
子任务标题1
子任务标题2
```
::: create-subtask-list
title: 子任务标题1
title: 子任务标题2
:::
EOF;
}
break;

View File

@ -3220,18 +3220,40 @@ export default {
});
},
async applyCreateTask(event, el) {
async applyCreateTask(type, event, el) {
const currentTarget = event.target;
if (currentTarget.classList.contains('applying') || currentTarget.classList.contains('applied')) {
return;
}
currentTarget.classList.add('applying')
if (type === 'task') {
if (this.dialogData.group_type !== 'project') {
currentTarget.classList.remove('applying')
$A.modalError('只有在项目中才能创建任务')
return
}
if (!this.dialogData.group_info) {
currentTarget.classList.remove('applying')
$A.modalError('项目不存在')
return;
}
} else if (type === 'subtask') {
if (this.dialogData.group_type !== 'task') {
currentTarget.classList.remove('applying')
$A.modalError('只有在任务中才能创建子任务')
return
}
if (!this.dialogData.group_info) {
currentTarget.classList.remove('applying')
$A.modalError('任务不存在')
return;
}
} else {
currentTarget.classList.remove('applying')
$A.modalError('未知类型')
return
}
let target = event.target;
while (target) {
@ -3246,14 +3268,9 @@ export default {
}
if (!target) {
currentTarget.classList.remove('applying')
$A.modalError('未找到任务内容')
$A.modalError('未找到内容')
return
}
if (!this.dialogData.group_info) {
currentTarget.classList.remove('applying')
$A.modalError('项目不存在')
return;
}
const allTaskElements = el.querySelectorAll('.apply-create-task');
const taskIndex = Array.from(allTaskElements).indexOf(target);
@ -3268,6 +3285,12 @@ export default {
.map(line => `<p>${line.trim()}</p>`)
.join('') : '';
if (type === 'subtask') {
return {
task_id: this.dialogData.group_info.id,
name: title,
};
}
return {
project_id: this.dialogData.group_info.id,
name: title,
@ -3276,29 +3299,40 @@ export default {
})
.filter(Boolean);
const typeCall = type === 'subtask' ? 'taskAddSub' : 'taskAdd';
const typeLabel = type === 'subtask' ? '子任务' : '任务';
const results = await Promise.all(taskList.map(item =>
this.$store.dispatch("taskAdd", item).then(
this.$store.dispatch(typeCall, item).then(
success => ({ success: true, data: success }),
error => ({ success: false, error: error })
)
));
const successTasks = results.filter(r => r.success).map(r => r.data);
const failedTasks = results.filter(r => !r.success).map(r => r.error);
let notice = `${this.$store.state.userInfo.nickname} 成功创建 ${successTasks.length}${typeLabel}`;
if (failedTasks.length > 0) {
$A.modalError(`成功创建 ${successTasks.length} 个任务,${failedTasks.length} 个任务创建失败`);
} else {
$A.messageSuccess(`成功创建 ${successTasks.length} 个任务`);
notice += `${failedTasks.length}${typeLabel}创建失败`;
}
currentTarget.classList.remove('applying')
currentTarget.classList.add('applied')
const {data} = await this.$store.dispatch("call", {
url: 'dialog/msg/sendnotice',
data: {
dialog_id: this.dialogId,
source: 'ai',
notice,
},
});
this.sendSuccess(data)
await this.$store.dispatch("call", {
url: 'dialog/msg/applied',
data: {
type: 'CreateTask',
msg_id: this.operateItem.id,
index: taskIndex,
msg_id: this.operateItem.id
},
});
},
@ -3411,7 +3445,14 @@ export default {
//
if (target.classList.contains('apply-create-task-button')) {
this.operateItem = this.findMsgByElement(el)
this.applyCreateTask(event, el)
this.applyCreateTask('task', event, el)
return;
}
//
if (target.classList.contains('apply-create-subtask-button')) {
this.operateItem = this.findMsgByElement(el)
this.applyCreateTask('subtask', event, el)
return;
}

View File

@ -39,10 +39,13 @@ const MarkdownUtils = {
const MarkdownPluginUtils = {
// 配置选项
config: {
maxItems: 200,
maxTitleLength: 200,
maxDescLength: 1000,
maxItems: 200,
defaultTitle: '创建任务'
buttonLabels: {
task: '创建任务',
subtask: '创建子任务'
}
},
// HTML转义函数
@ -65,90 +68,15 @@ const MarkdownPluginUtils = {
return value;
},
// 解析任务项
parseTaskItems(content) {
const items = [];
let currentItem = {};
let itemCount = 0;
content.forEach(line => {
line = line.trim();
if (!line) {
if (Object.keys(currentItem).length > 0) {
items.push(currentItem);
currentItem = {};
itemCount++;
}
return;
}
if (itemCount >= this.config.maxItems) {
return;
}
const [key, ...valueParts] = line.split(':');
const value = valueParts.join(':').trim();
if (key === 'title' && value) {
if (Object.keys(currentItem).length > 0) {
items.push(currentItem);
currentItem = {};
itemCount++;
}
currentItem.title = this.validateInput(value, this.config.maxTitleLength);
} else if (key === 'desc' && value) {
currentItem.desc = this.validateInput(value, this.config.maxDescLength);
}
});
if (Object.keys(currentItem).length > 0 && itemCount < this.config.maxItems) {
items.push(currentItem);
}
return items;
},
// 生成HTML
generateTaskHtml(items, status = null) {
if (!Array.isArray(items) || items.length === 0) {
return '';
}
const html = [
'<div class="apply-create-task">',
'<ul>'
];
items.forEach((item, index) => {
if (item.title) {
html.push(`<li>`);
html.push(`<div class="task-index">${index + 1}.</div>`);
html.push(`<div class="task-item">`);
html.push(`<div class="title">${this.escapeHtml(item.title)}</div>`);
if (item.desc) {
html.push(`<div class="desc">${this.escapeHtml(item.desc)}</div>`);
}
html.push('</div>');
html.push('</li>');
}
});
html.push(
'</ul>',
`<div class="apply-button"><div class="apply-create-task-button ${status||''}">${this.escapeHtml($A.L(this.config.defaultTitle))}</div></div>`,
'</div>'
);
return html.join('\n');
},
// 修改初始化插件函数
initCreateTaskPlugin(md) {
md.block.ruler.before('fence', 'create_task', (state, startLine, endLine, silent) => {
try {
md.block.ruler.before('fence', 'create-task', (state, startLine, endLine, silent) => {
const start = state.bMarks[startLine] + state.tShift[startLine];
const max = state.eMarks[startLine];
const firstLine = state.src.slice(start, max).trim();
const match = state.src.slice(start, max).trim().match(/^```\s*CreateTask\s*(applying|applied)?$/);
// 检查开始标记并获取status值
const match = firstLine.match(/^:::\s*(create-task-list|create-subtask-list)(?:\s+(\S+))?$/);
if (!match) {
return false;
}
@ -157,45 +85,101 @@ const MarkdownPluginUtils = {
return true;
}
// 获取按钮标题和状态
const listType = match[1] === 'create-task-list' ? 'task' : 'subtask';
const buttonTitle = this.config.buttonLabels[listType] || '';
const status = match[2] || '';
let nextLine = startLine + 1;
let content = [];
let found = false;
// 查找结束标记
while (nextLine < endLine) {
const line = state.src.slice(state.bMarks[nextLine], state.eMarks[nextLine]).trim();
if (line === '```') {
found = true;
const lineStart = state.bMarks[nextLine] + state.tShift[nextLine];
const lineMax = state.eMarks[nextLine];
const line = state.src.slice(lineStart, lineMax);
if (line.trim() === ':::') {
break;
}
content.push(line);
nextLine++;
}
if (!found) {
return false;
// 解析任务
const tasks = [];
let currentTask = null;
let isCollectingDesc = false;
let descLines = [];
content.forEach(line => {
const titleMatch = line.trim().match(/^title:\s*(.+)$/);
const descMatch = line.trim().match(/^desc:\s*(.*)$/);
if (titleMatch) {
// 如果已经有一个任务在处理中,保存它
if (currentTask) {
if (descLines.length > 0) {
currentTask.desc = descLines.join('\n');
}
tasks.push(currentTask);
}
// 创建 token 并设置为空字符串内容
const token = state.push('html_block', '', 0);
// 如果有内容则解析并生成HTML
if (content.length > 0) {
const items = this.parseTaskItems(content);
const html = this.generateTaskHtml(items, match[1]);
token.content = html || '';
} else {
token.content = ''; // 空内容直接返回空字符串
// 开始新的任务
currentTask = {title: titleMatch[1]};
isCollectingDesc = false;
descLines = [];
} else if (descMatch) {
isCollectingDesc = true;
if (descMatch[1]) {
descLines.push(descMatch[1]);
}
token.map = [startLine, nextLine + 1];
state.line = nextLine + 1;
return true;
} catch (error) {
console.error('Error in create_task parser:', error);
return false;
} else if (isCollectingDesc && line.trim() && !line.trim().startsWith('title:')) {
// 收集多行描述但不包括空行和新的title行
descLines.push(line.trim());
}
});
// 处理最后一个任务
if (currentTask) {
if (descLines.length > 0) {
currentTask.desc = descLines.join('\n');
}
tasks.push(currentTask);
}
// 生成HTML
const showIndex = tasks.length > 1;
const taskItems = tasks.slice(0, this.config.maxItems).map((task, index) => [
'<li>',
showIndex ? `<div class="task-index">${index + 1}.</div>` : '',
'<div class="task-item">',
`<div class="title">${this.escapeHtml(this.validateInput(task.title, this.config.maxTitleLength))}</div>`,
task.desc && match[1] === 'create-task-list' ? `<div class="desc">${this.escapeHtml(this.validateInput(task.desc, this.config.maxDescLength))}</div>` : '',
'</div>',
'</li>'
].join(''));
const htmls = [
'<div class="apply-create-task">',
'<ul>',
taskItems.join(''),
'</ul>',
'<div class="apply-button">',
`<div class="apply-create-${listType}-button${status ? ' ' + status : ''}">${$A.L(buttonTitle)}</div>`,
'</div>',
'</div>'
];
// 添加token
const token = state.push('html_block', '', 0);
token.content = htmls.join('');
token.map = [startLine, nextLine];
state.line = nextLine + 1;
return true;
})
}
};

View File

@ -2118,7 +2118,7 @@
}
}
&.applied {
color: #c5c8ce;
color: #a5a8ae;
background-color: #f7f7f7;
border-color: #dcdee2;
&:before {