vue3+elementPlus pc和小程序ai聊天文生图

news2024/11/16 17:40:02

websocket封装可以看上一篇文章
在这里插入图片描述

//pc端
<template>
  <div class="common-layout theme-white">
    <el-container>
      <el-aside>
        <div class="title-box">
          <span>AI Chat</span>
        </div>
        <div class="chat-list">
          <!-- <div class="search-box">
            <el-input v-model="chatName" class="w-50 m-2" size="small" placeholder="搜索会话" @keyup="searchChat">
              <template #prefix>
                <el-icon class="el-input__icon">
                  <Search />
                </el-icon>
              </template>
            </el-input>
          </div> -->

          <div class="content" :style="{ height: leftBoxHeight + 'px' }">
            <el-row v-for="chat in chatList" :key="chat.chat_id">
              <div :class="chat.chat_id === activeChat.chat_id ? 'chat-list-item active' : 'chat-list-item'"
                @click="changeChat(chat)">
                <el-image :src="chat.icon" class="avatar" />
                <span class="chat-title-input" v-if="chat.edit">
                  <el-input v-model="tmpChatTitle" size="small" @keydown="titleKeydown($event, chat)"
                    placeholder="请输入会话标题" />
                </span>
                <span v-else class="chat-title">{{ chat.title }}</span>
                <span class="btn btn-check" v-if="chat.edit || chat.removing">
                  <el-icon @click="confirm($event, chat)">
                    <Check />
                  </el-icon>
                  <el-icon @click="cancel($event, chat)">
                    <Close />
                  </el-icon>
                </span>
                <span class="btn" v-else>
                  <el-icon title="编辑" @click="editChatTitle($event, chat)">
                    <Edit />
                  </el-icon>
                  <el-icon title="删除会话" @click="removeChat($event, chat)">
                    <Delete />
                  </el-icon>
                </span>
              </div>
            </el-row>
          </div>
        </div>

        <div class="tool-box">
          <el-dropdown :hide-on-click="true" class="user-info" trigger="click">
            <span class="el-dropdown-link">
              <el-image src="/images/logo.png" />
              <span class="username">{{ phoneNumber }}</span>
              <el-icon>
                <ArrowDown />
              </el-icon>
            </span>
            <template #dropdown>
              <el-dropdown-menu style="width: 296px;">
                <el-dropdown-item @click="showConfig">
                  <el-icon>
                    <Tools />
                  </el-icon>
                  <span>修改密码</span>
                </el-dropdown-item>

                <!-- <el-dropdown-item @click="clearAllChats">
                  <el-icon>
                    <Delete />
                  </el-icon>
                  <span>清除所有会话</span>
                </el-dropdown-item> -->

                <el-dropdown-item @click="logout">
                  <i class="iconfont icon-logout"></i>
                  <span>注销</span>
                </el-dropdown-item>
              </el-dropdown-menu>
            </template>
          </el-dropdown>
        </div>
      </el-aside>
      <el-main v-loading="loading" element-loading-background="rgba(122, 122, 122, 0.3)"
        element-loading-text="正在生成...">
        <div class="chat-head">
          <div class="chat-config">
            <span class="role-select-label">选择类型:</span>
            <el-select v-model="roleId" filterable placeholder="类型" class="role-select" @change="newChat">
              <el-option v-for="item in roles" :key="item.id" :label="item.name" :value="item.id">
                <div class="role-option">
                  <el-image :src="item.icon"></el-image>
                  <span>{{ item.name }}</span>
                </div>
              </el-option>
            </el-select>
            <!-- <el-select v-model="modelID" placeholder="模型" @change="newChat">
              <el-option v-for="item in models" :key="item.id" :label="item.name" :value="item.id" />
            </el-select> -->
            <el-button type="primary" @click="newChat">
              <el-icon>
                <Plus />
              </el-icon>
              新建对话
            </el-button>

            <!-- <el-button type="success" @click="exportChat" plain>
              <i class="iconfont icon-export"></i>
              <span>导出会话</span>
            </el-button> -->

            <!-- <el-button type="warning" @click="showFeedbackDialog = true">
              <el-icon>
                <Promotion />
              </el-icon>
              <span>意见反馈</span>
            </el-button> -->
          </div>
        </div>

        <div class="right-box" :style="{ height: mainWinHeight + 'px' }">
          <div>
            <div id="container">
              <div class="chat-box" id="chat-box" :style="{ height: chatBoxHeight + 'px' }">
                <!-- <div v-if="showHello"> -->
                <!-- <welcome @send="autofillPrompt" /> -->
                <!-- </div> -->
                <div v-for="item in chatData" :key="item.id">
                  <chat-prompt v-if="item.type === 'prompt'" :icon="item.icon"
                    :created-at="dateFormat(item['created_at'])" :tokens="item['tokens']" :model="getModelValue(modelID)"
                    :content="item.content" />
                  <chat-reply v-else-if="item.type === 'reply'" :icon="item.icon" :org-content="item.orgContent"
                    :created-at="dateFormat(item['created_at'])" :tokens="item['tokens']" :content="item.content" />
                  <chat-mid-journey v-else-if="item.type === 'mj'" :content="item.content" :role-id="item.role_id"
                    :chat-id="item.chat_id" :icon="item.icon" @disable-input="disableInput(true)"
                    @enable-input="enableInput" :created-at="dateFormat(item['created_at'])" />
                </div>
              </div>

              <div class="re-generate">
                <div class="btn-box">
                  <el-button type="info" v-if="showStopGenerate" @click="stopGenerate" plain>
                    <el-icon>
                      <VideoPause />
                    </el-icon>
                    停止生成
                  </el-button>

                  <el-button type="primary" v-if="showReGenerate" @click="reGenerate" plain>
                    <el-icon>
                      <RefreshRight />
                    </el-icon>
                    重新生成
                  </el-button>
                </div>
              </div>

              <div class="input-box">
                <div class="input-container">
                  <el-input ref="textInput" v-model="prompt" v-on:keydown="inputKeyDown" autofocus type="textarea"
                    :rows="2" placeholder="按 Enter 键发送消息,使用 Ctrl + Enter 换行" />
                  <!-- <span class="send-btn" style="right: 50px;">
                    <el-button @click="sendImage">
                      <el-icon>
                        <PictureFilled />
                      </el-icon>
                    </el-button>
                  </span> -->
                  <span class="send-btn">
                    <el-button @click="sendMessage">
                      <el-icon>
                        <Promotion />
                      </el-icon>
                    </el-button>
                  </span>
                </div>
              </div><!-- end input box -->

            </div><!-- end container -->
          </div><!-- end loading -->
        </div>
      </el-main>
    </el-container>




    <!--账户信息 -->
    <PasswordDialog :show="showConfigDialog" @hide="showConfigDialog = false" />
    <!-- 上传图片 -->
    <el-dialog class="config-dialog" v-model="showimgUpload" :close-on-click-modal="true" :before-close="close"
      style="width:60%" title="上传图片">
      <el-upload class="avatar-uploader" :auto-upload="true" :before-upload="beforeUpload" :show-file-list="false"
        :http-request="afterRead" accept="image/png, image/jpeg,image/jpg">
        <img v-if="imageUrl" :src="imageUrl" class="avatar" />
        <el-icon v-else class="avatar-uploader-icon">
          <Plus />
        </el-icon>
      </el-upload>
      <template #footer>
        <span class="dialog-footer">
          <el-button type="primary" @click="handleConfirm">
            确定
          </el-button>
        </span>
      </template>
    </el-dialog>

  </div>
