Kaynağa Gözat

feat(sidepanel): 重构聊天功能并添加文件上传支持

- 更新 API 地址和客户端 ID
- 添加文件上传接口和相关逻辑
- 重构聊天消息处理流程,支持系统消息和大模型回复
- 优化消息列表渲染和滚动逻辑
- 调整用户认证方式,使用用户信息 ID 替代 token
- 重新组织消息类型和处理策略
chd 4 ay önce
ebeveyn
işleme
5ce26f7291

+ 2 - 2
.env

@@ -1,5 +1,5 @@
 VITE_OPENAI_API_KEY_TONG=sk-e9855234f47346049809ce23ed3ebe3f
 VITE_MAX_FILE_NUMBER=10
-VITE_APP_BASE_API='http://192.168.1.118:7777'
+VITE_APP_BASE_API='http://192.168.1.166:7777'
 # 终端ID
-VITE_CLIENT_ID = '153062e567553ec0bf1a02b8f1f563b1'
+VITE_CLIENT_ID = '765be25e4e78b101b896cb3ecac39b1b'

+ 11 - 0
src/api/index.js

@@ -102,3 +102,14 @@ export function getSmsCaptcha(data) {
         data
     })
 }
+
+export function uploadFile(data) {
+    return request({
+        url: `/messages/file/addFile`,
+        method: 'post',
+        data: data,
+        headers: {
+            'Content-Type': 'multipart/form-data'
+        },
+    })
+}

+ 9 - 0
src/api/modal.js

@@ -0,0 +1,9 @@
+import request from '@/utils/request'
+
+export function askQues(data) {
+    return request({
+        url: '/ai/model/question',
+        method: 'get',
+        data: data
+    })
+}

+ 81 - 95
src/entrypoints/sidepanel/Chat.vue

@@ -14,7 +14,7 @@
         </div>
         // 添加一个 ref 用于获取消息列表容器
         const messagesContainer = ref(null)
-        
+
         // 修改模板中的消息列表 div,添加 ref
         <div class="messages" ref="messagesContainer">
           <div v-for="(message, index) in messages" :key="index"
@@ -62,7 +62,11 @@
           <div class="card_list">
             <div v-for="(v, i) in pageInfoList" :key="i"
               :class="`card-content ${pageInfoList.length > 1 ? 'card_width' : ''}`">
-              <img :src="v?.favIconUrl" style="width: 24px;display: block" />
+              <div class="loading-more-indicator" v-if="v.loading">
+                <div class="loading-spinner"></div>
+              </div>
+              <img v-else :src="v?.favIconUrl" style="width: 24px;display: block" />
+
               <div class="title-wrapper">
                 <span class="els title-scroller">{{ v?.title }}</span>
                 <span class="els url-scroller">{{ v?.url }}</span>
@@ -93,7 +97,6 @@
             @click="handleStopAsk">
             <svg-icon icon-class="stop" color="red" />
           </el-button>
-
         </div>
       </div>
 
@@ -116,7 +119,8 @@ import {
   getFileContent,
   buildObjPrompt,
   modelFileUpload,
-  controllerList
+  controllerList,
+  formatMessage
 } from '@/entrypoints/sidepanel/utils/ai-service.js'
 import { storeToRefs } from 'pinia'
 import { ElMessage } from 'element-plus'
@@ -134,13 +138,15 @@ import { useAutoResizeTextarea } from '@/entrypoints/sidepanel/hook/useAutoResiz
 import { getPageInfo, getXlsxValue, handleInput } from './utils/index.js'
 import { useMsgStore } from '@/store/modules/msg.ts'
 import { useUserStore } from '@/store/modules/user'
-import { putChat } from "@/api/index.js";
+import { putChat, uploadFile } from "@/api/index.js";
+import {askQues} from '@/api/modal.js'
 import {debounce} from 'lodash'
 const userStore = useUserStore()
 import { getChatDetail } from '@/api/index.js'
 // 在其他状态变量附近添加
 const isLoadingMore = ref(false)
 import { v4 as uuidv4 } from 'uuid';
+import content from '../content.js'
 // 滚动条引用
 const scrollbar = ref(null)
 const scrollToBottomRef = ref(null)
@@ -152,7 +158,7 @@ const drawerRef = useTemplateRef('historyComponentRef')
 const msgStore = useMsgStore()
 const { pageInfoList, messages, msgUuid, AIModel,page,hasNext } = storeToRefs(useMsgStore())
 const formInfo = ref('')
