5 changed files with 396 additions and 16 deletions
@ -0,0 +1,214 @@ |
|||||
|
<!-- |
||||
|
@ 作者: han2015 |
||||
|
@ 时间: 2025-05-12 15:39:13 |
||||
|
@ 备注: aibot组件 |
||||
|
--> |
||||
|
<script lang="ts" setup> |
||||
|
import { |
||||
|
Promotion, |
||||
|
Remove |
||||
|
} from '@element-plus/icons-vue' |
||||
|
import { doAiTraining,doAiChat,aiChatData} from "@/api/doc/space" |
||||
|
import {ElText,ElInput, ButtonInstance} from "element-plus"; |
||||
|
import { VueMarkdown } from '@crazydos/vue-markdown' |
||||
|
import rehypeRaw from 'rehype-raw' |
||||
|
import remarkGfm from 'remark-gfm' |
||||
|
import { display } from 'html2canvas/dist/types/css/property-descriptors/display'; |
||||
|
|
||||
|
//选中的咨询模式 |
||||
|
const checkedModel = ref([]) |
||||
|
//支持的模式 |
||||
|
const aimodels = [{name:'新对话',key:"context"}, {name:'联网检索',key:"onlineSearch"}, {name:'公司知识库',key:"useDataset"}] |
||||
|
const baseURL=import.meta.env.VITE_APP_BASE_API |
||||
|
const conversation=ref("") |
||||
|
const myquestion=ref('') |
||||
|
const controller = ref<AbortController | null>(null) |
||||
|
const inputState=ref(true) |
||||
|
|
||||
|
const interact_msg=ref<{ask:boolean,think:string,content:string}[]>([]) |
||||
|
const props = withDefaults(defineProps<{ |
||||
|
userid:string, |
||||
|
closefunc:()=>void, |
||||
|
agent:{model:boolean,name:string,uuid:string} |
||||
|
}>(),{}) |
||||
|
|
||||
|
const respMsg=ref("") |
||||
|
|
||||
|
/* 中断函数,供按钮调用 */ |
||||
|
function abortFetch() { |
||||
|
if (controller.value) { |
||||
|
controller.value.abort() |
||||
|
controller.value = null |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async function onSendTextToAI(){ |
||||
|
if(myquestion.value==="")return; |
||||
|
inputState.value=false |
||||
|
interact_msg.value.push({ask:true,think:"", content:myquestion.value}) |
||||
|
|
||||
|
const params={ |
||||
|
"onlineSearch":"否", |
||||
|
"useDataset":"否" |
||||
|
} |
||||
|
for (let item of checkedModel.value){ |
||||
|
if(item==="context") conversation.value="" |
||||
|
if(item==="onlineSearch") params.onlineSearch="是" |
||||
|
if(item==="useDataset") params.useDataset="是" |
||||
|
} |
||||
|
|
||||
|
controller.value = new AbortController(); |
||||
|
try{ |
||||
|
const res= await doAiChat(`${baseURL}/aibot/agents/${props.agent.uuid}/chat`,{ |
||||
|
inputs: params, |
||||
|
query:myquestion.value, |
||||
|
response_mode:"streaming", |
||||
|
conversation_id:conversation.value, |
||||
|
user:atob(props.userid), |
||||
|
},controller.value.signal |
||||
|
) |
||||
|
|
||||
|
if (!res.ok) { |
||||
|
throw new Error(`HTTP ${res.status} ${res.statusText}`); |
||||
|
} |
||||
|
|
||||
|
myquestion.value="" |
||||
|
const reader = res.body!.getReader(); |
||||
|
const decoder = new TextDecoder('utf-8'); |
||||
|
|
||||
|
let chunk = ''; // 行缓存 |
||||
|
while (true) { |
||||
|
const {done, value} = await reader.read() |
||||
|
if (done) break; |
||||
|
|
||||
|
// 服务器可能一次返回多行,需要手动按行拆分 |
||||
|
chunk += decoder.decode(value, {stream: true}); |
||||
|
const lines = chunk.split(/\n/); |
||||
|
chunk = lines.pop() || '' |
||||
|
for (const line of lines) { |
||||
|
if (!line.trim()) continue; // 跳过空行 |
||||
|
if (line.startsWith('data: ')) { |
||||
|
const data = line.slice(6); |
||||
|
const json = JSON.parse(data); |
||||
|
if(json.event==="message"){ |
||||
|
conversation.value=json.conversation_id |
||||
|
respMsg.value+=json.answer |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}catch (e: any) { |
||||
|
if (e.name === 'AbortError') { |
||||
|
console.log('用户手动中断') |
||||
|
} |
||||
|
inputState.value=true |
||||
|
} |
||||
|
|
||||
|
const arr=respMsg.value.split("</think>") |
||||
|
if(arr.length===1){ |
||||
|
interact_msg.value.push({ask:false,think:"",content:arr[0]}) |
||||
|
}else{ |
||||
|
//思考模式 |
||||
|
interact_msg.value.push({ask:false,think:arr[0],content:arr[1]}) |
||||
|
} |
||||
|
|
||||
|
respMsg.value="" |
||||
|
} |
||||
|
|
||||
|
//渲染完页面再执行 |
||||
|
onMounted(() => { |
||||
|
//loadKnownLibList() |
||||
|
}); |
||||
|
|
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<el-drawer |
||||
|
:model-value="agent.model" |
||||
|
:title="agent.name+' : 知识库'" |
||||
|
direction="rtl" |
||||
|
size="60%" |
||||
|
@close="closefunc" |
||||
|
:style="{padding:'17px'}"> |
||||
|
|
||||
|
<el-row :gutter="24" style="height: 99%;"> |
||||
|
<el-col style="position: relative;background: white;"> |
||||
|
<div class="reply_area" > |
||||
|
<template v-for="msg of interact_msg"> |
||||
|
<el-text v-if="msg.ask" class="t_ask" >{{ msg.content }}</el-text> |
||||
|
<div v-else class="t_resp"> |
||||
|
<el-text style="white-space: pre-line" v-html="msg.think"></el-text> |
||||
|
<VueMarkdown :markdown="msg.content" :rehype-plugins="[rehypeRaw]" :remark-plugins="[remarkGfm]" ></VueMarkdown> |
||||
|
</div> |
||||
|
|
||||
|
</template> |
||||
|
<VueMarkdown :markdown="respMsg" :rehype-plugins="[rehypeRaw]" class="t_resp"></VueMarkdown> |
||||
|
</div> |
||||
|
<div class="question_com"> |
||||
|
<el-checkbox-group v-model="checkedModel" style="display:flex; margin-bottom: 10px;"> |
||||
|
<el-checkbox-button v-for="mod in aimodels" :value="mod.key"> |
||||
|
{{ mod.name }} |
||||
|
</el-checkbox-button> |
||||
|
</el-checkbox-group> |
||||
|
<el-input placeholder="问灵犀..." v-model="myquestion" input-style="border-radius: 20px;" |
||||
|
resize="none" :autosize="{minRows: 4}" type="textarea" /> |
||||
|
<el-button :style="{display :inputState ? '':'none'}" type="primary" :icon="Promotion" circle @click="onSendTextToAI"/> |
||||
|
<el-button :style="{display :inputState ? 'none':''}" type="primary" :icon="Remove" circle @click="abortFetch"/> |
||||
|
<span>内容由 AI 生成,请仔细甄别</span> |
||||
|
</div> |
||||
|
</el-col> |
||||
|
</el-row> |
||||
|
|
||||
|
</el-drawer> |
||||
|
</template> |
||||
|
|
||||
|
<style lang="scss" scoped> |
||||
|
.app_container { |
||||
|
padding: 10px 30px 0px 30px; |
||||
|
height: 100%; |
||||
|
width: 100%; |
||||
|
} |
||||
|
.question_com{ |
||||
|
position: fixed; |
||||
|
bottom: 25px; |
||||
|
width: 52%; |
||||
|
margin: 0 50px; |
||||
|
text-align: center; |
||||
|
display: block; |
||||
|
button{ |
||||
|
position: absolute; |
||||
|
bottom: 25px; |
||||
|
right: 5px; |
||||
|
} |
||||
|
} |
||||
|
.reply_area{ |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
margin: 15px 15px 110px 0px; |
||||
|
} |
||||
|
.t_ask{ |
||||
|
align-self: end; |
||||
|
line-height: 34px; |
||||
|
background-color: rgb(188 211 241); |
||||
|
padding: 0 30px; |
||||
|
border-radius:10px; |
||||
|
margin: 15px; |
||||
|
} |
||||
|
.t_resp{ |
||||
|
align-self: start; |
||||
|
line-height: 30px; |
||||
|
margin: 20px 33px; |
||||
|
font-size: 18px; |
||||
|
color: black; |
||||
|
} |
||||
|
.dynamic-width-message-box-byme .el-message-box__message{ |
||||
|
width: 100%; |
||||
|
} |
||||
|
</style> |
||||
|
<style> |
||||
|
think { |
||||
|
color: #939393; |
||||
|
margin-bottom: 8px; |
||||
|
display: block; |
||||
|
} |
||||
|
</style> |
||||
Loading…
Reference in new issue