一、前言
本文源自微博客且已获授权,请尊重版权.
书接上文,上文中,我们介绍了通义千问AI落地
的后端接口。那么,接下来我们将继续介绍前端如何调用接口以及最后的效果;首先看效果:
上述就是落地到本微博客以后的页面效果,由于是基于落在现有项目之上,因此什么登录注册等基本功能都省去了,言归正传,下面我们将正式介绍通义千问AI落地的前端实现。
二、前端实现
2.1、前端依赖
前端所需依赖基本如下(本项目前端是基于Nuxtjs的,这样又益与SSE,所以有nuxt相关依赖
):
"dependencies": {
"@nuxtjs/axios": "^5.13.6",
"dayjs": "^1.11.12",
"element-ui": "^2.15.1",
"highlight.js": "^11.9.0", //代码高亮组件
"mavon-editor": "^2.10.4", //富文本展示
"nuxt": "^2.0.0",
"@stomp/stompjs": "^6.0.0", //
"ws": "^7.0.0" //websocket
}
2.2、页面布局
如上动图所示,前端项目主要是左右分布。其中,左侧负责管理各个session会话,包括激活会话、展示会话、删除会话等;
右侧则主要负责消息处理,包括消息收发、处理GPT生成的消息等。由于使用组件化开发,因此各个组件比较多,接下来的内容将采用 总-分
结构介绍。
2.2.1、主聊天页面
主聊天页面聚合了左侧的session管理和右侧的消息管理,内容如下:
<template>
<!-- 最外层页面于窗口同宽,使聊天面板居中 -->
<div class="home-view">
<!-- 整个聊天面板 -->
<div class="chat-panel">
<!-- 左侧的会话列表 -->
<div class="session-panel hidden-sm-and-down">
<div class="title">ChatGPT助手</div>
<div class="description">构建你的AI助手</div>
<div class="session-list">
<SessionItem
v-for="(session, index) in sessionList"
:key="session.id+index"
:active="session.id === activeSession.id"
:session="sessionList[index]"
class="session"
@click.native="sessionSwitch(session,index)"
@delete="deleteSession"
>
</SessionItem>
</div>
<div class="button-wrapper">
<div class="new-session">
<el-button @click="createSession">
<el-icon :size="15" class="el-icon-circle-plus-outline"></el-icon>
新的聊天
</el-button>
</div>
</div>
</div>
<!-- 右侧的消息记录 -->
<div class="message-panel">
<!-- 会话名称 -->
<div class="header">
<div class="front">
<div v-if="!isEdit" class="title">
<el-input style="font-size: 20px"
v-model="activeSession.topic"
@keyup.enter.native="editTopic()"
></el-input>
</div>
<div v-else class="title" style="margin-top: 6px;" @dblclick="editTopic()">
{
{ activeSession.topic }}
</div>
<div class="description">与ChatGPT的 {
{ activeSession?.messageSize ?? 0 }} 条对话</div>
</div>
<!-- 尾部的编辑按钮 -->
<div class="rear">
<i v-if="isEdit" @click="editTopic" class="el-icon-edit rear-icon"></i>
<i v-else @click="editTopic" class="el-icon-check rear-icon"></i>
</div>
</div>
<el-divider></el-divider>
<div class="message-list" id="messageListId">
<!-- 过渡效果 -->
<transition-group name="list">
<message-row
v-for="(message, index) in activeSession.messages"
:key="message.id+`${index}`"
:message="message"
></message-row>
</transition-group>
</div>
<div class="toBottom" v-if="!this.isScrolledToBottom">
<el-tooltip class="item" effect="light" content="直达最新" placement="top-center">
<el-button class="el-icon-bottom bottom-icon" @click="toBottom"></el-button>
</el-tooltip>
</div>
<!-- 监听发送事件 -->
<MessageInput @send="sendMessage" :isSend="isSend"></MessageInput>
</div>
</div>
</div>
</template>
<script>
import MessageInput from '@/components/gpt/MessageInput'
import MessageRow from '@/components/gpt/MessageRow'
import SessionItem from "@/components/gpt/SessionItem";
import {Client} from "@stomp/stompjs";
import dayjs from "dayjs";
import {scrollToBottom} from '@/utils/CommonUtil'
export default {
name: 'gpt',
layout: 'gpt',
middleware: 'auth', //权限中间件,要求用户登录以后才能使用
components: {
MessageInput, MessageRow, SessionItem
},
created() {
this.loadChart();
},
mounted() {
this.handShake()
this.$nextTick(() => {
this.messageListEl = document.getElementById('messageListId');
if (this.messageListEl) {
this.messageListEl.addEventListener('scroll', this.onScroll);
}
});
},
beforeUnmount() {
this.closeClient();
},
beforeDestroy() {
if (this.messageListEl) {
this.messageListEl.removeEventListener('scroll', this.onScroll);
}
},
watch: {
activeSession(newVal) {
if (newVal) {
//确保dom加载完毕
this.$nextTick(() => {
this.toBottom();