mirror of
https://github.com/yangjian102621/geekai.git
synced 2025-09-19 17:56:39 +08:00
新增 Plus 黑色风格模板
This commit is contained in:
parent
b6d8465127
commit
5a6f070f92
@ -57,11 +57,13 @@ func (s *Server) ChatHandle(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger.Info("Receive a message: ", string(message))
|
logger.Info("Receive a message: ", string(message))
|
||||||
|
replyMessage(client, "当前 TOKEN 无效,请使用合法的 TOKEN 登录!", false)
|
||||||
|
replyMessage(client, "", true)
|
||||||
// TODO: 当前只保持当前会话的上下文,部保存用户的所有的聊天历史记录,后期要考虑保存所有的历史记录
|
// TODO: 当前只保持当前会话的上下文,部保存用户的所有的聊天历史记录,后期要考虑保存所有的历史记录
|
||||||
err = s.sendMessage(session, chatRole, string(message), client, false)
|
//err = s.sendMessage(session, chatRole, string(message), client, false)
|
||||||
if err != nil {
|
//if err != nil {
|
||||||
logger.Error(err)
|
// logger.Error(err)
|
||||||
}
|
//}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
BIN
web/src/assets/img/bg_01.jpeg
Normal file
BIN
web/src/assets/img/bg_01.jpeg
Normal file
Binary file not shown.
After Width: | Height: | Size: 824 B |
BIN
web/src/assets/img/bg_02.jpeg
Normal file
BIN
web/src/assets/img/bg_02.jpeg
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
@ -62,6 +62,7 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
|
min-height 20px;
|
||||||
word-break break-word;
|
word-break break-word;
|
||||||
padding: 8px 10px;
|
padding: 8px 10px;
|
||||||
color var(--content-color)
|
color var(--content-color)
|
||||||
|
85
web/src/components/plus/ChatPrompt.vue
Normal file
85
web/src/components/plus/ChatPrompt.vue
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
<template>
|
||||||
|
<div class="chat-line chat-line-right">
|
||||||
|
<div class="chat-item">
|
||||||
|
<div class="content">{{ content }}</div>
|
||||||
|
<div class="triangle"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="chat-icon">
|
||||||
|
<img :src="icon" alt="User"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent} from "vue"
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'ChatPrompt',
|
||||||
|
props: {
|
||||||
|
content: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
type: String,
|
||||||
|
default: 'images/user-icon.png',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="stylus">
|
||||||
|
.chat-line-right {
|
||||||
|
justify-content: flex-end;
|
||||||
|
|
||||||
|
.chat-icon {
|
||||||
|
margin-left 5px;
|
||||||
|
|
||||||
|
img {
|
||||||
|
border-radius 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-item {
|
||||||
|
position: relative;
|
||||||
|
padding: 0 5px 0 0;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.triangle {
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-top: 5px solid transparent;
|
||||||
|
border-bottom: 5px solid transparent;
|
||||||
|
border-left: 5px solid #223A34;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
word-break break-word;
|
||||||
|
padding: 12px 15px;
|
||||||
|
background-color: #223A34;
|
||||||
|
color var(--content-color);
|
||||||
|
font-size: var(--content-font-size);
|
||||||
|
border-radius: 5px;
|
||||||
|
|
||||||
|
p {
|
||||||
|
line-height 1.5
|
||||||
|
}
|
||||||
|
|
||||||
|
p:last-child {
|
||||||
|
margin-bottom: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
p:first-child {
|
||||||
|
margin-top 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
93
web/src/components/plus/ChatReply.vue
Normal file
93
web/src/components/plus/ChatReply.vue
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
<template>
|
||||||
|
<div class="chat-line chat-line-left">
|
||||||
|
<div class="chat-icon">
|
||||||
|
<img :src="icon" alt="ChatGPT">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="chat-item">
|
||||||
|
<div class="triangle"></div>
|
||||||
|
<div class="content" v-html="content"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent} from "vue"
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'ChatReply',
|
||||||
|
props: {
|
||||||
|
content: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
type: String,
|
||||||
|
default: 'images/gpt-icon.png',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="stylus">
|
||||||
|
.chat-line-left {
|
||||||
|
justify-content: flex-start;
|
||||||
|
|
||||||
|
.chat-icon {
|
||||||
|
margin-right 5px;
|
||||||
|
|
||||||
|
img {
|
||||||
|
border-radius 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-item {
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
padding: 0 0 0 5px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.triangle {
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-top: 5px solid transparent;
|
||||||
|
border-bottom: 5px solid transparent;
|
||||||
|
border-right: 5px solid #404042;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
min-height 20px;
|
||||||
|
word-break break-word;
|
||||||
|
padding: 12px 15px;
|
||||||
|
color var(--content-color)
|
||||||
|
background-color: #404042;
|
||||||
|
font-size: var(--content-font-size);
|
||||||
|
border-radius: 5px;
|
||||||
|
|
||||||
|
p {
|
||||||
|
line-height 1.5
|
||||||
|
}
|
||||||
|
|
||||||
|
p:last-child {
|
||||||
|
margin-bottom: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
p:first-child {
|
||||||
|
margin-top 0
|
||||||
|
}
|
||||||
|
|
||||||
|
p > code {
|
||||||
|
color #cc0000
|
||||||
|
background-color #f1f1f1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
@ -3,17 +3,14 @@ import {createApp} from 'vue'
|
|||||||
import ElementPlus from "element-plus"
|
import ElementPlus from "element-plus"
|
||||||
import "element-plus/dist/index.css"
|
import "element-plus/dist/index.css"
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import Chat from './views/Chat.vue'
|
import ChatPlus from "@/views/ChatPlus.vue";
|
||||||
import NotFound from './views/404.vue'
|
import NotFound from './views/404.vue'
|
||||||
import TestPage from './views/Test.vue'
|
import TestPage from './views/Test.vue'
|
||||||
import './utils/prototype'
|
import './utils/prototype'
|
||||||
import {Global} from "@/utils/storage";
|
|
||||||
|
|
||||||
Global['Chat'] = Chat
|
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
name: 'home', path: '/', component: Chat, meta: {
|
name: 'chat-plus', path: '/', component: ChatPlus, meta: {
|
||||||
title: 'ChatGPT-Plus'
|
title: 'ChatGPT-Plus'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -132,7 +132,7 @@ export default defineComponent({
|
|||||||
connectingMessageBox: null, // 保存重连的消息框对象
|
connectingMessageBox: null, // 保存重连的消息框对象
|
||||||
errorMessage: null, // 错误信息提示框
|
errorMessage: null, // 错误信息提示框
|
||||||
socket: null,
|
socket: null,
|
||||||
toolBoxHeight: 61 + 42, // 工具框的高度
|
toolBoxHeight: 61 + 52, // 工具框的高度
|
||||||
inputBoxWidth: window.innerWidth - 20,
|
inputBoxWidth: window.innerWidth - 20,
|
||||||
sending: true,
|
sending: true,
|
||||||
loading: true
|
loading: true
|
||||||
@ -486,6 +486,8 @@ export default defineComponent({
|
|||||||
|
|
||||||
.body {
|
.body {
|
||||||
background-color: rgba(247, 247, 248, 1);
|
background-color: rgba(247, 247, 248, 1);
|
||||||
|
|
||||||
|
background-image url("~@/assets/img/bg_01.jpeg")
|
||||||
display flex;
|
display flex;
|
||||||
//justify-content center;
|
//justify-content center;
|
||||||
align-items flex-start;
|
align-items flex-start;
|
||||||
@ -544,7 +546,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
.input-box {
|
.input-box {
|
||||||
padding 10px;
|
padding 10px;
|
||||||
width 100%;
|
background #ffffff;
|
||||||
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0
|
bottom: 0
|
||||||
|
651
web/src/views/ChatPlus.vue
Normal file
651
web/src/views/ChatPlus.vue
Normal file
@ -0,0 +1,651 @@
|
|||||||
|
<template>
|
||||||
|
<div class="body">
|
||||||
|
<el-row>
|
||||||
|
<div class="chat-head">
|
||||||
|
<el-row class="row-center">
|
||||||
|
<el-col :span="12">
|
||||||
|
<div class="title-box">
|
||||||
|
<el-image :src="logo" class="logo"/>
|
||||||
|
<span>ChatGPT-Plus</span>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<div class="tool-box">
|
||||||
|
|
||||||
|
<el-button type="danger" class="clear-history" size="small" circle @click="clearChatHistory">
|
||||||
|
<el-icon>
|
||||||
|
<Delete/>
|
||||||
|
</el-icon>
|
||||||
|
</el-button>
|
||||||
|
|
||||||
|
<el-button type="info" size="small" class="config" ref="send-btn" circle
|
||||||
|
@click="showConnectDialog = true">
|
||||||
|
<el-icon>
|
||||||
|
<Tools/>
|
||||||
|
</el-icon>
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
</el-row>
|
||||||
|
<el-row>
|
||||||
|
<div class="left-box">
|
||||||
|
<div class="grid-content">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="right-box" :style="{height: mainWinHeight+'px'}">
|
||||||
|
<div v-loading="loading">
|
||||||
|
<div id="container">
|
||||||
|
<div class="chat-box" id="chat-box" :style="{height: chatBoxHeight+'px'}">
|
||||||
|
<div v-for="chat in chatData" :key="chat.id">
|
||||||
|
<chat-prompt
|
||||||
|
v-if="chat.type==='prompt'"
|
||||||
|
:icon="chat.icon"
|
||||||
|
:content="chat.content"/>
|
||||||
|
<chat-reply v-else-if="chat.type==='reply'"
|
||||||
|
:icon="chat.icon"
|
||||||
|
:content="chat.content"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div><!-- end chat box -->
|
||||||
|
|
||||||
|
<el-row class="chat-tool-box">
|
||||||
|
<el-tooltip
|
||||||
|
class="box-item"
|
||||||
|
effect="dark"
|
||||||
|
content="进入AI绘画模式"
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
|
<el-icon @click="drawImage">
|
||||||
|
<Picture/>
|
||||||
|
</el-icon>
|
||||||
|
</el-tooltip>
|
||||||
|
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<div class="input-box">
|
||||||
|
<div class="input-container">
|
||||||
|
<el-input
|
||||||
|
ref="text-input"
|
||||||
|
v-model="inputValue"
|
||||||
|
:autosize="{ minRows: 5, maxRows: 10 }"
|
||||||
|
v-on:keydown="inputKeyDown"
|
||||||
|
v-on:focus="focus"
|
||||||
|
autofocus
|
||||||
|
type="textarea"
|
||||||
|
placeholder="先聊五毛钱吧..."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div><!-- end input box -->
|
||||||
|
|
||||||
|
</div><!-- end container -->
|
||||||
|
</div><!-- end loading -->
|
||||||
|
</div>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<config-dialog v-model:show="showConnectDialog"></config-dialog>
|
||||||
|
|
||||||
|
<div class="token-dialog">
|
||||||
|
<el-dialog
|
||||||
|
v-model="showLoginDialog"
|
||||||
|
:show-close="false"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
title="请输入口令继续访问"
|
||||||
|
>
|
||||||
|
<el-row>
|
||||||
|
<el-input v-model="token" placeholder="在此输入口令" @keyup="loginInputKeyup">
|
||||||
|
<template #prefix>
|
||||||
|
<el-icon class="el-input__icon">
|
||||||
|
<Lock/>
|
||||||
|
</el-icon>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
<el-button type="primary" @click="submitToken">提交</el-button>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-row class="row-center">
|
||||||
|
<p>打开微信扫下面二维码免费领取口令</p>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-row class="row-center">
|
||||||
|
<el-image src="images/wx.png" fit="cover"/>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
</el-dialog>
|
||||||
|
</div> <!--end token dialog-->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {defineComponent, nextTick} from 'vue'
|
||||||
|
import ChatPrompt from "@/components/plus/ChatPrompt.vue";
|
||||||
|
import ChatReply from "@/components/plus/ChatReply.vue";
|
||||||
|
import {randString} from "@/utils/libs";
|
||||||
|
import {ElMessage, ElMessageBox} from 'element-plus'
|
||||||
|
import {Tools, Lock, Delete, Picture} from '@element-plus/icons-vue'
|
||||||
|
import ConfigDialog from '@/components/ConfigDialog.vue'
|
||||||
|
import {httpPost, httpGet} from "@/utils/http";
|
||||||
|
import {getSessionId, setSessionId} from "@/utils/storage";
|
||||||
|
import hl from 'highlight.js'
|
||||||
|
import 'highlight.js/styles/a11y-dark.css'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: "ChatPlus",
|
||||||
|
components: {ChatPrompt, ChatReply, Tools, Lock, Delete, Picture, ConfigDialog},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
title: 'ChatGPT 控制台',
|
||||||
|
logo: 'images/logo.png',
|
||||||
|
chatData: [],
|
||||||
|
chatRoles: [],
|
||||||
|
role: 'gpt',
|
||||||
|
inputValue: '', // 聊天内容
|
||||||
|
showConnectDialog: false,
|
||||||
|
showLoginDialog: false,
|
||||||
|
token: '', // 会话 token
|
||||||
|
replyIcon: 'images/avatar/gpt.png', // 回复信息的头像
|
||||||
|
|
||||||
|
lineBuffer: '', // 输出缓冲行
|
||||||
|
connectingMessageBox: null, // 保存重连的消息框对象
|
||||||
|
errorMessage: null, // 错误信息提示框
|
||||||
|
socket: null,
|
||||||
|
mainWinHeight: 0, // 主窗口高度
|
||||||
|
chatBoxHeight: 0, // 聊天内容框高度
|
||||||
|
sending: true,
|
||||||
|
loading: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted: function () {
|
||||||
|
nextTick(() => {
|
||||||
|
this.resizeElement();
|
||||||
|
})
|
||||||
|
window.addEventListener("resize", () => {
|
||||||
|
this.resizeElement();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.connect();
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
resizeElement: function () {
|
||||||
|
this.chatBoxHeight = window.innerHeight - 61 - 115 - 38;
|
||||||
|
this.mainWinHeight = window.innerHeight - 61;
|
||||||
|
},
|
||||||
|
// 创建 socket 会话连接
|
||||||
|
connect: function () {
|
||||||
|
// 初始化 WebSocket 对象
|
||||||
|
const sessionId = getSessionId();
|
||||||
|
const socket = new WebSocket(process.env.VUE_APP_WS_HOST + `/api/chat?sessionId=${sessionId}&role=${this.role}`);
|
||||||
|
socket.addEventListener('open', () => {
|
||||||
|
// 获取聊天角色
|
||||||
|
if (this.chatRoles.length === 0) {
|
||||||
|
httpGet("/api/config/chat-roles/get").then((res) => {
|
||||||
|
// ElMessage.success('创建会话成功!');
|
||||||
|
this.chatRoles = res.data;
|
||||||
|
this.loading = false
|
||||||
|
}).catch(() => {
|
||||||
|
ElMessage.error("获取聊天角色失败");
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sending = false; // 允许用户发送消息
|
||||||
|
if (this.errorMessage !== null) {
|
||||||
|
this.errorMessage.close(); // 关闭错误提示信息
|
||||||
|
}
|
||||||
|
// 加载聊天记录
|
||||||
|
this.fetchChatHistory();
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.addEventListener('message', event => {
|
||||||
|
if (event.data instanceof Blob) {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.readAsText(event.data, "UTF-8");
|
||||||
|
reader.onload = () => {
|
||||||
|
const data = JSON.parse(String(reader.result));
|
||||||
|
if (data.type === 'start') {
|
||||||
|
this.chatData.push({
|
||||||
|
type: "reply",
|
||||||
|
id: randString(32),
|
||||||
|
icon: this.replyIcon,
|
||||||
|
content: "",
|
||||||
|
cursor: true
|
||||||
|
});
|
||||||
|
} else if (data.type === 'end') {
|
||||||
|
this.sending = false;
|
||||||
|
this.lineBuffer = ''; // 清空缓冲
|
||||||
|
} else {
|
||||||
|
this.lineBuffer += data.content;
|
||||||
|
let md = require('markdown-it')();
|
||||||
|
this.chatData[this.chatData.length - 1]["content"] = md.render(this.lineBuffer);
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
hl.configure({ignoreUnescapedHTML: true})
|
||||||
|
const lines = document.querySelectorAll('.chat-line');
|
||||||
|
const blocks = lines[lines.length - 1].querySelectorAll('pre code');
|
||||||
|
blocks.forEach((block) => {
|
||||||
|
hl.highlightElement(block)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 将聊天框的滚动条滑动到最底部
|
||||||
|
nextTick(() => {
|
||||||
|
document.getElementById('container').scrollTo(0, document.getElementById('container').scrollHeight)
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.addEventListener('close', () => {
|
||||||
|
// 停止送消息
|
||||||
|
this.sending = true;
|
||||||
|
this.checkSession();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.socket = socket;
|
||||||
|
},
|
||||||
|
|
||||||
|
checkSession: function () {
|
||||||
|
// 检查会话
|
||||||
|
httpGet("/api/session/get").then(() => {
|
||||||
|
// 自动重新连接
|
||||||
|
this.connect();
|
||||||
|
}).catch((res) => {
|
||||||
|
if (res.code === 400) {
|
||||||
|
this.showLoginDialog = true;
|
||||||
|
if (this.errorMessage !== null) {
|
||||||
|
this.errorMessage.close();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (this.errorMessage === null) {
|
||||||
|
this.errorMessage = ElMessage({
|
||||||
|
message: '当前无法连接服务器,可检查网络设置是否正常',
|
||||||
|
type: 'error',
|
||||||
|
duration: 0,
|
||||||
|
showClose: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 3 秒后继续重连
|
||||||
|
setTimeout(() => this.checkSession(), 3000)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
drawImage: function () {
|
||||||
|
ElMessage({
|
||||||
|
message: '客观别急,AI 绘画服服务正在紧锣密鼓搭建中...',
|
||||||
|
type: 'info',
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 更换角色
|
||||||
|
changeRole: function () {
|
||||||
|
this.loading = true
|
||||||
|
// 清空对话列表
|
||||||
|
this.chatData = [];
|
||||||
|
this.connect();
|
||||||
|
for (const key in this.chatRoles) {
|
||||||
|
if (this.chatRoles[key].key === this.role) {
|
||||||
|
this.replyIcon = this.chatRoles[key].icon;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 从后端获取聊天历史记录
|
||||||
|
fetchChatHistory: function () {
|
||||||
|
httpPost("/api/chat/history", {role: this.role}).then((res) => {
|
||||||
|
if (this.chatData.length > 0) { // 如果已经有聊天记录了,就不追加了
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = res.data
|
||||||
|
const md = require('markdown-it')();
|
||||||
|
for (let i = 0; i < data.length; i++) {
|
||||||
|
if (data[i].type === "prompt") {
|
||||||
|
this.chatData.push(data[i]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
data[i].content = md.render(data[i].content);
|
||||||
|
this.chatData.push(data[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
hl.configure({ignoreUnescapedHTML: true})
|
||||||
|
const blocks = document.querySelector("#chat-box").querySelectorAll('pre code');
|
||||||
|
blocks.forEach((block) => {
|
||||||
|
hl.highlightElement(block)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}).catch(() => {
|
||||||
|
// console.error(e.message)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
inputKeyDown: function (e) {
|
||||||
|
if (e.keyCode === 13) {
|
||||||
|
if (this.sending) {
|
||||||
|
e.preventDefault();
|
||||||
|
} else {
|
||||||
|
this.sendMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 发送消息
|
||||||
|
sendMessage: function (e) {
|
||||||
|
// 强制按钮失去焦点
|
||||||
|
if (e) {
|
||||||
|
let target = e.target;
|
||||||
|
if (target.nodeName === "SPAN") {
|
||||||
|
target = e.target.parentNode;
|
||||||
|
}
|
||||||
|
target.blur();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.sending || this.inputValue.trim().length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 追加消息
|
||||||
|
this.chatData.push({
|
||||||
|
type: "prompt",
|
||||||
|
id: randString(32),
|
||||||
|
icon: 'images/avatar/user.png',
|
||||||
|
content: this.inputValue
|
||||||
|
});
|
||||||
|
|
||||||
|
this.sending = true;
|
||||||
|
this.socket.send(this.inputValue);
|
||||||
|
this.$refs["text-input"].blur();
|
||||||
|
this.inputValue = '';
|
||||||
|
// 等待 textarea 重新调整尺寸之后再自动获取焦点
|
||||||
|
setTimeout(() => this.$refs["text-input"].focus(), 100);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取焦点
|
||||||
|
focus: function () {
|
||||||
|
setTimeout(function () {
|
||||||
|
document.getElementById('chat-box').scrollTo(0, document.getElementById('chat-box').scrollHeight)
|
||||||
|
}, 200)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 提交 Token
|
||||||
|
submitToken: function () {
|
||||||
|
this.showLoginDialog = false;
|
||||||
|
this.loading = true
|
||||||
|
|
||||||
|
// 获取会话
|
||||||
|
httpPost("/api/login", {
|
||||||
|
token: this.token
|
||||||
|
}).then((res) => {
|
||||||
|
setSessionId(res.data)
|
||||||
|
this.connect();
|
||||||
|
this.loading = false;
|
||||||
|
}).catch(() => {
|
||||||
|
ElMessage.error("口令错误");
|
||||||
|
this.token = '';
|
||||||
|
this.showLoginDialog = true;
|
||||||
|
this.loading = false;
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 登录输入框输入事件处理
|
||||||
|
loginInputKeyup: function (e) {
|
||||||
|
if (e.keyCode === 13) {
|
||||||
|
this.submitToken();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 清空聊天记录
|
||||||
|
clearChatHistory: function () {
|
||||||
|
ElMessageBox.confirm(
|
||||||
|
'确认要清空当前角色聊天历史记录吗?<br/>此操作不可以撤销!',
|
||||||
|
'操作提示:',
|
||||||
|
{
|
||||||
|
confirmButtonText: '确认',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning',
|
||||||
|
dangerouslyUseHTMLString: true,
|
||||||
|
showClose: true,
|
||||||
|
closeOnClickModal: false,
|
||||||
|
center: true,
|
||||||
|
}
|
||||||
|
).then(() => {
|
||||||
|
httpPost("/api/chat/history/clear", {role: this.role}).then(() => {
|
||||||
|
ElMessage.success("当前角色会话已清空");
|
||||||
|
this.chatData = [];
|
||||||
|
}).catch(() => {
|
||||||
|
ElMessage.error("删除失败")
|
||||||
|
})
|
||||||
|
}).catch(() => {
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="stylus">
|
||||||
|
#app {
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.body {
|
||||||
|
height 100%;
|
||||||
|
|
||||||
|
.chat-head {
|
||||||
|
width 100%;
|
||||||
|
height 60px;
|
||||||
|
background-color: #28292A
|
||||||
|
border-bottom 1px solid #4f4f4f;
|
||||||
|
|
||||||
|
.title-box {
|
||||||
|
padding-top 6px;
|
||||||
|
display flex
|
||||||
|
color #ffffff;
|
||||||
|
font-size 20px;
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
background-color #ffffff
|
||||||
|
border-radius 50%;
|
||||||
|
width 45px;
|
||||||
|
height 45px;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
padding-top: 12px;
|
||||||
|
padding-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-box {
|
||||||
|
padding-top 16px;
|
||||||
|
padding-right 20px;
|
||||||
|
display flex;
|
||||||
|
justify-content flex-end;
|
||||||
|
align-items center;
|
||||||
|
|
||||||
|
.el-image {
|
||||||
|
margin-right 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clear-history, .config {
|
||||||
|
margin-left 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-row {
|
||||||
|
overflow hidden;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.left-box {
|
||||||
|
display flex
|
||||||
|
min-width 220px;
|
||||||
|
max-width 250px;
|
||||||
|
background-color: #28292A
|
||||||
|
border-top: 1px solid #2F3032
|
||||||
|
border-right: 1px solid #2F3032
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-box {
|
||||||
|
min-width: 0;
|
||||||
|
flex: 1;
|
||||||
|
background-color #232425
|
||||||
|
border-left 1px solid #4f4f4f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#container {
|
||||||
|
overflow hidden;
|
||||||
|
width 100%;
|
||||||
|
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-box {
|
||||||
|
overflow-y: scroll;
|
||||||
|
border-bottom 1px solid #4f4f4f
|
||||||
|
|
||||||
|
// 变量定义
|
||||||
|
--content-font-size: 16px;
|
||||||
|
--content-color: #c1c1c1;
|
||||||
|
|
||||||
|
font-family 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
.chat-line {
|
||||||
|
padding 10px 5px;
|
||||||
|
font-size 14px;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
|
||||||
|
.chat-icon {
|
||||||
|
img {
|
||||||
|
width 45px;
|
||||||
|
height 45px;
|
||||||
|
border 1px solid #666;
|
||||||
|
border-radius 50%;
|
||||||
|
padding 1px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-tool-box {
|
||||||
|
padding 10px;
|
||||||
|
border-top: 1px solid #2F3032
|
||||||
|
|
||||||
|
.el-icon svg {
|
||||||
|
color #cccccc
|
||||||
|
width 1em;
|
||||||
|
background-color #232425
|
||||||
|
cursor pointer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-box {
|
||||||
|
background-color #232425
|
||||||
|
display: flex;
|
||||||
|
justify-content: start;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.input-container {
|
||||||
|
width: 100%
|
||||||
|
margin: 0;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow: 0 2px 15px rgba(0, 0, 0, 0.1);
|
||||||
|
background-color #232425
|
||||||
|
padding: 5px 10px;
|
||||||
|
|
||||||
|
.el-textarea__inner {
|
||||||
|
box-shadow: none
|
||||||
|
padding 5px 0
|
||||||
|
background-color #232425
|
||||||
|
color #B5B7B8
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-textarea__inner::-webkit-scrollbar {
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-container {
|
||||||
|
margin-left 10px;
|
||||||
|
|
||||||
|
.el-row {
|
||||||
|
flex-wrap nowrap
|
||||||
|
//width 106px;
|
||||||
|
align-items center
|
||||||
|
}
|
||||||
|
|
||||||
|
.send {
|
||||||
|
width 60px;
|
||||||
|
height 40px;
|
||||||
|
background-color: var(--el-color-success)
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-disabled {
|
||||||
|
background-color: var(--el-button-disabled-bg-color);
|
||||||
|
border-color: var(--el-button-disabled-border-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#container::-webkit-scrollbar {
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-center {
|
||||||
|
justify-content center
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-message-box {
|
||||||
|
width 90%;
|
||||||
|
max-width 420px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-message {
|
||||||
|
min-width: 100px;
|
||||||
|
max-width 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token-dialog {
|
||||||
|
.el-dialog {
|
||||||
|
--el-dialog-width 90%;
|
||||||
|
max-width 400px;
|
||||||
|
|
||||||
|
.el-dialog__body {
|
||||||
|
padding 10px 10px 20px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-row {
|
||||||
|
flex-wrap nowrap
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin-left 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
Loading…
Reference in New Issue
Block a user