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
0434bde16f
commit
0b6c478b4f
@ -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);
|
||||
|
||||
@ -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,44 +264,82 @@ 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("已应用整理结果");
|
||||
}
|
||||
}
|
||||
|
||||
15
resources/assets/js/utils/ai.js
vendored
15
resources/assets/js/utils/ai.js
vendored
@ -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,
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user