189 lines
4.6 KiB
Vue
189 lines
4.6 KiB
Vue
<template>
|
||
<section class="chat">
|
||
<el-row align="middle mar-b16">
|
||
<div class="modelInfo">
|
||
<img :src="config.model.avatar" alt=""/>
|
||
<div class="fill mar-l8">
|
||
<b v-text="config.model.name"/>
|
||
<div class="desc" v-text="config.model.desc"/>
|
||
</div>
|
||
</div>
|
||
<el-row justify="end" class="fill mar-l8 gap-8">
|
||
<!--聊天操作栏-->
|
||
<div class="optIcon clear"/>
|
||
<div class="optIcon setting" @click="$emit('setting')"/>
|
||
</el-row>
|
||
</el-row>
|
||
<thinking-bar v-show="loading"/>
|
||
<chat-content class="fill" :list="chatHistory"/>
|
||
<el-row class="mar-t8" align="middle">
|
||
<chat-input class="fill mar-r8" v-model="inputText" @enter="handleSend"/>
|
||
<el-button type="primary" @click="handleSend">发送</el-button>
|
||
</el-row>
|
||
</section>
|
||
</template>
|
||
|
||
<script>
|
||
import {dayjs} from "element-plus";
|
||
import ChatContent from "../components/chatContent";
|
||
import ChatInput from "../components/chatInput";
|
||
import ThinkingBar from "../components/thinkingBar";
|
||
import {USER_AVATAR} from "../utils/env";
|
||
|
||
export default {
|
||
name: "chat",
|
||
components: {ChatContent, ChatInput, ThinkingBar},
|
||
props: {
|
||
config: {default: () => ({})}
|
||
},
|
||
data() {
|
||
return {
|
||
loading: false,
|
||
inputText: "",
|
||
chatHistory: []
|
||
}
|
||
},
|
||
methods: {
|
||
handleSend() {
|
||
if (!!this.inputText) {
|
||
const ai = this.config.model
|
||
const myMsg = {
|
||
avatar: USER_AVATAR,
|
||
name: "我",
|
||
time: dayjs().format("YYYY-MM-DD HH:mm:ss"),
|
||
msg: this.inputText,
|
||
chatType: 0, //信息类型,0文字,1图片
|
||
uid: "me", //uid
|
||
role: "user"
|
||
}
|
||
this.chatHistory.push(myMsg)
|
||
this.loading = true
|
||
const aiMsg = {
|
||
avatar: ai.avatar,
|
||
name: ai.name,
|
||
time: dayjs().format("YYYY-MM-DD HH:mm:ss"),
|
||
msg: "",
|
||
chatType: 0, //信息类型,0文字,1图片
|
||
uid: "ai", //uid
|
||
role: "assistant"
|
||
}
|
||
this.chatHistory.push(aiMsg)
|
||
if (this.config.stream) {
|
||
ai.chatStream(this.chatHistory).then(reader => this.streamOutput(reader, this.chatHistory.at(-1))).finally(() => this.loading = false)
|
||
} else {
|
||
ai.chat(this.chatHistory).then(reply => {
|
||
const decodeArr = reply.split("")
|
||
decodeArr.forEach(e => this.chatHistory.at(-1).msg += e)
|
||
}).finally(() => this.loading = false)
|
||
}
|
||
} else {
|
||
this.$message.error("请不要发送空消息!")
|
||
}
|
||
},
|
||
streamOutput(reader, chat) {
|
||
return reader.read().then(({done, value}) => {
|
||
if (done) {
|
||
return;
|
||
}
|
||
if (!chat.reminder) {
|
||
chat.reminder = ""
|
||
}
|
||
let decode = new TextDecoder().decode(value)
|
||
decode = chat.reminder + decode
|
||
let decodedArray = decode.split("data: ");
|
||
let longstr = "";
|
||
decodedArray.forEach(decoded => {
|
||
decoded = decoded.trim();
|
||
try {
|
||
if (longstr != "") {
|
||
decoded = longstr + decoded;
|
||
longstr = "";
|
||
}
|
||
} catch (e) {
|
||
longstr = decoded;
|
||
decoded = "";
|
||
}
|
||
if (!!decoded && decoded !== "[DONE]") {
|
||
const choices = JSON.parse(decoded).choices
|
||
if (choices?.length > 0) {
|
||
const response = choices[0].delta.content || "";
|
||
chat.msg += response
|
||
}
|
||
}
|
||
})
|
||
return this.streamOutput(reader, chat)
|
||
})
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.chat {
|
||
color: #fff;
|
||
display: flex;
|
||
flex-direction: column;
|
||
|
||
.modelInfo {
|
||
display: flex;
|
||
align-items: center;
|
||
line-height: 1.4;
|
||
|
||
& > img {
|
||
border: 2px solid #fff;
|
||
border-radius: 50%;
|
||
padding: 4px;
|
||
width: 45px;
|
||
height: 45px;
|
||
}
|
||
|
||
b {
|
||
font-size: 20px;
|
||
}
|
||
|
||
.desc {
|
||
color: #999;
|
||
font-size: 14px;
|
||
}
|
||
}
|
||
|
||
.optIcon {
|
||
box-sizing: border-box;
|
||
height: 40px;
|
||
min-width: 40px;
|
||
background-size: 24px 24px;
|
||
background-repeat: no-repeat;
|
||
background-position: top center;
|
||
cursor: pointer;
|
||
opacity: .6;
|
||
padding-top: 24px;
|
||
|
||
&:after {
|
||
font-size: 12px;
|
||
display: block;
|
||
text-align: center;
|
||
}
|
||
|
||
&:hover {
|
||
opacity: 1;
|
||
}
|
||
|
||
&.clear {
|
||
background-image: url("../assets/clear.svg");
|
||
|
||
&:after {
|
||
content: "清空历史";
|
||
}
|
||
}
|
||
|
||
&.setting {
|
||
background-image: url("../assets/setting.svg");
|
||
|
||
&:after {
|
||
content: "设置";
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</style>
|