提交 ea3442f9 authored 作者: 张伊明's avatar 张伊明

修复窗口视角后重新提交请求的bug

上级 a6ad2831
......@@ -2,39 +2,28 @@
<div class="writting-wrapper">
<div class="writting-header">
<div class="tab-box">
<div class="tab"
:class="{ tabActive: item.active }"
v-for="(item, index) in tabList"
:key="index">
<div class="tab" :class="{ tabActive: item.active }" v-for="(item, index) in tabList" :key="index">
{{ item.name }}
</div>
</div>
<div class="edit-box"></div>
<div class="btn-box">
<div class="btn"
@click="exportContent">
<div class="btn" @click="exportContent">
<div class="icon">
<img src="./assets/images/export-icon.png"
alt="" />
<img src="./assets/images/export-icon.png" alt="" />
</div>
<div class="text">{{ "导出" }}</div>
</div>
<div class="btn"
@click="handleSwitchMode">
<div class="btn" @click="handleSwitchMode">
<div class="icon">
<img v-if="isEditMode"
src="./assets/images/preview-icon.png"
alt="" />
<img v-else
src="./assets/images/edit.png"
alt="" />
<img v-if="isEditMode" src="./assets/images/preview-icon.png" alt="" />
<img v-else src="./assets/images/edit.png" alt="" />
</div>
<div class="text">{{ isEditMode ? "预览" : "编辑" }}</div>
</div>
<div class="btn btn1">
<div class="icon">
<img src="./assets/images/save-icon.png"
alt="" />
<img src="./assets/images/save-icon.png" alt="" />
</div>
<div class="text text1">{{ "保存" }}</div>
</div>
......@@ -42,22 +31,17 @@
</div>
<div class="writting-main">
<div class="left-box">
<div class="process-box"
v-if="isShowProcess">
<div class="back"
@click="handleBack">{{ "< 返回" }} </div>
<div class="process-box" v-if="isShowProcess">
<div class="back" @click="handleBack">{{ "< 返回" }}</div>
<div class="process-main-box">
<div class="steps-box">
<div class="steps-header">
<div class="icon">
<img src="./assets/images/right-arrow.png"
alt="" />
<img src="./assets/images/right-arrow.png" alt="" />
</div>
<div class="text">{{ "执行步骤:" }}</div>
</div>
<div class="steps-content"
ref="scrollProcessContainer"
v-html="renderedProcess"></div>
<div class="steps-content" ref="scrollProcessContainer" v-html="renderedProcess"></div>
</div>
<div class="tool-box">
<div class="tool-header">{{ "工具调用" }}</div>
......@@ -65,79 +49,78 @@
</div>
</div>
</div>
<div class="sider"
v-else>
<div class="sider-box"
v-if="false">
<div class="sider" v-else>
<div class="sider-box" v-if="false">
<div class="header">报文主题</div>
<div class="title-box">
<div class="title">主题名称</div>
<el-input :disabled="true"
<el-input
:disabled="true"
style="width: 476px; height: 32px"
class="title-input"
placeholder="输入主题名称,如:大而美法案"
v-model="writtingTitle" />
v-model="writtingTitle"
/>
</div>
<div class="description-box">
<div class="title">主题描述</div>
<el-input :disabled="true"
<el-input
:disabled="true"
class="description-input"
type="textarea"
style="width: 476px"
:rows="8"
placeholder="输入报文主题描述,如:从科技领域方面分析大而美法案通过后对中国可能产生的影响"
v-model="descText" />
v-model="descText"
/>
</div>
</div>
<div class="sider-box">
<div class="header">报文模板</div>
<div class="template-box">
<div class="template"
<div
class="template"
:class="{ tempActive: tempActiveIndex === index }"
v-for="(temp, index) in tempList"
:key="index"
@click="handleClickTemp(temp, index)">
@click="handleClickTemp(temp, index)"
>
<div class="header">
<div class="title">{{ temp.title }}</div>
<div class="icon">
<img src="./assets/images/template-icon.png"
alt="" />
<img src="./assets/images/template-icon.png" alt="" />
</div>
</div>
<div class="content">{{ temp.desc }}</div>
<div class="active-icon"
v-if="tempActiveIndex === index">
<img src="./assets/images/active-icon.png"
alt="" />
<div class="active-icon" v-if="tempActiveIndex === index">
<img src="./assets/images/active-icon.png" alt="" />
</div>
<div class="selected-icon"
v-if="tempActiveIndex === index">
<img src="./assets/images/selected-icon.png"
alt="" />
<div class="selected-icon" v-if="tempActiveIndex === index">
<img src="./assets/images/selected-icon.png" alt="" />
</div>
</div>
</div>
</div>
<div class="sider-box">
<div class="header">加载本地文件</div>
<el-upload action=""
<el-upload
action=""
:auto-upload="false"
accept=".pdf"
limit="1"
:on-exceed="handleExceed"
ref="upload"
:on-change="handleFileChange">
<el-button class="sider-upload-btn"
type="primary">
:on-change="handleFileChange"
:file-list="uploadFileList"
>
<el-button class="sider-upload-btn" type="primary">
<el-icon class="sider-upload-btn-text">
<Upload />
</el-icon>
<span class="sider-upload-btn-text">上传文件</span>
</el-button>
<template #tip>
<div class="sider-upload-btn-tip">
支持扩展名:.doc .docx .pdf
</div>
<div class="sider-upload-btn-tip">支持扩展名:.doc .docx .pdf</div>
</template>
</el-upload>
</div>
......@@ -145,48 +128,40 @@
<div class="submit-area">
<div class="tips">
<div class="tips-icon">
<img src="./assets/images/tips-icon.png"
alt="" />
<img src="./assets/images/tips-icon.png" alt="" />
</div>
<div class="tips-text">内容由AI生成,无法确保真实准确,仅供参考</div>
</div>
<div class="process-footer-box"
v-if="isShowProcess">
<div class="process-footer-box" v-if="isShowProcess">
<div class="footer-left">
{{ isGenerating ? "报文生成中..." : "报文已生成" }}
</div>
<div class="footer-right">
<div class="icon"></div>
<div class="text"
@click="handleGenerate">{{ "停止" }}</div>
<div class="text" @click="handleGenerate">{{ "停止" }}</div>
</div>
</div>
<div class="submit-btn"
@click="getStreamChat"
v-else>
<div class="submit-btn" @click="getStreamChat" v-else>
<div class="submit-icon">
<img src="./assets/images/ai.png"
alt="" />
<img src="./assets/images/ai.png" alt="" />
</div>
<div class="submit-text">生成报文</div>
</div>
</div>
</div>
<div class="main-box">
<div v-if="isEditMode"
class="edit-panel">
<v-md-editor v-model="reportContent"
<div v-if="isEditMode" class="edit-panel">
<v-md-editor
v-model="reportContent"
height="calc(100% - 40px)"
:disabled-menus="[]"
@upload-image="handleUploadImage"
@save="handleSave"
left-toolbar="undo redo clear | h bold italic strikethrough quote | ul ol table hr | link image code | save"
right-toolbar="preview toc sync-scroll fullscreen" />
right-toolbar="preview toc sync-scroll fullscreen"
/>
</div>
<div v-else
class="content-box"
ref="scrollContainer"
v-html="renderedContent"></div>
<div v-else class="content-box" ref="scrollContainer" v-html="renderedContent"></div>
</div>
</div>
</div>
......@@ -213,18 +188,18 @@ VMdEditor.use(vuepressTheme, {
const isGenerating = ref(false);
const isShowProcess = ref(false);
const uploadFileList = ref([])
const upload = ref()
const uploadFileList = ref([]);
const upload = ref();
//新上传文件替换
const handleExceed = (files) => {
const handleExceed = files => {
if (upload.value) {
upload.value.clearFiles()
const file = files[0]
file.uid = genFileId()
upload.value.handleStart(file)
upload.value.clearFiles();
const file = files[0];
file.uid = genFileId();
upload.value.handleStart(file);
}
}
};
const handleFileChange = (file, files) => {
// 只保留最后选中的1个文件(覆盖原有文件)
......@@ -235,12 +210,8 @@ const handleFileChange = (file, files) => {
}
};
const handleBack = () => {
handleGenerate()
processContent.value = ""
isGenerating.value = false;
isShowProcess.value = false;
handleGenerate();
};
const isEditMode = ref(false);
......@@ -286,6 +257,8 @@ const curTempTitle = ref("法案");
// 停止生成
const handleGenerate = () => {
isShowProcess.value = false;
processContent.value = "";
abortController.value.abort();
};
......@@ -296,50 +269,49 @@ const getStreamChat = async (search, inputValue) => {
if (uploadFileList.value.length > 0) {
const rawFile = uploadFileList.value[0].raw;
if (!rawFile) {
ElMessage.error('文件解析失败,请重新选择');
ElMessage.error("文件解析失败,请重新选择");
return;
}
callSseWithPdf(rawFile)
callSseWithPdf(rawFile);
} else {
const params = {
query: writtingTitle.value, // "输出一篇报文"
desc: descText.value,
topic: curTempTitle.value // 政令、智库、法案、清单
};
callSseWithAi(params)
callSseWithAi(params);
}
};
const callSseWithPdf = async (selectedFile) => {
const callSseWithPdf = async selectedFile => {
abortController.value = new AbortController();
try {
// 构造FormData(和后端字段名保持一致)
const formData = new FormData();
formData.append('pdf', selectedFile);
formData.append("pdf", selectedFile);
// 调用fetchEventSource(核心:支持POST+FormData+SSE)
await fetchEventSource('/pdfSse/api/v1/order/pdf/extract/report/sse', {
method: 'POST', // 关键:设置POST方法
await fetchEventSource("/pdfSse/api/v1/order/pdf/extract/report/sse", {
method: "POST", // 关键:设置POST方法
body: formData, // 关键:传递PDF文件的FormData
signal: abortController.value.signal, // 中断信号
headers: {
// 禁用默认的SSE协议头(避免和文件上传冲突)
'Accept': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
Accept: "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive"
},
// 禁用自动重连(可选,根据后端配置)
retry: 0,
openWhenHidden: true,
// 核心:原生onmessage回调(无需手动分割/解析)
async onopen (res) {
async onopen(res) {
console.log("流式回答开始", res);
isGenerating.value = true;
isShowProcess.value = true;
},
async onmessage (res) {
const { data, event } = res
const jsonData = JSON.parse(data)
async onmessage(res) {
const { data, event } = res;
const jsonData = JSON.parse(data);
switch (event) {
case "progress":
processContent.value += `${getFormattedTime()}:${jsonData.message}\r\n`;
......@@ -351,13 +323,12 @@ const callSseWithPdf = async (selectedFile) => {
desc: descText.value,
topic: "政令",
result: data // 政令、智库、法案、清单
})
});
default:
break;
}
},
onerror (error) {
onerror(error) {
ElMessage({
message: "写报生成报错!",
type: "warning"
......@@ -368,14 +339,14 @@ const callSseWithPdf = async (selectedFile) => {
}
});
} catch (error) {
if (error.name !== 'AbortError') {
if (error.name !== "AbortError") {
ElMessage.error(`请求失败:${error.message}`);
isLoading.value = false;
}
}
};
const callSseWithAi = async (params) => {
const callSseWithAi = async params => {
abortController.value = new AbortController();
fetchEventSource("/sseWrite/api/v1/workflow/invoke", {
method: "POST",
......@@ -385,12 +356,12 @@ const callSseWithAi = async (params) => {
body: JSON.stringify(params),
signal: abortController.value.signal,
openWhenHidden: true,
async onopen (res) {
async onopen(res) {
console.log("流式回答开始", res);
isGenerating.value = true;
isShowProcess.value = true;
},
async onmessage (res) {
async onmessage(res) {
let msgData = JSON.parse(res.data);
console.log("resss", msgData.data);
console.log("msgData", msgData);
......@@ -421,7 +392,7 @@ const callSseWithAi = async (params) => {
updateProcess(processContent.value, scrollProcessContainer.value);
}
},
onerror (error) {
onerror(error) {
ElMessage({
message: "写报生成报错!",
type: "warning"
......@@ -439,14 +410,16 @@ const callSseWithAi = async (params) => {
abortController.value = new AbortController();
throw new Error(error);
});
}
};
const getFormattedTime = () => {
const now = new Date();
// 补零函数:确保单个数字补为两位(如 1 → 01,9 → 09)
const pad = n => n.toString().padStart(2, '0');
const pad = n => n.toString().padStart(2, "0");
return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`;
return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(
now.getMinutes()
)}:${pad(now.getSeconds())}`;
};
const writtingTitle = ref("");
......@@ -489,7 +462,6 @@ const tempActiveIndex = ref(0);
const handleClickTemp = (item, index) => {
tempActiveIndex.value = index;
curTempTitle.value = item.title;
};
// 导出
......@@ -503,7 +475,7 @@ const exportContent = () => {
URL.revokeObjectURL(url);
};
onMounted(() => { });
onMounted(() => {});
onUnmounted(() => {
if (abortController.value) {
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论