si@aidatagov.com 1 week ago
parent
commit
9d3edeb7bb
  1. 3
      vue-fastapi-frontend/src/main.js
  2. 14
      vue-fastapi-frontend/src/views/aichat/MdRenderer.vue
  3. 141
      vue-fastapi-frontend/src/views/aichat/aichat.vue

3
vue-fastapi-frontend/src/main.js

@ -52,8 +52,7 @@ const app = createApp(App)
// 全局方法挂载 // 全局方法挂载
app.config.globalProperties.useDict = useDict app.config.globalProperties.useDict = useDict
app.config.globalProperties.download = download app.config.globalProperties.download = download
app.config.globalProperties.download = download app.config.globalProperties.formatDateTime = formatDateTime
app.config.globalProperties.formatDateTime = formatDateTime
app.config.globalProperties.resetForm = resetForm app.config.globalProperties.resetForm = resetForm
app.config.globalProperties.handleTree = handleTree app.config.globalProperties.handleTree = handleTree
app.config.globalProperties.addDateRange = addDateRange app.config.globalProperties.addDateRange = addDateRange

14
vue-fastapi-frontend/src/views/aichat/MdRenderer.vue

@ -15,6 +15,11 @@
<el-icon><FullScreen/></el-icon> <el-icon><FullScreen/></el-icon>
</el-button> </el-button>
</div> </div>
<div v-if="item.type === 'docs'" style="width: 100%;margin-top: 5px">
<div v-for="doc in item.docs">
<el-link type="primary" @click="downLoadFile(doc)" :underline="false">{{ doc.file_name }}</el-link>
</div>
</div>
<div v-else style="width: 100%;margin-top: 5px"> <div v-else style="width: 100%;margin-top: 5px">
<markdown :markdown-string="item.content"></markdown> <markdown :markdown-string="item.content"></markdown>
</div> </div>
@ -29,7 +34,7 @@ import chatTable from './chatTable.vue'
import htmlCharts from './htmlCharts.vue' import htmlCharts from './htmlCharts.vue'
import {Download, FullScreen} from "@element-plus/icons-vue"; import {Download, FullScreen} from "@element-plus/icons-vue";
import { ref, watch} from 'vue' import { ref, watch} from 'vue'
const { proxy } = getCurrentInstance();
const props = defineProps({ const props = defineProps({
source: Array, source: Array,
is_large: Boolean, is_large: Boolean,
@ -41,6 +46,13 @@ function fullscreenG6(data){
emit('fullscreenG6',data) emit('fullscreenG6',data)
} }
function downLoadFile(doc){
let data = {file:doc.file_name,bucket: doc.bucket,sessionId: doc.session_id}
proxy.download("/default-api/aichat/file/download", {
...data,
}, doc.file_name);
}
function downLoadTable(data){ function downLoadTable(data){
const worksheet = XLSX.utils.json_to_sheet(data); const worksheet = XLSX.utils.json_to_sheet(data);
// 簿簿 // 簿簿

141
vue-fastapi-frontend/src/views/aichat/aichat.vue

@ -150,7 +150,7 @@
</div> </div>
</div> </div>
<!-- 文件导入对话框 --> <!-- 文件导入对话框 -->
<el-dialog :z-index="9999" title="文件导入" v-model="upload.open" width="400px" append-to-body> <el-dialog :z-index="99999" title="文件导入" v-model="upload.open" width="400px" append-to-body>
<el-upload v-if="upload.open" <el-upload v-if="upload.open"
ref="uploadRef" ref="uploadRef"
:headers="upload.headers" :headers="upload.headers"
@ -180,7 +180,6 @@
<script setup> <script setup>
import { ref, nextTick, computed, watch, reactive, onMounted } from 'vue' import { ref, nextTick, computed, watch, reactive, onMounted } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import antvg6 from './antv-g6.vue'
import OperationButton from './OperationButton.vue' import OperationButton from './OperationButton.vue'
import MdRenderer from '@/views/aichat/MdRenderer.vue' import MdRenderer from '@/views/aichat/MdRenderer.vue'
import fullscreenG6 from '@/views/aichat/fullscreenG6.vue' import fullscreenG6 from '@/views/aichat/fullscreenG6.vue'
@ -485,79 +484,83 @@ watch(
} }
) )
const getWrite = (reader) => { const getWrite = (reader) => {
// 1tempResult
let tempResult = ''; let tempResult = '';
/**
* 处理流式数据 const write_stream = ({ done, value }) => {
* @param {done, value} obj - 包含 done value 属性的对象 if (done) return;
*/ const decoder = new TextDecoder('utf-8');
const write_stream = ({ done,value }) => { let str = decoder.decode(value, { stream: true });
try { console.log(str)
if (done) { tempResult += str;
return
// 2使
const split = tempResult.match(/data:.*?}\r\n/g);
if (split) {
// 3
for (let i = 0; i < split.length; i++) {
const chunkStr = split[i];
tempResult = tempResult.replace(chunkStr, '', 1); // 4
try {
const chunk = JSON.parse(chunkStr.replace('data:', '').trim());
processChunk(chunk);
} catch (e) {
console.error('解析错误:', e, chunkStr);
}
} }
const decoder = new TextDecoder('utf-8');
let str = decoder.decode(value, { stream: true }); //
// chunk return reader.read().then(write_stream);
tempResult += str; } else {
console.log(tempResult) //
const split = tempResult.match(/data:.*}\r\n/g); return reader.read().then(write_stream);
if (split) { }
str = split.join(''); };
tempResult = tempResult.replace(str, '');
const processChunk = (chunk) => {
const lastMsg = chatList.value[chatList.value.length - 1];
// 5
if (chunk.docs?.length) {
lastMsg.content.push({ content: chunk.docs, type: "docs" });
} else if (chunk.G6_ER?.length) {
lastMsg.content.push({ content: chunk.G6_ER, type: "G6_ER" });
} else if (chunk.html_image?.length) {
lastMsg.content.push({ content: chunk.html_image, type: "html_image" });
} else if (chunk.table?.length) {
lastMsg.content.push({ content: chunk.table, type: "table" });
} else if (chunk.choices?.length) {
// 6
// const text = chunk.choices[0].delta.content.replace(/\n/g, "\n\n");
//
const content = chunk.choices[0].delta.content;
const isNewParagraph = content.startsWith('\n\n');
//
const text = isNewParagraph
? content.replace(/\n{2,}/g, '\n\n')
: content.replace(/\n/g, ' ');
const lastContent = lastMsg.content[lastMsg.content.length - 1];
console.log(lastContent)
if (lastContent?.type === "text") {
lastContent.content += text;
} else { } else {
return reader.read().then(write_stream); lastMsg.content.push({ content: text, type: "text" });
}
// str "data:"
if (str && str.startsWith('data:')) {
split.forEach((chunkStr) => {
const chunk = JSON.parse(chunkStr.replace('data:', '').trim());
if (chunk.docs && chunk.docs[0].length > 0){
//
chatList.value[chatList.value.length - 1].content.push({"content":chunk.docs[0],"type":"docs"})
}else if (chunk.G6_ER && chunk.G6_ER.length > 0){
//ER
chatList.value[chatList.value.length - 1].content.push({"content":chunk.G6_ER,"type":"G6_ER"})
}else if (chunk.html_image && chunk.html_image.length > 0){
//htmlecharts
chatList.value[chatList.value.length - 1].content.push({"content":chunk.html_image,"type":"html_image"})
}else if (chunk.table && chunk.table.length > 0){
//
chatList.value[chatList.value.length - 1].content.push({"content":chunk.table,"type":"table"})
}else {
//
let last_answer = chatList.value[chatList.value.length - 1].content
chunk.choices[0].delta.content = chunk.choices[0].delta.content.replace("\n","\n\n")
if (last_answer.length > 0) {
if(last_answer[last_answer.length - 1].type === 'text'){
chatList.value[chatList.value.length - 1].content[last_answer.length - 1].content += chunk.choices[0].delta.content
}else{
chatList.value[chatList.value.length - 1].content.push({"content":chunk.choices[0].delta.content,"type":"text"})
}
}else{
chatList.value[chatList.value.length - 1].content.push({"content":chunk.choices[0].delta.content,"type":"text"})
}
}
nextTick(() => {
//
scrollDiv.value.setScrollTop(getMaxHeight())
})
if (chunk.isEnd || chunk.is_end){
chatList.value[chatList.value.length - 1].isEnd = true
chatList.value[chatList.value.length - 1].time = formatDate(new Date())
nextTick(() => {
//
scrollDiv.value.setScrollTop(getMaxHeight())
})
return Promise.resolve();
}
});
} }
} catch (e) {
return Promise.reject(e);
} }
return reader.read().then(write_stream);
// 7
if (chunk.isEnd || chunk.is_end) {
lastMsg.isEnd = true;
console.log(lastMsg)
lastMsg.time = formatDate(new Date());
}
nextTick(() => scrollDiv.value.setScrollTop(getMaxHeight()));
}; };
return write_stream
return write_stream;
}; };
const stopChat = (index) => { const stopChat = (index) => {

Loading…
Cancel
Save