vue实现模拟 ai 对话功能

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>
相关推荐
chinahcp200822 分钟前
CSS保持元素宽高比,固定元素宽高比
前端·css·html·css3·html5
暖木生晖22 分钟前
flex-wrap子元素是否换行
javascript·css·css3·flex
gnip1 小时前
浏览器跨标签页通信方案详解
前端·javascript
gnip2 小时前
运行时模块批量导入
前端·javascript
hyy27952276842 小时前
企业级WEB应用服务器TOMCAT
java·前端·tomcat
若梦plus3 小时前
http基于websocket协议通信分析
前端·网络协议
不羁。。3 小时前
【web站点安全开发】任务3:网页开发的骨架HTML与美容术CSS
前端·css·html
这是个栗子3 小时前
【问题解决】Vue调试工具Vue Devtools插件安装后不显示
前端·javascript·vue.js
姑苏洛言3 小时前
待办事项小程序开发
前端·javascript