add reply content to clipboard function is ready

This commit is contained in:
RockYang 2023-04-12 18:32:01 +08:00
parent a88f55372c
commit 676457f350
7 changed files with 286 additions and 75 deletions

View File

@ -61,15 +61,19 @@ func (s *Server) ChatHandle(c *gin.Context) {
return return
} }
logger.Info("Receive a message: ", string(message)) logger.Info("Receive a message: ", string(message))
//replyMessage(client, "当前 TOKEN 无效,请使用合法的 TOKEN 登录!", false) replyMessage(client, "当前 TOKEN 无效,请使用合法的 TOKEN 登录!", false)
//replyMessage(client, "![](images/wx.png)", true) replyMessage(client, "![](images/wx.png)", true)
ctx, cancel := context.WithCancel(context.Background()) //ctx, cancel := context.WithCancel(context.Background())
s.ReqCancelFunc[sessionId] = cancel //s.ReqCancelFunc[sessionId] = cancel
// 回复消息 //// 回复消息
err = s.sendMessage(ctx, session, chatRole, string(message), client, false) //err = s.sendMessage(ctx, session, chatRole, string(message), client, false)
if err != nil { //if err != nil {
logger.Error(err) // logger.Error(err)
} //} else {
// replyChunkMessage(client, types.WsMessage{Type: types.WsEnd, IsHelloMsg: false})
// logger.Info("回答完毕: " + string(message))
//}
} }
}() }()
} }
@ -232,7 +236,6 @@ func (s *Server) sendMessage(ctx context.Context, session types.ChatSession, rol
err = json.Unmarshal([]byte(line[6:]), &responseBody) err = json.Unmarshal([]byte(line[6:]), &responseBody)
if err != nil { // 数据解析出错 if err != nil { // 数据解析出错
logger.Error(err, line) logger.Error(err, line)
replyChunkMessage(ws, types.WsMessage{Type: types.WsEnd, IsHelloMsg: false})
replyMessage(ws, ErrorMsg, false) replyMessage(ws, ErrorMsg, false)
replyMessage(ws, "![](images/wx.png)", true) replyMessage(ws, "![](images/wx.png)", true)
break break
@ -246,9 +249,8 @@ func (s *Server) sendMessage(ctx context.Context, session types.ChatSession, rol
message.Role = responseBody.Choices[0].Delta.Role message.Role = responseBody.Choices[0].Delta.Role
replyChunkMessage(ws, types.WsMessage{Type: types.WsStart, IsHelloMsg: false}) replyChunkMessage(ws, types.WsMessage{Type: types.WsStart, IsHelloMsg: false})
continue continue
} else if responseBody.Choices[0].FinishReason != "" { // 输出完成或者输出中断了 } else if responseBody.Choices[0].FinishReason != "" {
replyChunkMessage(ws, types.WsMessage{Type: types.WsEnd, IsHelloMsg: false}) break // 输出完成或者输出中断了
break
} else { } else {
content := responseBody.Choices[0].Delta.Content content := responseBody.Choices[0].Delta.Content
contents = append(contents, content) contents = append(contents, content)
@ -262,9 +264,7 @@ func (s *Server) sendMessage(ctx context.Context, session types.ChatSession, rol
// 监控取消信号 // 监控取消信号
select { select {
case <-ctx.Done(): case <-ctx.Done():
// 结束输出 _ = response.Body.Close() // 关闭响应流
replyChunkMessage(ws, types.WsMessage{Type: types.WsEnd, IsHelloMsg: false})
_ = response.Body.Close()
return errors.New("用户取消了请求:" + prompt) return errors.New("用户取消了请求:" + prompt)
default: default:
continue continue
@ -306,7 +306,7 @@ func (s *Server) sendMessage(ctx context.Context, session types.ChatSession, rol
} }
} }
return err return nil
} }
// 随机获取一个 API Key如果请求失败则更换 API Key 重试 // 随机获取一个 API Key如果请求失败则更换 API Key 重试

71
web/package-lock.json generated
View File

@ -1,15 +1,16 @@
{ {
"name": "yycloud-webssh", "name": "chatgpt-plus",
"version": "0.1.0", "version": "0.1.0",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "yycloud-webssh", "name": "chatgpt-plus",
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@element-plus/icons-vue": "^2.1.0", "@element-plus/icons-vue": "^2.1.0",
"axios": "^0.27.2", "axios": "^0.27.2",
"clipboard": "^2.0.11",
"core-js": "^3.8.3", "core-js": "^3.8.3",
"element-plus": "^2.1.11", "element-plus": "^2.1.11",
"good-storage": "^1.1.1", "good-storage": "^1.1.1",
@ -3994,6 +3995,16 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/clipboard": {
"version": "2.0.11",
"resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.11.tgz",
"integrity": "sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==",
"dependencies": {
"good-listener": "^1.2.2",
"select": "^1.1.2",
"tiny-emitter": "^2.0.0"
}
},
"node_modules/clipboardy": { "node_modules/clipboardy": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmmirror.com/clipboardy/-/clipboardy-2.3.0.tgz", "resolved": "https://registry.npmmirror.com/clipboardy/-/clipboardy-2.3.0.tgz",
@ -4832,6 +4843,11 @@
"node": ">=0.4.0" "node": ">=0.4.0"
} }
}, },
"node_modules/delegate": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz",
"integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw=="
},
"node_modules/depd": { "node_modules/depd": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmmirror.com/depd/-/depd-1.1.2.tgz", "resolved": "https://registry.npmmirror.com/depd/-/depd-1.1.2.tgz",
@ -6157,6 +6173,14 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/good-listener": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz",
"integrity": "sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==",
"dependencies": {
"delegate": "^3.1.2"
}
},
"node_modules/good-storage": { "node_modules/good-storage": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmmirror.com/good-storage/-/good-storage-1.1.1.tgz", "resolved": "https://registry.npmmirror.com/good-storage/-/good-storage-1.1.1.tgz",
@ -9194,6 +9218,11 @@
"node": ">= 8.9.0" "node": ">= 8.9.0"
} }
}, },
"node_modules/select": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
"integrity": "sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA=="
},
"node_modules/select-hose": { "node_modules/select-hose": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmmirror.com/select-hose/-/select-hose-2.0.0.tgz", "resolved": "https://registry.npmmirror.com/select-hose/-/select-hose-2.0.0.tgz",
@ -10050,6 +10079,11 @@
"integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==",
"dev": true "dev": true
}, },
"node_modules/tiny-emitter": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
"integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q=="
},
"node_modules/to-fast-properties": { "node_modules/to-fast-properties": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmmirror.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz", "resolved": "https://registry.npmmirror.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
@ -14236,6 +14270,16 @@
"integrity": "sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==", "integrity": "sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==",
"dev": true "dev": true
}, },
"clipboard": {
"version": "2.0.11",
"resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.11.tgz",
"integrity": "sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==",
"requires": {
"good-listener": "^1.2.2",
"select": "^1.1.2",
"tiny-emitter": "^2.0.0"
}
},
"clipboardy": { "clipboardy": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmmirror.com/clipboardy/-/clipboardy-2.3.0.tgz", "resolved": "https://registry.npmmirror.com/clipboardy/-/clipboardy-2.3.0.tgz",
@ -14896,6 +14940,11 @@
"resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
}, },
"delegate": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz",
"integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw=="
},
"depd": { "depd": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmmirror.com/depd/-/depd-1.1.2.tgz", "resolved": "https://registry.npmmirror.com/depd/-/depd-1.1.2.tgz",
@ -15959,6 +16008,14 @@
"slash": "^3.0.0" "slash": "^3.0.0"
} }
}, },
"good-listener": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz",
"integrity": "sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==",
"requires": {
"delegate": "^3.1.2"
}
},
"good-storage": { "good-storage": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmmirror.com/good-storage/-/good-storage-1.1.1.tgz", "resolved": "https://registry.npmmirror.com/good-storage/-/good-storage-1.1.1.tgz",
@ -18302,6 +18359,11 @@
"ajv-keywords": "^3.5.2" "ajv-keywords": "^3.5.2"
} }
}, },
"select": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
"integrity": "sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA=="
},
"select-hose": { "select-hose": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmmirror.com/select-hose/-/select-hose-2.0.0.tgz", "resolved": "https://registry.npmmirror.com/select-hose/-/select-hose-2.0.0.tgz",
@ -19010,6 +19072,11 @@
"integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==",
"dev": true "dev": true
}, },
"tiny-emitter": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
"integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q=="
},
"to-fast-properties": { "to-fast-properties": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmmirror.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz", "resolved": "https://registry.npmmirror.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz",

View File

@ -11,6 +11,7 @@
"dependencies": { "dependencies": {
"@element-plus/icons-vue": "^2.1.0", "@element-plus/icons-vue": "^2.1.0",
"axios": "^0.27.2", "axios": "^0.27.2",
"clipboard": "^2.0.11",
"core-js": "^3.8.3", "core-js": "^3.8.3",
"element-plus": "^2.1.11", "element-plus": "^2.1.11",
"good-storage": "^1.1.1", "good-storage": "^1.1.1",

View File

@ -6,13 +6,14 @@
<div class="chat-item"> <div class="chat-item">
<div class="triangle"></div> <div class="triangle"></div>
<div class="content" v-html="content"></div> <div class="content reply-content" :data-clipboard-text="orgContent" v-html="content"></div>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import {defineComponent} from "vue" import {defineComponent} from "vue"
import {randString} from "@/utils/libs";
export default defineComponent({ export default defineComponent({
name: 'ChatReply', name: 'ChatReply',
@ -21,14 +22,22 @@ export default defineComponent({
type: String, type: String,
default: '', default: '',
}, },
orgContent: {
type: String,
default: '',
},
icon: { icon: {
type: String, type: String,
default: 'images/gpt-icon.png', default: 'images/gpt-icon.png',
} }
}, },
data() { data() {
return {} return {
id: randString(32),
clipboard: null,
}
}, },
}) })
</script> </script>

View File

@ -6,7 +6,7 @@
<div class="chat-item"> <div class="chat-item">
<div class="triangle"></div> <div class="triangle"></div>
<div class="content" v-html="content"></div> <div class="content reply-content" :data-clipboard-text="orgContent" v-html="content"></div>
</div> </div>
</div> </div>
</template> </template>
@ -21,6 +21,10 @@ export default defineComponent({
type: String, type: String,
default: '', default: '',
}, },
orgContent: {
type: String,
default: '',
},
icon: { icon: {
type: String, type: String,
default: 'images/gpt-icon.png', default: 'images/gpt-icon.png',

View File

@ -12,7 +12,12 @@
:key="item.key" :key="item.key"
:label="item.name" :label="item.name"
:value="item.key" :value="item.key"
/> >
<div class="role-option">
<el-image :src="item.icon"></el-image>
<span>{{ item.name }}</span>
</div>
</el-option>
</el-select> </el-select>
<el-button type="danger" class="clear-history" size="small" circle @click="clearChatHistory"> <el-button type="danger" class="clear-history" size="small" circle @click="clearChatHistory">
@ -36,29 +41,50 @@
:content="chat.content"/> :content="chat.content"/>
<chat-reply v-else-if="chat.type==='reply'" <chat-reply v-else-if="chat.type==='reply'"
:icon="chat.icon" :icon="chat.icon"
:org-content="chat.orgContent"
:content="chat.content"/> :content="chat.content"/>
</div> </div>
</div><!-- end chat box --> </div><!-- end chat box -->
<div class="input-box" :style="{width: inputBoxWidth+'px'}"> <div class="input-box" :style="{width: inputBoxWidth+'px'}">
<div class="input-container"> <div class="re-generate">
<el-input <div class="btn-box">
ref="text-input" <el-button type="info" v-if="showStopGenerate" @click="stopGenerate" plain>
v-model="inputValue" <el-icon>
:autosize="{ minRows: 1, maxRows: 10 }" <VideoPause/>
v-on:keydown="inputKeyDown" </el-icon>
v-on:focus="focus" 停止生成
autofocus </el-button>
type="textarea"
placeholder="开始你的提问" <el-button type="primary" v-if="showReGenerate" @click="reGenerate" plain>
/> <el-icon>
<RefreshRight/>
</el-icon>
重新生成
</el-button>
</div>
</div> </div>
<div class="btn-container"> <div class="input-wrapper">
<el-row> <div class="input-container">
<el-button type="success" class="send" :disabled="sending" v-on:click="sendMessage">发送</el-button> <el-input
</el-row> ref="text-input"
v-model="inputValue"
:autosize="{ minRows: 1, maxRows: 10 }"
v-on:keydown="inputKeyDown"
v-on:focus="focus"
autofocus
type="textarea"
placeholder="开始你的提问"
/>
</div>
<div class="btn-container">
<el-row>
<el-button type="success" class="send" :disabled="sending" v-on:click="sendMessage">发送</el-button>
</el-row>
</div>
</div> </div>
</div><!-- end input box --> </div><!-- end input box -->
@ -75,7 +101,7 @@
title="请输入口令继续访问" title="请输入口令继续访问"
> >
<el-row> <el-row>
<el-input v-model="token" placeholder="在此输入口令" @keyup="loginInputKeyup"> <el-input v-model="token" placeholder="在此输入口令" type="password" @keyup="loginInputKeyup">
<template #prefix> <template #prefix>
<el-icon class="el-input__icon"> <el-icon class="el-input__icon">
<Lock/> <Lock/>
@ -104,16 +130,17 @@ import ChatPrompt from "@/components/ChatPrompt.vue";
import ChatReply from "@/components/ChatReply.vue"; import ChatReply from "@/components/ChatReply.vue";
import {isMobile, randString} from "@/utils/libs"; import {isMobile, randString} from "@/utils/libs";
import {ElMessage, ElMessageBox} from 'element-plus' import {ElMessage, ElMessageBox} from 'element-plus'
import {Tools, Lock, Delete} from '@element-plus/icons-vue' import {Tools, Lock, Delete, VideoPause, RefreshRight} from '@element-plus/icons-vue'
import ConfigDialog from '@/components/ConfigDialog.vue' import ConfigDialog from '@/components/ConfigDialog.vue'
import {httpPost, httpGet} from "@/utils/http"; import {httpPost, httpGet} from "@/utils/http";
import {getSessionId, getUserInfo, setLoginUser} from "@/utils/storage"; import {getSessionId, getUserInfo, setLoginUser} from "@/utils/storage";
import hl from 'highlight.js' import hl from 'highlight.js'
import 'highlight.js/styles/a11y-dark.css' import 'highlight.js/styles/a11y-dark.css'
import Clipboard from "clipboard";
export default defineComponent({ export default defineComponent({
name: "XChat", name: "XChat",
components: {ChatPrompt, ChatReply, Tools, Lock, Delete, ConfigDialog}, components: {RefreshRight, VideoPause, ChatPrompt, ChatReply, Tools, Lock, Delete, ConfigDialog},
data() { data() {
return { return {
title: 'ChatGPT 控制台', title: 'ChatGPT 控制台',
@ -128,6 +155,11 @@ export default defineComponent({
userInfo: {}, userInfo: {},
showLoginDialog: false, showLoginDialog: false,
showStopGenerate: false,
showReGenerate: false,
canReGenerate: false, //
previousText: '', //
token: '', // token token: '', // token
replyIcon: 'images/avatar/gpt.png', // replyIcon: 'images/avatar/gpt.png', //
@ -148,6 +180,15 @@ export default defineComponent({
return; return;
} }
const clipboard = new Clipboard('.reply-content');
clipboard.on('success', () => {
ElMessage.success('复制成功!');
})
clipboard.on('error', () => {
ElMessage.error('复制失败!');
})
nextTick(() => { nextTick(() => {
this.chatBoxHeight = window.innerHeight - this.toolBoxHeight; this.chatBoxHeight = window.innerHeight - this.toolBoxHeight;
ElMessage.warning("强烈建议使用PC浏览器访问获的更好的聊天体验") ElMessage.warning("强烈建议使用PC浏览器访问获的更好的聊天体验")
@ -207,13 +248,21 @@ export default defineComponent({
content: "", content: "",
cursor: true cursor: true
}); });
if (data['is_hello_msg'] !== true) {
this.canReGenerate = true;
}
} else if (data.type === 'end') { } else if (data.type === 'end') {
this.sending = false; this.sending = false;
if (data['is_hello_msg'] !== true) {
this.showReGenerate = true;
}
this.showStopGenerate = false;
this.lineBuffer = ''; // this.lineBuffer = ''; //
} else { } else {
this.lineBuffer += data.content; this.lineBuffer += data.content;
this.chatData[this.chatData.length - 1]['orgContent'] = this.lineBuffer;
let md = require('markdown-it')(); let md = require('markdown-it')();
this.chatData[this.chatData.length - 1]["content"] = md.render(this.lineBuffer); this.chatData[this.chatData.length - 1]['content'] = md.render(this.lineBuffer);
nextTick(() => { nextTick(() => {
hl.configure({ignoreUnescapedHTML: true}) hl.configure({ignoreUnescapedHTML: true})
@ -296,7 +345,7 @@ export default defineComponent({
this.chatData.push(data[i]); this.chatData.push(data[i]);
continue; continue;
} }
data[i].orgContent = data[i].content;
data[i].content = md.render(data[i].content); data[i].content = md.render(data[i].content);
this.chatData.push(data[i]); this.chatData.push(data[i]);
} }
@ -347,8 +396,11 @@ export default defineComponent({
}); });
this.sending = true; this.sending = true;
this.showStopGenerate = true;
this.showReGenerate = false;
this.socket.send(this.inputValue); this.socket.send(this.inputValue);
this.$refs["text-input"].blur(); this.$refs["text-input"].blur();
this.previousText = this.inputValue;
this.inputValue = ''; this.inputValue = '';
// textarea // textarea
setTimeout(() => this.$refs["text-input"].focus(), 100); setTimeout(() => this.$refs["text-input"].focus(), 100);
@ -412,6 +464,26 @@ export default defineComponent({
}) })
}).catch(() => { }).catch(() => {
}) })
},
//
stopGenerate: function () {
this.showStopGenerate = false;
httpPost("/api/chat/stop").then(() => {
console.log("stopped generate.")
this.sending = false;
if (this.canReGenerate) {
this.showReGenerate = true;
}
})
},
//
reGenerate: function () {
this.sending = true;
this.showStopGenerate = true;
this.showReGenerate = false;
this.socket.send('重新生成上述问题的答案:' + this.previousText);
} }
}, },
@ -485,54 +557,77 @@ export default defineComponent({
.input-box { .input-box {
padding 10px; padding 10px;
background #ffffff; background #ffffff;
position: absolute; position: absolute;
bottom: 0 bottom: 0
display: flex; display: flex;
justify-content: start; justify-content: start;
align-items: center; align-items: center;
flex-flow: column;
.input-container { .re-generate {
overflow hidden position relative
width 100% display flex
margin: 0; justify-content center
border: none;
border-radius: 6px;
box-shadow: 0 2px 15px rgba(0, 0, 0, 0.1);
background-color: rgba(255, 255, 255, 1);
padding: 5px 10px;
.el-textarea__inner { .btn-box {
box-shadow: none position absolute
padding 5px 0 bottom 20px
}
.el-textarea__inner::-webkit-scrollbar { .el-icon {
width: 0; margin-right 5px;
height: 0; }
} }
} }
.btn-container { .input-wrapper {
margin-left 10px; width 100%;
display flex;
.el-row { .input-container {
flex-wrap nowrap overflow hidden
//width 106px; width 100%
align-items center margin: 0;
border: none;
border-radius: 6px;
box-shadow: 0 2px 15px rgba(0, 0, 0, 0.1);
background-color: rgba(255, 255, 255, 1);
padding: 5px 10px;
.el-textarea__inner {
box-shadow: none
padding 5px 0
}
.el-textarea__inner::-webkit-scrollbar {
width: 0;
height: 0;
}
} }
.send { .btn-container {
width 60px; margin-left 10px;
height 40px;
background-color: var(--el-color-success)
}
.is-disabled { .el-row {
background-color: var(--el-button-disabled-bg-color); flex-wrap nowrap
border-color: var(--el-button-disabled-border-color); //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);
}
} }
} }
// end of input wrapper
} }
} }
@ -584,4 +679,26 @@ export default defineComponent({
} }
} }
.el-select-dropdown {
.el-select-dropdown__item {
padding 8px 5px;
.role-option {
display flex
flex-flow row
.el-image {
width 20px
height 20px
border-radius 50%
}
span {
margin-left 5px;
height 20px;
line-height 20px;
}
}
}
}
</style> </style>

View File

@ -79,6 +79,7 @@
:content="chat.content"/> :content="chat.content"/>
<chat-reply v-else-if="chat.type==='reply'" <chat-reply v-else-if="chat.type==='reply'"
:icon="chat.icon" :icon="chat.icon"
:org-content="chat.orgContent"
:content="chat.content"/> :content="chat.content"/>
</div> </div>
</div><!-- end chat box --> </div><!-- end chat box -->
@ -192,6 +193,7 @@ import {httpPost, httpGet} from "@/utils/http";
import {getSessionId, getUserInfo, setLoginUser} from "@/utils/storage"; import {getSessionId, getUserInfo, setLoginUser} from "@/utils/storage";
import hl from 'highlight.js' import hl from 'highlight.js'
import 'highlight.js/styles/a11y-dark.css' import 'highlight.js/styles/a11y-dark.css'
import Clipboard from "clipboard";
export default defineComponent({ export default defineComponent({
name: "ChatPlus", name: "ChatPlus",
@ -250,6 +252,15 @@ export default defineComponent({
return; return;
} }
const clipboard = new Clipboard('.reply-content');
clipboard.on('success', () => {
ElMessage.success('复制成功!');
})
clipboard.on('error', () => {
ElMessage.error('复制失败!');
})
nextTick(() => { nextTick(() => {
this.resizeElement(); this.resizeElement();
}) })
@ -326,7 +337,8 @@ export default defineComponent({
} else { } else {
this.lineBuffer += data.content; this.lineBuffer += data.content;
let md = require('markdown-it')(); let md = require('markdown-it')();
this.chatData[this.chatData.length - 1]["content"] = md.render(this.lineBuffer); this.chatData[this.chatData.length - 1]['orgContent'] = this.lineBuffer;
this.chatData[this.chatData.length - 1]['content'] = md.render(this.lineBuffer);
nextTick(() => { nextTick(() => {
hl.configure({ignoreUnescapedHTML: true}) hl.configure({ignoreUnescapedHTML: true})
@ -415,6 +427,7 @@ export default defineComponent({
continue; continue;
} }
data[i].orgContent = data[i].content;
data[i].content = md.render(data[i].content); data[i].content = md.render(data[i].content);
this.chatData.push(data[i]); this.chatData.push(data[i]);
} }