You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
213 lines
5.8 KiB
213 lines
5.8 KiB
|
4 months ago
|
<!--
|
||
|
|
@ 作者: 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'
|
||
|
|
|
||
|
|
//选中的咨询模式
|
||
|
|
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>
|