You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

748 lines
18 KiB
Vue

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<view class="content">
<view class="top-bar">
<image
@click="changeMode"
class="version"
:src="
isGPT3 ? '../../static/image/3.5.png' : '../../static/image/4.0.png'
" />
<view class="title">FONCHAT</view>
<view></view>
</view>
<view class="bottom">
<div v-show="!acqStatus">
<div class="line"></div>
</div>
<view
class="chat-content"
id="chat-content"
ref="chatContent">
<view
class="chat-wrapper"
v-for="(item, index) in chatList"
:key="index">
<view
class="chat-friend"
v-if="item.uid !== 'admin'">
<span style="color: #175abd"
>智能助手<span
style="font-size: 20rpx"
v-show="!acqStatus && index === chatList.length - 1"
>(正在思索……)</span
></span
>
<image
style="
height: 40px;
width: 40px;
position: absolute;
left: -48px;
top: 5px;
"
src="../../static/image/avator.png" />
<view
class="chat-text"
v-if="item.chatType == 0">
<view
v-if="item.msg"
class="chat-word"
>{{ item.msg }}</view
>
</view>
<view
class="chat-image"
v-if="item.chatType == 1">
<image
:src="item.msg"
alt="表情"
v-if="item.extend.imageType == 1"
style="width: 100px; height: 100px" />
<image
style="border-radius: 10px"
:src="item.msg"
v-else>
</image>
</view>
</view>
<view
class="chat-me"
v-else>
<image
:src="item.headimage"
alt=""
style="
height: 40px;
width: 40px;
position: absolute;
right: -48px;
top: 5px;
" />
<view
class="chat-text"
v-if="item.chatType == 0">
<span style="font-size: 16px">{{ item.msg }}</span>
</view>
<view
class="chat-image"
v-if="item.chatType == 1">
<image
:src="item.msg"
alt="表情"
v-if="item.extend.imageType == 1"
style="width: 100px; height: 100px" />
<a-image
style="max-width: 300px; border-radius: 10px"
:src="item.msg"
v-else>
</a-image>
</view>
</view>
</view>
</view>
</view>
<!-- 下面是两个是占位元素 -->
<view style="height: 12vh"></view>
<view class="text-area"> </view>
<view
class="text-area"
style="position: fixed">
<u--input
style="
width: 500rpx;
height: 60rpx;
border-radius: 40rpx;
background-color: #d8d8d8;
margin-right: 20rpx;
"
:disabled="!acqStatus"
@confirm="sendText"
v-model="inputMsg"
border="none"
placeholder="请输入内容"></u--input>
<u-button
style="
border-radius: 40rpx;
width: 140rpx;
height: 64rpx;
background-color: #175abd;
"
@click="sendText"
type="primary"
text="发送"></u-button>
<u-upload
:fileList="fileList"
name="6"
accept="image"
@afterRead="upLoaded"
:maxCount="1">
<u-icon
size="30"
style="margin-bottom: 30rpx; margin-left: 30rpx"
name="plus-circle-fill"></u-icon>
</u-upload>
</view>
</view>
</template>
<script>
import api from "@/http/";
import moment from "moment";
import {
OPENAI_API_KEY,
GPT_MODEL,
FREQUENCY_PENALTY,
MAX_TOKENS,
PRESENCE_PENALTY,
TEMPERATURE,
TOP_P,
} from "utils/openAiConfig";
export default {
data() {
return {
chatList: [],
inputMsg: "",
acqStatus: true,
gptMode: "gpt-3.5-turbo",
isGPT3: true,
fileList: [],
};
},
onLoad(item) {},
methods: {
//发送文字信息
sendText() {
this.$nextTick(() => {
this.acqStatus = false;
});
const dateNow = moment().format("YYYY/M/DD/ HH:mm:ss");
let params = {};
console.log(this.inputMsg);
if (this.inputMsg) {
let chatMsg = {
headimage: "../../static/image/head.jpg",
name: GPT_MODEL,
time: dateNow,
msg: this.inputMsg,
fileList:
this.fileList.length > 0
? JSON.parse(JSON.stringify(this.fileList))
: [],
chatType: 0, //信息类型0文字1图片
uid: "admin", //uid
};
console.log(chatMsg, 888);
this.sendMsg(chatMsg);
//如果是文字模式则进入
(params.model = this.gptMode),
(params.max_tokens = MAX_TOKENS),
(params.temperature = TEMPERATURE),
(params.top_p = TOP_P),
(params.presence_penalty = PRESENCE_PENALTY),
(params.frequency_penalty = FREQUENCY_PENALTY);
let chatBeforResMsg = {
headimage: "",
name: "",
time: moment().format("YYYY/M/DD/ HH:mm:ss"),
msg: "",
chatType: 0, //信息类型0文字1图片
uid: "ai", //uid
};
this.chatCompletion(params, chatBeforResMsg);
this.inputMsg = "";
this.fileList = [];
} else {
this.$nextTick(() => {
this.acqStatus = true;
});
}
},
// 自动滚动到底部
scrollToBottom() {
this.$nextTick(() => {
uni
.createSelectorQuery()
.select(".chat-content")
.boundingClientRect((data) => {
uni.pageScrollTo({
duration: 100, //过渡时间
scrollTop: 1600, //到达目标class的top值
});
})
.exec();
});
},
// 切换模式
changeMode(e) {
this.acqStatus = true;
this.isGPT3 = !this.isGPT3;
this.gptMode = this.isGPT3 ? "gpt-3.5-turbo" : "gpt-4-1106-preview";
// 清空对话
this.chatList = [];
},
// 从接口获取对话结果
async chatCompletion(params, chatBeforResMsg) {
let conversation = this.contextualAssemblyData();
params.messages = conversation.map((item) => {
return {
role: item.speaker === "user" ? "user" : "assistant",
content: item.text,
};
});
params.stream = true;
//新增一个空的消息
this.sendMsg(chatBeforResMsg);
const self = this;
const currentResLocation = this.chatList.length - 1;
// 获取当前环境地址
const baseUrl = "http://114.218.158.24:9020/";
const token =
"46d71a72d8d845ad7ed23eba9bdde260e635407190c2ce1bf7fd22088e41682ea07773ec65cae8946d2003f264d55961f96e0fc5da10eb96d3a348c1664e9644e756eda7154e1af9e70d1c9d2f100823a26885ea6df3249fe619995cb79dc5dbd5ead32d43b955d6b3ce83129097bb21bb8169898f48692de4f966db140c71b85a2065acfc948561c465279fc05194a79a1115f3b00170944b6c4bd6c52ada909a075c55d18d76c2ed2175602421b34b27362a05c350733ed73382471df0a08950f7f1e812a610c17bdac82d82d54be38969f6b41201af79b8d36ef177c5b94b533b1600017241188832aaee0ff1844b2560f527e9f563e3c561bffc356ffe5777a3d2030a9579e443bb04a2b565d05f9d2d3d1efaefdb703ae0575f1542aeba992ba5ba7c2db5b5573509b172bc26aaf8c05b27bc981ec23f0873a801f42c51";
try {
await fetch(baseUrl + "chat/completion", {
method: "POST",
timeout: 10000,
body: JSON.stringify({
...params,
}),
headers: {
"Content-Type": "application/json",
Accept: "application/json",
Authorization: token,
},
}).then((response) => {
const reader = response.body.getReader();
function readStream(reader) {
console.log(reader, 666);
return reader.read().then(({ done, value }) => {
if (done) {
return;
}
console.log(self.chatList, currentResLocation, 777);
if (!self.chatList[currentResLocation].reminder) {
self.chatList[currentResLocation].reminder = "";
}
let decoded = new TextDecoder().decode(value);
decoded = self.chatList[currentResLocation].reminder + decoded;
let decodedArray = decoded.split("data: ");
decodedArray.forEach((decoded) => {
if (decoded !== "") {
if (decoded.trim() === "[DONE]") {
return;
} else {
const response = JSON.parse(decoded).choices[0].delta
.content
? JSON.parse(decoded).choices[0].delta.content
: "";
self.chatList[currentResLocation].msg =
self.chatList[currentResLocation].msg + response;
self.scrollToBottom();
}
}
});
return readStream(reader);
});
}
this.chatList[currentResLocation].msg =
this.chatList[currentResLocation].msg;
readStream(reader);
this.$nextTick(() => {
this.acqStatus = true;
});
});
} catch (error) {
console.error(error);
}
},
//组装上下文数据
contextualAssemblyData() {
const conversation = [];
for (var chat of this.chatList) {
let role = [];
if (chat.uid == "admin") {
let my;
if (this.gptMode === "gpt-4-1106-preview-vision-preview") {
if (chat.fileList.length > 0) {
chat.fileList.forEach((item) => {
if (item) {
role.push({
type: "image_url",
image_url: item,
});
}
});
}
role.unshift({
type: "text",
text: chat.msg,
});
my = { speaker: "user", text: role };
} else {
my = { speaker: "user", text: chat.msg };
}
conversation.push(my);
} else if (chat.uid == "ai") {
let ai = { speaker: "agent", text: chat.msg };
conversation.push(ai);
}
}
return conversation;
},
// 发送信息
sendMsg(msgList) {
this.chatList.push(msgList);
this.scrollToBottom();
},
// 当监听到向上滚动的时候
scrollToUpper() {
console.log("滚动到顶部");
},
uploadFilePromise(url) {
return new Promise((resolve, reject) => {
const Authorization = uni.getStorageSync('token');
let a = uni.uploadFile({
url: 'http://114.218.158.24:9020/upload/img',
filePath: url,
name: 'file',
formData: {
source: 'gpt',
mask:''
},
header: {
Authorization
},
success: res => {
resolve(res.data);
this.fileList.push(
JSON.parse(res.data).data.ori_url
);
console.log(this.fileList, 888);
}
});
});
},
async upLoaded(file, lists, name) {
this.uploadFilePromise(file.file.url)
},
},
};
</script>
<style lang="scss" scoped>
.uni-body {
height: 100vh;
background-color: #fff;
background-size: auto 100%;
background-attachment: fixed;
}
page {
height: 100vh;
background-color: #fff;
background-size: auto 100%;
background-attachment: fixed;
}
/deep/ .u-input__content {
color: #fff;
padding-left: 40rpx;
}
/deep/ .u-upload{
flex: none !important;
}
.text-area {
// position: fixed;
width: 95%;
bottom: 0;
height: 6vh;
background-size: 100% 100%;
background-color: #f2f2f2;
display: flex;
justify-content: space-between;
padding: 30rpx 40rpx 0rpx 20rpx;
}
.content {
width: 97%;
background-color: #fff;
background-size: auto 100%;
background-attachment: fixed;
height: 100%;
// overflow: hidden;
.top-bar {
width: 100%;
z-index: 100;
position: fixed;
height: 180rpx;
// 背景色渐变
background: linear-gradient(to bottom, #175abd 0%, #25a7f2 100%);
display: flex;
align-items: flex-end;
justify-content: space-between;
.version {
margin-bottom: 25rpx;
width: 100rpx;
height: 56rpx;
margin-left: 30rpx;
}
.title {
margin-bottom: 30rpx;
font-size: 36rpx;
color: #fff;
font-weight: bold;
margin-right: 120rpx;
}
}
.bottom {
width: 100%;
height: 80vh;
background-size: 100% 100%;
// background-color: rgb(50, 54, 68);
border-radius: 20px;
padding: 99px 20px 20px 20px;
box-sizing: border-box;
position: relative;
.chat-content {
width: 100%;
height: 100%;
// overflow-y: scroll;
// overflow-x: hidden;
padding: 20px;
box-sizing: border-box;
&::-webkit-scrollbar {
width: 3px;
/* 设置滚动条宽度 */
}
&::-webkit-scrollbar-thumb {
background-color: #8b67ef;
/* 设置滚动条滑块的背景色 */
}
.chat-friend {
width: 100%;
float: left;
margin-bottom: 20px;
position: relative;
display: flex;
margin-left: 30px;
flex-direction: column;
justify-content: flex-end;
align-items: flex-start;
.chat-text {
float: left;
max-width: 90%;
padding: 15px;
color: #535353;
border-radius: 20px 20px 20px 5px;
border: 1px solid #d8d8d8;
background-color: #fff;
}
.chat-image {
image {
max-width: 300px;
max-height: 200px;
border-radius: 10px;
}
}
.info-time {
margin: 10px 0;
font-size: 14px;
display: flex;
align-items: center;
justify-content: flex-start;
color: #8b67ef;
image {
width: 30px;
height: 30px;
border-radius: 50%;
vertical-align: middle;
margin-right: 10px;
}
span {
line-height: 30px;
}
span:last-child {
margin-left: 10px;
vertical-align: middle;
}
}
}
.chat-me {
width: 100%;
float: right;
margin-bottom: 20px;
position: relative;
margin-right: 28px;
display: flex;
flex-direction: column;
justify-content: flex-end;
align-items: flex-end;
.chat-text {
float: right;
max-width: 90%;
padding: 15px;
border-radius: 20px 20px 5px 20px;
background-color: #175abd;
color: #fff;
word-break: break-all;
// 让文字一个一个显示
}
.chat-image {
image {
max-width: 300px;
max-height: 200px;
border-radius: 10px;
}
}
.info-time {
margin: 10px 0;
color: #8b67ef;
font-size: 14px;
align-items: center;
display: flex;
justify-content: flex-end;
image {
width: 30px;
height: 30px;
border-radius: 50%;
vertical-align: middle;
margin-left: 10px;
}
span {
line-height: 30px;
}
span:first-child {
margin-right: 10px;
vertical-align: middle;
}
}
}
}
.chatInputs {
width: 90%;
position: absolute;
bottom: 0;
margin: 3%;
display: flex;
.boxinput {
width: 50px;
height: 50px;
background-color: rgb(50, 54, 68);
border-radius: 15px;
border: 1px solid rgb(80, 85, 103);
box-shadow: 0px 0px 5px 0px rgb(0, 136, 255);
position: relative;
cursor: pointer;
image {
width: 30px;
height: 30px;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
}
.emoji {
transition: 0.3s;
width: 50px;
min-width: 50px;
}
.luyin {
color: #fff;
margin-left: 1.5%;
font-size: 30px;
text-align: center;
transition: 0.3s;
width: 50px;
min-width: 50px;
}
.inputs {
width: 95%;
height: 50px;
background-color: rgb(66, 70, 86);
border-radius: 15px;
border: 2px solid rgb(34, 135, 225);
padding: 10px;
box-sizing: border-box;
transition: 0.2s;
font-size: 20px;
color: #fff;
font-weight: 100;
margin: 0 20px;
&:focus {
outline: none;
}
}
}
}
.line {
position: relative;
width: 100%;
margin-left: 2%;
height: 2px;
background: linear-gradient(to right, #175abd 0%, #25a7f2 100%);
animation: shrink-and-expand 2s ease-in-out infinite;
}
.line::before,
.line::after {
content: "";
position: absolute;
top: 0;
width: 50%;
height: 100%;
background: inherit;
}
.line::before {
border-top-left-radius: 2px;
border-bottom-left-radius: 2px;
left: 0;
transform-origin: left;
animation: shrink-left 2s ease-in-out infinite;
}
.line::after {
border-top-left-radius: 2px;
border-bottom-left-radius: 2px;
right: 0;
transform-origin: right;
animation: shrink-right 2s ease-in-out infinite;
}
@keyframes shrink-and-expand {
0%,
100% {
transform: scaleX(1);
}
50% {
transform: scaleX(0);
}
}
@keyframes shrink-left {
0%,
50% {
transform: scaleX(1);
}
50.1%,
100% {
transform: scaleX(0);
}
}
@keyframes shrink-right {
0%,
50% {
transform: scaleX(1);
}
50.1%,
100% {
transform: scaleX(0);
}
}
.chat-word {
// 文字缓慢出现
animation: word 0.5s linear;
}
@keyframes word {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
}
</style>