</template>
<script setup>
import { nextTick, onMounted, ref } from 'vue'
import ChatPrompt from "@/components/ChatPrompt.vue";
import ChatReply from "@/components/ChatReply.vue";
import {
  ArrowDown,
  Check,
  Close,
  Delete,
  Edit,
  Plus,
  Promotion,
  RefreshRight,
  Search,
  Tools,
  VideoPause,
  PictureFilled
} from '@element-plus/icons-vue'
import 'highlight.js/styles/a11y-dark.css'
import { dateFormat, isMobile, randString, removeArrayItem, UUID } from "@/utils/libs";
import { ElMessage, ElMessageBox } from "element-plus";
import hl from "highlight.js";
import { getSessionId, getUserToken, removeUserToken } from "@/store/session";
import { httpGet, httpPost } from "@/utils/http";
import { useRouter } from "vue-router";
import Clipboard from "clipboard";
import PasswordDialog from "@/components/PasswordDialog.vue";
import imgUpload from "@/components/imgUpload.vue";
import { checkSession } from "@/action/session";
import Welcome from "@/components/Welcome.vue";
import ChatMidJourney from "@/components/ChatMidJourney.vue";
import { createWebscoket, onSend, closeWs, start, onMessage } from '@/utils/socket.js'
import { chineseChar2englishChar } from "@/utils/validate";
import Compressor from "compressorjs";

const title = ref('ChatGPT-智能助手');
const models = ref([])
const modelID = ref(0)
const chatData = ref([]);
const allChats = ref([]); // 会话列表
const chatList = ref(allChats.value);
const activeChat = ref({});
const mainWinHeight = ref(0); // 主窗口高度
const chatBoxHeight = ref(0); // 聊天内容框高度
const leftBoxHeight = ref(0);
const loading = ref(false);
const loginUser = ref(null);
const roles = ref([{
  "id": 1,
  "created_at": 0,
  "updated_at": 0,
  "key": "gpt",
  "name": "文生图",
  "context": null,
  "hello_msg": "您好,我是您的AI智能助手,我会尽力回答您的问题或提供有用的建议。",
  "icon": "/images/avatar/gpt.png",
  "enable": true,
  "sort": 1
}]);
const roleId = ref(1)
const newChatItem = ref(null);
const router = useRouter();
const showConfigDialog = ref(false);
const showimgUpload = ref(false);
const isLogin = ref(false)
const showHello = ref(true)
const textInput = ref(null)
const showFeedbackDialog = ref(false)
const showDemoNotice = ref(false)
const showNoticeKey = ref("SHOW_DEMO_NOTICE_")
const socketMsg = ref(null)
const imageUrl = ref(null)
const token = ref("")
const openId = ref("")
const phoneNumber = ref('')
const password = ref('')
if (isMobile()) {
  router.replace("/mobile")
}