-
+const uploadLoading = ref(false)
 const {
   taklToHtml,
   sendLoading,
@@ -226,7 +232,7 @@ async function addMessage(msg, raw, type) {
     id:+new Date(),
     type: type || '',
     rawContent: raw ?? msg,
-    senderId: userStore.token,
+    senderId: userStore.userInfo.id,
     receiverId:-1, //大模型
     content: msg,
     sortKey: moment().valueOf(),
@@ -248,7 +254,7 @@ async function addMessage(msg, raw, type) {
 
 const handleSummary = async () => {
   let params = []
-  if (AIModel.value.file === true) {
+  if (1) {
     pageInfoList.value.forEach((pageInfo) => {
       params.push({ role: 'system', content: `fileid://${pageInfo.fileId}` })
     })
@@ -376,21 +382,49 @@ async function handleAsk() {
   inputMessage.value = ''
   if (sendLoading.value) return
   const msg = await addMessage(str)
-  putChat(msg)
-  if (type.value === FunctionList.Intelligent_Form_filling) {
-    const res = await fetchRes(str)
-    if (res.status === 'ok') {
-      formInfo.value = res.data
-      console.log(res, 55558)
-    } else {
-      type.value = FunctionList.File_Operation
-      isShowPage.value = false
-      taklToHtml.value = false
-      pageInfoList.value = []
-    }
-  } else streamRes(taklToHtml.value)
+  await putChat(msg)
+      const obj = reactive({
+      type: type || '',
+      rawContent:'',
+      senderId: -1,
+      receiverId: userStore.userInfo.id, //大模型
+      content: '',
+      sortKey: moment().valueOf(),
+      role: 'system',
+      conversationId: msgUuid.value,
+      })
+  sendLoading.value = true
+    
+  messages.value.push(obj)
+    scrollbar.value?.setScrollTop(99999)
+  const res = await askQues({
+    conversationId: msgUuid.value,
+    modelName: '通义千问-Max',
+    question: msg.rawContent,
+    id:'699637194561691650'
+  })
+  const result = res.data[res.data.length - 1].content
+  obj.content = formatMessage(result)
+  obj.rawContent = result
+    putChat({
+      ...obj,
+      content:''
+    })
+  sendLoading.value = false
+    scrollbar.value?.setScrollTop(99999)
+  // if (type.value === FunctionList.Intelligent_Form_filling) {
+  //   const res = await fetchRes(str)
+  //   if (res.status === 'ok') {
+  //     formInfo.value = res.data
+  //     console.log(res, 55558)
+  //   } else {
+  //     type.value = FunctionList.File_Operation
+  //     isShowPage.value = false
+  //     taklToHtml.value = false
+  //     pageInfoList.value = []
+  //   }
+  // } else streamRes(taklToHtml.value)
 }
-
 function handleCapture() {
   ElMessage({
     message: '开发中...',
@@ -402,83 +436,35 @@ function handleCapture() {
 function hisRecords() {
   drawerRef.value.drawer = true
 }
-const handleUpload = async (file) => {
-  if (AIModel.value.file === true) {
-    const id = await modelFileUpload(file)
-    pageInfoList.value.unshift({
-      fileId: id,
-      title: file.name,
-      url: 'File',
-      favIconUrl: fileLogo
-    })
-    isShowPage.value = true
-    taklToHtml.value = true
-    return
-  }
+const createFileObj = async (file) => {
+  let formData = new FormData()
+  formData.append('avatarFile', file)
+  handleUpload(formData,file.name)
+}
+const handleUpload = async (formData,name) => {
   if (type.value === FunctionList.Intelligent_Form_filling) {
-    const fileExtension = file.name.split('.').pop().toLowerCase()
-    if (fileExtension === 'xlsx') {
-      const readData = await getXlsxValue(file)
-      readData[0].forEach((header, i) => {
-        // if (!xlsxData.value[header]) xlsxData.value[header] = []
-        xlsxData.value[header] = readData[1][i]
-      })
-      const msg = addMessage(`已上传文件:${file.name}`, buildExcelUnderstandingPrompt(readData[0], file?.name, formInfo.value))
-      putChat(msg)
-      const { rawContent, status } = await streamRes()
-      if (status === 'ok') {
-        let form = []
-        if (rawContent.includes('json')) form = JSON.parse(rawContent.split('json')[1].split('```')[0])
-        else form = JSON.parse(rawContent)
-        handleInput(xlsxData.value, form)
-      }
-    } else {
-      const msg = await addMessage(`文件上传中`)
-      try {
-        sendLoading.value = true
-        putChat(msg)
-        const data = await getFileValue(file)
-        msg.content = `已上传文件:${file.name}`
-        msg.rawContent = data
-        const res2 = await getFormKeyAndValue(data, formInfo.value)
-        xlsxData.value = res2.data
-        msg.rawContent = buildObjPrompt(res2.data, formInfo.value)
-        const { rawContent, status } = await streamRes()
-        if (status === 'ok') {
-          let form = []
-          if (rawContent.includes('json')) form = JSON.parse(rawContent.split('json')[1].split('```')[0])
-          else form = JSON.parse(rawContent)
-          handleInput(xlsxData.value, form)
-        }
-      } catch (error) {
-        msg.content = '文件解析出错'
-      } finally {
-        sendLoading.value = false
-        // putChat({
-        //   id: msgUuid.value,
-        //   msg: msg
-        // })
-      }
-    }
-    type.value = FunctionList.File_Operation
-    isShowPage.value = false
-    taklToHtml.value = false
     pageInfoList.value = []
-    return
+  }
+  isShowPage.value = true
+  const obj = reactive({
+    title: name,
+    url: '上传中...',
+    favIconUrl: fileLogo,
+    loading: true,
+    content: {
+      mainContent: ''
+    }
+  })
+  pageInfoList.value.push(obj)
+  const res = await uploadFile(formData)
+  if (type.value === FunctionList.Intelligent_Form_filling) {
+    obj.url = '解析中...'
+
   }
   if (type.value === FunctionList.File_Operation) {
-    let formData = new FormData()
-    formData.append('file', file)
-    const res = await getFileContent(formData)
-    pageInfoList.value.unshift({
-      title: file.name,
-      url: 'File',
-      favIconUrl: fileLogo,
-      content: {
-        mainContent: res.data
-      }
-    })
-    isShowPage.value = true
+    obj.loading = false
+    obj.url = res.data.url
+
   }
 }
 async function getFileValue(file) {

+ 2 - 2
src/entrypoints/sidepanel/component/tools.vue

@@ -25,12 +25,12 @@ watchEffect(() => {
         </el-option-group>
       </el-select>
       <el-tooltip effect="dark" content="阅读此页,开启后将会根据左侧网页中的内容做出回答" placement="top">
-        <el-button v-permission="['agent:summary']" class="tools_btn" link :icon="Reading" @click="emit('readClick')" />
+        <el-button class="tools_btn" link :icon="Reading" @click="emit('readClick')" />
       </el-tooltip>
       <el-upload style="display:inline-block" :before-upload="(file: any) => emit('uploadFile', file)" :multiple="false"
         name="file" :show-file-list="false" :accept="'.xlsx,.pdf,.doc,.docx'">
         <el-tooltip effect="dark" content="文件上传" placement="top">
-          <el-button v-permission="['agent:upload']" class="tools_btn" link :icon="Paperclip" />
+          <el-button class="tools_btn" link :icon="Paperclip" />
         </el-tooltip>
       </el-upload>
       <!-- <el-tooltip effect="dark" content="截屏" placement="top">

+ 3 - 3
src/entrypoints/sidepanel/hook/useMsg.ts

@@ -113,7 +113,7 @@ export function useMsg(scrollbar?: any) {
     type: type || '',
     rawContent: '',
     senderId: -1,
-    receiverId: userStore.token, //大模型
+      receiverId: userStore.userInfo.id, //大模型
     content: '',
       sortKey: moment().valueOf(),
     role: 'system',
@@ -231,7 +231,7 @@ export function useMsg(scrollbar?: any) {
       type: type || '',
       rawContent: '',
       senderId: -1,
-      receiverId: userStore.token, 
+      receiverId: userStore.userInfo.id, 
       content: '',
       sortKey: moment().valueOf(),
       role: 'system',
@@ -318,7 +318,7 @@ export function useMsg(scrollbar?: any) {
       type: type || '',
       rawContent: '',
       senderId: -1,
-      receiverId: userStore.token, //大模型
+      receiverId: userStore.userInfo.id, //大模型
       content: '',
       sortKey: moment().valueOf(),
       role: 'system',

+ 7 - 1
src/store/modules/msg.ts

@@ -4,6 +4,7 @@ import { getChatDetail } from '@/api/index'
 import {
   formatMessage,
 } from '@/entrypoints/sidepanel/utils/ai-service.js'
+import { v4 as uuidv4 } from 'uuid';
 export const useMsgStore = defineStore('msg', {
   state: () => ({
     msgUuid: <string>'',
@@ -31,7 +32,9 @@ export const useMsgStore = defineStore('msg', {
           sort: 'sortKey,desc',
           conversationId: this.msgUuid
         })
-        this.messages = res.list.reverse().map(_ => ({
+        console.log(res);
+        
+        this.messages = res.data.list.reverse().map(_ => ({
           id:_.id,
           type: _.type,
           rawContent: _.rawContent,
@@ -48,6 +51,9 @@ export const useMsgStore = defineStore('msg', {
           this.page++
         }
         else this.hasNext = false
+      } else {
+        this.msgUuid = uuidv4()
+        chrome.storage.local.set({ msgUuid: this.msgUuid })
       }
     },