feat: 优化报告AI整理功能,优化报告编辑逻辑,移除冗余代码

This commit is contained in:
kuaifan 2025-11-08 21:53:02 +00:00
parent 0434bde16f
commit 0b6c478b4f
3 changed files with 155 additions and 64 deletions

View File

@ -125,6 +125,8 @@ export default {
showModal: false,
closing: false,
loadIng: 0,
pendingAutoSubmit: false,
autoSubmitTimer: null,
//
inputValue: '',
@ -160,6 +162,7 @@ export default {
beforeDestroy() {
emitter.off('openAIAssistant', this.onOpenAIAssistant);
this.clearActiveSSEClients();
this.clearAutoSubmitTimer();
},
computed: {
selectedModelOption({modelMap, inputModel}) {
@ -179,19 +182,26 @@ export default {
* 打开助手弹窗并应用参数
*/
onOpenAIAssistant(params) {
if ($A.isJson(params)) {
this.inputValue = params.value || '';
this.inputPlaceholder = params.placeholder || this.defaultPlaceholder || this.$L('请输入你的问题...');
this.inputRows = params.rows || this.defaultInputRows;
this.inputAutosize = params.autosize || this.defaultInputAutosize;
this.inputMaxlength = params.maxlength || this.defaultInputMaxlength;
this.applyHook = params.onApply || null;
this.beforeSendHook = params.onBeforeSend || null;
this.renderHook = params.onRender || null;
if (!$A.isJson(params)) {
params = {};
}
this.inputValue = params.value || '';
this.inputPlaceholder = params.placeholder || this.defaultPlaceholder || this.$L('请输入你的问题...');
this.inputRows = params.rows || this.defaultInputRows;
this.inputAutosize = params.autosize || this.defaultInputAutosize;
this.inputMaxlength = params.maxlength || this.defaultInputMaxlength;
this.applyHook = params.onApply || null;
this.beforeSendHook = params.onBeforeSend || null;
this.renderHook = params.onRender || null;
this.pendingAutoSubmit = !!params.autoSubmit;
//
this.responses = [];
this.showModal = true;
this.clearActiveSSEClients();
this.clearAutoSubmitTimer();
this.$nextTick(() => {
this.scheduleAutoSubmit();
});
},
/**
@ -596,6 +606,49 @@ export default {
this.activeSSEClients = [];
},
/**
* 清除自动提交定时器
*/
clearAutoSubmitTimer() {
if (this.autoSubmitTimer) {
clearTimeout(this.autoSubmitTimer);
this.autoSubmitTimer = null;
}
},
/**
* 调度自动提交
*/
scheduleAutoSubmit() {
if (!this.pendingAutoSubmit) {
return;
}
const attemptSubmit = () => {
if (!this.pendingAutoSubmit) {
return;
}
if (this.canAutoSubmit()) {
this.pendingAutoSubmit = false;
this.clearAutoSubmitTimer();
this.onSubmit();
return;
}
this.autoSubmitTimer = setTimeout(attemptSubmit, 200);
};
this.clearAutoSubmitTimer();
this.autoSubmitTimer = setTimeout(attemptSubmit, 0);
},
/**
* 检查是否可以自动提交
*/
canAutoSubmit() {
return !this.modelsLoading
&& !!this.selectedModelOption
&& this.responses.length === 0
&& this.loadIng === 0;
},
/**
* 新建响应卡片
*/
@ -719,9 +772,11 @@ export default {
return;
}
this.closing = true;
this.pendingAutoSubmit = false;
this.clearAutoSubmitTimer();
this.clearActiveSSEClients();
this.showModal = false;
this.responses = [];
this.clearActiveSSEClients();
setTimeout(() => {
this.closing = false;
}, 300);

View File

@ -40,7 +40,7 @@
</div>
</FormItem>
<FormItem :label="$L('汇报内容')" class="report-content-editor">
<TEditor v-model="reportData.content" height="100%"/>
<TEditor ref="reportEditor" v-model="reportData.content" height="100%"/>
</FormItem>
<FormItem class="report-foot">
<div class="report-bottoms">
@ -48,7 +48,6 @@
<Button
type="default"
class="report-bottom"
:loading="aiOrganizeLoading"
@click="onOrganize">
<Icon type="md-construct" />
{{ $L("AI 整理汇报") }}
@ -56,26 +55,16 @@
</div>
</FormItem>
</Form>
<Modal
v-model="organizePreviewVisible"
:title="$L('整理结果预览')"
:mask-closable="false"
:styles="{
width: '90%',
maxWidth: '800px'
}">
<div class="report-content organize-preview user-select-auto" v-html="organizeResult.html"></div>
<div slot="footer" class="adaption">
<Button type="default" @click="closeOrganizePreview">{{ $L("取消") }}</Button>
<Button type="primary" @click="applyOrganize" :loading="aiOrganizeLoading">{{ $L("应用到汇报") }}</Button>
</div>
</Modal>
</div>
</template>
<script>
import UserSelect from "../../../components/UserSelect.vue";
import {mapState} from "vuex";
import emitter from "../../../store/events";
import {MarkdownConver} from "../../../utils/markdown";
import {extractPlainText} from "../../../utils/text";
import {REPORT_AI_SYSTEM_PROMPT} from "../../../utils/ai";
const TEditor = () => import('../../../components/TEditor');
export default {
@ -93,12 +82,6 @@ export default {
return {
loadIng: 0,
receiveLoad: 0,
aiOrganizeLoading: false,
organizePreviewVisible: false,
organizeResult: {
html: '',
model: '',
},
reportData: {
sign: "",
@ -281,46 +264,84 @@ export default {
$A.messageWarning("请先填写汇报内容");
return;
}
if (this.aiOrganizeLoading) {
return;
}
this.aiOrganizeLoading = true;
this.$store.dispatch("call", {
url: 'report/ai_organize',
method: 'post',
data: {
content: this.reportData.content,
title: this.reportData.title,
type: this.reportData.type,
},
timeout: 60 * 1000,
}).then(({data}) => {
this.organizeResult = data || {html: '', model: ''};
if (!this.organizeResult.html) {
$A.messageWarning("AI 未返回整理内容");
return;
}
this.organizePreviewVisible = true;
}).catch(({msg}) => {
$A.messageError(msg);
}).finally(() => {
this.aiOrganizeLoading = false;
emitter.emit('openAIAssistant', {
placeholder: this.$L('补充你想强调的重点或特殊说明AI 将在此基础上整理汇报'),
onBeforeSend: this.handleReportAIBeforeSend,
onApply: this.handleReportAIApply,
autoSubmit: true,
});
},
closeOrganizePreview() {
this.organizePreviewVisible = false;
buildReportAIContextData() {
const sections = [];
const meta = [];
const title = (this.reportData.title || '').trim();
if (title) {
meta.push(`标题:${title}`);
}
if (this.reportData.sign) {
meta.push(`周期:${this.reportData.sign}`);
}
if (this.reportData.type) {
const typeMap = {weekly: this.$L('周报'), daily: this.$L('日报')};
meta.push(`类型:${typeMap[this.reportData.type] || this.reportData.type}`);
}
if (meta.length > 0) {
sections.push('## 汇报信息');
sections.push(...meta);
}
const plain = extractPlainText(this.reportData.content || '');
if (plain) {
const limit = 3200;
const slice = plain.slice(0, limit);
sections.push('## 当前汇报正文');
sections.push(slice + (plain.length > limit ? '...' : ''));
}
return sections.join('\n').trim();
},
applyOrganize() {
if (!this.organizeResult.html) {
$A.messageWarning("没有可应用的内容");
handleReportAIBeforeSend(context = []) {
const prepared = [
['system', REPORT_AI_SYSTEM_PROMPT]
];
const contextPrompt = this.buildReportAIContextData();
if (contextPrompt) {
let assistantContext = [
'以下是当前汇报草稿,请在此基础上整理结构、补充要点:',
contextPrompt,
].join('\n');
if ($A.getObject(context, [0,0]) === 'human') {
assistantContext += "\n----\n请根据以上背景再结合用户输入给出结果++++";
}
prepared.push(['human', assistantContext]);
}
if (context.length > 0) {
prepared.push(...context);
}
return prepared;
},
handleReportAIApply({rawOutput}) {
if (!rawOutput) {
$A.messageWarning("AI 未生成内容");
return;
}
this.reportData.content = this.organizeResult.html;
this.organizePreviewVisible = false;
const html = MarkdownConver(rawOutput).trim();
if (!html) {
$A.modalError("AI 内容解析失败,请重试");
return;
}
this.reportData.content = html;
this.$nextTick(() => {
const editor = this.$refs.reportEditor;
if (editor && typeof editor.focus === 'function') {
editor.focus();
}
});
$A.messageSuccess("已应用整理结果");
}
}
}
</script>
</script>

View File

@ -322,6 +322,20 @@ const PROJECT_AI_SYSTEM_PROMPT = `你是一名资深的项目规划顾问,帮
- 列表名称应当互不重复且语义明确
- 若上下文包含已有名称或列表请在此基础上迭代优化`;
const REPORT_AI_SYSTEM_PROMPT = `你是一名资深团队管理教练,需要根据提供的周报/日报草稿进行整理。
工作目标
1. 提取并归纳已完成事项的成果影响和量化数据
2. 梳理下周期/次日的计划确保每条计划都是可执行动作
3. 暴露存在的风险阻塞以及需要管理者协助的事项
4. 若上下文提到关注重点或特殊受众需在描述中明确回应
输出要求
- 使用 Markdown 编写至少包含以下一级标题## 本周期完成## 下周期计划## 风险与支持
- 每个章节使用有序或无序列表保持语句简洁可度量
- 若原文包含数据或里程碑保留并突出这些数字
- 若某一章节没有信息请输出暂无而非留空`;
export {
AIModelNames,
AINormalizeJsonContent,
@ -330,4 +344,5 @@ export {
MESSAGE_AI_SYSTEM_PROMPT,
TASK_AI_SYSTEM_PROMPT,
PROJECT_AI_SYSTEM_PROMPT,
REPORT_AI_SYSTEM_PROMPT,
}