mirror of
https://github.com/kuaifan/dootask.git
synced 2025-12-11 18:42:54 +00:00
feat: 移除冗余的AI任务和项目生成逻辑,优化代码结构
This commit is contained in:
parent
0deb3113b5
commit
0434bde16f
@ -3024,115 +3024,6 @@ class ProjectController extends AbstractController
|
||||
return Base::retSuccess('复制成功', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} api/project/task/ai_generate 使用 AI 助手生成任务
|
||||
*
|
||||
* @apiDescription 需要token身份,使用AI根据用户输入和上下文信息生成任务标题和详细描述
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup project
|
||||
* @apiName task__ai_generate
|
||||
*
|
||||
* @apiParam {String} content 用户输入的任务描述(必填)
|
||||
* @apiParam {String} [current_title] 当前已有的任务标题(用于优化改进)
|
||||
* @apiParam {String} [current_content] 当前已有的任务内容(HTML格式,用于优化改进)
|
||||
* @apiParam {String} [template_name] 选中的任务模板名称
|
||||
* @apiParam {String} [template_content] 选中的任务模板内容(HTML格式)
|
||||
* @apiParam {Boolean} [has_owner] 是否已设置负责人
|
||||
* @apiParam {Boolean} [has_time_plan] 是否已设置计划时间
|
||||
* @apiParam {String} [priority_level] 任务优先级等级名称
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
* @apiSuccess {String} data.title AI 生成的任务标题
|
||||
* @apiSuccess {String} data.content AI 生成的任务内容(HTML 格式)
|
||||
* @apiSuccess {Array} data.subtasks 当任务较复杂时生成的子任务名称列表
|
||||
*/
|
||||
public function task__ai_generate()
|
||||
{
|
||||
User::auth();
|
||||
|
||||
// 获取用户输入的任务描述
|
||||
$content = Request::input('content');
|
||||
if (empty($content)) {
|
||||
return Base::retError('任务描述不能为空');
|
||||
}
|
||||
|
||||
// 获取上下文信息
|
||||
$context = [
|
||||
'current_title' => Request::input('current_title', ''),
|
||||
'current_content' => Request::input('current_content', ''),
|
||||
'template_name' => Request::input('template_name', ''),
|
||||
'template_content' => Request::input('template_content', ''),
|
||||
'has_owner' => boolval(Request::input('has_owner', false)),
|
||||
'has_time_plan' => boolval(Request::input('has_time_plan', false)),
|
||||
'priority_level' => Request::input('priority_level', ''),
|
||||
];
|
||||
|
||||
// 如果当前内容是HTML格式,转换为markdown
|
||||
if (!empty($context['current_content'])) {
|
||||
$context['current_content'] = Base::html2markdown($context['current_content']);
|
||||
}
|
||||
if (!empty($context['template_content'])) {
|
||||
$context['template_content'] = Base::html2markdown($context['template_content']);
|
||||
}
|
||||
|
||||
$result = AI::generateTask($content, $context);
|
||||
if (Base::isError($result)) {
|
||||
return Base::retError('生成任务失败', $result);
|
||||
}
|
||||
return Base::retSuccess('生成任务成功', $result['data']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} api/project/ai/generate 使用 AI 助手生成项目
|
||||
*
|
||||
* @apiDescription 需要token身份,根据需求说明自动生成项目名称及任务列表
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup project
|
||||
* @apiName ai__generate
|
||||
*
|
||||
* @apiParam {String} content 项目需求或背景描述(必填)
|
||||
* @apiParam {String} [current_name] 当前草拟的项目名称
|
||||
* @apiParam {Array|String} [current_columns] 已有任务列表(数组或以逗号/换行分隔的字符串)
|
||||
* @apiParam {Array} [template_examples] 可参考的模板示例,格式:[ {name: 模板名, columns: [列表...] }, ... ]
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
* @apiSuccess {String} data.name AI 生成的项目名称
|
||||
* @apiSuccess {Array} data.columns AI 生成的任务列表名称数组
|
||||
*/
|
||||
public function ai__generate()
|
||||
{
|
||||
User::auth();
|
||||
|
||||
$content = trim((string)Request::input('content', ''));
|
||||
if ($content === '') {
|
||||
return Base::retError('项目需求描述不能为空');
|
||||
}
|
||||
|
||||
$templateExamples = Request::input('template_examples', []);
|
||||
if (!is_array($templateExamples)) {
|
||||
$templateExamples = [];
|
||||
} else {
|
||||
$templateExamples = array_slice($templateExamples, 0, 6);
|
||||
}
|
||||
|
||||
$context = [
|
||||
'current_name' => Request::input('current_name', ''),
|
||||
'current_columns' => Request::input('current_columns', []),
|
||||
'template_examples' => $templateExamples,
|
||||
];
|
||||
|
||||
$result = AI::generateProject($content, $context);
|
||||
if (Base::isError($result)) {
|
||||
return Base::retError('生成项目失败', $result);
|
||||
}
|
||||
|
||||
return Base::retSuccess('生成项目成功', $result['data']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/project/flow/list 工作流列表
|
||||
*
|
||||
|
||||
@ -318,244 +318,6 @@ class AI
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过 openAI 生成任务标题和描述
|
||||
* @param string $text 用户提供的提示词
|
||||
* @param array $context 上下文信息
|
||||
* @return array
|
||||
*/
|
||||
public static function generateTask($text, $context = [])
|
||||
{
|
||||
// 构建上下文提示信息
|
||||
$contextPrompt = self::buildTaskContextPrompt($context);
|
||||
|
||||
$post = json_encode([
|
||||
"model" => "gpt-5-mini",
|
||||
"reasoning_effort" => "minimal",
|
||||
"messages" => [
|
||||
[
|
||||
"role" => "system",
|
||||
"content" => <<<EOF
|
||||
你是一个专业的任务管理专家,擅长将想法和需求转化为清晰、可执行的项目任务。
|
||||
|
||||
任务生成要求:
|
||||
1. 根据输入内容分析并生成合适的任务标题和详细描述
|
||||
2. 标题要简洁明了,准确概括任务核心目标,长度控制在8-30个字符
|
||||
3. 描述需覆盖任务背景、具体要求、交付标准、风险提示等关键信息
|
||||
4. 描述内容使用Markdown格式,合理组织标题、列表、加粗等结构
|
||||
5. 内容需适配项目管理系统,表述专业、逻辑清晰,并与用户输入语言保持一致
|
||||
6. 优先遵循用户在输入中给出的风格、长度或复杂度要求;默认情况下将详细描述控制在120-200字内,如用户要求简单或简短,则控制在80-120字内
|
||||
7. 当任务具有多个执行步骤、阶段或协作角色时,请拆解出 2-6 个关键子任务;如无必要,可返回空数组
|
||||
8. 子任务应聚焦单一可执行动作,名称控制在8-30个字符内,避免重复和含糊表述
|
||||
|
||||
返回格式要求:
|
||||
必须严格按照以下 JSON 结构返回,禁止输出额外文字或 Markdown 代码块标记;即使某项为空,也保留对应字段:
|
||||
{
|
||||
"title": "任务标题",
|
||||
"content": "任务的详细描述内容,使用Markdown格式,根据实际情况组织结构",
|
||||
"subtasks": [
|
||||
"子任务名称1",
|
||||
"子任务名称2"
|
||||
]
|
||||
}
|
||||
|
||||
内容格式建议(非强制):
|
||||
- 可以使用标题、列表、加粗等Markdown格式
|
||||
- 可以包含任务背景、具体要求、验收标准等部分
|
||||
- 根据任务性质灵活组织内容结构
|
||||
- 仅在确有必要时生成子任务,并确保每个子任务都是独立、可执行、便于追踪的动作
|
||||
- 若用户明确要求简洁或简单,保持描述紧凑,避免添加冗余段落或重复信息
|
||||
|
||||
上下文信息处理指南:
|
||||
- 如果已有标题和内容,优先考虑优化改进而非完全重写
|
||||
- 如果使用了任务模板,严格按照模板的结构和格式要求生成
|
||||
- 如果已设置负责人或时间计划,在任务描述中体现相关要求
|
||||
- 根据优先级等级调整任务的紧急程度和详细程度
|
||||
|
||||
注意事项:
|
||||
- 标题要体现任务的核心动作和目标
|
||||
- 描述要包含足够的细节让执行者理解任务
|
||||
- 如果涉及技术开发,要明确技术要求和实现方案
|
||||
- 如果涉及设计,要说明设计要求和期望效果
|
||||
- 如果涉及测试,要明确测试范围和验收标准
|
||||
EOF
|
||||
],
|
||||
[
|
||||
"role" => "user",
|
||||
"content" => $contextPrompt . "\n\n请根据以上上下文并结合以下提示词生成一个完整的项目任务(包含标题和详细描述):\n\n" . $text
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
$ai = new self($post);
|
||||
$ai->setTimeout(60);
|
||||
|
||||
$res = $ai->request();
|
||||
if (Base::isError($res)) {
|
||||
return Base::retError("任务生成失败", $res);
|
||||
}
|
||||
|
||||
// 清理可能的markdown代码块标记
|
||||
$content = $res['data'];
|
||||
$content = preg_replace('/^\s*```json\s*/', '', $content);
|
||||
$content = preg_replace('/\s*```\s*$/', '', $content);
|
||||
|
||||
if (empty($content)) {
|
||||
return Base::retError("任务生成结果为空");
|
||||
}
|
||||
|
||||
// 解析JSON
|
||||
$parsedData = Base::json2array($content);
|
||||
if (!$parsedData || !isset($parsedData['title']) || !isset($parsedData['content'])) {
|
||||
return Base::retError("任务生成格式错误", $content);
|
||||
}
|
||||
|
||||
$title = trim($parsedData['title']);
|
||||
$markdownContent = trim($parsedData['content']);
|
||||
$rawSubtasks = $parsedData['subtasks'] ?? [];
|
||||
|
||||
if (empty($title) || empty($markdownContent)) {
|
||||
return Base::retError("生成的任务标题或内容为空", $parsedData);
|
||||
}
|
||||
|
||||
$subtasks = [];
|
||||
if (is_array($rawSubtasks)) {
|
||||
foreach ($rawSubtasks as $raw) {
|
||||
if (is_array($raw)) {
|
||||
$name = trim($raw['title'] ?? $raw['name'] ?? '');
|
||||
} else {
|
||||
$name = trim($raw);
|
||||
}
|
||||
|
||||
if (!empty($name)) {
|
||||
$subtasks[] = $name;
|
||||
}
|
||||
|
||||
if (count($subtasks) >= 8) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Base::retSuccess("success", [
|
||||
'title' => $title,
|
||||
'content' => Base::markdown2html($markdownContent), // 将 Markdown 转换为 HTML
|
||||
'subtasks' => $subtasks
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过 openAI 生成项目名称与任务列表
|
||||
* @param string $text 用户提供的提示词
|
||||
* @param array $context 上下文信息
|
||||
* @return array
|
||||
*/
|
||||
public static function generateProject($text, $context = [])
|
||||
{
|
||||
$text = trim((string)$text);
|
||||
if ($text === '') {
|
||||
return Base::retError("项目提示词不能为空");
|
||||
}
|
||||
|
||||
$context['current_name'] = trim($context['current_name'] ?? '');
|
||||
$context['current_columns'] = self::normalizeProjectColumns($context['current_columns'] ?? []);
|
||||
|
||||
if (!empty($context['template_examples']) && is_array($context['template_examples'])) {
|
||||
$examples = [];
|
||||
foreach ($context['template_examples'] as $item) {
|
||||
$name = trim($item['name'] ?? '');
|
||||
$columns = self::normalizeProjectColumns($item['columns'] ?? []);
|
||||
if (empty($columns)) {
|
||||
continue;
|
||||
}
|
||||
$examples[] = [
|
||||
'name' => $name,
|
||||
'columns' => $columns,
|
||||
];
|
||||
if (count($examples) >= 6) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
$context['template_examples'] = $examples;
|
||||
} else {
|
||||
$context['template_examples'] = [];
|
||||
}
|
||||
|
||||
$contextPrompt = self::buildProjectContextPrompt($context);
|
||||
|
||||
$post = json_encode([
|
||||
"model" => "gpt-5-mini",
|
||||
"reasoning_effort" => "minimal",
|
||||
"messages" => [
|
||||
[
|
||||
"role" => "system",
|
||||
"content" => <<<EOF
|
||||
你是一名资深的项目规划顾问,帮助团队快速搭建符合需求的项目。
|
||||
|
||||
生成要求:
|
||||
1. 产出一个简洁、有辨识度的项目名称(不超过18个汉字或36个字符)
|
||||
2. 给出 3 - 8 个项目任务列表,用于看板列或阶段分组
|
||||
3. 任务列表名称保持 4 - 12 个字符,聚焦阶段或责任划分,避免冗长描述
|
||||
4. 结合用户描述的业务特征,必要时可包含里程碑或交付节点
|
||||
5. 尽量参考上下文提供的现有内容或模板,不要与之完全重复
|
||||
|
||||
输出格式:
|
||||
必须严格返回 JSON,禁止携带额外说明或 Markdown 代码块,结构如下:
|
||||
{
|
||||
"name": "项目名称",
|
||||
"columns": ["列表1", "列表2", "列表3"]
|
||||
}
|
||||
|
||||
校验标准:
|
||||
- 列表名称应当互不重复且语义明确
|
||||
- 若上下文包含已有名称或列表,请在此基础上迭代优化
|
||||
EOF
|
||||
],
|
||||
[
|
||||
"role" => "user",
|
||||
"content" => ($contextPrompt ? $contextPrompt . "\n\n" : "") . "请根据以上信息,并结合以下提示词生成适合的项目名称和任务列表:\n\n" . $text
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$ai = new self($post);
|
||||
$ai->setTimeout(45);
|
||||
|
||||
$res = $ai->request();
|
||||
if (Base::isError($res)) {
|
||||
return Base::retError("项目生成失败", $res);
|
||||
}
|
||||
|
||||
$content = $res['data'];
|
||||
$content = preg_replace('/^\s*```json\s*/', '', $content);
|
||||
$content = preg_replace('/\s*```\s*$/', '', $content);
|
||||
|
||||
if (empty($content)) {
|
||||
return Base::retError("项目生成结果为空");
|
||||
}
|
||||
|
||||
$parsedData = Base::json2array($content);
|
||||
if (!$parsedData || !isset($parsedData['name'])) {
|
||||
return Base::retError("项目生成格式错误", $content);
|
||||
}
|
||||
|
||||
$name = trim($parsedData['name']);
|
||||
$columns = self::normalizeProjectColumns($parsedData['columns'] ?? []);
|
||||
|
||||
if ($name === '') {
|
||||
return Base::retError("生成的项目名称为空", $parsedData);
|
||||
}
|
||||
|
||||
if (empty($columns)) {
|
||||
$columns = $context['current_columns'];
|
||||
}
|
||||
|
||||
return Base::retSuccess("success", [
|
||||
'name' => $name,
|
||||
'columns' => $columns,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对工作汇报内容进行分析
|
||||
* @param Report $report
|
||||
@ -685,194 +447,6 @@ class AI
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建任务生成的上下文提示信息
|
||||
* @param array $context 上下文信息
|
||||
* @return string
|
||||
*/
|
||||
private static function buildTaskContextPrompt($context)
|
||||
{
|
||||
$prompts = [];
|
||||
|
||||
// 当前任务信息
|
||||
if (!empty($context['current_title']) || !empty($context['current_content'])) {
|
||||
$prompts[] = "## 当前任务信息";
|
||||
if (!empty($context['current_title'])) {
|
||||
$prompts[] = "当前标题:" . $context['current_title'];
|
||||
}
|
||||
if (!empty($context['current_content'])) {
|
||||
$prompts[] = "当前内容:" . $context['current_content'];
|
||||
}
|
||||
$prompts[] = "请在此基础上优化改进,而不是完全重写。";
|
||||
}
|
||||
|
||||
// 任务模板信息
|
||||
if (!empty($context['template_name']) || !empty($context['template_content'])) {
|
||||
$prompts[] = "## 任务模板要求";
|
||||
if (!empty($context['template_name'])) {
|
||||
$prompts[] = "模板名称:" . $context['template_name'];
|
||||
}
|
||||
if (!empty($context['template_content'])) {
|
||||
$prompts[] = "模板内容结构:" . $context['template_content'];
|
||||
}
|
||||
$prompts[] = "请严格按照此模板的结构和格式要求生成内容。";
|
||||
}
|
||||
|
||||
// 项目状态信息
|
||||
$statusInfo = [];
|
||||
if (!empty($context['has_owner'])) {
|
||||
$statusInfo[] = "已设置负责人";
|
||||
}
|
||||
if (!empty($context['has_time_plan'])) {
|
||||
$statusInfo[] = "已设置计划时间";
|
||||
}
|
||||
if (!empty($context['priority_level'])) {
|
||||
$statusInfo[] = "优先级:" . $context['priority_level'];
|
||||
}
|
||||
|
||||
if (!empty($statusInfo)) {
|
||||
$prompts[] = "## 任务状态";
|
||||
$prompts[] = implode(",", $statusInfo);
|
||||
$prompts[] = "请在任务描述中体现相应的要求和约束。";
|
||||
}
|
||||
|
||||
return empty($prompts) ? "" : implode("\n", $prompts);
|
||||
}
|
||||
|
||||
private static function buildProjectContextPrompt($context)
|
||||
{
|
||||
$prompts = [];
|
||||
|
||||
if (!empty($context['current_name']) || !empty($context['current_columns'])) {
|
||||
$prompts[] = "## 当前项目草稿";
|
||||
if (!empty($context['current_name'])) {
|
||||
$prompts[] = "已有名称:" . $context['current_name'];
|
||||
}
|
||||
if (!empty($context['current_columns'])) {
|
||||
$prompts[] = "现有任务列表:" . implode("、", $context['current_columns']);
|
||||
}
|
||||
$prompts[] = "请在此基础上进行优化和补充。";
|
||||
}
|
||||
|
||||
if (!empty($context['template_examples'])) {
|
||||
$prompts[] = "## 常用模板示例";
|
||||
foreach ($context['template_examples'] as $example) {
|
||||
$line = '';
|
||||
if (!empty($example['name'])) {
|
||||
$line .= $example['name'] . ":";
|
||||
}
|
||||
$line .= implode("、", $example['columns']);
|
||||
$prompts[] = "- " . $line;
|
||||
}
|
||||
$prompts[] = "可以借鉴以上结构,但要结合用户需求生成更贴合的方案。";
|
||||
}
|
||||
|
||||
return empty($prompts) ? "" : implode("\n", $prompts);
|
||||
}
|
||||
|
||||
public static function messageSystemPrompt()
|
||||
{
|
||||
return <<<EOF
|
||||
你是一名专业的沟通助手,协助用户编写得体、清晰且具行动指向的即时消息。
|
||||
|
||||
写作要求:
|
||||
1. 根据用户提供的需求与上下文生成完整消息,语气需符合业务沟通场景,保持真诚、礼貌且高效
|
||||
2. 默认使用简洁的短段落,可使用 Markdown 基础格式(加粗、列表、引用)增强结构,但不要输出代码块或 JSON
|
||||
3. 如果上下文包含引用信息或草稿,请在消息中自然呼应相关要点
|
||||
4. 如无特别说明,将消息长度控制在 60-180 字;若需更短或更长,遵循用户描述
|
||||
5. 如需提出行动或问题,请明确表达,避免含糊
|
||||
|
||||
输出规范:
|
||||
- 仅返回可直接发送的消息内容
|
||||
- 禁止在内容前后添加额外说明、标签或引导语
|
||||
EOF;
|
||||
}
|
||||
|
||||
public static function buildMessageContextPrompt($context)
|
||||
{
|
||||
$prompts = [];
|
||||
|
||||
if (!empty($context['dialog_name']) || !empty($context['dialog_type']) || !empty($context['group_type'])) {
|
||||
$prompts[] = "## 会话信息";
|
||||
if (!empty($context['dialog_name'])) {
|
||||
$prompts[] = "名称:" . Base::cutStr($context['dialog_name'], 60);
|
||||
}
|
||||
if (!empty($context['dialog_type'])) {
|
||||
$typeMap = ['group' => '群聊', 'user' => '单聊'];
|
||||
$prompts[] = "类型:" . ($typeMap[$context['dialog_type']] ?? $context['dialog_type']);
|
||||
}
|
||||
if (!empty($context['group_type'])) {
|
||||
$prompts[] = "分类:" . Base::cutStr($context['group_type'], 60);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($context['members']) && is_array($context['members'])) {
|
||||
$members = array_slice(array_filter($context['members']), 0, 10);
|
||||
if (!empty($members)) {
|
||||
$prompts[] = "## 会话成员";
|
||||
$prompts[] = implode(",", array_map(fn($name) => Base::cutStr($name, 30), $members));
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($context['recent_messages']) && is_array($context['recent_messages'])) {
|
||||
$prompts[] = "## 最近消息";
|
||||
foreach ($context['recent_messages'] as $item) {
|
||||
$sender = Base::cutStr(trim($item['sender'] ?? ''), 40) ?: '成员';
|
||||
$summary = Base::cutStr(trim($item['summary'] ?? ''), 120);
|
||||
if ($summary !== '') {
|
||||
$prompts[] = "- {$sender}:{$summary}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($context['quote_summary'])) {
|
||||
$prompts[] = "## 引用消息";
|
||||
$quoteUser = Base::cutStr(trim($context['quote_user'] ?? ''), 40);
|
||||
$quoteText = Base::cutStr(trim($context['quote_summary']), 200);
|
||||
if ($quoteUser !== '') {
|
||||
$prompts[] = "{$quoteUser}:{$quoteText}";
|
||||
} else {
|
||||
$prompts[] = $quoteText;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($context['current_draft'])) {
|
||||
$prompts[] = "## 当前草稿";
|
||||
$prompts[] = Base::cutStr(trim($context['current_draft']), 200);
|
||||
}
|
||||
|
||||
return empty($prompts) ? "" : implode("\n", $prompts);
|
||||
}
|
||||
|
||||
private static function normalizeProjectColumns($columns)
|
||||
{
|
||||
if (is_string($columns)) {
|
||||
$columns = preg_split('/[\n\r,,;;|]/u', $columns);
|
||||
}
|
||||
|
||||
$normalized = [];
|
||||
if (is_array($columns)) {
|
||||
foreach ($columns as $item) {
|
||||
if (is_array($item)) {
|
||||
$item = $item['name'] ?? $item['title'] ?? reset($item);
|
||||
}
|
||||
$item = trim((string)$item);
|
||||
if ($item === '') {
|
||||
continue;
|
||||
}
|
||||
$item = mb_substr($item, 0, 30);
|
||||
if (!in_array($item, $normalized)) {
|
||||
$normalized[] = $item;
|
||||
}
|
||||
if (count($normalized) >= 8) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $normalized;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过 openAI 生成职场笑话、心灵鸡汤
|
||||
* @param bool $noCache 是否禁用缓存
|
||||
|
||||
@ -1956,8 +1956,6 @@ API请求的基础URL路径,如果没有请留空
|
||||
Grok是由xAI开发的生成式人工智能聊天机器人,旨在通过实时回答用户问题来提供帮助。
|
||||
Ollama 是一个轻量级、可扩展的框架,旨在让用户能够在本地机器上构建和运行大型语言模型。
|
||||
|
||||
AI 列表
|
||||
AI 设置
|
||||
思考中...
|
||||
|
||||
请先填写 Base URL
|
||||
@ -2224,7 +2222,6 @@ AI 正在生成分析...
|
||||
最后更新:
|
||||
暂无 AI 分析,点击右侧按钮生成。
|
||||
AI 整理汇报
|
||||
整理结果预览
|
||||
应用到汇报
|
||||
请先填写汇报内容
|
||||
AI 未返回整理内容
|
||||
|
||||
@ -27899,30 +27899,6 @@
|
||||
"id": "Ollama adalah kerangka kerja ringan dan skalabel yang dirancang untuk memungkinkan pengguna membangun dan menjalankan model bahasa besar pada mesin lokal.",
|
||||
"ru": "Ollama - это легкий, масштабируемый фреймворк, разработанный для того, чтобы пользователи могли создавать и запускать крупные языковые модели на локальных машинах."
|
||||
},
|
||||
{
|
||||
"key": "AI 列表",
|
||||
"zh": "",
|
||||
"zh-CHT": "AI 列表",
|
||||
"en": "AI List",
|
||||
"ko": "AI 목록",
|
||||
"ja": "AIリスト",
|
||||
"de": "KI-Liste",
|
||||
"fr": "Liste d'IA",
|
||||
"id": "Daftar AI",
|
||||
"ru": "Список AI"
|
||||
},
|
||||
{
|
||||
"key": "AI 设置",
|
||||
"zh": "",
|
||||
"zh-CHT": "AI 設置",
|
||||
"en": "AI Settings",
|
||||
"ko": "AI 설정",
|
||||
"ja": "AI設定",
|
||||
"de": "KI-Einstellungen",
|
||||
"fr": "Paramètres de l'IA",
|
||||
"id": "Pengaturan AI",
|
||||
"ru": "Настройки AI"
|
||||
},
|
||||
{
|
||||
"key": "思考中...",
|
||||
"zh": "",
|
||||
|
||||
@ -155,62 +155,6 @@
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
<!--AI BOT-->
|
||||
<DrawerOverlay v-model="aibotShow" placement="right" :size="720">
|
||||
<template v-if="aibotShow" #title>
|
||||
{{ $L('AI 列表') }}
|
||||
</template>
|
||||
<template v-if="aibotShow" #more>
|
||||
<a href="javascript:void(0)" @click="applyClick({value: 'robot-setting'}, 'openai')" v-if="userIsAdmin">{{ $L('机器人设置') }}</a>
|
||||
</template>
|
||||
<div v-if="aibotShow" class="ivu-modal-wrap-apply">
|
||||
<div class="ivu-modal-wrap-apply-body full-body">
|
||||
<ul class="ivu-modal-wrap-ul">
|
||||
<li v-for="(item, key) in aibotList" :key="key">
|
||||
<div class="modal-item-img">
|
||||
<img :src="item.src">
|
||||
</div>
|
||||
<div class="modal-item-info">
|
||||
<div class="modal-item-name">
|
||||
<h4>{{ item.label }}</h4>
|
||||
<div v-if="item.tag" class="modal-item-tag" @click="applyClick({value: 'robot-setting'}, item.value)">
|
||||
{{ item.tag }}
|
||||
<em v-if="item.tags.length > 1">+{{ item.tags.length - 1 }}</em>
|
||||
</div>
|
||||
</div>
|
||||
<p class="modal-item-desc" @click="openDetail(item.desc)">{{ item.desc }}</p>
|
||||
<div class="modal-item-btns">
|
||||
<Button icon="md-chatbubbles" :loading="aibotDialogSearchLoad == item.value" @click="onGoToChat(item.value)">{{ $L('开始聊天') }}</Button>
|
||||
<Button v-if="userIsAdmin" icon="md-settings" @click="applyClick({value: 'robot-setting'}, item.value)">{{ $L('设置') }}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</DrawerOverlay>
|
||||
|
||||
<!--AI BOT 设置-->
|
||||
<DrawerOverlay v-model="aibotSettingShow" placement="right" :size="950">
|
||||
<template v-if="aibotSettingShow" #title>
|
||||
{{ $L('AI 设置') }}
|
||||
</template>
|
||||
<div v-if="aibotSettingShow" class="ivu-modal-wrap-apply">
|
||||
<div class="ivu-modal-wrap-apply-body">
|
||||
<Tabs v-model="aibotTabAction" :animated="false" class="ai-tabs">
|
||||
<TabPane v-for="(item, key) in aibotList" :key="key" :label="item.label" :name="item.value">
|
||||
<div class="aibot-setting">
|
||||
<SystemAibot
|
||||
v-if="aibotTabAction == item.value"
|
||||
:type="item.value"
|
||||
@on-update-setting="handleAITags"/>
|
||||
</div>
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
</DrawerOverlay>
|
||||
|
||||
<!--签到-->
|
||||
<DrawerOverlay v-model="signInShow" placement="right" :size="500">
|
||||
<template v-if="signInShow" #title>
|
||||
@ -347,7 +291,6 @@
|
||||
import {mapGetters, mapState} from "vuex";
|
||||
import DrawerOverlay from "../../components/DrawerOverlay";
|
||||
import UserSelect from "../../components/UserSelect";
|
||||
import SystemAibot from "./setting/components/SystemAibot";
|
||||
import SystemCheckin from "./setting/components/SystemCheckin";
|
||||
import Checkin from "./setting/checkin";
|
||||
import SystemMeeting from "./setting/components/SystemMeeting";
|
||||
@ -357,16 +300,14 @@ import SystemEmailSetting from "./setting/components/SystemEmailSetting";
|
||||
import SystemAppPush from "./setting/components/SystemAppPush";
|
||||
import SystemAiAssistant from "./setting/components/SystemAiAssistant";
|
||||
import emitter from "../../store/events";
|
||||
import {AIBotList, AIModelNames} from "../../utils/ai";
|
||||
import ImgUpload from "../../components/ImgUpload.vue";
|
||||
import {webhookEventOptions} from "../../utils/other";
|
||||
import {webhookEventOptions} from "../../utils/webhook";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ImgUpload,
|
||||
UserSelect,
|
||||
DrawerOverlay,
|
||||
SystemAibot,
|
||||
SystemCheckin,
|
||||
Checkin,
|
||||
SystemMeeting,
|
||||
@ -388,12 +329,6 @@ export default {
|
||||
mybotModifyLoad: 0,
|
||||
webhookEventOptions,
|
||||
//
|
||||
aibotShow: false,
|
||||
aibotList: AIBotList,
|
||||
aibotSettingShow: false,
|
||||
aibotTabAction: "openai",
|
||||
aibotDialogSearchLoad: "",
|
||||
//
|
||||
signInShow: false,
|
||||
signInSettingShow: false,
|
||||
//
|
||||
@ -445,7 +380,6 @@ export default {
|
||||
{value: "recent", label: "最近打开", sort: 47},
|
||||
{value: "report", label: "工作报告", sort: 50},
|
||||
{value: "mybot", label: "我的机器人", sort: 55},
|
||||
{value: "robot", label: "AI 机器人", sort: 60, show: this.microAppsIds.includes('ai')},
|
||||
{value: "signin", label: "签到打卡", sort: 70},
|
||||
{value: "meeting", label: "在线会议", sort: 80},
|
||||
{value: "createGroup", label: "创建群组", sort: 85},
|
||||
@ -560,14 +494,6 @@ export default {
|
||||
case 'mybot-del':
|
||||
this.delMybot(params);
|
||||
break;
|
||||
case 'robot':
|
||||
this.getAITags();
|
||||
this.aibotShow = true;
|
||||
break;
|
||||
case 'robot-setting':
|
||||
this.aibotTabAction = params;
|
||||
this.aibotSettingShow = true;
|
||||
break;
|
||||
case 'signin':
|
||||
this.signInShow = true;
|
||||
break;
|
||||
@ -677,65 +603,6 @@ export default {
|
||||
this.mybotModifyLoad--;
|
||||
});
|
||||
},
|
||||
// 获取AI标签
|
||||
getAITags() {
|
||||
this.$store.dispatch("call", {
|
||||
url: 'system/setting/aibot_models',
|
||||
}).then(({data}) => {
|
||||
this.handleAITags(data);
|
||||
});
|
||||
},
|
||||
// 处理AI标签
|
||||
handleAITags(data) {
|
||||
for (let key in data) {
|
||||
const match = key.match(/^(.*?)_models$/);
|
||||
if (match) {
|
||||
const value = match[1];
|
||||
this.aibotList.map(h => {
|
||||
if (h.value == value) {
|
||||
const items = AIModelNames(data[key])
|
||||
h.tags = items.map(item => item.label);
|
||||
h.tag = data[key.slice(0, -1)];
|
||||
items.some(item => {
|
||||
if (item.value == h.tag) {
|
||||
h.tag = item.label;
|
||||
return true;
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
// 开始聊天
|
||||
onGoToChat(type) {
|
||||
let dialogId = 0;
|
||||
this.cacheDialogs.some(h => {
|
||||
if (h.email == `ai-${type}@bot.system`) {
|
||||
dialogId = h.id;
|
||||
return true;
|
||||
}
|
||||
})
|
||||
if (dialogId) {
|
||||
this.$store.dispatch("openDialog", dialogId)
|
||||
return
|
||||
}
|
||||
//
|
||||
this.aibotDialogSearchLoad = type;
|
||||
this.$store.dispatch("call", {
|
||||
url: 'users/search/ai',
|
||||
data: {type},
|
||||
}).then(({data}) => {
|
||||
this.$store.dispatch("openDialogUserid", data.userid).catch(({msg}) => {
|
||||
$A.modalError(msg)
|
||||
}).finally(_ => {
|
||||
this.aibotDialogSearchLoad = '';
|
||||
});
|
||||
}).catch(({msg}) => {
|
||||
this.aibotDialogSearchLoad = '';
|
||||
$A.messageError(msg || '机器人暂未开启');
|
||||
});
|
||||
},
|
||||
// 会议
|
||||
onMeeting(name) {
|
||||
switch (name) {
|
||||
|
||||
@ -690,7 +690,7 @@ import emitter from "../../../store/events";
|
||||
import Forwarder from "./Forwarder/index.vue";
|
||||
import {throttle} from "lodash";
|
||||
import transformEmojiToHtml from "../../../utils/emoji";
|
||||
import {webhookEventOptions} from "../../../utils/other";
|
||||
import {webhookEventOptions} from "../../../utils/webhook";
|
||||
|
||||
export default {
|
||||
name: "DialogWrapper",
|
||||
|
||||
3
resources/assets/js/utils/ai.js
vendored
3
resources/assets/js/utils/ai.js
vendored
@ -42,8 +42,6 @@ const AINormalizeJsonContent = (content) => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const AIBotList = []
|
||||
|
||||
const AIBotMap = {
|
||||
openai: "ChatGPT",
|
||||
claude: "Claude",
|
||||
@ -327,7 +325,6 @@ const PROJECT_AI_SYSTEM_PROMPT = `你是一名资深的项目规划顾问,帮
|
||||
export {
|
||||
AIModelNames,
|
||||
AINormalizeJsonContent,
|
||||
AIBotList,
|
||||
AIBotMap,
|
||||
AISystemConfig,
|
||||
MESSAGE_AI_SYSTEM_PROMPT,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user