mirror of
https://github.com/kuaifan/dootask.git
synced 2026-01-22 09:48:11 +00:00
refactor: 统一 dootask:// 链接处理与资源格式指南
- 将 dootask:// 协议链接处理逻辑从 AIAssistant 迁移到 DialogMarkdown 组件 - 新增 beforeNavigate prop 支持导航前回调(如关闭弹窗) - 后端 BotReceiveMsgTask 添加条件性资源格式指南提示词 - 前端 ai.js 新增 SEARCH_AI_SYSTEM_PROMPT 和 DOOTASK_RESOURCE_FORMAT_GUIDE - SearchBox 改用统一的 SEARCH_AI_SYSTEM_PROMPT 常量 - 重构 ai.js 代码组织,添加注释说明各常量用途
This commit is contained in:
parent
495b25e2b1
commit
22926e19cd
@ -675,6 +675,20 @@ class BotReceiveMsgTask extends AbstractTask
|
|||||||
$prompt[] = implode("\n", $contextLines);
|
$prompt[] = implode("\n", $contextLines);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 4、追加条件性格式指南(放在最后,优先级最低)
|
||||||
|
$prompt[] = <<<'EOF'
|
||||||
|
<optional-format-guide>
|
||||||
|
当你的回答中包含 DooTask 系统资源(任务、项目、文件等)时,建议使用以下链接格式使其可点击:
|
||||||
|
- 任务: [任务名称](dootask://task/{task_id}/{parent_id}),其中 parent_id 为主任务ID,主任务时为 0
|
||||||
|
- 项目: [项目名称](dootask://project/{project_id})
|
||||||
|
- 文件: [文件名称](dootask://file/{file_id})
|
||||||
|
- 联系人: [用户名](dootask://contact/{userid})
|
||||||
|
- 消息: [消息预览](dootask://message/{dialog_id}/{msg_id})
|
||||||
|
|
||||||
|
注意:此格式指南不影响正常对话,仅在涉及上述资源时参考。如果与当前对话无关,请忽略。
|
||||||
|
</optional-format-guide>
|
||||||
|
EOF;
|
||||||
|
|
||||||
$extras['system_message'] = implode("\n----\n", array_filter($prompt));
|
$extras['system_message'] = implode("\n----\n", array_filter($prompt));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -84,7 +84,7 @@
|
|||||||
v-if="response.rawOutput"
|
v-if="response.rawOutput"
|
||||||
class="ai-assistant-output-markdown no-dark-content"
|
class="ai-assistant-output-markdown no-dark-content"
|
||||||
:text="response.displayOutput || response.rawOutput"
|
:text="response.displayOutput || response.rawOutput"
|
||||||
@click="onContentClick"/>
|
:before-navigate="() => { showModal = false }"/>
|
||||||
<div v-else class="ai-assistant-output-placeholder">
|
<div v-else class="ai-assistant-output-placeholder">
|
||||||
{{ response.status === 'error' ? (response.error || $L('发送失败')) : $L('等待 AI 回复...') }}
|
{{ response.status === 'error' ? (response.error || $L('发送失败')) : $L('等待 AI 回复...') }}
|
||||||
</div>
|
</div>
|
||||||
@ -900,71 +900,6 @@ export default {
|
|||||||
return distance <= threshold;
|
return distance <= threshold;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理内容区域的点击事件
|
|
||||||
*/
|
|
||||||
onContentClick(e) {
|
|
||||||
const target = e.target;
|
|
||||||
if (target.tagName !== 'A') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const href = target.getAttribute('href');
|
|
||||||
if (!href || !href.startsWith('dootask://')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
|
|
||||||
// 解析 dootask:// 协议链接
|
|
||||||
// 格式: dootask://type/id 或 dootask://type/id1/id2 例如 dootask://task/123 或 dootask://message/789/1234
|
|
||||||
const match = href.match(/^dootask:\/\/(\w+)\/(\d+)(?:\/(\d+))?$/);
|
|
||||||
if (!match) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [, type, id, id2] = match;
|
|
||||||
const numId = parseInt(id, 10);
|
|
||||||
const numId2 = id2 ? parseInt(id2, 10) : null;
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case 'task':
|
|
||||||
this.$store.dispatch('openTask', {id: (numId2 && numId2 > 0) ? numId2 : numId});
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'project':
|
|
||||||
this.showModal = false;
|
|
||||||
this.goForward({name: 'manage-project', params: {projectId: numId}});
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'file':
|
|
||||||
this.showModal = false;
|
|
||||||
this.goForward({name: 'manage-file', params: {folderId: 0, fileId: null, shakeId: numId}});
|
|
||||||
this.$store.state.fileShakeId = numId;
|
|
||||||
setTimeout(() => {
|
|
||||||
this.$store.state.fileShakeId = 0;
|
|
||||||
}, 600);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'contact':
|
|
||||||
this.$store.dispatch('openDialogUserid', numId).catch(({msg}) => {
|
|
||||||
$A.modalError(msg || this.$L('打开会话失败'));
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'message':
|
|
||||||
this.$store.dispatch('openDialog', numId).then(() => {
|
|
||||||
if (numId2) {
|
|
||||||
this.$store.state.dialogSearchMsgId = numId2;
|
|
||||||
}
|
|
||||||
}).catch(({msg}) => {
|
|
||||||
$A.modalError(msg || this.$L('打开会话失败'));
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// ==================== 会话管理方法 ====================
|
// ==================== 会话管理方法 ====================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -94,6 +94,7 @@
|
|||||||
import {mapState} from "vuex";
|
import {mapState} from "vuex";
|
||||||
import emitter from "../store/events";
|
import emitter from "../store/events";
|
||||||
import transformEmojiToHtml from "../utils/emoji";
|
import transformEmojiToHtml from "../utils/emoji";
|
||||||
|
import {SEARCH_AI_SYSTEM_PROMPT} from "../utils/ai";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'SearchBox',
|
name: 'SearchBox',
|
||||||
@ -593,33 +594,8 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
handleAISearchBeforeSend(context = []) {
|
handleAISearchBeforeSend(context = []) {
|
||||||
const systemPrompt = [
|
|
||||||
'你是一个智能搜索助手,负责帮助用户在 DooTask 系统中搜索和整理信息。',
|
|
||||||
'你可以使用 intelligent_search 工具来搜索任务、项目、文件和联系人。',
|
|
||||||
'',
|
|
||||||
'请根据用户的搜索需求:',
|
|
||||||
'1. 调用搜索工具获取相关结果',
|
|
||||||
'2. 对搜索结果进行分类整理',
|
|
||||||
'3. 以清晰的格式呈现给用户',
|
|
||||||
'4. 如有需要,可以进行多次搜索以获取更全面的结果',
|
|
||||||
'',
|
|
||||||
'## 链接格式要求',
|
|
||||||
'在返回结果时,请使用以下格式创建可点击的链接:',
|
|
||||||
'- 任务: [任务名称](dootask://task/任务ID/主任务ID)',
|
|
||||||
'- 项目: [项目名称](dootask://project/项目ID)',
|
|
||||||
'- 文件: [文件名称](dootask://file/文件ID)',
|
|
||||||
'- 联系人: [联系人名称](dootask://contact/用户ID)',
|
|
||||||
'- 消息: [消息内容预览](dootask://message/对话ID/消息ID)',
|
|
||||||
'',
|
|
||||||
'示例:',
|
|
||||||
'- [完成项目报告](dootask://task/123/0)(主任务)',
|
|
||||||
'- [编写测试用例](dootask://task/456/123)(子任务)',
|
|
||||||
'- [产品开发项目](dootask://project/456)',
|
|
||||||
'- [关于报销的讨论](dootask://message/789/1234)',
|
|
||||||
].join('\n');
|
|
||||||
|
|
||||||
const prepared = [
|
const prepared = [
|
||||||
['system', systemPrompt]
|
['system', SEARCH_AI_SYSTEM_PROMPT]
|
||||||
];
|
];
|
||||||
|
|
||||||
if (context.length > 0) {
|
if (context.length > 0) {
|
||||||
|
|||||||
@ -13,6 +13,11 @@ export default {
|
|||||||
type: String,
|
type: String,
|
||||||
default: ''
|
default: ''
|
||||||
},
|
},
|
||||||
|
// 导航前回调(如关闭弹窗)
|
||||||
|
beforeNavigate: {
|
||||||
|
type: Function,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@ -72,7 +77,68 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
onCLick(e) {
|
onCLick(e) {
|
||||||
|
const target = e.target;
|
||||||
|
if (target.tagName === 'A') {
|
||||||
|
const href = target.getAttribute('href');
|
||||||
|
if (href && href.startsWith('dootask://')) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
this.handleDooTaskLink(href);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
this.$emit('click', e)
|
this.$emit('click', e)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理 dootask:// 协议链接
|
||||||
|
* 格式: dootask://type/id 或 dootask://type/id1/id2
|
||||||
|
*/
|
||||||
|
handleDooTaskLink(href) {
|
||||||
|
const match = href.match(/^dootask:\/\/(\w+)\/(\d+)(?:\/(\d+))?$/);
|
||||||
|
if (!match) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [, type, id, id2] = match;
|
||||||
|
const numId = parseInt(id, 10);
|
||||||
|
const numId2 = id2 ? parseInt(id2, 10) : null;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'task':
|
||||||
|
this.$store.dispatch('openTask', { id: (numId2 && numId2 > 0) ? numId2 : numId });
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'project':
|
||||||
|
this.beforeNavigate?.();
|
||||||
|
this.goForward({ name: 'manage-project', params: { projectId: numId } });
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'file':
|
||||||
|
this.beforeNavigate?.();
|
||||||
|
this.goForward({ name: 'manage-file', params: { folderId: 0, fileId: null, shakeId: numId } });
|
||||||
|
this.$store.state.fileShakeId = numId;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.$store.state.fileShakeId = 0;
|
||||||
|
}, 600);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'contact':
|
||||||
|
this.$store.dispatch('openDialogUserid', numId).catch(({ msg }) => {
|
||||||
|
$A.modalError(msg);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'message':
|
||||||
|
this.$store.dispatch('openDialog', numId).then(() => {
|
||||||
|
if (numId2) {
|
||||||
|
this.$store.state.dialogSearchMsgId = numId2;
|
||||||
|
}
|
||||||
|
}).catch(({ msg }) => {
|
||||||
|
$A.modalError(msg);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
186
resources/assets/js/utils/ai.js
vendored
186
resources/assets/js/utils/ai.js
vendored
@ -1,60 +1,8 @@
|
|||||||
import {languageList, languageName} from "../language";
|
import {languageList, languageName} from "../language";
|
||||||
|
|
||||||
const withLanguagePreferencePrompt = (prompt) => {
|
/**
|
||||||
if (typeof prompt !== 'string' || !prompt) {
|
* AI 服务商标识与显示名映射
|
||||||
return prompt;
|
*/
|
||||||
}
|
|
||||||
const label = languageList[languageName] || languageName || '';
|
|
||||||
if (!label) {
|
|
||||||
return prompt;
|
|
||||||
}
|
|
||||||
return `${prompt}\n\n输出语言策略:\n- 默认使用 ${label} 输出。\n- 即使上下文或引用包含其他语言,也保持 ${label} 输出。\n- 仅当我明确指定其他语言时,才切换到该语言。`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const AIModelNames = (str) => {
|
|
||||||
const lines = str.split('\n').filter(line => line.trim());
|
|
||||||
|
|
||||||
return lines.map(line => {
|
|
||||||
const [value, label] = line.split('|').map(s => s.trim());
|
|
||||||
|
|
||||||
return {
|
|
||||||
value,
|
|
||||||
label: label || value
|
|
||||||
};
|
|
||||||
}, []).filter(item => item.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
const AINormalizeJsonContent = (content) => {
|
|
||||||
if (!content) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const raw = String(content).trim();
|
|
||||||
if (!raw) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const candidates = [raw];
|
|
||||||
const block = raw.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
||||||
if (block && block[1]) {
|
|
||||||
candidates.push(block[1].trim());
|
|
||||||
}
|
|
||||||
const start = raw.indexOf('{');
|
|
||||||
const end = raw.lastIndexOf('}');
|
|
||||||
if (start !== -1 && end !== -1 && end > start) {
|
|
||||||
candidates.push(raw.slice(start, end + 1));
|
|
||||||
}
|
|
||||||
for (const candidate of candidates) {
|
|
||||||
if (!candidate) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return JSON.parse(candidate);
|
|
||||||
} catch (e) {
|
|
||||||
// continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const AIBotMap = {
|
const AIBotMap = {
|
||||||
openai: "ChatGPT",
|
openai: "ChatGPT",
|
||||||
claude: "Claude",
|
claude: "Claude",
|
||||||
@ -67,6 +15,9 @@ const AIBotMap = {
|
|||||||
wenxin: "文心一言",
|
wenxin: "文心一言",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AI 系统配置表单与平台配置
|
||||||
|
*/
|
||||||
const AISystemConfig = {
|
const AISystemConfig = {
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
@ -259,6 +210,9 @@ const AISystemConfig = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 即时消息生成系统提示词
|
||||||
|
*/
|
||||||
const MESSAGE_AI_SYSTEM_PROMPT = `你是一名专业的沟通助手,协助用户编写得体、清晰且具行动指向的即时消息。
|
const MESSAGE_AI_SYSTEM_PROMPT = `你是一名专业的沟通助手,协助用户编写得体、清晰且具行动指向的即时消息。
|
||||||
|
|
||||||
写作要求:
|
写作要求:
|
||||||
@ -272,6 +226,9 @@ const MESSAGE_AI_SYSTEM_PROMPT = `你是一名专业的沟通助手,协助用
|
|||||||
- 仅返回可直接发送的消息内容
|
- 仅返回可直接发送的消息内容
|
||||||
- 禁止在内容前后添加额外说明、标签或引导语`;
|
- 禁止在内容前后添加额外说明、标签或引导语`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 任务生成系统提示词
|
||||||
|
*/
|
||||||
const TASK_AI_SYSTEM_PROMPT = `你是一个专业的任务管理专家,擅长将想法和需求转化为清晰、可执行的项目任务。
|
const TASK_AI_SYSTEM_PROMPT = `你是一个专业的任务管理专家,擅长将想法和需求转化为清晰、可执行的项目任务。
|
||||||
|
|
||||||
任务生成要求:
|
任务生成要求:
|
||||||
@ -315,6 +272,9 @@ const TASK_AI_SYSTEM_PROMPT = `你是一个专业的任务管理专家,擅长
|
|||||||
- 如果涉及设计,要说明设计要求和期望效果
|
- 如果涉及设计,要说明设计要求和期望效果
|
||||||
- 如果涉及测试,要明确测试范围和验收标准`;
|
- 如果涉及测试,要明确测试范围和验收标准`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目创建系统提示词
|
||||||
|
*/
|
||||||
const PROJECT_AI_SYSTEM_PROMPT = `你是一名资深的项目规划顾问,帮助团队快速搭建符合需求的项目。
|
const PROJECT_AI_SYSTEM_PROMPT = `你是一名资深的项目规划顾问,帮助团队快速搭建符合需求的项目。
|
||||||
|
|
||||||
生成要求:
|
生成要求:
|
||||||
@ -335,6 +295,9 @@ const PROJECT_AI_SYSTEM_PROMPT = `你是一名资深的项目规划顾问,帮
|
|||||||
- 列表名称应当互不重复且语义明确
|
- 列表名称应当互不重复且语义明确
|
||||||
- 若上下文包含已有名称或列表,请在此基础上迭代优化`;
|
- 若上下文包含已有名称或列表,请在此基础上迭代优化`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 周报/日报整理系统提示词
|
||||||
|
*/
|
||||||
const REPORT_AI_SYSTEM_PROMPT = `你是一名资深团队管理教练,需要根据提供的周报/日报草稿进行整理。
|
const REPORT_AI_SYSTEM_PROMPT = `你是一名资深团队管理教练,需要根据提供的周报/日报草稿进行整理。
|
||||||
|
|
||||||
工作目标:
|
工作目标:
|
||||||
@ -349,6 +312,9 @@ const REPORT_AI_SYSTEM_PROMPT = `你是一名资深团队管理教练,需要
|
|||||||
- 若原文包含数据或里程碑,保留并突出这些数字
|
- 若原文包含数据或里程碑,保留并突出这些数字
|
||||||
- 若某一章节没有信息,请输出“暂无”而非留空`;
|
- 若某一章节没有信息,请输出“暂无”而非留空`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 汇报分析系统提示词
|
||||||
|
*/
|
||||||
const REPORT_ANALYSIS_SYSTEM_PROMPT = `你是一名经验丰富的团队管理顾问,擅长阅读和分析员工提交的工作汇报,能够快速提炼重点并给出可执行建议。
|
const REPORT_ANALYSIS_SYSTEM_PROMPT = `你是一名经验丰富的团队管理顾问,擅长阅读和分析员工提交的工作汇报,能够快速提炼重点并给出可执行建议。
|
||||||
|
|
||||||
输出要求:
|
输出要求:
|
||||||
@ -358,9 +324,112 @@ const REPORT_ANALYSIS_SYSTEM_PROMPT = `你是一名经验丰富的团队管理
|
|||||||
4. 语气保持专业、客观、中立,不过度夸赞或批评
|
4. 语气保持专业、客观、中立,不过度夸赞或批评
|
||||||
5. 控制在 200-400 字之间,可视内容复杂度略微增减,但保持紧凑`;
|
5. 控制在 200-400 字之间,可视内容复杂度略微增减,但保持紧凑`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 智能搜索系统提示词
|
||||||
|
*/
|
||||||
|
const SEARCH_AI_SYSTEM_PROMPT = `你是一个智能搜索助手,负责帮助用户在 DooTask 系统中搜索和整理信息。
|
||||||
|
你可以使用 intelligent_search 工具来搜索任务、项目、文件和联系人。
|
||||||
|
|
||||||
|
请根据用户的搜索需求:
|
||||||
|
1. 调用搜索工具获取相关结果
|
||||||
|
2. 对搜索结果进行分类整理
|
||||||
|
3. 以清晰的格式呈现给用户
|
||||||
|
4. 如有需要,可以进行多次搜索以获取更全面的结果`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DooTask 资源格式指南(条件性提示词)
|
||||||
|
* 仅在 AI 返回 DooTask 资源时生效,不影响普通对话
|
||||||
|
*/
|
||||||
|
const DOOTASK_RESOURCE_FORMAT_GUIDE = `
|
||||||
|
<optional-format-guide>
|
||||||
|
当你的回答中包含 DooTask 系统资源(任务、项目、文件等)时,建议使用以下链接格式使其可点击:
|
||||||
|
- 任务: [任务名称](dootask://task/{task_id}/{parent_id}),其中 parent_id 为主任务ID,主任务时为 0
|
||||||
|
- 项目: [项目名称](dootask://project/{project_id})
|
||||||
|
- 文件: [文件名称](dootask://file/{file_id})
|
||||||
|
- 联系人: [用户名](dootask://contact/{userid})
|
||||||
|
- 消息: [消息预览](dootask://message/{dialog_id}/{msg_id})
|
||||||
|
|
||||||
|
注意:此格式指南不影响正常对话,仅在涉及上述资源时参考。如果与当前对话无关,请忽略。
|
||||||
|
</optional-format-guide>
|
||||||
|
`.trim();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 输出语言偏好提示
|
||||||
|
* 用于引导 AI 按指定语言输出
|
||||||
|
*/
|
||||||
|
const LANGUAGE_PREFERENCE_PROMPT = (label) => `输出语言策略:
|
||||||
|
- 默认使用 ${label} 输出。
|
||||||
|
- 即使上下文或引用包含其他语言,也保持 ${label} 输出。
|
||||||
|
- 仅当我明确指定其他语言时,才切换到该语言。`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注入语言偏好提示与资源格式指南
|
||||||
|
* 返回拼接后的完整提示词
|
||||||
|
*/
|
||||||
|
const withLanguagePreferencePrompt = (prompt) => {
|
||||||
|
if (typeof prompt !== 'string' || !prompt) {
|
||||||
|
return prompt;
|
||||||
|
}
|
||||||
|
const label = languageList[languageName] || languageName || '';
|
||||||
|
if (!label) {
|
||||||
|
return prompt;
|
||||||
|
}
|
||||||
|
return `${prompt}\n\n${LANGUAGE_PREFERENCE_PROMPT(label)}\n\n${DOOTASK_RESOURCE_FORMAT_GUIDE}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析模型列表文本为选项数组
|
||||||
|
* 支持以 "|" 分隔显示名
|
||||||
|
*/
|
||||||
|
const AIModelNames = (str) => {
|
||||||
|
const lines = str.split('\n').filter(line => line.trim());
|
||||||
|
|
||||||
|
return lines.map(line => {
|
||||||
|
const [value, label] = line.split('|').map(s => s.trim());
|
||||||
|
|
||||||
|
return {
|
||||||
|
value,
|
||||||
|
label: label || value
|
||||||
|
};
|
||||||
|
}, []).filter(item => item.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 尝试从内容中提取并解析 JSON
|
||||||
|
* 支持代码块与裸 JSON
|
||||||
|
*/
|
||||||
|
const AINormalizeJsonContent = (content) => {
|
||||||
|
if (!content) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const raw = String(content).trim();
|
||||||
|
if (!raw) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const candidates = [raw];
|
||||||
|
const block = raw.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
||||||
|
if (block && block[1]) {
|
||||||
|
candidates.push(block[1].trim());
|
||||||
|
}
|
||||||
|
const start = raw.indexOf('{');
|
||||||
|
const end = raw.lastIndexOf('}');
|
||||||
|
if (start !== -1 && end !== -1 && end > start) {
|
||||||
|
candidates.push(raw.slice(start, end + 1));
|
||||||
|
}
|
||||||
|
for (const candidate of candidates) {
|
||||||
|
if (!candidate) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return JSON.parse(candidate);
|
||||||
|
} catch (e) {
|
||||||
|
// continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
AIModelNames,
|
|
||||||
AINormalizeJsonContent,
|
|
||||||
AIBotMap,
|
AIBotMap,
|
||||||
AISystemConfig,
|
AISystemConfig,
|
||||||
MESSAGE_AI_SYSTEM_PROMPT,
|
MESSAGE_AI_SYSTEM_PROMPT,
|
||||||
@ -368,5 +437,8 @@ export {
|
|||||||
PROJECT_AI_SYSTEM_PROMPT,
|
PROJECT_AI_SYSTEM_PROMPT,
|
||||||
REPORT_AI_SYSTEM_PROMPT,
|
REPORT_AI_SYSTEM_PROMPT,
|
||||||
REPORT_ANALYSIS_SYSTEM_PROMPT,
|
REPORT_ANALYSIS_SYSTEM_PROMPT,
|
||||||
|
SEARCH_AI_SYSTEM_PROMPT,
|
||||||
withLanguagePreferencePrompt,
|
withLanguagePreferencePrompt,
|
||||||
|
AIModelNames,
|
||||||
|
AINormalizeJsonContent,
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user