onMounted(() => {
  resizeElement();
  token.value = localStorage.getItem('token')
  openId.value = localStorage.getItem('openId')
  phoneNumber.value = localStorage.getItem('phoneNumber')
  password.value = localStorage.getItem('password')
  models.value = {
    phoneNumber: phoneNumber.value,
    password: password.value
  }


  const clipboard = new Clipboard('.copy-reply, .copy-code-btn');
  clipboard.on('success', () => {
    ElMessage.success('复制成功!');
  })

  clipboard.on('error', () => {
    ElMessage.error('复制失败!');
  })
  if (token.value) {
    let host = process.env.VUE_APP_WS_HOST
    createWebscoket(host + token.value, messagesCallBack)
    chatData.value = []; // 初始化聊天数据
    previousText.value = '';//上一次提问
    enableInput()
    activelyClose.value = false;//主动关闭
    chatData.value.push({
      content: '您好,我是您的AI智能助手,我会尽力回答您的问题或提供有用的建议。',
      type: "reply",
      id: randString(32),
      icon: '/images/logo.png',
      orgContent: '您好,我是您的AI智能助手,我会尽力回答您的问题或提供有用的建议。',
    })
    window.onresize = () => resizeElement();
  } else {
    router.push('/login')
  }

});
const getRoleById = function (rid) {
  for (let i = 0; i < roles.value.length; i++) {
    if (roles.value[i]['id'] === rid) {
      return roles.value[i];
    }
  }
  return null;
}
//新消息监听
const messagesCallBack = (msg) => {
  let socketMsg = msg.msg.replace(/\ufeff/g, "");
  let socketMsgTwo = JSON.parse(socketMsg)
  if (socketMsgTwo.msgType != -1 || socketMsgTwo.msgType == 200) {
    loading.value = true
    if (socketMsgTwo.contentUrl) {
      chatData.value.push({
        type: "mj",
        key: randString(32),
        icon: '/images/logo.png',
        content: socketMsgTwo.contentUrl
      })
      loading.value = false
    }
    // 将聊天框的滚动条滑动到最底部
    nextTick(() => {
      document.getElementById('chat-box').scrollTo(0, document.getElementById('chat-box').scrollHeight)
    })
    enableInput()
  }
}
//发送socket消息到服务器
const sendMessagext = (data) => {
  onSend(JSON.stringify(data))
}
//断开socket
const closeWsTxt = () => {
  closeWs()
}



const prompt = ref('');
const showStopGenerate = ref(false); // 停止生成
const showReGenerate = ref(false); // 重新生成
const previousText = ref(''); // 上一次提问
const lineBuffer = ref(''); // 输出缓冲行
const activelyClose = ref(false); // 主动关闭
const canSend = ref(true);


// 发送消息
const sendMessage = function () {
  console.log(prompt.value)
  start()
  if (canSend.value === false) {
    ElMessage.warning("AI 正在作答中,请稍后...");
    return
  }
  if (prompt.value.trim().length === 0 || canSend.value === false) {
    return false;
  }else{
    loading.value = true
  }
  // 追加消息
  chatData.value.push({
    content: md.render(prompt.value),
    type: "prompt",
    id: randString(32),
    icon: '',
    created_at: new Date().getTime(),
  });

  nextTick(() => {
    document.getElementById('chat-box').scrollTo(0, document.getElementById('chat-box').scrollHeight)
  })

  showHello.value = false
  disableInput(false)
  // socket.value.send(prompt.value);
  var data = {
    userId: openId.value,
    toUserId: openId.value,
    content: chineseChar2englishChar(prompt.value),
    msgType: 2
  }
  //websocket 发送
  sendMessagext(data)
  previousText.value = prompt.value;//上一次tiwen
  prompt.value = '';
  return true;
}


//发送图片
const sendImage = function () {
  showimgUpload.value = true
}
//上传前
const beforeUpload = (file) => {
  if (file.type.indexOf("image") < 0) {
    ElMessage.error('请上传正确的图片格式')
    return false;
  }
};
//上传后
const afterRead = (file) => {
  // 压缩图片并上传
  new Compressor(file.file, {
    quality: 0.6,
    success(result) {
      const formData = new FormData();
      formData.append('file', result, result.name);
      // 执行上传操作
      httpPost('/api/upload', formData).then((res) => {
        user.value.avatar = res.data
        ElMessage.success({ message: "上传成功", duration: 500 })
      }).catch((e) => {
        ElMessage.error('图片上传失败:' + e.message)
      })
    },
    error(err) {
      console.log(err.message);
    },
  });
};
//图片确定
const handleConfirm = () => {
  showimgUpload.value = false
}
const resizeElement = function () {
  chatBoxHeight.value = window.innerHeight - 51 - 82 - 38;
  mainWinHeight.value = window.innerHeight - 51;
  leftBoxHeight.value = window.innerHeight - 43 - 47 - 45;
};

