<template>
|
<view class="content">
|
<u-navbar :bgColor="'#17181D'" placeholder :title="now_model.title" :titleStyle ="'color: #fff;'">
|
<view slot="left">
|
<u-icon @click="showLeftList = true" name="list" size="24" color="#fff"></u-icon>
|
</view>
|
<view @click="create_newChat" slot="right">
|
<u-icon name="plus" size="24" color="#fff"></u-icon>
|
</view>
|
</u-navbar>
|
<view class="body">
|
<view v-for="(item,index) in arr" :key="index" :id="'message' + item.message_id">
|
<view class="body_l" v-if="item.role == 'assistant'">
|
<view class="body_lpic a">
|
<image :src="now_model.icon" mode=""></image>
|
</view>
|
<view class="body_box">
|
<view class="body_jt">
|
<u-icon name="play-left-fill" color="#F6F7FB" size="20"></u-icon>
|
</view>
|
<!-- <view class="body_lcon pdg" v-html="item.content"></view> -->
|
<view class="body_rcon pdg">
|
<zero-markdown-view :markdown="item.content"></zero-markdown-view>
|
</view>
|
<view style="position: absolute;right: 0;bottom: 10rpx;">
|
<u-icon @click="copy(item)" name="file-text-fill" size="30"></u-icon>
|
</view>
|
</view>
|
</view>
|
<view class="body_r" v-if="item.role == 'user'">
|
<view class="body_box">
|
<view class="body_rjt">
|
<u-icon name="play-right-fill" color="#F6F7FB" size="20"></u-icon>
|
</view>
|
<!-- <view class="body_rcon pdg" v-html="item.content"> -->
|
<view class="body_rcon pdg">
|
<zero-markdown-view :markdown="item.content"></zero-markdown-view>
|
</view>
|
</view>
|
<view class="body_rpic a">
|
<!-- <image v-if="token != ''" :src="vals.avatar" mode="aspectFill"></image> -->
|
<image :src="userInfo.avatar ? userInfo.avatar : '../../static/head.png'" mode="aspectFill">
|
</image>
|
</view>
|
</view>
|
</view>
|
</view>
|
<view class="body_l" v-if="loading" style="margin-left: 30rpx;" id="loading">
|
<view class="body_lpic a">
|
<image :src="now_model.icon" mode=""></image>
|
</view>
|
<view class="body_box ">
|
<view class="body_lcon pdg" style="display: flex;overflow-x: hidden;">
|
<u-loading-icon :show="loading"></u-loading-icon>
|
<view v-if="!typeingText" style="padding: 30rpx;">{{ now_model.title }}思考中...</view>
|
<!-- <zero-markdown-view v-if="!typeingText" style="padding: 0 20rpx;" :markdown="now_model.title">思考中...</zero-markdown-view> -->
|
<!-- <view v-else style="padding: 30rpx;">{{ typeingText }}...</view> -->
|
<zero-markdown-view v-else style="padding: 0 20rpx;" :markdown="typeingText"></zero-markdown-view>
|
</view>
|
</view>
|
</view>
|
<view class="flooer safebottom">
|
<view class="flooer_l">
|
<u--textarea v-model="prompt" placeholder="请输入内容,开始体验与人工智能交流吧" border="none" autoHeight
|
style="background-color: #F6F7FB;" :maxlength="256" holdKeyboard :showConfirmBar="false"
|
:cursorSpacing="28"></u--textarea>
|
<!-- <u--input placeholder="请输入内容,开始体验与人工智能交流吧" border="none" clearable v-model="prompt"
|
placeholderStyle="color: #7F8084"></u--input> -->
|
</view>
|
<view class="flooer_r pdg" @click="sendout">
|
<view class="flooer_rpic a">
|
<image src="../../static/fasong.png" mode=""></image>
|
</view>
|
</view>
|
</view>
|
<view style="height: 108rpx;"></view>
|
<u-popup :show="showLeftList" mode="left" closeOnClickOverlay @close="showLeftList = false">
|
<view class="left-wrap">
|
<view class="group-list">
|
<view class="group-item group-add">
|
<view @click="create_newChat" class="group-title">
|
<u-icon name="plus" size="15" color="#fff"></u-icon>
|
<view class="title">创建新对话</view>
|
</view>
|
</view>
|
<view class="dialog-main-list">
|
<view v-for="chat_item in chatList" :key="chat_item.chat_id" class="dialog-main-item"
|
:class="[chat_item.chat_id == chat_id ? 'dialog-main-sels' : '']" @click="getMessages(chat_item)">
|
<u-icon name="chat" size="20" color="#fff"></u-icon>
|
<view class="dialog-msg-text"> {{chat_item.title}} </view>
|
<view @click.stop="del(chat_item)" class="dialog-del-icon">
|
<u-icon name="trash" size="18" color="#fff"></u-icon>
|
</view>
|
</view>
|
</view>
|
<view class="dialog-main-bottom">
|
<view @click="delAll" class="item">
|
<u-icon name="trash" size="18" color="#fff"></u-icon>
|
<view class="title">清除所有会话</view>
|
</view>
|
</view>
|
</view>
|
</view>
|
</u-popup>
|
<m-tabbar v-if="showTab" fixed fill :current="tabIndex" :tabbar="tabbar"></m-tabbar>
|
</view>
|
</template>
|
|
<script>
|
import setting from "@/common/config";
|
//markdown相关插件
|
// import markdownFunc from '@/uni_modules/jo-markdown/components/jo-markdown/index.js';
|
// #ifdef H5
|
import axios from 'axios'
|
// import VConsole from 'vconsole'
|
// const vConsole = new VConsole()
|
// #endif
|
import {
|
mapState
|
} from "vuex"
|
var that;
|
import mixin from "@/mixin/checkLoaded";
|
export default {
|
mixins: [mixin],
|
data() {
|
return {
|
prompt: '',
|
arr: [],
|
timer: null,
|
loading: false,
|
chat_id: '',
|
typeingText: "",
|
typeingId: "",
|
now_model: {},
|
showLeftList: false,
|
chatList: [],
|
showTab: false,
|
tabIndex: 0
|
}
|
},
|
computed: {
|
...mapState(['config', 'num', 'userInfo','tabbar'])
|
},
|
onLoad(op) {
|
this.getTabIndex()
|
if (op.model) {
|
this.now_model = JSON.parse(op.model)
|
} else {
|
this.now_model = this.$store.state.main_model
|
}
|
uni.setNavigationBarTitle({
|
title: this.now_model.title
|
})
|
this.arr.push({
|
role: 'assistant',
|
content: this.now_model.first_message ? this.now_model.first_message : '开始和AI聊天吧~',
|
message_id: 'aa'
|
})
|
let that = this;
|
let pong = '';
|
let userInfo = uni.getStorageSync('userInfo')
|
uni.connectSocket({
|
url: setting.ws
|
});
|
uni.onSocketOpen(function(res) {
|
console.log('chat页WebSocket连接已打开!');
|
// 绑定UID
|
sendMsg({
|
type: 'login',
|
uid: userInfo.member_id
|
// uid: 3
|
})
|
// 设置心跳包
|
pong = setInterval(() => {
|
sendMsg({
|
type: 'pong'
|
})
|
}, 20 * 1000);
|
|
});
|
uni.onSocketError(function(res) {
|
console.log('WebSocket连接打开失败,请检查!');
|
});
|
uni.onSocketMessage(function(res) {
|
// console.log('收到服务器内容:' + res.data);
|
let json = JSON.parse(res.data)
|
// console.log(json)
|
if (json.hasOwnProperty('id')) {
|
that.typeingId = json.id
|
if (!json.choices[0].delta.finish_reason) {
|
if (json.choices[0].delta.hasOwnProperty('content')) {
|
that.typeingText += json.choices[0].delta.content
|
// 此处自行斟酌是否替换
|
// that.typeingText = that.typeingText.replace(/[cogptn]+/ig, "ChatGLM-6B")
|
}
|
}
|
}
|
|
});
|
// socket断开时通知后端改变登录状态
|
uni.onSocketClose(function(res) {
|
console.log('WebSocket 已关闭!');
|
});
|
//上文中的sendMsg原型
|
const sendMsg = function(data) {
|
uni.sendSocketMessage({
|
data: JSON.stringify(data)
|
})
|
}
|
this.getChats(true)
|
},
|
onUnload() {
|
uni.closeSocket()
|
},
|
onShow() {
|
that = this;
|
uni.getStorage({
|
key: 'token',
|
success: function(res) {
|
that.token = res.data;
|
|
},
|
fail() {
|
that.token = '';
|
}
|
});
|
},
|
onBackPress() {
|
uni.hideLoading()
|
clearInterval(this.timer);
|
},
|
onHide() {
|
uni.hideLoading()
|
clearInterval(this.timer);
|
},
|
methods: {
|
getTabIndex() {
|
let list = this.tabbar.list;
|
for (var i = 0; i < list.length; i++) {
|
if (list[i].pagePath == '/pages/index/chat') {
|
this.tabIndex = i
|
this.showTab = true
|
}
|
}
|
},
|
create_newChat() {
|
if (this.chatList.length > 0 && this.chatList[0].chat_id == '') {
|
return
|
}
|
if (this.showLeftList) {
|
this.showLeftList = false;
|
}
|
this.chat_id = '';
|
this.chatList.unshift({
|
chat_id: '',
|
title: '新聊天对话'
|
})
|
this.arr = []
|
},
|
getChats(first = false) {
|
this.$request("/api/Chat/index", "POST", {
|
limit: 30,
|
}).then(res => {
|
console.log(res)
|
this.chatList = res.data.data
|
if (first) {
|
this.chatList.unshift({
|
chat_id: '',
|
title: '新聊天对话'
|
})
|
}
|
uni.hideLoading()
|
})
|
},
|
del(item) {
|
uni.showLoading({})
|
if (this.chat_id == item.chat_id) {
|
this.$failToast('请先选择其他会话后再删除本对话');
|
return
|
}
|
this.$request("/api/Chat/delete", "POST", {
|
chat_id: item.chat_id
|
}).then(res => {
|
console.log(res)
|
// this.chatList = res.data.data
|
this.getChats()
|
})
|
},
|
delAll() {
|
uni.showLoading({})
|
this.$request("/api/Chat/delAll", "POST").then(res => {
|
console.log(res)
|
// this.chatList = res.data.data
|
this.getChats()
|
if (this.showLeftList) {
|
this.showLeftList = false;
|
}
|
})
|
},
|
getMessages(item) {
|
console.log(item)
|
if (item.chat_id == '') {
|
return
|
}
|
this.chat_id = item.chat_id;
|
uni.showLoading({
|
|
})
|
this.$request("/api/Message/index", "POST", {
|
chat_id: this.chat_id,
|
limit: 200,
|
sort: 'message_id',
|
order: 'asc',
|
}).then((res) => {
|
console.log(res);
|
if (res.status == 200) {
|
this.arr = res.data.data
|
this.$nextTick(() => {
|
uni.pageScrollTo({
|
selector: "#message" + this.arr[this.arr.length - 1].message_id,
|
duration: 100
|
});
|
this.showLeftList = false;
|
});
|
uni.hideLoading()
|
} else {
|
this.$failToast('服务器繁忙,请稍后再试');
|
}
|
|
});
|
},
|
sendout() {
|
if (!this.prompt) {
|
this.$failToast("请输入内容");
|
return;
|
}
|
|
this.loading = true;
|
this.arr.push({
|
role: 'user',
|
content: this.prompt,
|
message_id: this.arr.length + 1
|
});
|
let message = this.prompt;
|
this.prompt = ""
|
this.$nextTick(() => {
|
uni.pageScrollTo({
|
selector: "#loading",
|
duration: 100
|
});
|
});
|
setTimeout(() => {
|
this.sub(message)
|
}, 200);
|
},
|
sub(message) {
|
// 条件编译处理,h5端uni.request与socket会互相阻塞
|
// #ifndef H5
|
this.$request("/api/Message/sendV2", "POST", {
|
message: message,
|
chat_id: this.chat_id,
|
model_id: this.now_model.assistant_id
|
// member_id: 3 // 调试之用,线上环境为token解码
|
}).then((res) => {
|
console.log(res);
|
if (res.status == 200) {
|
this.loading = false;
|
this.typeingText = "";
|
this.typeingId = "";
|
this.arr.push({
|
role: res.data.role,
|
// 此处自行斟酌是否替换
|
content: res.data.content,
|
// content: res.data.content.replace(/[cogptn]+/ig, "ChatGLM-6B")
|
});
|
if (!this.chat_id) {
|
this.chatList[0].chat_id = res.data.chat_id
|
this.chatList[0].title = message
|
this.chat_id = res.data.chat_id
|
}
|
this.$store.commit('useNum', 1);
|
this.$nextTick(() => {
|
uni.pageScrollTo({
|
scrollTop: 20000,
|
duration: 100
|
});
|
});
|
} else if (res.status == 901) {
|
this.loading = false;
|
this.$failToast('服务器繁忙,请稍后再试');
|
} else if (res.status == 411) {
|
this.loading = false;
|
uni.showModal({
|
content: res.msg
|
})
|
} else {
|
this.loading = false;
|
this.$failToast(res.msg);
|
}
|
}).catch(err => {
|
console.log(err)
|
this.loading = false;
|
this.$failToast(err.errMsg);
|
})
|
// #endif
|
// #ifdef H5
|
axios({
|
method: 'post',
|
url: setting.base_url + "/api/Message/sendV2",
|
data: {
|
message: message,
|
chat_id: this.chat_id,
|
model_id: this.now_model.assistant_id
|
},
|
headers: {
|
'content-type': 'application/x-www-form-urlencoded;charset=utf-8',
|
'Authorization': this.$store.state.token
|
}
|
}).then(response => {
|
console.log(response)
|
let res = response.data
|
if (res.status == 200) {
|
this.loading = false;
|
this.typeingText = "";
|
this.typeingId = "";
|
this.arr.push({
|
role: res.data.role,
|
content: res.data.content
|
});
|
if (!this.chat_id) {
|
this.chatList[0].chat_id = res.data.chat_id
|
this.chatList[0].title = message
|
this.chat_id = res.data.chat_id
|
}
|
this.$store.commit('useNum', 1);
|
this.$nextTick(() => {
|
uni.pageScrollTo({
|
scrollTop: 20000,
|
duration: 100
|
});
|
});
|
} else if (res.status == 901) {
|
this.loading = false;
|
this.$failToast('服务器繁忙,请稍后再试');
|
} else if (res.status == 411) {
|
this.loading = false;
|
uni.showModal({
|
content: res.msg
|
})
|
} else {
|
this.loading = false;
|
this.$failToast(res.msg);
|
}
|
}).catch(error => {
|
// reject(error);
|
});
|
// #endif
|
|
},
|
copy(item) {
|
uni.setClipboardData({
|
data: item.content,
|
success() {
|
uni.showModal({
|
title: '提示',
|
content: '内容已复制到系统剪贴板'
|
})
|
}
|
})
|
}
|
}
|
}
|
</script>
|
|
<style lang="scss">
|
.safebottom {
|
padding-bottom: calc(112rpx + env(safe-area-inset-bottom));
|
}
|
.left-wrap {
|
width: 525rpx;
|
height: 100vh;
|
overflow-y: scroll;
|
background-color: #202123;
|
color: #fff;
|
|
.group-list {
|
padding: 10px 10px;
|
|
.group-item {
|
border: 1px solid #4d4d4f;
|
width: 100%;
|
height: 44px;
|
color: #fff;
|
border-radius: 8px;
|
margin-bottom: 10px;
|
position: relative;
|
|
.group-title {
|
color: #fff;
|
font-size: 14px;
|
white-space: nowrap;
|
overflow: hidden;
|
display: -webkit-box;
|
display: -ms-flexbox;
|
display: flex;
|
-webkit-box-align: center;
|
-ms-flex-align: center;
|
align-items: center;
|
cursor: pointer;
|
padding: 10px 15px;
|
width: 100%;
|
height: 100%;
|
-webkit-box-sizing: border-box;
|
box-sizing: border-box;
|
line-height: 18px;
|
|
.title {
|
padding-left: 20rpx;
|
}
|
}
|
}
|
|
.dialog-main-list {
|
padding-top: 12px;
|
overflow-y: scroll;
|
height: calc(100vh - 340rpx);
|
|
.dialog-main-item {
|
margin-bottom: 12px;
|
display: flex;
|
align-items: center;
|
padding: 17px 14px;
|
border-radius: 4px;
|
|
.dialog-msg-text {
|
color: #fff;
|
font-size: 12px;
|
margin-left: 9px;
|
overflow: hidden;
|
white-space: nowrap;
|
text-overflow: ellipsis;
|
}
|
|
.dialog-del-icon {
|
margin-left: auto;
|
}
|
}
|
|
.dialog-main-sels {
|
background-color: #343541;
|
}
|
}
|
|
.dialog-main-bottom {
|
position: absolute;
|
left: 0;
|
right: 0;
|
bottom: 0;
|
padding: 13px 17px;
|
border-top: 1px solid #4c4d4f;
|
|
.item {
|
padding: 17px 0px;
|
border: 1px solid #4c4d4f;
|
border-radius: 4px;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
|
.title {
|
color: #fff;
|
font-size: 13px;
|
margin-left: 8px;
|
}
|
}
|
}
|
}
|
}
|
</style>
|
|
<style>
|
@import '../../components/css/index.css';
|
</style>
|