mirror of
				https://github.com/yangjian102621/geekai.git
				synced 2025-11-04 08:13:43 +08:00 
			
		
		
		
	refactor AI chat message struct, allow users to set whether the AI responds in stream, compatible with the GPT-o1 model
This commit is contained in:
		@@ -132,12 +132,13 @@ const content =ref(processPrompt(props.data.content))
 | 
			
		||||
const files = ref([])
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  // if (!finalTokens.value) {
 | 
			
		||||
  //   httpPost("/api/chat/tokens", {text: props.data.content, model: props.data.model}).then(res => {
 | 
			
		||||
  //     finalTokens.value = res.data;
 | 
			
		||||
  //   }).catch(() => {
 | 
			
		||||
  //   })
 | 
			
		||||
  // }
 | 
			
		||||
  processFiles()
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const processFiles = () => {
 | 
			
		||||
  if (!props.data.content) {
 | 
			
		||||
    return
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const linkRegex = /(https?:\/\/\S+)/g;
 | 
			
		||||
  const links = props.data.content.match(linkRegex);
 | 
			
		||||
@@ -159,8 +160,7 @@ onMounted(() => {
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
  content.value = md.render(content.value.trim())
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
const isExternalImg = (link, files) => {
 | 
			
		||||
  return isImage(link) && !files.find(file => file.url === link)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,9 @@
 | 
			
		||||
            <el-radio value="chat">对话样式</el-radio>
 | 
			
		||||
          </el-radio-group>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
 | 
			
		||||
        <el-form-item label="流式输出:">
 | 
			
		||||
          <el-switch v-model="data.stream" @change="(val) => {store.setChatStream(val)}" />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
      </el-form>
 | 
			
		||||
    </div>
 | 
			
		||||
  </el-dialog>
 | 
			
		||||
@@ -28,6 +30,7 @@ const store = useSharedStore();
 | 
			
		||||
 | 
			
		||||
const data = ref({
 | 
			
		||||
  style: store.chatListStyle,
 | 
			
		||||
  stream: store.chatStream,
 | 
			
		||||
})
 | 
			
		||||
// eslint-disable-next-line no-undef
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,8 @@ import Storage from 'good-storage'
 | 
			
		||||
export const useSharedStore = defineStore('shared', {
 | 
			
		||||
    state: () => ({
 | 
			
		||||
        showLoginDialog: false,
 | 
			
		||||
        chatListStyle: Storage.get("chat_list_style","chat")
 | 
			
		||||
        chatListStyle: Storage.get("chat_list_style","chat"),
 | 
			
		||||
        chatStream: Storage.get("chat_stream",true),
 | 
			
		||||
    }),
 | 
			
		||||
    getters: {},
 | 
			
		||||
    actions: {
 | 
			
		||||
@@ -14,6 +15,10 @@ export const useSharedStore = defineStore('shared', {
 | 
			
		||||
        setChatListStyle(value) {
 | 
			
		||||
            this.chatListStyle = value;
 | 
			
		||||
            Storage.set("chat_list_style", value);
 | 
			
		||||
        },
 | 
			
		||||
        setChatStream(value) {
 | 
			
		||||
            this.chatStream = value;
 | 
			
		||||
            Storage.set("chat_stream", value);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -9,8 +9,6 @@
 | 
			
		||||
 * Util lib functions
 | 
			
		||||
 */
 | 
			
		||||
import {showConfirmDialog} from "vant";
 | 
			
		||||
import {httpDownload} from "@/utils/http";
 | 
			
		||||
import {showMessageError} from "@/utils/dialog";
 | 
			
		||||
 | 
			
		||||
// generate a random string
 | 
			
		||||
export function randString(length) {
 | 
			
		||||
@@ -183,6 +181,10 @@ export function isImage(url) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function processContent(content) {
 | 
			
		||||
    if (!content) {
 | 
			
		||||
        return ""
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // 如果是图片链接地址,则直接替换成图片标签
 | 
			
		||||
    const linkRegex = /(https?:\/\/\S+)/g;
 | 
			
		||||
    const links = content.match(linkRegex);
 | 
			
		||||
 
 | 
			
		||||
@@ -106,7 +106,7 @@
 | 
			
		||||
                <el-dropdown-menu class="tools-dropdown">
 | 
			
		||||
                  <el-checkbox-group v-model="toolSelected">
 | 
			
		||||
                    <el-dropdown-item v-for="item in tools" :key="item.id">
 | 
			
		||||
                      <el-checkbox :value="item.id" :label="item.label" @change="changeTool" />
 | 
			
		||||
                      <el-checkbox :value="item.id" :label="item.label" />
 | 
			
		||||
                      <el-tooltip :content="item.description" placement="right">
 | 
			
		||||
                        <el-icon><InfoFilled /></el-icon>
 | 
			
		||||
                      </el-tooltip>
 | 
			
		||||
@@ -271,6 +271,12 @@ watch(() => store.chatListStyle, (newValue) => {
 | 
			
		||||
const tools = ref([])
 | 
			
		||||
const toolSelected = ref([])
 | 
			
		||||
const loadHistory = ref(false)
 | 
			
		||||
const stream = ref(store.chatStream)
 | 
			
		||||
 | 
			
		||||
watch(() => store.chatStream, (newValue) => {
 | 
			
		||||
  stream.value = newValue
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// 初始化角色ID参数
 | 
			
		||||
if (router.currentRoute.value.query.role_id) {
 | 
			
		||||
@@ -491,16 +497,6 @@ const newChat = () => {
 | 
			
		||||
  connect()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 切换工具
 | 
			
		||||
const changeTool = () => {
 | 
			
		||||
  if (!isLogin.value) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  loadHistory.value = false
 | 
			
		||||
  socket.value.close()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// 切换会话
 | 
			
		||||
const loadChat = function (chat) {
 | 
			
		||||
  if (!isLogin.value) {
 | 
			
		||||
@@ -598,6 +594,7 @@ const lineBuffer = ref(''); // 输出缓冲行
 | 
			
		||||
const socket = ref(null);
 | 
			
		||||
const canSend = ref(true);
 | 
			
		||||
const sessionId = ref("")
 | 
			
		||||
const isNewMsg = ref(true)
 | 
			
		||||
const connect = function () {
 | 
			
		||||
  const chatRole = getRoleById(roleId.value);
 | 
			
		||||
  // 初始化 WebSocket 对象
 | 
			
		||||
@@ -612,8 +609,7 @@ const connect = function () {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  loading.value = true
 | 
			
		||||
  const toolIds = toolSelected.value.join(',')
 | 
			
		||||
  const _socket = new WebSocket(host + `/api/chat/new?session_id=${sessionId.value}&role_id=${roleId.value}&chat_id=${chatId.value}&model_id=${modelID.value}&token=${getUserToken()}&tools=${toolIds}`);
 | 
			
		||||
  const _socket = new WebSocket(host + `/api/chat/new?session_id=${sessionId.value}&role_id=${roleId.value}&chat_id=${chatId.value}&model_id=${modelID.value}&token=${getUserToken()}`);
 | 
			
		||||
  _socket.addEventListener('open', () => {
 | 
			
		||||
    enableInput()
 | 
			
		||||
    if (loadHistory.value) {
 | 
			
		||||
@@ -629,15 +625,22 @@ const connect = function () {
 | 
			
		||||
        reader.readAsText(event.data, "UTF-8");
 | 
			
		||||
        reader.onload = () => {
 | 
			
		||||
          const data = JSON.parse(String(reader.result));
 | 
			
		||||
          if (data.type === 'start') {
 | 
			
		||||
          if (data.type === 'error') {
 | 
			
		||||
            ElMessage.error(data.message)
 | 
			
		||||
            return
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          if (isNewMsg.value && data.type !== 'end') {
 | 
			
		||||
            const prePrompt = chatData.value[chatData.value.length-1]?.content
 | 
			
		||||
            chatData.value.push({
 | 
			
		||||
              type: "reply",
 | 
			
		||||
              id: randString(32),
 | 
			
		||||
              icon: chatRole['icon'],
 | 
			
		||||
              prompt:prePrompt,
 | 
			
		||||
              content: "",
 | 
			
		||||
              content: data.content,
 | 
			
		||||
            });
 | 
			
		||||
            isNewMsg.value = false
 | 
			
		||||
            lineBuffer.value = data.content;
 | 
			
		||||
          } else if (data.type === 'end') { // 消息接收完毕
 | 
			
		||||
            // 追加当前会话到会话列表
 | 
			
		||||
            if (newChatItem.value !== null) {
 | 
			
		||||
@@ -663,6 +666,7 @@ const connect = function () {
 | 
			
		||||
              nextTick(() => {
 | 
			
		||||
                document.getElementById('chat-box').scrollTo(0, document.getElementById('chat-box').scrollHeight)
 | 
			
		||||
              })
 | 
			
		||||
              isNewMsg.value = true
 | 
			
		||||
            }).catch(() => {
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
@@ -688,6 +692,7 @@ const connect = function () {
 | 
			
		||||
 | 
			
		||||
  _socket.addEventListener('close', () => {
 | 
			
		||||
    disableInput(true)
 | 
			
		||||
    loadHistory.value = false
 | 
			
		||||
    connect()
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
@@ -775,7 +780,7 @@ const sendMessage = function () {
 | 
			
		||||
 | 
			
		||||
  showHello.value = false
 | 
			
		||||
  disableInput(false)
 | 
			
		||||
  socket.value.send(JSON.stringify({type: "chat", content: content}));
 | 
			
		||||
  socket.value.send(JSON.stringify({tools: toolSelected.value, content: content, stream: stream.value}));
 | 
			
		||||
  tmpChatTitle.value = content
 | 
			
		||||
  prompt.value = ''
 | 
			
		||||
  files.value = []
 | 
			
		||||
@@ -813,7 +818,7 @@ const loadChatHistory = function (chatId) {
 | 
			
		||||
  chatData.value = []
 | 
			
		||||
  httpGet('/api/chat/history?chat_id=' + chatId).then(res => {
 | 
			
		||||
    const data = res.data
 | 
			
		||||
    if (!data || data.length === 0) { // 加载打招呼信息
 | 
			
		||||
    if ((!data || data.length === 0) && chatData.value.length === 0) { // 加载打招呼信息
 | 
			
		||||
      const _role = getRoleById(roleId.value)
 | 
			
		||||
      chatData.value.push({
 | 
			
		||||
        chat_id: chatId,
 | 
			
		||||
@@ -852,7 +857,7 @@ const stopGenerate = function () {
 | 
			
		||||
// 重新生成
 | 
			
		||||
const reGenerate = function (prompt) {
 | 
			
		||||
  disableInput(false)
 | 
			
		||||
  const text = '重新生成下面问题的答案:' + prompt;
 | 
			
		||||
  const text = '重新回答下述问题:' + prompt;
 | 
			
		||||
  // 追加消息
 | 
			
		||||
  chatData.value.push({
 | 
			
		||||
    type: "prompt",
 | 
			
		||||
@@ -860,7 +865,7 @@ const reGenerate = function (prompt) {
 | 
			
		||||
    icon: loginUser.value.avatar,
 | 
			
		||||
    content: text
 | 
			
		||||
  });
 | 
			
		||||
  socket.value.send(JSON.stringify({type: "chat", content: prompt}));
 | 
			
		||||
  socket.value.send(JSON.stringify({tools: toolSelected.value, content: text, stream: stream.value}));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const chatName = ref('')
 | 
			
		||||
 
 | 
			
		||||
@@ -231,10 +231,7 @@ const connect = (userId) => {
 | 
			
		||||
      reader.onload = () => {
 | 
			
		||||
        const data = JSON.parse(String(reader.result))
 | 
			
		||||
        switch (data.type) {
 | 
			
		||||
          case "start":
 | 
			
		||||
            text.value = ""
 | 
			
		||||
            break
 | 
			
		||||
          case "middle":
 | 
			
		||||
          case "content":
 | 
			
		||||
            text.value += data.content
 | 
			
		||||
            html.value = md.render(processContent(text.value))
 | 
			
		||||
            break
 | 
			
		||||
 
 | 
			
		||||
@@ -2,46 +2,38 @@
 | 
			
		||||
  <div class="admin-login">
 | 
			
		||||
    <div class="main">
 | 
			
		||||
      <div class="contain">
 | 
			
		||||
        <div class="logo">
 | 
			
		||||
          <el-image :src="logo" fit="cover" @click="router.push('/')"/>
 | 
			
		||||
        <div class="logo" @click="router.push('/')">
 | 
			
		||||
          <el-image :src="logo" fit="cover"/>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div class="header">{{ title }}</div>
 | 
			
		||||
        <h1 class="header">{{ title }}</h1>
 | 
			
		||||
        <div class="content">
 | 
			
		||||
          <div class="block">
 | 
			
		||||
            <el-input placeholder="请输入用户名" size="large" v-model="username" autocomplete="off" autofocus
 | 
			
		||||
                      @keyup="keyupHandle">
 | 
			
		||||
              <template #prefix>
 | 
			
		||||
                <el-icon>
 | 
			
		||||
                  <UserFilled/>
 | 
			
		||||
                </el-icon>
 | 
			
		||||
              </template>
 | 
			
		||||
            </el-input>
 | 
			
		||||
          </div>
 | 
			
		||||
          <el-input v-model="username" placeholder="请输入用户名" size="large"
 | 
			
		||||
                    autocomplete="off" autofocus @keyup.enter="login">
 | 
			
		||||
            <template #prefix>
 | 
			
		||||
              <el-icon>
 | 
			
		||||
                <UserFilled/>
 | 
			
		||||
              </el-icon>
 | 
			
		||||
            </template>
 | 
			
		||||
          </el-input>
 | 
			
		||||
 | 
			
		||||
          <div class="block">
 | 
			
		||||
            <el-input placeholder="请输入密码" size="large" v-model="password" show-password autocomplete="off"
 | 
			
		||||
                      @keyup="keyupHandle">
 | 
			
		||||
              <template #prefix>
 | 
			
		||||
                <el-icon>
 | 
			
		||||
                  <Lock/>
 | 
			
		||||
                </el-icon>
 | 
			
		||||
              </template>
 | 
			
		||||
            </el-input>
 | 
			
		||||
          </div>
 | 
			
		||||
          <el-input v-model="password" placeholder="请输入密码" size="large"
 | 
			
		||||
                    show-password autocomplete="off" @keyup.enter="login">
 | 
			
		||||
            <template #prefix>
 | 
			
		||||
              <el-icon>
 | 
			
		||||
                <Lock/>
 | 
			
		||||
              </el-icon>
 | 
			
		||||
            </template>
 | 
			
		||||
          </el-input>
 | 
			
		||||
 | 
			
		||||
          <el-row class="btn-row">
 | 
			
		||||
            <el-button class="login-btn" size="large" type="primary" @click="login">登录</el-button>
 | 
			
		||||
          </el-row>
 | 
			
		||||
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <captcha v-if="enableVerify" @success="doLogin" ref="captchaRef"/>
 | 
			
		||||
 | 
			
		||||
      <footer class="footer">
 | 
			
		||||
        <footer-bar/>
 | 
			
		||||
      </footer>
 | 
			
		||||
      <footer-bar class="footer"/>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
@@ -80,12 +72,6 @@ getSystemInfo().then(res => {
 | 
			
		||||
  ElMessage.error("加载系统配置失败: " + e.message)
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const keyupHandle = (e) => {
 | 
			
		||||
  if (e.key === 'Enter') {
 | 
			
		||||
    login();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const login = function () {
 | 
			
		||||
  if (username.value === '') {
 | 
			
		||||
    return ElMessage.error('请输入用户名');
 | 
			
		||||
 
 | 
			
		||||
@@ -225,7 +225,7 @@ const newChat = (item) => {
 | 
			
		||||
  }
 | 
			
		||||
  showPicker.value = false
 | 
			
		||||
  const options = item.selectedOptions
 | 
			
		||||
  router.push(`/mobile/chat/session?title=新对话&role_id=${options[0].value}&model_id=${options[1].value}&chat_id=0}`)
 | 
			
		||||
  router.push(`/mobile/chat/session?title=新对话&role_id=${options[0].value}&model_id=${options[1].value}`)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const changeChat = (chat) => {
 | 
			
		||||
 
 | 
			
		||||
@@ -123,7 +123,7 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import {nextTick, onMounted, onUnmounted, ref} from "vue";
 | 
			
		||||
import {nextTick, onMounted, onUnmounted, ref, watch} from "vue";
 | 
			
		||||
import {showImagePreview, showNotify, showToast} from "vant";
 | 
			
		||||
import {onBeforeRouteLeave, useRouter} from "vue-router";
 | 
			
		||||
import {processContent, randString, renderInputText, UUID} from "@/utils/libs";
 | 
			
		||||
@@ -135,7 +135,8 @@ import ChatReply from "@/components/mobile/ChatReply.vue";
 | 
			
		||||
import {getSessionId, getUserToken} from "@/store/session";
 | 
			
		||||
import {checkSession} from "@/store/cache";
 | 
			
		||||
import Clipboard from "clipboard";
 | 
			
		||||
import {showLoginDialog} from "@/utils/dialog";
 | 
			
		||||
import { showMessageError} from "@/utils/dialog";
 | 
			
		||||
import {useSharedStore} from "@/store/sharedata";
 | 
			
		||||
 | 
			
		||||
const winHeight = ref(0)
 | 
			
		||||
const navBarRef = ref(null)
 | 
			
		||||
@@ -167,49 +168,51 @@ if (chatId.value) {
 | 
			
		||||
    title.value = res.data.title
 | 
			
		||||
    modelId.value = res.data.model_id
 | 
			
		||||
    roleId.value = res.data.role_id
 | 
			
		||||
    loadModels()
 | 
			
		||||
  }).catch(() => {
 | 
			
		||||
    loadModels()
 | 
			
		||||
  })
 | 
			
		||||
} else {
 | 
			
		||||
  title.value = "新建对话"
 | 
			
		||||
  chatId.value = UUID()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 加载模型
 | 
			
		||||
httpGet('/api/model/list').then(res => {
 | 
			
		||||
  models.value = res.data
 | 
			
		||||
  if (!modelId.value) {
 | 
			
		||||
    modelId.value = models.value[0].id
 | 
			
		||||
  }
 | 
			
		||||
  for (let i = 0; i < models.value.length; i++) {
 | 
			
		||||
    models.value[i].text = models.value[i].name
 | 
			
		||||
    models.value[i].mValue = models.value[i].value
 | 
			
		||||
    models.value[i].value = models.value[i].id
 | 
			
		||||
  }
 | 
			
		||||
  modelValue.value = getModelName(modelId.value)
 | 
			
		||||
  // 加载角色列表
 | 
			
		||||
  httpGet(`/api/app/list/user`).then((res) => {
 | 
			
		||||
    roles.value = res.data;
 | 
			
		||||
    if (!roleId.value) {
 | 
			
		||||
      roleId.value = roles.value[0]['id']
 | 
			
		||||
const loadModels = () => {
 | 
			
		||||
  // 加载模型
 | 
			
		||||
  httpGet('/api/model/list').then(res => {
 | 
			
		||||
    models.value = res.data
 | 
			
		||||
    if (!modelId.value) {
 | 
			
		||||
      modelId.value = models.value[0].id
 | 
			
		||||
    }
 | 
			
		||||
    // build data for role picker
 | 
			
		||||
    for (let i = 0; i < roles.value.length; i++) {
 | 
			
		||||
      roles.value[i].text = roles.value[i].name
 | 
			
		||||
      roles.value[i].value = roles.value[i].id
 | 
			
		||||
      roles.value[i].helloMsg = roles.value[i].hello_msg
 | 
			
		||||
    for (let i = 0; i < models.value.length; i++) {
 | 
			
		||||
      models.value[i].text = models.value[i].name
 | 
			
		||||
      models.value[i].mValue = models.value[i].value
 | 
			
		||||
      models.value[i].value = models.value[i].id
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    role.value = getRoleById(roleId.value)
 | 
			
		||||
    columns.value = [roles.value, models.value]
 | 
			
		||||
    // 新建对话
 | 
			
		||||
    if (!chatId.value) {
 | 
			
		||||
      connect(chatId.value, roleId.value, modelId.value)
 | 
			
		||||
    }
 | 
			
		||||
  }).catch((e) => {
 | 
			
		||||
    showNotify({type: "danger", message: '获取聊天角色失败: ' + e.messages})
 | 
			
		||||
    modelValue.value = getModelName(modelId.value)
 | 
			
		||||
    // 加载角色列表
 | 
			
		||||
    httpGet(`/api/app/list/user`,{id: roleId.value}).then((res) => {
 | 
			
		||||
      roles.value = res.data;
 | 
			
		||||
      if (!roleId.value) {
 | 
			
		||||
        roleId.value = roles.value[0]['id']
 | 
			
		||||
      }
 | 
			
		||||
      // build data for role picker
 | 
			
		||||
      for (let i = 0; i < roles.value.length; i++) {
 | 
			
		||||
        roles.value[i].text = roles.value[i].name
 | 
			
		||||
        roles.value[i].value = roles.value[i].id
 | 
			
		||||
        roles.value[i].helloMsg = roles.value[i].hello_msg
 | 
			
		||||
      }
 | 
			
		||||
      role.value = getRoleById(roleId.value)
 | 
			
		||||
      columns.value = [roles.value, models.value]
 | 
			
		||||
      selectedValues.value = [roleId.value, modelId.value]
 | 
			
		||||
      connect()
 | 
			
		||||
    }).catch((e) => {
 | 
			
		||||
      showNotify({type: "danger", message: '获取聊天角色失败: ' + e.messages})
 | 
			
		||||
    })
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    showNotify({type: "danger", message: "加载模型失败: " + e.message})
 | 
			
		||||
  })
 | 
			
		||||
}).catch(e => {
 | 
			
		||||
  showNotify({type: "danger", message: "加载模型失败: " + e.message})
 | 
			
		||||
})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const url = ref(location.protocol + '//' + location.host + '/mobile/chat/export?chat_id=' + chatId.value)
 | 
			
		||||
 | 
			
		||||
@@ -239,11 +242,12 @@ const newChat = (item) => {
 | 
			
		||||
  roleId.value = options[0].value
 | 
			
		||||
  modelId.value = options[1].value
 | 
			
		||||
  modelValue.value = getModelName(modelId.value)
 | 
			
		||||
  chatId.value = ""
 | 
			
		||||
  chatId.value = UUID()
 | 
			
		||||
  chatData.value = []
 | 
			
		||||
  role.value = getRoleById(roleId.value)
 | 
			
		||||
  title.value = "新建对话"
 | 
			
		||||
  connect(chatId.value, roleId.value, modelId.value)
 | 
			
		||||
  loadHistory.value = true
 | 
			
		||||
  connect()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const chatData = ref([])
 | 
			
		||||
@@ -280,51 +284,60 @@ md.use(mathjaxPlugin)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const onLoad = () => {
 | 
			
		||||
  if (chatId.value) {
 | 
			
		||||
    checkSession().then(() => {
 | 
			
		||||
      httpGet('/api/chat/history?chat_id=' + chatId.value).then(res => {
 | 
			
		||||
        // 加载状态结束
 | 
			
		||||
        finished.value = true;
 | 
			
		||||
        const data = res.data
 | 
			
		||||
        if (data && data.length > 0) {
 | 
			
		||||
          for (let i = 0; i < data.length; i++) {
 | 
			
		||||
            if (data[i].type === "prompt") {
 | 
			
		||||
              chatData.value.push(data[i]);
 | 
			
		||||
              continue;
 | 
			
		||||
            }
 | 
			
		||||
  // checkSession().then(() => {
 | 
			
		||||
  //   connect()
 | 
			
		||||
  // }).catch(() => {
 | 
			
		||||
  // })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
            data[i].orgContent = data[i].content;
 | 
			
		||||
            data[i].content = md.render(processContent(data[i].content))
 | 
			
		||||
            chatData.value.push(data[i]);
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          nextTick(() => {
 | 
			
		||||
            hl.configure({ignoreUnescapedHTML: true})
 | 
			
		||||
            const blocks = document.querySelector("#message-list-box").querySelectorAll('pre code');
 | 
			
		||||
            blocks.forEach((block) => {
 | 
			
		||||
              hl.highlightElement(block)
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
            scrollListBox()
 | 
			
		||||
          })
 | 
			
		||||
        }
 | 
			
		||||
        connect(chatId.value, roleId.value, modelId.value);
 | 
			
		||||
      }).catch(() => {
 | 
			
		||||
        error.value = true
 | 
			
		||||
const loadChatHistory = () => {
 | 
			
		||||
  httpGet('/api/chat/history?chat_id=' + chatId.value).then(res => {
 | 
			
		||||
    const role = getRoleById(roleId.value)
 | 
			
		||||
    // 加载状态结束
 | 
			
		||||
    finished.value = true;
 | 
			
		||||
    const data = res.data
 | 
			
		||||
    if (data.length === 0) {
 | 
			
		||||
      chatData.value.push({
 | 
			
		||||
        type: "reply",
 | 
			
		||||
        id: randString(32),
 | 
			
		||||
        icon: role.icon,
 | 
			
		||||
        content: role.hello_msg,
 | 
			
		||||
        orgContent: role.hello_msg,
 | 
			
		||||
      })
 | 
			
		||||
    }).catch(() => {
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (let i = 0; i < data.length; i++) {
 | 
			
		||||
      if (data[i].type === "prompt") {
 | 
			
		||||
        chatData.value.push(data[i]);
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      data[i].orgContent = data[i].content;
 | 
			
		||||
      data[i].content = md.render(processContent(data[i].content))
 | 
			
		||||
      chatData.value.push(data[i]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    nextTick(() => {
 | 
			
		||||
      hl.configure({ignoreUnescapedHTML: true})
 | 
			
		||||
      const blocks = document.querySelector("#message-list-box").querySelectorAll('pre code');
 | 
			
		||||
      blocks.forEach((block) => {
 | 
			
		||||
        hl.highlightElement(block)
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      scrollListBox()
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
  }).catch(() => {
 | 
			
		||||
    error.value = true
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 离开页面时主动关闭 websocket 连接,节省网络资源
 | 
			
		||||
onBeforeRouteLeave(() => {
 | 
			
		||||
  if (socket.value !== null) {
 | 
			
		||||
    activelyClose.value = true;
 | 
			
		||||
    clearTimeout(heartbeatHandle.value)
 | 
			
		||||
    socket.value.close();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
// 创建 socket 连接
 | 
			
		||||
@@ -334,16 +347,15 @@ const showReGenerate = ref(false); // 重新生成
 | 
			
		||||
const previousText = ref(''); // 上一次提问
 | 
			
		||||
const lineBuffer = ref(''); // 输出缓冲行
 | 
			
		||||
const socket = ref(null);
 | 
			
		||||
const activelyClose = ref(false); // 主动关闭
 | 
			
		||||
const canSend = ref(true);
 | 
			
		||||
const heartbeatHandle = ref(null)
 | 
			
		||||
const connect = function (chat_id, role_id, model_id) {
 | 
			
		||||
  let isNewChat = false;
 | 
			
		||||
  if (!chat_id) {
 | 
			
		||||
    isNewChat = true;
 | 
			
		||||
    chat_id = UUID();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
const canSend = ref(true)
 | 
			
		||||
const isNewMsg = ref(true)
 | 
			
		||||
const loadHistory = ref(true)
 | 
			
		||||
const store = useSharedStore()
 | 
			
		||||
const stream = ref(store.chatStream)
 | 
			
		||||
watch(() => store.chatStream, (newValue) => {
 | 
			
		||||
  stream.value = newValue
 | 
			
		||||
});
 | 
			
		||||
const connect = function () {
 | 
			
		||||
  // 初始化 WebSocket 对象
 | 
			
		||||
  const _sessionId = getSessionId();
 | 
			
		||||
  let host = process.env.VUE_APP_WS_HOST
 | 
			
		||||
@@ -354,38 +366,15 @@ const connect = function (chat_id, role_id, model_id) {
 | 
			
		||||
      host = 'ws://' + location.host;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 心跳函数
 | 
			
		||||
  const sendHeartbeat = () => {
 | 
			
		||||
    if (socket.value !== null) {
 | 
			
		||||
      new Promise((resolve) => {
 | 
			
		||||
        socket.value.send(JSON.stringify({type: "heartbeat", content: "ping"}))
 | 
			
		||||
        resolve("success")
 | 
			
		||||
      }).then(() => {
 | 
			
		||||
        heartbeatHandle.value = setTimeout(() => sendHeartbeat(), 5000)
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const _socket = new WebSocket(host + `/api/chat/new?session_id=${_sessionId}&role_id=${role_id}&chat_id=${chat_id}&model_id=${model_id}&token=${getUserToken()}`);
 | 
			
		||||
  const _socket = new WebSocket(host + `/api/chat/new?session_id=${_sessionId}&role_id=${roleId.value}&chat_id=${chatId.value}&model_id=${modelId.value}&token=${getUserToken()}`);
 | 
			
		||||
  _socket.addEventListener('open', () => {
 | 
			
		||||
    loading.value = false
 | 
			
		||||
    previousText.value = '';
 | 
			
		||||
    canSend.value = true;
 | 
			
		||||
    activelyClose.value = false;
 | 
			
		||||
 | 
			
		||||
    if (isNewChat) { // 加载打招呼信息
 | 
			
		||||
      chatData.value.push({
 | 
			
		||||
        type: "reply",
 | 
			
		||||
        id: randString(32),
 | 
			
		||||
        icon: role.value.icon,
 | 
			
		||||
        content: role.value.hello_msg,
 | 
			
		||||
        orgContent: role.value.hello_msg,
 | 
			
		||||
      })
 | 
			
		||||
    if (loadHistory.value) { // 加载历史消息
 | 
			
		||||
     loadChatHistory()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 发送心跳消息
 | 
			
		||||
    sendHeartbeat()
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  _socket.addEventListener('message', event => {
 | 
			
		||||
@@ -394,20 +383,27 @@ const connect = function (chat_id, role_id, model_id) {
 | 
			
		||||
      reader.readAsText(event.data, "UTF-8");
 | 
			
		||||
      reader.onload = () => {
 | 
			
		||||
        const data = JSON.parse(String(reader.result));
 | 
			
		||||
        if (data.type === 'start') {
 | 
			
		||||
        if (data.type === 'error') {
 | 
			
		||||
          showMessageError(data.message)
 | 
			
		||||
          return
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (isNewMsg.value && data.type !== 'end') {
 | 
			
		||||
          chatData.value.push({
 | 
			
		||||
            type: "reply",
 | 
			
		||||
            id: randString(32),
 | 
			
		||||
            icon: role.value.icon,
 | 
			
		||||
            content: ""
 | 
			
		||||
            content: data.content
 | 
			
		||||
          });
 | 
			
		||||
          if (isNewChat) {
 | 
			
		||||
          if (!title.value) {
 | 
			
		||||
            title.value = previousText.value
 | 
			
		||||
          }
 | 
			
		||||
          lineBuffer.value = data.content;
 | 
			
		||||
          isNewMsg.value = false
 | 
			
		||||
        } else if (data.type === 'end') { // 消息接收完毕
 | 
			
		||||
          enableInput()
 | 
			
		||||
          lineBuffer.value = ''; // 清空缓冲
 | 
			
		||||
 | 
			
		||||
          isNewMsg.value = true
 | 
			
		||||
        } else {
 | 
			
		||||
          lineBuffer.value += data.content;
 | 
			
		||||
          const reply = chatData.value[chatData.value.length - 1]
 | 
			
		||||
@@ -443,17 +439,11 @@ const connect = function (chat_id, role_id, model_id) {
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  _socket.addEventListener('close', () => {
 | 
			
		||||
    if (activelyClose.value || socket.value === null) { // 忽略主动关闭
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    // 停止发送消息
 | 
			
		||||
    canSend.value = true;
 | 
			
		||||
    canSend.value = true
 | 
			
		||||
    loadHistory.value = false
 | 
			
		||||
    // 重连
 | 
			
		||||
    checkSession().then(() => {
 | 
			
		||||
      connect(chat_id, role_id, model_id)
 | 
			
		||||
    }).catch(() => {
 | 
			
		||||
      showLoginDialog(router)
 | 
			
		||||
    });
 | 
			
		||||
    connect()
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  socket.value = _socket;
 | 
			
		||||
@@ -501,7 +491,7 @@ const sendMessage = () => {
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  disableInput(false)
 | 
			
		||||
  socket.value.send(JSON.stringify({type: "chat", content: prompt.value}));
 | 
			
		||||
  socket.value.send(JSON.stringify({stream: stream.value, content: prompt.value}));
 | 
			
		||||
  previousText.value = prompt.value;
 | 
			
		||||
  prompt.value = '';
 | 
			
		||||
  return true;
 | 
			
		||||
@@ -524,7 +514,7 @@ const reGenerate = () => {
 | 
			
		||||
    icon: loginUser.value.avatar,
 | 
			
		||||
    content: renderInputText(text)
 | 
			
		||||
  });
 | 
			
		||||
  socket.value.send(JSON.stringify({type: "chat", content: previousText.value}));
 | 
			
		||||
  socket.value.send(JSON.stringify({stream: stream.value, content: previousText.value}));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const showShare = ref(false)
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user