// 新建会话
const newChat = function () {
  // 获取当前聊天角色图标
  let icon = '';
  roles.value.forEach(item => {
    if (item['id'] === roleId.value) {
      icon = item['icon']
    }
  })
  newChatItem.value = {
    chat_id: "",
    icon: icon,
    role_id: roleId.value,
    model_id: modelID.value,
    title: '',
    edit: false,
    removing: false,
  };
  activeChat.value = {} //取消激活的会话高亮
  showStopGenerate.value = false;
  showReGenerate.value = false;
  connect(null, roleId.value)
}


const titleKeydown = (e, chat) => {
  if (e.keyCode === 13) {
    e.stopPropagation();
    confirm(e, chat)
  }
}


const md = require('markdown-it')({
  breaks: true,
  highlight: function (str, lang) {
    const codeIndex = parseInt(Date.now()) + Math.floor(Math.random() * 10000000)
    // 显示复制代码按钮
    const copyBtn = `<span class="copy-code-btn" data-clipboard-action="copy" data-clipboard-target="#copy-target-${codeIndex}">复制</span>
<textarea style="position: absolute;top: -9999px;left: -9999px;z-index: -9999;" id="copy-target-${codeIndex}">${str.replace(/<\/textarea>/g, '&lt;/textarea>')}</textarea>`
    if (lang && hl.getLanguage(lang)) {
      const langHtml = `<span class="lang-name">${lang}</span>`
      // 处理代码高亮
      const preCode = hl.highlight(lang, str, true).value
      // 将代码包裹在 pre 中
      return `<pre class="code-container"><code class="language-${lang} hljs">${preCode}</code>${copyBtn} ${langHtml}</pre>`
    }

    // 处理代码高亮
    const preCode = md.utils.escapeHtml(str)
    // 将代码包裹在 pre 中
    return `<pre class="code-container"><code class="language-${lang} hljs">${preCode}</code>${copyBtn}</pre>`
  }
});
// 取消修改
const cancel = function (event, chat) {
  event.stopPropagation();
  chat.edit = false;
  chat.removing = false;
}


const disableInput = (force) => {
  canSend.value = false;
  showReGenerate.value = false;
  showStopGenerate.value = !force;
}

const enableInput = () => {
  canSend.value = true;
  showReGenerate.value = previousText.value !== "";
  showStopGenerate.value = false;
}
// 自动填充 prompt
const autofillPrompt = (text) => {
  prompt.value = text
  textInput.value.focus()
  // sendMessage()
}
// 登录输入框输入事件处理
const inputKeyDown = function (e) {
  if (e.keyCode === 13) {
    if (e.ctrlKey) { // Ctrl + Enter 换行
      prompt.value += "\n";
      return;
    }
    e.preventDefault();
    sendMessage();
  }
}

//账户信息
const showConfig = function () {
  showConfigDialog.value = true;
}



const logout = function () {
  activelyClose.value = true;
  localStorage.clear();
  router.push('/login');
  ElMessage.success('退出成功');
}

const stopGenerate = function () {
  showStopGenerate.value = false;
  httpGet("/api/chat/stop?session_id=" + getSessionId()).then(() => {
    enableInput()
  })
}

// 重新生成
const reGenerate = function () {
  disableInput(false)
  const text = '重新生成上述问题的答案:' + previousText.value;
  // 追加消息
  loading.value=true
  chatData.value.push({
    type: "prompt",
    id: randString(32),
    icon: '/images/avatar/mid_journey.png',
    content: md.render(text)
  });
  var data = {
    userId: openId.value,
    toUserId: openId.value,
    content: chineseChar2englishChar(prompt.value),
    msgType: 2
  }

  sendMessagext(data)

}






</script>

<style scoped lang="stylus">
@import "@/assets/css/chat-plus.styl"
</style>
<style>
.avatar-uploader .el-upload {
  border: 1px dashed #d9d9d9;
  border-radius: 6px;
  cursor: pointer;
  position: relative;
  overflow: hidden;
}

.avatar-uploader .el-upload:hover {
  border-color: #409EFF;
}

.avatar-uploader-icon {
  font-size: 28px;
  color: #8c939d;
  width: 178px;
  height: 178px;
  line-height: 178px;
  text-align: center;
}

.avatar {
  width: 178px;
  height: 178px;
  display: block;
}</style>

在这里插入图片描述

