提交 7157f6a4 authored 作者: 安云鹏's avatar 安云鹏

tj

上级 5ea53c1a
......@@ -51,6 +51,29 @@ export const useWrittingAsstaintStore = defineStore('writtingAsstaint', {
containerRef:null
},
// ====头
//list
tabList:[
{
type:'translate',
name:'翻译',
active: true
},
{
type:'mind',
name:'思维导图',
active: false
},
{
type:'message',
name:'写报',
active: false
}
],
headerTabType:'translate',
// 底部
bottomProgressNum:0, //文档解析 假进度
}),
getters: {
......@@ -60,7 +83,8 @@ export const useWrittingAsstaintStore = defineStore('writtingAsstaint', {
const now = new Date()
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())}`
}
},
},
actions: {
......@@ -128,6 +152,20 @@ export const useWrittingAsstaintStore = defineStore('writtingAsstaint', {
this._keepStepsViewOnError();
await this._showErrorDialog(message);
},
//header tab切换
async handleHeaderTab(type){
this.headerTabType=type
},
async generateWrite(){
console.log(3)
await this.fetchReportData({
query: this.writtingTitle,
desc: this.descText,
topic: this.curTempTitle,
result: this.clauseTranslationMessages[this.clauseTranslationMessages.length-1]
});
},
// ========== 路由参数处理 ==========
async setRouteParams(query) {
......@@ -279,6 +317,8 @@ export const useWrittingAsstaintStore = defineStore('writtingAsstaint', {
this.abortController = new AbortController();
this.processLog = '';
// 进度初始化0
this.bottomProgressNum=0
try {
const formData = new FormData();
formData.append('pdf', selectedFile);
......@@ -314,13 +354,17 @@ export const useWrittingAsstaintStore = defineStore('writtingAsstaint', {
case 'progress':
// 仅更新执行步骤
if (jsonData.message) {
this.processLog += `${this.formattedTime}:${jsonData.message}\r\n`;
this.processLog = `${this.formattedTime}:${jsonData.message}\r\n`;
}
this.bottomProgressNum+=1
break;
case 'metadata':
if (jsonData && jsonData.payload) {
this.pdfMetadata = jsonData.payload;
}
if(this.bottomProgressNum<90){
this.bottomProgressNum+=5
}
break;
case 'clause_translation':
// 保存条款翻译消息并显示侧边栏
......@@ -328,16 +372,32 @@ export const useWrittingAsstaintStore = defineStore('writtingAsstaint', {
this.clauseTranslationMessages.push(jsonData);
this.isShowClauseTranslation = true;
this.isShowSteps = true; // 翻译出现时,步骤侧边栏也显示
// 假进度
if(this.bottomProgressNum<90){
this.bottomProgressNum+=5
}
}
break;
case 'result':
if (jsonData && Object.keys(jsonData).length) {
await this.fetchReportData({
console.log(
{
query: this.writtingTitle,
desc: this.descText,
topic: this.curTempTitle,
result: jsonData
});
}
)
// 假进度完成
this.bottomProgressNum=100
this.tabList[2].active=true //有结果之后放开写报按钮
// await this.fetchReportData({
// query: this.writtingTitle,
// desc: this.descText,
// topic: this.curTempTitle,
// result: jsonData
// });
}
break;
case 'error':
......@@ -374,6 +434,8 @@ export const useWrittingAsstaintStore = defineStore('writtingAsstaint', {
// ========== AI 生成报文 SSE(更新报文内容 + 执行步骤) ==========
async fetchReportData(params) {
console.log(7)
if (this.abortController) this.abortController.abort();
this.abortController = new AbortController();
this.processLog = '';
......@@ -425,7 +487,6 @@ export const useWrittingAsstaintStore = defineStore('writtingAsstaint', {
lastFlushedIndex = Math.max(lastFlushedIndex, lineIdx + 1);
}
};
try {
const { fetchEventSource } = await import('@microsoft/fetch-event-source');
await fetchEventSource('/sseWrite/api/v1/workflow/invoke', {
......@@ -502,8 +563,8 @@ export const useWrittingAsstaintStore = defineStore('writtingAsstaint', {
// 路由参数优先
this.isGenerating = true;
this.isShowProcess = true;
if (Object.keys(this.routeQuery).length !== 0) {
const { fileId } = this.routeQuery;
// 外部跳转:根据 topic 决定调用哪种数据获取接口,再触发生成
......
<template>
<div class="writtingBottom">
<div class="notParsed" v-if="isfile==0" >
<div class="analysis" @click="analysisClick(1)">
<div class="parsed" v-if="store.isGenerating">
<div class="analysis" @click="store.resetGenerateState">
<div class="icon"></div>
<span class="text-tip-2-bold">停止</span>
</div>
<div class="progress">
<div class="login">
<el-progress type="circle" :percentage="store.bottomProgressNum" :width="24" :height="24" style="margin-right: 15px;" :show-text="false" color="rgb(5, 95, 194)"/>
<span class="text-tip-2-bold">文档翻译中</span>
</div>
<div class="text-tip-2" >
<div ref="processContainerRef" v-html="renderedProcess"></div>
</div>
</div>
</div>
<div class="notParsed" v-else >
<div class="parsedItem" v-if="store.bottomProgressNum<100">
<div class="analysis" @click="onAnalysisClick()">
<img src="@/assets/icons/aiBox/ai-logo.png" alt="">
<span class="text-tip-1">文档解析</span>
</div>
......@@ -10,42 +29,61 @@
<span class="text-tip-2">内容由AI生成,无法确保真实准确,仅供参考</span>
</div>
</div>
<div class="parsed" v-else-if="isfile==1">
<div class="analysis" @click="analysisClick(0)">
<div class="icon"></div>
<span class="text-tip-2-bold">停止</span>
<div class="parsedItem" v-else-if="store.bottomProgressNum>=100">
<div class="analysis" @click="onWriteClick()">
<img src="@/assets/icons/aiBox/ai-logo.png" alt="">
<span class="text-tip-1">智能写报</span>
</div>
<div class="progress">
<div class="login">
<el-progress type="circle" :percentage="25" width="24" height="24" style="margin-right: 15px;" :show-text="false" color="rgb(5, 95, 194)"/>
<span class="text-tip-2-bold">文档翻译中</span>
<el-progress type="circle" :percentage="50" :width="24" :height="24" style="margin-right: 15px;" :show-text="false" color="rgb(5, 95, 194)"/>
<span class="text-tip-2-bold">智能写报中</span>
</div>
<div class="text-tip-2" >
<div ref="processContainerRef" v-html="renderedProcess"></div>
</div>
</div>
<span class="text-tip-2">
内容由AI生成,无法确保真实准确,仅供参考
</span>
</div>
</div>
</div>
</template>
<script setup>
import { onMounted, onUnmounted, ref, nextTick } from "vue";
import { onMounted, onUnmounted, ref, nextTick ,watch} from "vue";
import { useRoute } from "vue-router";
import { ElMessage } from "element-plus";
import { useWrittingAsstaintStore } from "@/stores/writtingAsstaintStore";
const isfile=ref(1) //0未解析 1已解析
const analysisClick=(type)=>{
if(type==0){
console.log('停止')
isfile.value=type
return
}else{
console.log('开始解析')
isfile.value=type
}
import { useStream } from "@/hooks/useStream";
// 子组件直接获取Pinia Store(核心优化)
const store = useWrittingAsstaintStore();
const emit = defineEmits(["generate","write"]);
const onAnalysisClick=()=>{
store.isShowSteps = !store.isShowSteps
emit("generate");
}
const onWriteClick=()=>{
emit("write");
}
const { renderedProcess, updateProcess, clearContent } = useStream();
const processContainerRef = ref(null);
// 监听 store.processLog 变化,更新步骤内容并滚动
watch(
() => store.processLog,
async (newLog) => {
if (newLog !== undefined && newLog !== null) {
await updateProcess(newLog, processContainerRef.value);
}
},
{ immediate: true }
);
defineExpose({
processContainerRef
});
</script>
<style lang="scss" scoped>
.writtingBottom{
......@@ -58,6 +96,11 @@ const analysisClick=(type)=>{
justify-content: space-between;
align-items: center;
height: 64px;
.parsedItem{
display: flex;
justify-content: space-between;
width: 100%;
}
.analysis{
margin-left: 22px;
display: flex;
......@@ -94,6 +137,7 @@ const analysisClick=(type)=>{
justify-content: space-between;
align-items: center;
height: 64px;
.analysis{
border: 1px solid var(--color-primary-100);
background-color: #fff;
......
<template>
<div class="headerBox">
<div class="logo" v-if="isLogo==0">
<img src="@/assets/icons/tool-item-icon1.png" alt="">
<span class="text-title-3-bold">智能写库</span>
</div>
<div class="tabBox" v-else-if="isLogo==1">
<div class="tabBox" v-if="store.isGenerating||store.isShowSteps">
<div class="fileName">
<img src="@/assets/icons/pdf-icon.png" alt=" ">
<span class="text-tip-1-bold">Promoting the Export of the American AI Technology Stack.pdf</span>
<span class="text-tip-1-bold">{{ store.uploadFileList[0]?.name||'文件错误' }}</span>
</div>
<div class="tab">
<div class="tabList text-tip-1-bold" v-for="(item,index) in tabList" :key="index" :class="{'on':tabType==item.type}" @click="onTabListClick(item.type)">{{ item.name }}</div>
<div class="tabList text-tip-1-bold" v-for="(item,index) in store.tabList" :key="index" :class="{'on':store.headerTabType==item.type}"
:style="!item.active?'color:#bfbfbf;cursor: no-drop;':''"
@click="onTabListClick(item.type,item.active)">{{ item.name }}</div>
</div>
<div class="switch">
<el-switch v-model="cmSwitch"/>
<el-switch v-model="store.isShowOriginal"/>
<div class="iconBOx">
<img src="@/assets/icons/translate-icon.png" alt="">
<span class="text-tip-1">显示原文</span>
</div>
<el-button :icon="Search" @click="onSearchClick">查找</el-button>
<el-button @click="onSearchClick">查找</el-button>
</div>
</div>
<div class="logo" v-else>
<img src="@/assets/icons/tool-item-icon1.png" alt="">
<span class="text-title-3-bold">智能写库</span>
</div>
</div>
</template>
......@@ -29,37 +33,16 @@ import { onMounted, onUnmounted, ref, nextTick } from "vue";
import { useRoute } from "vue-router";
import { ElMessage } from "element-plus";
import { useWrittingAsstaintStore } from "@/stores/writtingAsstaintStore";
/**
* 判断展示logo 0展示logo 1展示tab
*/
const isLogo=ref(1)
// 子组件直接获取Pinia Store(核心优化)
const store = useWrittingAsstaintStore();
/**
* tab切换
*/
const tabList=ref([
{
type:'translate',
name:'翻译'
},
{
type:'mind',
name:'思维导图'
},
{
type:'message',
name:'写报'
}
])
const tabType=ref('translate')
const onTabListClick=(type)=>{
tabType.value=type
const onTabListClick= async (type,active)=>{
if(!active) return
store.handleHeaderTab(type)
}
//
//
const onSearchClick=()=>{}
const cmSwitch=ref()
</script>
<style lang="scss" scoped>
.headerBox{
......
<template>
<div class="left-box-wrapper">
<div class="back" @click="store.resetGenerateState" v-if="store.isGenerating">&lt; 返回</div>
<!-- <div class="back" @click="store.resetGenerateState" v-if="store.isGenerating">&lt; 返回</div> -->
<div class="left-box" :class="{ 'has-back-btn': store.isGenerating }"
v-if="!store.isShowClauseTranslation && !store.isShowSteps">
<div class="left-box-input">
......@@ -11,15 +11,15 @@
<div class="header">报文主题</div>
<div class="title-box">
<div class="title">主题名称</div>
<el-input :disabled="store.isDisableTemplate" style="width: 476px; height: 32px"
<el-input :disabled="store.isDisableTemplate" style="width: 476px; height: 32px ;background: #f7f8f9;"
class="title-input" placeholder="输入主题名称,如:大而美法案" v-model="store.writtingTitle" />
</div>
<div class="description-box">
<!-- <div class="description-box">
<div class="title">主题描述</div>
<el-input :disabled="store.isDisableTemplate" class="description-input" type="textarea"
style="width: 476px" :rows="8" placeholder="输入报文主题描述,如:从科技领域方面分析大而美法案通过后对中国可能产生的影响"
v-model="store.descText" />
</div>
</div> -->
</div>
<!-- 报文模板 -->
......@@ -88,24 +88,24 @@
</div>
<!-- 提交区域 -->
<div class="submit-area">
<!-- <div class="submit-area">
<div class="tips">
<div class="tips-icon">
<img src="../assets/images/tips-icon.png" alt="" />
</div>
<div class="tips-text">内容由AI生成,无法确保真实准确,仅供参考</div>
</div>
</div> -->
<!-- 生成按钮 -->
<div class="submit-btn" @click="triggerGenerate" v-if="!store.isGenerating">
<!-- <div class="submit-btn" @click="triggerGenerate" v-if="!store.isGenerating">
<div class="submit-icon">
<img src="../assets/images/ai.png" alt="" />
</div>
<div class="submit-text">生成报文</div>
</div>
</div> -->
<!-- 生成中状态 -->
<div class="process-footer-box" v-else>
<!-- <div class="process-footer-box" v-else>
<div class="footer-left">
{{ store.isGenerating ? "报文生成中..." : "报文已生成" }}
</div>
......@@ -114,11 +114,11 @@
<div class="text">停止</div>
</div>
</div>
</div>
</div> end -->
</div>
<!-- 步骤侧边栏(拆分出来) -->
<div class="left-box process" :class="{ 'has-back-btn': store.isGenerating }" v-if="store.isShowSteps">
<!-- <div class="left-box process" :class="{ 'has-back-btn': store.isGenerating }" v-if="store.isShowSteps">
<div class="left-box-input">
<div class="process-box">
<div class="process-main-box">
......@@ -137,17 +137,17 @@
</div>
</div>
</div>
</div>
<div class="submit-area">
</div> -->
<!-- <div class="submit-area">
<div class="tips">
<div class="tips-icon">
<img src="../assets/images/tips-icon.png" alt="" />
</div>
<div class="tips-text">内容由AI生成,无法确保真实准确,仅供参考</div>
</div>
</div> -->
<!-- 生成中状态 -->
<div class="process-footer-box">
<!-- <div class="process-footer-box">
<div class="footer-left">
{{ store.isGenerating ? "报文生成中..." : "报文已生成" }}
</div>
......@@ -155,25 +155,23 @@
<div class="icon"></div>
<div class="text">停止</div>
</div>
</div>
</div>
</div>
<!-- 条款翻译侧边栏 -->
<div class="left-box translation-box" :class="{ 'has-back-btn': store.isGenerating }"
v-if="store.isShowClauseTranslation">
</div> -->
<!-- </div>
</div> -->
<!-- 条款翻译侧边栏 srot -->
<div class="left-box translation-box" :class="{ 'has-back-btn': store.isGenerating }" v-if="store.isShowClauseTranslation&&store.headerTabType=='message'">
<div class="translation-main-box">
<div class="translation-actions" v-if="!store.isGenerating">
<!-- <div class="translation-actions" v-if="!store.isGenerating">
<div class="back-input-btn" @click="store.backToInputAndClear">返回输入栏</div>
</div>
</div> -->
<!-- 政令标题卡片 -->
<div class="metadata-card" v-if="store.pdfMetadata">
<!-- <div class="metadata-card" v-if="store.pdfMetadata">
<div class="card-header">
<div class="chinese-name">{{ store.pdfMetadata.name }}</div>
<div class="type-tag">{{ store.pdfMetadata.signing_date }}</div>
</div>
<div class="english-name">{{ store.pdfMetadata.order_title }}</div>
</div>
</div> -->
<div class="translation-header-new">
<div class="header-left">共{{ store.clauseTranslationMessages.length }}章节</div>
<div class="header-right">
......@@ -200,9 +198,9 @@
</div>
</div>
<!-- 步骤侧边栏显隐按钮 -->
<div class="toggle-steps-btn" @click="store.isShowSteps = !store.isShowSteps">
<!-- <div class="toggle-steps-btn" @click="store.isShowSteps = !store.isShowSteps">
<div class="arrow" :class="{ 'is-active': store.isShowSteps }"></div>
</div>
</div> -->
</div>
</div>
</template>
......@@ -213,7 +211,7 @@ import { ElButton, ElIcon, ElInput, ElUpload, ElSwitch } from "element-plus";
import { Upload } from "@element-plus/icons-vue";
import { useWrittingAsstaintStore } from "@/stores/writtingAsstaintStore";
import { useStream } from "@/hooks/useStream";
console.log('测试一下git')
// 子组件直接获取Pinia Store(核心优化)
const store = useWrittingAsstaintStore();
// 组件内部引用
......@@ -308,7 +306,7 @@ defineExpose({
width: 521px;
height: 100%;
padding-top: 22px;
padding-bottom: 29px;
padding-bottom: 10px;
box-sizing: border-box;
border-right: 1px solid rgba(234, 236, 238, 1);
border-top: 1px solid rgba(234, 236, 238, 1);
......
<template>
<div >
<!-- 右侧子组件:绑定ref -->
<writtingMainBox ref="mainBoxRef" :report-content="store.reportContent" />
</div>
</template>
<script setup>
import { ref, watch, onMounted, onUnmounted } from "vue";
const mainBoxRef = ref(null); // 右侧子组件ref
</script>
<style lang="scss" scoped>
</style>
\ No newline at end of file
<template>
<div >
我是思维导图
</div>
</template>
<script setup>
import { ref, watch, onMounted, onUnmounted } from "vue";
</script>
<style lang="scss" scoped>
</style>
\ No newline at end of file
<template>
<div class="translation-content" ref="translationContentRef">
<!-- :class="{ active: store.highlightClauseId === item.payload?.clause_number }"
:data-clause-number="item.payload?.clause_number" -->
<div class="translation-item" v-for="(item, index) in store.clauseTranslationMessages" :key="index">
<div class="item-body">
<div class="original-text" v-if="store.isShowOriginal">
<span class="index-badge">{{item.payload?.clause_section }}</span>
{{ item.payload?.clause_content }}
</div>
<div class="translated-text">
<span class="clause-title">{{ getChineseNumber(item.payload?.clause_number) }}</span>
{{ item.payload?.clause_content_zh }}
</div>
</div>
</div>
</div>
</template>
<script setup>
import { onMounted, onUnmounted, ref, nextTick ,watch} from "vue";
import { useRoute } from "vue-router";
import { ElMessage } from "element-plus";
import { useWrittingAsstaintStore } from "@/stores/writtingAsstaintStore";
import { useStream } from "@/hooks/useStream";
// 子组件直接获取Pinia Store(核心优化)
const store = useWrittingAsstaintStore();
const translationContentRef = ref(null);
// 数字转中文序号
const getChineseNumber = (num) => {
const zh = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九', '十'];
const n = parseInt(num);
if (n <= 10) return zh[n];
if (n < 20) return '十' + zh[n % 10];
if (n < 100) {
return zh[Math.floor(n / 10)] + '十' + (n % 10 === 0 ? '' : zh[n % 10]);
}
return num;
};
</script>
<style lang="scss" scoped>
.translation-content{
overflow-y: auto;
margin-top: 30px;
height: calc(100% - 50px);
.translation-item{
margin-bottom: 24px;
.item-body{
display: flex;
justify-content: space-between;
width: 85%;
margin: 0 auto;
.original-text{
min-width: 48%;
line-height: 30px;
margin-right: 62px;
flex: 2;
}
.translated-text{
width: 100%;
line-height: 30px;
}
}
// .item-header{
// width: 48%;
// }
// .item-body{
// width: 48%;
// }
}
}
</style>
\ No newline at end of file
......@@ -31,19 +31,32 @@
<WrittingHeader></WrittingHeader>
<div class="writting-main">
<!-- 左侧子组件:绑定ref -->
<writtingleftBox ref="leftBoxRef" @generate="handleGenerate" />
<!-- 右侧子组件:绑定ref -->
<writtingMainBox v-show="!!store.reportContent" ref="mainBoxRef" :report-content="store.reportContent" />
<!-- <writtingleftBox ref="leftBoxRef" @generate="handleGenerate" /> -->
<writtingleftBox ref="leftBoxRef" />
<!-- 翻译 -->
<WrittingTranslate v-if="store.isShowClauseTranslation&&store.headerTabType=='translate'"></WrittingTranslate>
<!-- 思维导图 -->
<WrittingMind v-else-if="store.isShowClauseTranslation&&store.headerTabType=='mind'"></WrittingMind>
<!-- 写报 -->
<WrittingMessage v-else-if="store.isShowClauseTranslation&&store.headerTabType=='message'"></WrittingMessage>
<!-- 无数据时显示占位图 -->
<div v-show="!store.reportContent" class="main-placeholder">
<div v-else class="main-placeholder">
<img src="./assets/images/container-image.png" alt="无数据占位图" />
<div class="placeholder-text">
<div v-if="store.isGenerating">智能体写报任务执行中...</div>
<div v-else>上传文件后点击“生成报文”开始写报...</div>
</div>
</div>
<!-- 右侧子组件:绑定ref -->
<writtingMainBox v-show="!!store.reportContent" ref="mainBoxRef" :report-content="store.reportContent" />
</div>
<WrittingBottom></WrittingBottom>
<WrittingBottom @generate="handleGenerate" @write="handleWrite"></WrittingBottom>
</div>
</div>
</template>
......@@ -57,13 +70,19 @@ import writtingleftBox from "./components/WrittingLeftBox.vue";
import writtingMainBox from "./components/WrittingMainBox.vue";
import WrittingHeader from "./components/WrittingHeader.vue"; //头
import WrittingBottom from "./components/WrittingBottom.vue"; //底部
import WrittingTranslate from "./components/WrittingTranslate.vue"; //翻译
import WrittingMind from "./components/WrittingTranslate.vue"; //思维导图
import WrittingMessage from "./components/WrittingMessage.vue"; //写报
// 获取路由实例(组件内读取)
const route = useRoute();
// 获取Pinia Store实例
const leftBoxRef = ref(null); // 左侧子组件ref
const mainBoxRef = ref(null); // 右侧子组件ref
const store = useWrittingAsstaintStore();
// 2. 核心:触发生成流程
......@@ -84,6 +103,18 @@ const handleGenerate = async () => {
console.error("生成报文失败:", error);
}
};
const handleWrite=async ()=>{
try {
console.log(1)
// // 等待DOM更新(确保子组件DOM已挂载)
await nextTick();
await store.generateWrite()
console.log(2)
} catch (error) {
ElMessage.error(error.message);
console.error("生成写报失败:", error);
}
}
// 生命周期
onMounted(async () => {
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论