html
复制代码
<template>
<div class="content" >
<div class="left-part">
<div class="left-part-title">
<img src="../../assets/images/main/ai-icon.png" alt="">
<span>AI问答模式</span>
</div>
<div class="left-part-btn" @click="startNewDialog">
<img src="../../assets/images/main/ai-chat-icon.png" alt="">
<span>开启新对话</span>
</div>
<div class="chart-history">
<div v-for="(i,j) in state.chartHistory" :key="j" class="chart-item">
<div class="chart-item-title">
{{ i.date }}
</div>
<div class="chart-item-list">
<div v-for="(k,l) in i.children" :key="l" @click="getCurrentHistory(k)">
{{ k.content }}
</div>
</div>
</div>
</div>
</div>
<div class="right-part">
<div class="right-part-dialog" ref="contentRef">
<div class="right-part-dialog-item" v-for="(i,j) in state.chatContentData" :key="j" :class="[i.sender]">
<div class="right-part-dialog-item-content" v-if="i.sender == 'people'">
{{ i.content }}
</div>
<div v-if="i.sender == 'bot'">
<div class="right-part-dialog-item-content">
{{ i.content }}
</div>
<div style="margin-top: 15px;" v-html="i.content1" >
</div>
</div>
</div>
</div>
<div class="right-part-input">
<div class="main-input">
<a-textarea :bordered="false" :disabled="state.inputDisabled" :auto-size="{ minRows: 5, maxRows: 5 }" v-model:value.trim="state.inputValue" @keydown.enter.prevent="handleEnterKey" placeholder="请输入内容" />
<div class="main-input-btn" :class="{ 'main-input-btn-active': state.inputValue}" @click.enter="sendQuestion">
<svg t="1752720657922" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="20272" width="200" height="200"><path d="M512 0C229.974122 0 0.946396 229.027726 0.946396 511.053604s229.027726 511.053604 511.053604 511.053605 511.053604-229.027726 511.053604-511.053605S794.025878 0 512 0z m-96.532348 779.829945v-123.031424l73.818854 20.820703-73.818854 102.210721z m232.813309-88.961183l-227.134935-70.033272L625.567468 382.343808 364.362292 603.80037 152.369686 539.445471l613.264325-297.168207-117.35305 448.591498z m0 0" fill="currentColor" p-id="20273"></path></svg>
</div>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import {getChatHistory} from '../../components/http/api/index'
import axios from 'axios'
import {onMounted, reactive,ref,nextTick} from 'vue'
const props=defineProps(['question'])
const state = reactive({
inputValue: props.question,
inputDisabled:false,
chartHistory:[
],
chatContentData:[
{
content: '你好!我是AI助手,有什么可以帮您的?',
content1:'',
sender: 'bot'
},
]
})
const contentRef = ref()
onMounted(() => {
// if(props.question){
// sendQuestion()
// }
chatHistory() // 获取聊天记录
})
// 点击历史记录
const getCurrentHistory=(val)=>{
if(state.inputDisabled) return
state.inputValue = val.content
sendQuestion()
}
const chatHistory = () => {
getChatHistory()
.then(res=>{
if(res.data&&res.data.length>0){
state.chartHistory = res.data
}else{
state.chartHistory =[]
}
})
}
const handleEnterKey = (e: KeyboardEvent) => {
// 检查是否同时按下了Shift键(允许换行)
if (e.shiftKey) {
// 允许默认行为(换行)
return;
}
// 如果没有按Shift键且输入内容不为空,则发送消息
if (!e.shiftKey && state.inputValue.trim()) {
sendQuestion()
}
// 如果按下了Shift+Enter,会自动换行(默认行为)
}
const sendQuestion = ()=>{
if (!state.inputValue.trim()) return
// 添加用户消息
state.chatContentData.push({
content: state.inputValue,
sender: 'people'
})
// 清空输入框
const userMessage = state.inputValue
state.inputValue = ''
state.inputDisabled = true
scrollToBottom() // 滚动条滚动到最底部
// 这里可以调用AI接口获取回复
getAIResponse(userMessage)
}
const startNewDialog = ()=>{
state.chatContentData=[
{
content: '你好!我是AI助手,有什么可以帮您的?',
sender: 'bot'
}
]
}
const scrollToBottom = () => {
if (contentRef.value) {
nextTick(() => {
contentRef.value.scrollTop = contentRef.value.scrollHeight
})
}
}
const getAIResponse = async (message: string) => {
try {
const token = 'Bearer private-x|eyJhbGciOHJvamVjdF9pZNrdiJ9.VFgYNiqhoeTKjwnKDHa7Qgn97L2NE4';
// 第一步:获取 conversation_id
const conversationResponse = await axios.post(
'http://125.22.614.25/api/ai_apaas/v1/app/conversation',
{
app_id: '78ed55c5-62-9e7c-e3710a0'
},
{
headers: {
'Authorization': token,
'X-Authorization': token,
'Content-Type': 'application/json'
},
timeout: 10000 // 设置超时时间为10秒
}
);
const conversationId = conversationResponse.data.conversation_id;
// 第二步:获取回答
const answerResponse = await axios.post(
'http://125.22.614.25/api/ai_apaas/v1/app/conversation/runs',
{
app_id: '78ed55c5-62-9e7c-e3710a0',
query: message,
stream: false,
conversation_id: conversationId
},
{
headers: {
'Authorization': token,
'X-Authorization': token,
'Content-Type': 'application/json'
}
}
);
// 添加AI回复到聊天记录
state.chatContentData.push({
content: answerResponse.data.answer,
sender: 'bot'
});
} catch (error) {
console.error('Error:', error);
// 出错时显示错误信息
state.chatContentData.push({
content: '获取回答时出错,请稍后重试',
sender: 'bot'
});
} finally {
state.inputDisabled = false;
scrollToBottom();
chatHistory() // 获取最新的聊天记录
}
};
</script>
<style lang="less" scoped>
.content {
width: 100%;
height: 100%;
display: flex;
.left-part {
width: 21%;
height: 100%;
background: #f6fbfb;
// padding:30px 5px 30px 25px;
padding-top: 30px;
border-radius: 8px 0 0 8px;
display: flex;
flex-direction: column;
align-items: center;
.left-part-title{
display: flex;
align-items: center;
gap:12px ;
font-weight: 600;
font-size: 24px;
line-height: 34px;
letter-spacing: 0%;
color: #333;
img{
width: 32px;
height: 32px;
}
}
.left-part-btn{
width: 136px;
height: 48px;
background: #d7edeb;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
gap:8px;
font-family: PingFang SC;
font-weight: 500;
font-size: 16px;
line-height: 100%;
letter-spacing: 0%;
color: #249d91;
cursor: pointer;
user-select: none;
margin-top:24px ;
margin-bottom: 64px;
}
.chart-history{
flex:1;
width: 100%;
overflow-y: auto;
padding-left: 20px;
display: flex;
flex-direction: column;
gap:30px;
.chart-item{
display: flex;
flex-direction: column;
gap:24px;
.chart-item-title{
font-weight: 600;
font-size: 16px;
line-height: 22px;
letter-spacing: 0%;
color: #333333;
}
.chart-item-list{
display: flex;
flex-direction: column;
gap:12px;
font-family: PingFang SC;
font-weight: 400;
font-size: 16px;
line-height: 22px;
color:#333;
cursor: pointer;
}
}
}
}
.right-part {
width: 79%;
height: 100%;
background: #fff;
padding: 15px 0;
padding-top: 25px;
display: flex;
flex-direction: column;
.right-part-dialog{
padding: 0 48px;
padding-bottom: 20px;
flex: 1;
width: 100%;
overflow-y: auto;
display: flex;
flex-direction: column;
gap:20px ;
.right-part-dialog-item.bot{
font-weight: 400;
font-size: 16px;
line-height: 26px;
color: #333;
.right-part-dialog-item-content{
padding: 13px 16px;
background: #def0ef;
font-family: PingFang SC;
font-weight: 400;
font-size: 16px;
line-height:22px;
letter-spacing: 0%;
color: #333;
border-radius: 8px;
display: inline-flex;
justify-content: flex-end;
word-break: break-all;
}
}
.right-part-dialog-item.people{
display: flex;
justify-content: flex-end;
.right-part-dialog-item-content{
padding: 13px 16px;
background: #249d91;
font-family: PingFang SC;
font-weight: 400;
font-size: 16px;
line-height:22px;
letter-spacing: 0%;
color: #ffff;
border-radius: 8px;
display: inline-flex;
justify-content: flex-end;
word-break: break-all;
}
}
}
.right-part-input{
padding: 0 48px;
width: 100%;
height: 129px;
.main-input{
width: 100%;
position: relative;
border:2px solid #249d91 ;
border-radius: 16px;
.ant-input{
padding: 11px;
}
.main-input-btn{
position: absolute;
color: #d5e0df;
width: 32px;
height: 32px;
right: 16px;
bottom: 16px;
cursor: not-allowed;
svg{
width: 100%;
height: 100%;
}
}
.main-input-btn-active{
color: #249d91;
cursor: pointer;
}
}
}
}
}
</style>