feat: 引入文本提取功能,优化AI内容解析逻辑,移除冗余代码

This commit is contained in:
kuaifan 2025-11-08 20:42:21 +00:00
parent ecb52c76b9
commit 0deb3113b5
5 changed files with 56 additions and 93 deletions

View File

@ -457,7 +457,7 @@ import SearchBox from "../components/SearchBox.vue";
import AIAssistant from "../components/AIAssistant.vue"; import AIAssistant from "../components/AIAssistant.vue";
import transformEmojiToHtml from "../utils/emoji"; import transformEmojiToHtml from "../utils/emoji";
import {languageName} from "../language"; import {languageName} from "../language";
import {PROJECT_AI_SYSTEM_PROMPT} from "../utils/ai"; import {AINormalizeJsonContent, PROJECT_AI_SYSTEM_PROMPT} from "../utils/ai";
import Draggable from 'vuedraggable' import Draggable from 'vuedraggable'
export default { export default {
@ -1197,39 +1197,8 @@ export default {
return []; return [];
}, },
normalizeAIJsonContent(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;
},
parseProjectAIContent(content) { parseProjectAIContent(content) {
const payload = this.normalizeAIJsonContent(content); const payload = AINormalizeJsonContent(content);
if (!payload || typeof payload !== 'object') { if (!payload || typeof payload !== 'object') {
return null; return null;
} }

View File

@ -343,6 +343,7 @@ import longpress from "../../../../directives/longpress";
import {inputLoadAdd, inputLoadIsLast, inputLoadRemove} from "./one"; import {inputLoadAdd, inputLoadIsLast, inputLoadRemove} from "./one";
import {languageList, languageName} from "../../../../language"; import {languageList, languageName} from "../../../../language";
import {isMarkdownFormat, MarkdownConver} from "../../../../utils/markdown"; import {isMarkdownFormat, MarkdownConver} from "../../../../utils/markdown";
import {extractPlainText} from "../../../../utils/text";
import {MESSAGE_AI_SYSTEM_PROMPT} from "../../../../utils/ai"; import {MESSAGE_AI_SYSTEM_PROMPT} from "../../../../utils/ai";
import emitter from "../../../../store/events"; import emitter from "../../../../store/events";
import historyMixin from "./history"; import historyMixin from "./history";
@ -1911,7 +1912,6 @@ export default {
emitter.emit('openAIAssistant', { emitter.emit('openAIAssistant', {
placeholder: this.$L('请简要描述消息的主题、语气或要点AI 将生成完整消息'), placeholder: this.$L('请简要描述消息的主题、语气或要点AI 将生成完整消息'),
onBeforeSend: this.handleMessageAIBeforeSend, onBeforeSend: this.handleMessageAIBeforeSend,
onRender: this.handleMessageAIRender,
onApply: this.handleMessageAIApply, onApply: this.handleMessageAIApply,
}); });
}, },
@ -1933,10 +1933,6 @@ export default {
return prepared; return prepared;
}, },
handleMessageAIRender({rawOutput}) {
return rawOutput || '';
},
handleMessageAIApply({rawOutput}) { handleMessageAIApply({rawOutput}) {
if (!rawOutput) { if (!rawOutput) {
$A.messageWarning('AI 未生成内容'); $A.messageWarning('AI 未生成内容');
@ -1991,7 +1987,7 @@ export default {
} }
} }
const draftText = this.extractPlainText(this.value); const draftText = extractPlainText(this.value);
if (draftText) { if (draftText) {
sections.push('## 当前草稿'); sections.push('## 当前草稿');
sections.push(this.cutText(draftText, 200)); sections.push(this.cutText(draftText, 200));
@ -2062,26 +2058,13 @@ export default {
} }
try { try {
const preview = $A.getMsgSimpleDesc(message); const preview = $A.getMsgSimpleDesc(message);
const plain = this.extractPlainText(preview || ''); const plain = extractPlainText(preview || '');
return this.cutText(plain, 160); return this.cutText(plain, 160);
} catch (error) { } catch (error) {
return ''; return '';
} }
}, },
extractPlainText(content) {
if (!content) {
return '';
}
const value = typeof content === 'string' ? content : JSON.stringify(content);
if (typeof window === 'undefined' || !window.document) {
return value.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim();
}
const div = document.createElement('div');
div.innerHTML = value;
return (div.textContent || div.innerText || '').replace(/\s+/g, ' ').trim();
},
cutText(text, limit = 60) { cutText(text, limit = 60) {
const value = (text || '').trim(); const value = (text || '').trim();
if (!value) { if (!value) {

View File

@ -201,7 +201,8 @@ import TaskExistTips from "./TaskExistTips.vue";
import TEditorTask from "../../../components/TEditorTask.vue"; import TEditorTask from "../../../components/TEditorTask.vue";
import nostyle from "../../../components/VMEditor/engine/nostyle"; import nostyle from "../../../components/VMEditor/engine/nostyle";
import {MarkdownConver} from "../../../utils/markdown"; import {MarkdownConver} from "../../../utils/markdown";
import {TASK_AI_SYSTEM_PROMPT} from "../../../utils/ai"; import {extractPlainText} from "../../../utils/text";
import {AINormalizeJsonContent, TASK_AI_SYSTEM_PROMPT} from "../../../utils/ai";
export default { export default {
name: "TaskAdd", name: "TaskAdd",
@ -639,15 +640,11 @@ export default {
buildTaskAIContextData() { buildTaskAIContextData() {
const prompts = []; const prompts = [];
const plainText = (value, limit = 600) => { const plainText = (value, limit = 600) => {
if (!value || typeof value !== 'string') { const text = extractPlainText(value || '');
if (!text) {
return ''; return '';
} }
return value return text.slice(0, limit).trim();
.replace(/<[^>]+>/g, ' ')
.replace(/&nbsp;/gi, ' ')
.replace(/\s+/g, ' ')
.slice(0, limit)
.trim();
}; };
const currentTitle = (this.addData.name || '').trim(); const currentTitle = (this.addData.name || '').trim();
@ -778,7 +775,7 @@ export default {
}, },
parseTaskAIContent(content) { parseTaskAIContent(content) {
const payload = this.normalizeAIJsonContent(content); const payload = AINormalizeJsonContent(content);
if (!payload || typeof payload !== 'object') { if (!payload || typeof payload !== 'object') {
return null; return null;
} }
@ -802,37 +799,6 @@ export default {
}; };
}, },
normalizeAIJsonContent(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;
},
normalizeAISubtasks(value) { normalizeAISubtasks(value) {
let raw = []; let raw = [];
if (Array.isArray(value)) { if (Array.isArray(value)) {

View File

@ -11,6 +11,37 @@ const AIModelNames = (str) => {
}, []).filter(item => item.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 AIBotList = [] const AIBotList = []
const AIBotMap = { const AIBotMap = {
@ -295,6 +326,7 @@ const PROJECT_AI_SYSTEM_PROMPT = `你是一名资深的项目规划顾问,帮
export { export {
AIModelNames, AIModelNames,
AINormalizeJsonContent,
AIBotList, AIBotList,
AIBotMap, AIBotMap,
AISystemConfig, AISystemConfig,

13
resources/assets/js/utils/text.js vendored Normal file
View File

@ -0,0 +1,13 @@
export function extractPlainText(content) {
if (!content) {
return '';
}
const value = typeof content === 'string' ? content : JSON.stringify(content);
if (typeof window === 'undefined' || !window.document) {
return value.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim();
}
const div = document.createElement('div');
div.innerHTML = value;
return (div.textContent || div.innerText || '').replace(/\s+/g, ' ').trim();
}