123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544 |
- <template>
- <view class="container" :style="{'top': viewTop + 'px', 'height': viewHeight + 'px'}">
- <view class="content">
- <!-- <scroll-view scroll-y="true" :show-scrollbar="false" :scroll-top="scroll_top" style="top: 0%;width: 80vw; height:65%;position: absolute;"> -->
- <scroll-view scroll-y="true" :show-scrollbar="false" :scroll-top="scroll_top" style="position: fixed; top: 70px;width: 80vw; bottom: 210upx;">
- <view v-if="recvMsgQueue.length > 0" class="scroll-view-text">
- <view style="margin-left: 13%; display: flex; align-items: center;justify-content: center;width: 100%; user-select: text;" v-for="(msgIndex,index) in recvMsgQueue ">
- <!-- ai 的消息结构体 -->
- <view v-if="isAItell(recvMsgQueue[index].who_msg)" style="width: 100%;height: 100%;margin-bottom: 1%;">
- <text style=" font-size: 10rpx;color: #B34FFF;font-size: 10rpx;margin-left: 2%;background: linear-gradient(90deg, #24BF74 10%, #FAEE07 90%);-webkit-background-clip: text;-webkit-text-fill-color: transparent;">
- {{ai_name}}
- </text>
- <view style="margin-bottom: 3%; margin-top: 1%;margin-left: 1%;padding: 1%; width: 70%;background: #2A2832;border-radius: 10rpx 10rpx 10rpx 10rpx;">
- <view @tap="handleClick()" style="width: 100%;background-color: #2A2832;color: white;" class="htmlContent" ref="htmlContent" v-html="returnText(recvMsgQueue[index].msg_text)"></view>
- <view @click="stopRecv()" v-if="recvStatus&&recvMsgQueue[index].isFinish==false" style="display: flex;width: 100%;align-items: center;justify-content: center;">
- <image src="../../static/closed.png" style="width: 50upx;height: 50upx;align-self: center;"></image>
- </view>
-
- </view>
-
-
- <view v-if="false" style="margin-left: 65%;">
- <image src="../../static/msg-opt-3.png" style="margin-left: 10upx;width: 30upx;height: 30upx;"></image>
- <image src="../../static/msg-opt-4.png" style="margin-left: 10upx;width: 30upx;height: 30upx;"></image>
- </view>
-
- </view>
-
- <!-- 我发送 的消息结构体 -->
- <view v-if="!isAItell(recvMsgQueue[index].who_msg)" style="width: 100%;height: 100%;margin-top: 1%;">
-
- <view style=" font-size: 10rpx;color: #B34FFF;font-size: 10rpx;margin-left: 2%;">
- {{user_name}}
- </view>
- <view style="margin-bottom: 3%; margin-top: 1%;margin-left: 1%;padding: 1%; width: 70%;background: #2A2832;border-radius: 10rpx 10rpx 10rpx 10rpx;">
- <view style="width: 100%;background-color: #2A2832;color: white;" class="htmlContent" ref="htmlContent" v-html="returnText(recvMsgQueue[index].msg_text)"></view>
-
-
- </view>
-
- <view v-if="false" style="margin-left: 65%;">
- <image src="../../static/msg-opt-1.png" style="margin-left: 10upx; width: 30upx;height: 30upx;"></image>
- <image src="../../static/msg-opt-2.png" style="margin-left: 10upx;width: 30upx;height: 30upx;"></image>
- <image src="../../static/msg-opt-3.png" style="margin-left: 10upx;width: 30upx;height: 30upx;"></image>
- <image src="../../static/msg-opt-4.png" style="margin-left: 10upx;width: 30upx;height: 30upx;"></image>
- </view>
-
- </view>
-
- </view>
- </view>
- <view v-else style="display:flex; width: 100%; height: 100%; color: #ffffff; justify-content: center; align-items: center;">
- <text style="font-size: 20px; font-weight: 400; text-align: center; line-height: 60px;">探索AI世界的奥秘,释放无限的创造潜能。\n开始您的创作吧!</text>
- </view>
- </scroll-view>
-
- <view style="display: flex;width: 70vw;height: 90upx;position: fixed;bottom:80upx;">
- <view style="display: flex;width: 90%; border-radius: 25px 25px 25px 25px;opacity: 1;border: 1px solid #32B3AA;width: 100%;" class="textarea-box">
-
- <view style="width: 90%; height: 100%; display: flex;align-items: center;justify-content: center" >
- <textarea v-model="inputValue" :cursor-spacing="15" class="textarea" auto-height="true"
- @keydown.enter="onKeydown('enter')" :disabled="disabled"
- @input="onKeyInput"
- placeholder="请输入消息内容(使用 Enter 发送)" :maxlength="-1"
- placeholder-class="input-placeholder"></textarea>
- </view>
- <view v-if="recvStatus==false" style="width: 10%; height: 100%; display: flex; color: aliceblue;align-items: center;justify-content: center" @click="sendMsg">
- <image src="../../static/img_send.png" style="width: 29.57upx;height: 37.67upx;" ></image>
- </view>
- </view>
- <view style=" display: flex;align-items: center;justify-content: center;width: 5%;position: relative;">
- <image @click="onclickdelete()" src="../../static/img_delete.png" style="width: 50upx;height: 70upx;"></image>
- </view>
- </view>
- </view>
-
- <view style="position: absolute;top: 10%;right: 5%;width: 200upx;display: flex;align-items: center;justify-content: center;">
- <view style="color: white;margin-right: 10upx;">关联上文</view>
- <image @click="is_open_related=false" v-show="is_open_related==true" src="../../static/img-on.png" style="width: 60upx;height: 30upx;"></image>
- <image @click="is_open_related=true" v-show="is_open_related==false" src="../../static/img-off.png" style="width: 60upx;height: 30upx;"></image>
- </view>
- </view>
- </template>
- <script>
- import marked from '../marked/marked.min.js';
- import config from '../index/config.js';
- import hljs from "../highlight.js/lib/common.js";
- import "../highlight.js/styles/atom-one-dark.css";
- // import '../v-copy.js';
- // 键盘的shift键是否被按下
- let shiftKeyPressed = false
- export default {
- components:{
- marked
- },
- data() {
- return {
- is_open_related:true,
- code:'',
- title: 'Hello',
- disabled: false,
- socketOpen:false,
- socketMsgQueue:[],
- recvMsgQueue:[],
- sendMsgQueue:[],
- inputValue: '',
- inputOldValue: '',
- _socketTask:null,
- scroll_text_:"\n",
- sendStatus:false,
- recvStatus:false,
- all_src_list:[],
- src_index:0,
- scroll_top:0,
- user_name:'ZYWZ', //用户名字
- ai_name:"SmartAssistant", //ai名字
- user_mobile:"18612220000",//用户手机号
- ani:'',
- ani_style:{
- width: "100upx",height: "100upx",rotate: 0,left:"25%",right:"25%",position:"absolute"
- },
- angle:359,
- }
- },
- mounted(){
- this.user_mobile = this.tools.get_user_info().mobile
- marked.setOptions({
- renderer: new marked.Renderer(),
- gfm: true,
- tables: true,
- breaks: false,
- pedantic: false,
- sanitize: false,
- smartLists: true,
- smartypants: false,
- highlight: function (code) {
- return hljs.highlightAuto(code).value;
- },
- langPrefix:"hljs language-"
- });
- this.initTcp();
-
-
- },
- props: {
- viewTop: {
- type: Number,
- default: 0
- },
- viewHeight: {
- type: Number,
- default: 0
- }
- },
- methods: {
- onclickdelete(){
- let self = this;
- if(self.recvMsgQueue.length>0&&self.recvMsgQueue[self.recvMsgQueue.length-1].isFinish){
- uni.showModal({
- showCancel:true,
- content:"是否删除记录",
- success:(res)=>{
- if(res.confirm){
- self.recvMsgQueue = []
- }
- }
- })
- }
-
- },
- // #ifdef H5 && VUE2
- onKeydown(keyname) {
- let self = this;
- if(self.inputOldValue!=self.inputValue){
- self.sendMsg();
- }else{
-
- }
- },
- onKeyup(keyname) {
- let self = this;
- self.inputOldValue=self.inputValue;
- },
- // #endif
- setCopy(content){
- // 使用#ifdef H5和#endif控制各端调用情况
-
- // 该方法不支持h5
- //#ifndef H5
- uni.setClipboardData({
- data: String(content), // 必须字符串
- success: function () {
- console.log('success');
- }
- });
- //#endif
-
- // h5端赋值方法,使用创建节点
- // #ifdef H5
- if (!document.queryCommandSupported('copy')) { // 兼容某些浏览器的判断
- console.log('该浏览器不支持')
- }
- let textarea = document.createElement("textarea")
- textarea.value = content
- textarea.readOnly = "readOnly"
- document.body.appendChild(textarea)
- textarea.select() // 选择对象
- textarea.setSelectionRange(0, content.length) // 核心
- let result = document.execCommand("copy") // 执行浏览器复制命令
- if (result) {
- uni.showToast({
- title: '复制成功',
- duration: 2000
- });
- }
- textarea.remove()
- // #endif
- },
- updateSrcList(){
- let self = this;
- let list = this.getIndexList(self.scroll_text_);
- for (var i = 0; i < list.length; i+=2) {
- let start = list[i];
- let end = list[i+1];
- self.all_src_list.push(self.scroll_text_.substring(start,end))
- }
- console.log("updateSrcList",self.all_src_list,self.scroll_text_)
- },
- stopRecv(){
- let self =this;
- self._socketTask.close()
- self.initTcp(true);
- self.finish_recv();
- },
- handleClick(e) {
- e = e || window.event;
- let target = e.target || e.srcElement;
- let name = target.tagName.toLowerCase();
- let self =this;
- if(name.substring(0,3)=="abc"){
- let name_list = name.split('_');
- if(name_list.length>0){
- let index = parseInt(name_list[1])-1;
- console.log("handleClick",index,self.all_src_list)
- self.setCopy(self.all_src_list[index])
- }
- console.log("handleClick",self.all_src_list[index])
- console.log(target.tagName.toLowerCase())
- }
- console.log(name.substring(0,3))
- },
- copyUpdates(){
- let self = this;
- let str = self.recvMsgQueue[self.recvMsgQueue.length-1].msg_text
- let key = "</code></pre>";
- let index = self.all_src_list.length;
- while(str.indexOf(key) != -1){
- index++;
- let s_tag = "</code><abc_"+index+" class=\"copy-button\">Copy</abc_"+index+"></pre>"
- console.log("s_tag",s_tag)
- str=str.replace("</code></pre>", s_tag)
- }
- // str=str.replaceAll("</code></pre>", "</code><abc class=\"copy-button\">Copy</abc></pre>")
- self.recvMsgQueue[self.recvMsgQueue.length-1].msg_text = str
- // console.log("copyUpdates",str)
- self.$forceUpdate();
- },
-
- getIndexList(box_str){
- let box = box_str;
- let boxarr=[];
- let pos = box.indexOf('```');
- while(pos>-1){
- boxarr.push(pos);
- pos= box.indexOf('```',pos+1);
- }
- return boxarr
-
- },
- returnText(text){
- return "<style> body { background-color: black; color: white; } pre {position: relative;}.copy-button { position: absolute; top: 0;right: 0;padding: 4px 8px;background-color: #333;color: #fff;cursor: pointer;user-select: none;} p {line-height: 2;} li{line-height: 1.5;} table {border-collapse: collapse;width: 100%;font-family: Arial, sans-serif;font-size: 14px;}table th,table td {border: 1px solid #ddd;padding: 8px;text-align: left;}</style>" +text
- },
- // 滚动窗口以显示最新的一条消息
- showLastMsg() {
- let self = this;
- let container = uni.createSelectorQuery().in(this).select(".scroll-view-text");
- // 利用uniapp提供的接口获取可视区域的高度和滚动高度
- // let query=uni.createSelectorQuery()
- // let container=query.select('.box');
- container.fields({
- // rect:true, //是否返回节点布局位置信息{left,top,right,bottom}
- size:true, //是否返回节点尺寸信息{width,height}
- scrollOffset:true //是否返回节点滚动信息{scrollLeft,scrollTop}
- },(res)=>{
- self.scroll_top = res.height;
- // console.log(res)
- }).exec()
- },
- isAItell(type){
- if(type===config.type_ai){
- return true;
- }
- return false;
- },
- make_avatar(type){
- if(type===config.type_ai){
- return "../../static/PubImgs_avatar_avatar7.png";
- }
- return "../../static/PubImgs_avatar_avatar8.png";
- },
- isShow(text){
- return text!="";
- },
- initAni(){
- let self = this;
- let action = uni.createAnimation({
- duration: 10000,
- transformOrigin:"50% 50% 0",
- timingFunction: "ease",
- delay: 0
- })
- self.angle+=360;
- action.rotate(self.angle).step()
- self.ani = action.export()
- setTimeout(self.initAni,10000)
- },
- tryReConnect(){
- let self = this;
- uni.showLoading({
- title:"正在尝试重新连接"
- })
- self.disabled = false;
- self.socketOpen = false;
- self.initTcp()
- },
- initTcp(isStopRecv=false){
- let self = this;
- self._socketTask = new WebSocket("ws://47.88.86.123:8000/ws") ;
- self._socketTask.onopen = function (res) {
- console.log('WebSocket连接已打开!',isStopRecv);
- if(!isStopRecv){
- uni.showLoading({
- title:"登录成功"
- })
-
- }
- self.socketMsgQueue = [];
- uni.hideLoading()
- self.socketOpen = true;
-
- };
- self._socketTask.onclose = (res)=>{
- // uni.showModal({
- // showCancel:false,
- // title:"WebSocket is onclose "
- // })
- console.log("WebSocket is onclose")
- self._socketTask.close()
- self.tryReConnect()
- };
- self._socketTask.onerror= function (res) {
- // uni.showModal({
- // showCancel:false,
- // title:"WebSocket连接打开失败,请检查"
- // })
- console.log('WebSocket连接打开失败,请检查!');
- };
-
- self._socketTask.onmessage = function (res) {
- console.log('收到服务器内容:' + res.data);
- let content = ''
- let data = JSON.parse(res.data);
- content = data.content+''
- if(self.sendStatus){
- self.sendStatus = false;
- // content = '▪ ';
- self.scroll_text_ = '';
- // let index = self.recvMsgQueue.length>0?self.recvMsgQueue.length:0
- self.recvMsgQueue.push({who_msg:config.type_ai,msg_type:0,msg_text:'',isFinish:false})
- self.startUpdateView()
- }
- // 在dom渲染完毕后 使聊天窗口滚动到最后一条消息
- self.$nextTick(() => {
- self.showLastMsg()
- })
- // console.log('收到服务器内容:' +content );
- let finish_reason = data.finish_reason+''
- if(content!='null'){
- self.scroll_text_+=content;
- // if(content=='.'||content=='。'){
- // self.scroll_text_+='\n▪ ';
- // }
- }
- if(finish_reason!='null'){
- self.finish_recv()
- // self.recvStatus = false;
- // self.disabled = false;
- // clearTimeout(self.initAni)
- // self.scroll_text_+="\n"
- // console.log("self.recvMsgQueue",self.scroll_text_)
- // self.stopUpdateView();
- // self.copyUpdates();
- // self.updateSrcList();
- // self.recvMsgQueue[self.recvMsgQueue.length-1].isFinish = true;
-
- }else{
- self.recvMsgQueue[self.recvMsgQueue.length-1].msg_text = marked.parse(self.scroll_text_);
- }
- // console.log("self.recvMsgQueue.length",self.recvMsgQueue.length)
- };
- },
- async finish_recv(){
- let self = this;
- self.recvStatus = false;
- self.disabled = false;
- clearTimeout(self.initAni)
- self.scroll_text_+="\n"
- console.log("self.recvMsgQueue",self.scroll_text_)
- self.stopUpdateView();
- self.copyUpdates();
- self.updateSrcList();
- self.recvMsgQueue[self.recvMsgQueue.length-1].isFinish = true;
- },
- async sendMsg(){
- // 如果内容为空
- if (!this.inputValue) {
- // 弹出提示框
- return uni.showToast({
- // 提示内容
- title: '内容不能为空',
- // 不显示图标
- icon: 'none'
- });
- }
- this.sendSocketMessage(this.inputValue)
- },
- onKeyInput(event) {
- // console.log("onKeyInput",event.target.value)
- let self = this;
- // self.inputOldValue=self.inputValue
- },
- startUpdateView(){
- let self = this;
- self.$forceUpdate();
- setTimeout(self.startUpdateView,500)
- },
- stopUpdateView(){
- let self = this;
- clearTimeout(self.startUpdateView)
- },
- sendSocketMessage(msg) {
- let self = this;
- if (this.socketOpen) {
- // uni.showLoading({
- // title:"正在发送"
- // })
- self.sendStatus = true;
- self.disabled = true;
- self.ani_style.rotate = 0;
- self.inputValue = '';
- self.initAni();
- self.recvMsgQueue.push({who_msg:config.type_self,msg_type:0,msg_text:msg,isFinish:false})
- self.recvStatus = true;
- let is_related = self.is_open_related ? "1": "0";
- let mobile = self.user_mobile;
- let msg_json = {"action":"send_msg","msg":msg,"mobile":mobile,"is_related":is_related};
- self._socketTask.send(JSON.stringify(msg_json))
- } else {
- this.socketMsgQueue.push(msg);
- }
- },
-
-
- }
- }
- </script>
- <style>
- .container{
- display: flex;
- box-sizing: border-box;
- flex-direction: column;
- position: fixed;
- /* position: relative; */
- /* background-color: #2A2832; */
- left: 0;
- width: 100%;
- justify-content: center;
- }
- .content {
- height: 100%;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- position: relative;
- background-color: #2A2832;
- }
- .text-area {
- display: flex;
- justify-content: center;
- }
- .title {
- font-size: 36rpx;
- color: #8f8f94;
- }
-
- .textarea-box,
- .textarea {
- width: 100%;
- height: 100%;
- margin-left: 3%;
- },
- textarea-box {
- width: 100% !important;
- }
-
- .textarea-box,
- .textarea,
- textarea,
- textarea-box {
- /* height: 120px; */
- }
- .textarea-box {
- background-color: #FFF;
- }
-
- uni-scroll-view .uni-scroll-view::-webkit-scrollbar {
- display: none;
- width: 0 !important;
- height: 0 !important;
- -webkit-appearance: none;
- background: transparent;
- color: transparent;
- }
-
- </style>
|