//移动端
<template>
  <div class="app-background">
    <van-config-provider theme="dark">
      <div class="mobile-chat">
        <van-sticky ref="navBarRef" :offset-top="0" position="top">
          <!-- <van-nav-bar left-arrow left-text="返回" @click-left="router.back()"> -->
          <!-- <template #title>
              <van-dropdown-menu>
                <van-dropdown-item :title="title">
                  <van-cell center title="角色"> {{ role.name }}</van-cell>
                  <van-cell center title="模型">{{ modelValue }}</van-cell>
                </van-dropdown-item>
              </van-dropdown-menu>
            </template> -->

          <!-- <template #right>
              <van-icon name="share-o" @click="showShare = true"/>
            </template> -->

          <!-- </van-nav-bar> -->
        </van-sticky>

        <!-- <van-share-sheet v-model:show="showShare" title="立即分享给好友" :options="shareOptions" @select="shareChat" /> -->

        <div class="chat-list-wrapper">
          <div id="message-list-box" :style="{ height: winHeight + 'px' }" class="message-list-box">
            <van-list v-model:error="error" :finished="finished" error-text="请求失败,点击重新加载" @load="onLoad">
              <van-cell v-for="item in chatData" :key="item" :border="false" class="message-line">
                <chat-prompt v-if="item.type === 'prompt'" :content="item.content"
                  :created-at="dateFormat(item['created_at'])" :icon="item.icon" :model="model"
                  :tokens="item['tokens']" />
                <chat-reply v-else-if="item.type === 'reply'" :content="item.content"
                  :created-at="dateFormat(item['created_at'])" :icon="item.icon" :org-content="item.orgContent"
                  :tokens="item['tokens']" />
                <chat-mid-journey v-else-if="item.type === 'mj'" :content="item.content" :icon="item.icon" :role-id="role"
                  :chat-id="chatId" @disable-input="disableInput(true)" @enable-input="enableInput"
                  :created-at="dateFormat(item['created_at'])" />
              </van-cell>
            </van-list>
          </div>
        </div>
        <div class="chat-box-wrapper">
          <van-sticky ref="bottomBarRef" :offset-bottom="0" position="bottom">

            <van-cell-group inset>
              <van-field v-model="prompt" center clearable placeholder="输入你的问题">
                <template #button>
                  <van-button size="small" type="primary" @click="sendMessage">发送</van-button>
                </template>
                <template #extra>
                  <div class="icon-box" style="margin-left: 10rpx;">
                    <van-icon v-if="showStopGenerate" name="stop-circle-o" @click="stopGenerate" />
                    <van-icon v-if="showReGenerate" name="play-circle-o" @click="reGenerate" />
                  </div>
                </template>
              </van-field>
            </van-cell-group>

          </van-sticky>
        </div>

      </div>
    </van-config-provider>
    <van-overlay z-index="100" :show="loading" class="wrapper">
        <van-loading vertical class="block">
          <template #icon>
            <van-icon name="star-o" size="30" />
          </template>
          正在生成...
        </van-loading>
      </van-overlay>
  </div>
</template>

<script setup>
import { nextTick, onMounted, ref } from "vue";
import { showToast } from "vant";
import { useRouter } from "vue-router";
import { dateFormat, randString, renderInputText, UUID } from "@/utils/libs";
import { getChatConfig } from "@/store/chat";
import { httpGet } from "@/utils/http";
import hl from "highlight.js";
import 'highlight.js/styles/a11y-dark.css'
import ChatPrompt from "@/components/mobile/ChatPrompt.vue";
import ChatReply from "@/components/mobile/ChatReply.vue";
import { getSessionId, getUserToken } from "@/store/session";
import { checkSession } from "@/action/session";
import { getMobileTheme } from "@/store/system";
import ChatMidJourney from "@/components/mobile/ChatMidJourney.vue";

import QRCode from "qrcode";
import { ElMessage } from "element-plus";
import Clipboard from "clipboard";
import InviteList from "@/components/InviteList.vue";

import { createWebscoket, onSend, closeWs, start, onMessage } from '@/utils/socket.js'
import { chineseChar2englishChar } from "@/utils/validate";

const winHeight = ref(0)
const navBarRef = ref(null)
const bottomBarRef = ref(null)
const router = useRouter()

const chatConfig = getChatConfig()
const role = chatConfig ? chatConfig.role : ''
const model = chatConfig ? chatConfig.model : ''
const modelValue = chatConfig ? chatConfig.modelValue : ""
const title = chatConfig ? chatConfig.title : ''
const chatId = chatConfig ? chatConfig.chatId : ''
const loginUser = ref(null)

const listBoxHeight = window.innerHeight
const inviteURL = ref("")
const qrImg = ref("")
const inviteChatCalls = ref(0)
const inviteImgCalls = ref(0)
const hits = ref(0)
const regNum = ref(0)
const rate = ref(0)
const isLogin = ref(false)
const token = ref("")
const openId = ref("")
const overlayshow = ref(false)


onMounted(() => {
  var url = window.location.href;
  console.log(url)
  var regex = /[?&]token=([^&#]+)/; // 匹配 ? 或 & 后面跟 token= 开头的部分
  var regexId = /[?&]openId=([^&#]+)/; // 匹配 ? 或 & 后面跟 token= 开头的部分
  var match = url.match(regex);
  token.value = decodeURIComponent(match[1]);
  openId.value = decodeURIComponent(url.match(regexId)[1]);
  localStorage.setItem('token', token.value)
  localStorage.setItem('openId', openId.value)
  winHeight.value = document.body.offsetHeight - navBarRef.value.$el.offsetHeight - bottomBarRef.value.$el.offsetHeight
  let host = process.env.VUE_APP_WS_HOST
  createWebscoket(host + token.value, messagesCallBack)
  loading.value = false
  previousText.value = '';
  canSend.value = true;
  activelyClose.value = false;
  chatData.value.push({
    content: '您好,我是您的AI智能助手,我会尽力回答您的问题或提供有用的建议。',
    type: "reply",
    id: randString(32),
    icon: '/images/logo.png',
    orgContent: '您好,我是您的AI智能助手,我会尽力回答您的问题或提供有用的建议。',
  })
})

const chatData = ref([])
const loading = ref(false)
const finished = ref(false)
const error = ref(false)

checkSession().then(user => {
  loginUser.value = user
}).catch(() => {
  router.push('/mobile')
})
//取url中的参数值
const getQuery = (name) => {
  // 正则:[找寻'&' + 'url参数名字' = '值' + '&']('&'可以不存在)
  let reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
  let r = window.location
  console.log(r);
  if (r != null) {
    // 对参数值进行解码
    return decodeURIComponent(r[2]);
  }
  return null;
}



//新消息监听
const messagesCallBack = (msg) => {
  let socketMsg = msg.msg.replace(/\ufeff/g, "");
  let socketMsgTwo = socketMsg ? JSON.parse(socketMsg) : ''

  if (socketMsgTwo.msgType != -1 || socketMsgTwo.msgType == 200) {
    loading.value = true
    if (socketMsgTwo.contentUrl) {
      chatData.value.push({
        type: "mj",
        key: randString(32),
        icon: '/images/logo.png',
        content: socketMsgTwo.contentUrl
      })
      loading.value = false
    }
    // 将聊天框的滚动条滑动到最底部
    nextTick(() => {
      document.getElementById('chat-box').scrollTo(0, document.getElementById('chat-box').scrollHeight)
      // localStorage.setItem("chat_id", chat_id)
    })
    enableInput()
  }
}

//发送socket消息到服务器
const sendMessagext = (data) => {
  onSend(JSON.stringify(data))
}
//断开socket
const closeWsTxt = () => {
  closeWs()
}

// 创建 socket 连接
const prompt = ref('');
const showStopGenerate = ref(false); // 停止生成
const showReGenerate = ref(false); // 重新生成
const previousText = ref(''); // 上一次提问
const lineBuffer = ref(''); // 输出缓冲行
const socket = ref(null);
const activelyClose = ref(false); // 主动关闭
const canSend = ref(true);
const connect = function () {

  let host = process.env.VUE_APP_WS_HOST

  createWebscoket(host + token.value, messagesCallBack)

  loading.value = false
  previousText.value = '';
  canSend.value = true;
  activelyClose.value = false;

  chatData.value.push({
    content: '您好,我是您的AI智能助手,我会尽力回答您的问题或提供有用的建议。',
    type: "reply",
    id: randString(32),
    icon: '/images/logo.png',
    orgContent: '您好,我是您的AI智能助手,我会尽力回答您的问题或提供有用的建议。',
  })



}

const disableInput = (force) => {
  canSend.value = false;
  // loading.value = true;
  showReGenerate.value = false;
  showStopGenerate.value = !force;
}

const enableInput = () => {
  canSend.value = true;
  // loading.value = false;
  showReGenerate.value = previousText.value !== "";
  showStopGenerate.value = false;
}

// 将聊天框的滚动条滑动到最底部
const scrollListBox = () => {
  document.getElementById('message-list-box').scrollTo(0, document.getElementById('message-list-box').scrollHeight + 46)
}

const sendMessage = () => {
  
  if (canSend.value === false) {
    
    showToast("AI 正在作答中,请稍后...");
    return
  }

  if (prompt.value.trim().length === 0) {
    return false;
  }else{
    loading.value = true
  }
  
  // 追加消息
  chatData.value.push({
    type: "prompt",
    id: randString(32),
    icon: '',
    content: renderInputText(prompt.value),
    created_at: new Date().getTime(),
  });

  nextTick(() => {
    scrollListBox()
  })

  disableInput(false)
  // socket.value.send(prompt.value);
  var data = {
    userId: openId.value,
    toUserId: openId.value,
    content: chineseChar2englishChar(prompt.value),
    msgType: 2
  }
  //websocket 发送
  sendMessagext(data)
  previousText.value = prompt.value;
  prompt.value = '';
  return true;
}

const stopGenerate = () => {
  showStopGenerate.value = false;
  enableInput()

}

const reGenerate = () => {
  disableInput(false)
  const text = '重新生成上述问题的答案:' + previousText.value;
  // 追加消息
  chatData.value.push({
    type: "prompt",
    id: randString(32),
    icon: '',
    content: renderInputText(text)
  });
  var data = {
    userId: openId.value,
    toUserId: openId.value,
    content: chineseChar2englishChar(prompt.value),
    msgType: 1
  }
  // socket.value.send(text);
  sendMessagext(data)
}

const showShare = ref(false)
const shareOptions = [
  { name: '微信', icon: 'wechat' },
  { name: '微博', icon: 'weibo' },
  { name: '复制链接', icon: 'link' },
  { name: '分享海报', icon: 'poster' },
]
const shareChat = () => {
  showShare.value = false
  router.push('/mobile/Invitation');
}
</script>

<style lang="stylus" scoped>
@import "@/assets/css/mobile/chat-session.css"
</style>
<style>
 .wrapper {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 100%;
  }

  .block {
    width: 120px;
    height: 120px;
    font-size: 20px;
    color: #1989fa;
    /* background-color: #fff; */
  }
</style>
//uniapp
<template>
	<view class="main">
		<u-navbar :fixed="true" :autoBack="false" @leftClick="goBack"></u-navbar>
		<web-view :src="url" @message="message"></web-view>
	</view>
</template>
<script>
	export default {
		data() {
			return {
				url: '',
				token: '',
				canBack: false,
				openId: ''
			}
		},
		onLoad() {
			this.token = uni.getStorageSync('token');
			this.openId = uni.getStorageSync('openid');
			this.url = 'https://xxxxx.com/mobile?token=' + encodeURIComponent(this.token) + '&openId=' +
				encodeURIComponent(this.openId)
			console.log(this.url)
		},
		methods: {
			message(event) {
				console.log(event.detail.data);
			},


		},

	}
</script>
<style>
	.main {
		width: 100%;
		height: 100vh;
	}
</style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1414222.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用vue_cli脚手架创建Vue项目(cmd和图形化方式)

使用vue_cli脚手架创建Vue项目&#xff08;cmd和图形化方式&#xff09; 创建项目(cmd方式) vue create vue_cli1.方向键选择manually select feature(手动选择方式创建)&#xff0c;回车 2.按空格键选择需要的组件&#xff1a;Babel、PWA、Router、Vuex、CSS&#xff0c;回…

【LeetCode】112. 路径总和(简单)——代码随想录算法训练营Day18

题目链接&#xff1a;112. 路径总和 题目描述 给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径&#xff0c;这条路径上所有节点值相加等于目标和 targetSum 。如果存在&#xff0c;返回 true &#xff1b;否则&…

Pandas.Series.product() 乘积(累乘积) 详解 含代码 含测试数据集 随Pandas版本持续更新

关于Pandas版本&#xff1a; 本文基于 pandas2.2.0 编写。 关于本文内容更新&#xff1a; 随着pandas的stable版本更迭&#xff0c;本文持续更新&#xff0c;不断完善补充。 传送门&#xff1a; Pandas API参考目录 传送门&#xff1a; Pandas 版本更新及新特性 传送门&…

以太网与PON网络的巅峰对决

在这网络的江湖中&#xff0c;各路江湖豪侠都神色匆忙地往同一个地方赶&#xff0c;豪侠们脸上都充满期待和焦虑&#xff0c;生怕错过了什么。这个地方就是传说中的园区网&#xff0c;因为在那里万众期待已久的以太网与PON网络的巅峰对决“将在今天上演。 一方是以太网大侠&am…

500行Python代码构建的AI搜索工具!

一个500行Python代码构建的AI搜索工具&#xff0c;而且还会开源&#xff0c;试了一下麻雀虽小该有的都有。 后端是Mixtral-8x7b 模型&#xff0c;托管在 LeptonAI 上&#xff0c;输出速度能达到每秒大约200个 token&#xff0c;用的搜索引擎是 Bing 的搜索 API。 作者还写了一…

【昕宝爸爸小模块】什么是POI,为什么它会导致内存溢出?

➡️博客首页 https://blog.csdn.net/Java_Yangxiaoyuan 欢迎优秀的你&#x1f44d;点赞、&#x1f5c2;️收藏、加❤️关注哦。 本文章CSDN首发&#xff0c;欢迎转载&#xff0c;要注明出处哦&#xff01; 先感谢优秀的你能认真的看完本文&…

六、Kotlin 类型进阶

1. 类的构造器 & init 代码块 1.1 主构造器 & 副构造器在使用时的注意事项 & 注解 JvmOverloads 推荐在类定义时为类提供一个主构造器&#xff1b; 在为类提供了主构造器的情况下&#xff0c;当再定义其他的副构造器时&#xff0c;要求副构造器必须调用到主构造器…

2024年预制菜行业市场发展趋势分析(2021-2023年预制菜行业数据分析)

近期&#xff0c;老干妈被称为预制菜、预制菜国标报送稿出炉等事件再次引起大众对于预制菜市场的讨论。随着国家对预制菜审核标准的严格化&#xff0c;预制菜市场未来走向将会如何&#xff1f;鲸参谋带大家从数据角度来了解。 首先来看下预制菜市场的行业发展情况。 根据鲸参…

Linux 驱动开发基础知识—— LED 驱动程序框架(四)

个人名片&#xff1a; &#x1f981;作者简介&#xff1a;一名喜欢分享和记录学习的在校大学生 &#x1f42f;个人主页&#xff1a;妄北y &#x1f427;个人QQ&#xff1a;2061314755 &#x1f43b;个人邮箱&#xff1a;2061314755qq.com &#x1f989;个人WeChat&#xff1a;V…

Python第三方扩展库NumPy

Python第三方扩展库NumPy NumPy(Numerical Python&#xff0c;注意使用时全部小写 numpy) 是 Python 语言的一个扩展程序库&#xff0c;支持大量的维度数组与矩阵运算&#xff0c;此外也针对数组运算提供大量的数学函数库。 在Windows平台上安装numpy&#xff0c;可在cmd命令…

python使用PaddleOCR实现《命名实体识别项目》OCR(已实现)(ai领域必看,简单易用)

1.简介&#xff1a; PaddleOCR是飞桨&#xff08;PaddlePaddle&#xff09;推出的一个端到端的光学字符识别开源工具集&#xff0c;支持中文、英文、数字以及特殊符号等各种类型的文字检测、识别和词语整体识别。该工具集使用PaddlePaddle深度学习框架技术&#xff0c;提供了多…

jenkins发布失败

今天用jenkins发布项目时失败了&#xff0c;而前几天还好好的。 云控制台看了下&#xff0c;发现根本就没打包。 报错如下&#xff1a; 从控制台可以看出&#xff0c;项目依赖没有下载下来&#xff0c;所以打包失败了。 根本原因是&#xff1a;在配置中给yarn指定的淘宝仓库…

day31_HTML

今日内容 0 复习昨日 1 表格标签 2 表单标签【重要】 3 框架标签 0 复习昨日 Javaweb开发,前端,服务器,数据库 前端,要学习HTML,CSS,JavaScript,JQuery HTML是用来编写网页的一种编程语言 语法 由各种标签组成,标签是尖括号<>,一般都是成对儿出现,前面叫做开标签,后面…

【代码随想录-数组】移除元素

💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学习,不断总结,共同进步,活到老学到老导航 檀越剑指大厂系列:全面总结 jav…

OJAC近屿智能张立赛博士揭秘GPT Store:技术创新、商业模式与未来趋势

Look&#xff01;&#x1f440;我们的大模型商业化落地产品&#x1f4d6;更多AI资讯请&#x1f449;&#x1f3fe;关注Free三天集训营助教在线为您火热答疑&#x1f469;&#x1f3fc;‍&#x1f3eb; 亲爱的伙伴们&#xff1a; 1月31日晚上8:30&#xff0c;由哈尔滨工业大学的…

【数据结构】栈、队列、数组、列表

数据结构是什么&#xff1f; 数据结构是计算机存储、组织数据的方式 是指数据相互之间是以什么方式排列在一起的。 数据结构是为了更加方便的管理和使用数据&#xff0c;需要结合具体的业务场景来进行选择。一般情况下&#xff0c;精心选择的数据结构可以带来更高的运行或者…

张维迎《博弈与社会》笔记(3)个体理性行为

博弈论的方法论 经济学、社会学、心理学的区别 上一节我们分析了社会的两个基本问题&#xff0c;从中可以发现&#xff0c;无论是解决协调问题还是合作问题&#xff0c;都需要我们对个人行为有深入认识。实际上&#xff0c;所有的社会科学都可看成是有关人类行为的科学&#x…

CTFshow元旦水友赛web部分题解

1.easy_include 看题目是一个文件包含题 post的内容被过滤掉.&#xff0c;而且开头必须是字母&#xff0c;但是如果想要文件包含需要file:///xxxx,这里开头就是/了&#xff0c;所以需要绕过&#xff0c;file伪协议可以用file://localhost路径让绕过开头必须是字母。 可以看…

移动Web——平面转换-平移

1、平面转换-平移 取值 像素单位数值百分比&#xff08;参照盒子自身尺寸计算结果&#xff09;正负均可 技巧 translate()只写一个值&#xff0c;表示沿着X轴移动单独设置X或Y轴移动距离&#xff1a;translateX()或translateY() <!DOCTYPE html> <html lang"en&q…

微信小程序开发 调查问卷

1. 需求 开发一个“调查问卷”的案例来学习常用表单组件的使用&#xff0c;收集用户填写胡表单信息提交给服务器&#xff0c;或者从服务器获取数据后显示在表单中。调查问卷分为单选、多选、单行填空、多行填空&#xff0c;选项为必填时候&#xff0c;提交弹出必填项未提交提示…