Procházet zdrojové kódy

feat(sidepanel): 实现聊天记录持久化和加载更多功能

- 将聊天记录保存到后端服务器
- 实现加载更多历史消息的功能
- 优化消息列表渲染逻辑
- 添加用户登录和登出的处理
- 调整 API 接口调用方式
chd před 8 měsíci
rodič
revize
0a9d886c5a

+ 1 - 1
.env

@@ -1,3 +1,3 @@
 VITE_OPENAI_API_KEY_TONG=sk-e9855234f47346049809ce23ed3ebe3f
 VITE_MAX_FILE_NUMBER=10
-VITE_APP_BASE_API='http://192.168.1.100:8088'
+VITE_APP_BASE_API='http://192.168.1.166:7777'

+ 1 - 0
package.json

@@ -25,6 +25,7 @@
     "openai": "^4.85.4",
     "pinia": "^3.0.1",
     "sass": "^1.85.1",
+    "uuid": "^11.1.0",
     "vant": "^4.9.17",
     "vite-plugin-svg-icons": "^2.0.1",
     "vue": "^3.5.12",

+ 8 - 8
src/api/index.js

@@ -1,31 +1,31 @@
 import request from '@/utils/request'
 export function getChatList(data) {
     return request({
-        url: '/statistics/frontList',
-        method: 'post',
+        url: '/messages/history/selConversationList',
+        method: 'get',
         data: data
     })
 }
 export function getChatDetail(data) {
     return request({
-        url: '/statistics/frontList',
-        method: 'post',
+        url: '/messages/history/pageList',
+        method: 'get',
         data: data
     })
 }
 
 export function deleteChat(data) {
     return request({
-        url: '/statistics/frontList',
-        method: 'delete',
+        url: '/messages/history/delMessages',
+        method: 'post',
         data: data
     })
 }
 
 export function putChat(data) {
     return request({
-        url: '/statistics/frontList',
-        method: 'put',
+        url: '/messages/history',
+        method: 'post',
         data: data
     })
 }

+ 10 - 1
src/entrypoints/background.js

@@ -1,6 +1,7 @@
 export default defineBackground(() => {
   let currentTabId
   let arr = []
+  let token = ''
   const url = 'http://192.168.1.166:7777/behavior/user/adds'
   // Executed when background is loaded
   chrome.sidePanel
@@ -141,7 +142,15 @@ export default defineBackground(() => {
     console.log('[Storage Changed] Area:', areaName);
     // 遍历所有被修改的键
     for (const [key, { oldValue, newValue }] of Object.entries(changes)) {
-      if (key === 'token') token = newValue
+      if (key === 'token') {
+        token = newValue
+        if (!newValue) {
+          chrome.runtime.sendMessage({
+            type: 'USER_LOGOUT',
+          }, function (response) {
+          });
+        }
+      }
     }
   });
   chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {

+ 6 - 2
src/entrypoints/content.js

@@ -36,9 +36,11 @@ export default defineContentScript({
         chrome.runtime.sendMessage({
           type: 'CLICK_EVENT',
           data: {
-            outerHTML: cleanPage2(e.target),
+            outerHtml: cleanPage2(e.target),
             innerText: e.target.innerText,
             url: window.location.href,
+            tag: e.target.tagName.toLowerCase(),
+            clickTime: +new Date()
           }
         }, function (response) {
         });
@@ -57,9 +59,11 @@ export default defineContentScript({
       chrome.runtime.sendMessage({
         type: 'CLICK_EVENT',
         data: {
-          outerHTML: cleanPage2(e.target),
+          outerHtml: cleanPage2(e.target),
           innerText: e.target.innerText,
           url: window.location.href,
+          tag: e.target.tagName.toLowerCase(),
+          clickTime: +new Date()
         }
       }, function (response) {
       });

+ 5 - 10
src/entrypoints/sidepanel/App.vue

@@ -17,21 +17,16 @@ import { useIndexedDB } from '@/entrypoints/sidepanel/hook/useIndexedDB'
 import { useUserStore } from '@/store/modules/user'
 
 const userStore = useUserStore()
-
-const { openDB, ...args } = useIndexedDB({
-  dbName: 'chatDB',
-  version: localStorage.getItem('dbVersion') ? Number(localStorage.getItem('dbVersion')) : 1
-})
-
 // 提供实例给子组件
-provide('indexedDBHook', args)
 
 onMounted(async () => {
-  console.log(userStore);
   await userStore.initStore()
-
+  chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
+    if (message.type === 'USER_LOGOUT') {
+      userStore.isLogin &&  userStore.logout()
+    }
+  })
   // 初始化数据库连接(不包含任何Store)
-  openDB()
   chrome.runtime.sendMessage({
     type: 'GET_CONTENT_INFO'
   })

+ 105 - 67
src/entrypoints/sidepanel/Chat.vue

@@ -7,7 +7,16 @@
     <!-- 消息列表 -->
     <div class="message-list" v-else>
       <el-scrollbar ref="scrollbar" @scroll="handleScroll">
-        <div class="messages">
+        <!-- 加载更多指示器 -->
+        <div v-if="isLoadingMore" class="loading-more-indicator">
+          <div class="loading-spinner"></div>
+          <span>加载更多消息...</span>
+        </div>
+        // 添加一个 ref 用于获取消息列表容器
+        const messagesContainer = ref(null)
+        
+        // 修改模板中的消息列表 div,添加 ref
+        <div class="messages" ref="messagesContainer">
           <div v-for="(message, index) in messages" :key="index"
             :class="['message-item', message.role === 'user'  ? 'self' : 'other']">
             <el-avatar :size="32" :src="message.role === 'user' ? userAvatar : avatar" />
@@ -32,7 +41,7 @@
                   <span class="dot"></span>
                 </span>
               </div>
-              <div class="timestamp ">{{ moment(message.timestamp).format('MM-DD HH:mm') }}
+              <div class="timestamp ">{{ moment(message.sortKey).format('MM-DD HH:mm') }}
                 <span v-if="message.add" style="cursor: pointer;" @click="handleInput">填充</span>
               </div>
             </div>
@@ -100,7 +109,6 @@
 import { ref, onMounted, nextTick, inject, useTemplateRef, reactive } from 'vue'
 import { ElScrollbar, ElAvatar, ElInput, ElButton } from 'element-plus'
 import moment from 'moment'
-import { cloneDeep } from 'lodash'
 import fileLogo from '@/assets/svg/file.svg'
 import {
   buildExcelUnderstandingPrompt,
@@ -125,8 +133,14 @@ import { mockData, startMsg, mockData2, options, FunctionList } from '@/entrypoi
 import { useAutoResizeTextarea } from '@/entrypoints/sidepanel/hook/useAutoResizeTextarea.ts'
 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 {debounce} from 'lodash'
+const userStore = useUserStore()
 import { getChatDetail } from '@/api/index.js'
-
+// 在其他状态变量附近添加
+const isLoadingMore = ref(false)
+import { v4 as uuidv4 } from 'uuid';
 // 滚动条引用
 const scrollbar = ref(null)
 const scrollToBottomRef = ref(null)
@@ -135,9 +149,8 @@ const isShowPage = ref(false)
 const tareRef = useTemplateRef('textareaRef')
 const drawerRef = useTemplateRef('historyComponentRef')
 // 获取父组件提供的 Hook 实例
-const { registerStore, useStore } = inject('indexedDBHook')
 const msgStore = useMsgStore()
-const { pageInfoList, messages, msgUuid, AIModel } = storeToRefs(useMsgStore())
+const { pageInfoList, messages, msgUuid, AIModel,page,hasNext } = storeToRefs(useMsgStore())
 const formInfo = ref('')
 
 const {
@@ -164,13 +177,44 @@ function handleStopAsk() {
   sendLoading.value = false
 }
 
-function handleScroll({ scrollTop }) {
-  // scrollTop 滚动条的位置
+function handleScroll(a) {
   const { wrapRef } = scrollbar.value
   const { scrollHeight, clientHeight } = wrapRef
-  scrollToBottomRef.value.showButton = scrollHeight - scrollTop - clientHeight >= 350
+  scrollToBottomRef.value.showButton = scrollHeight - a.scrollTop - clientHeight >= 350
+  
+  // 检测是否滚动到顶部
+  if (a.scrollTop <= 10 && hasNext.value) {
+    handleScrollToTop()
+  }
 }
-
+const messagesContainer = ref(null)
+let innerText = ''
+// 处理滚动到顶部的事件
+const handleScrollToTop = debounce(async function () {
+  console.log('滚动到顶部,可以加载更多历史消息')
+  if (!sendLoading.value && !isLoadingMore.value) {
+    isLoadingMore.value = true
+    try {
+      // 记录当前第一条消息的位置
+      const firstMessage = messagesContainer.value?.firstElementChild.querySelector('.timestamp')
+      innerText = firstMessage.innerText
+      const oldHeight = firstMessage?.offsetTop || 0
+      await msgStore.changePage()
+      setTimeout(() => {
+        if (firstMessage) {
+          console.log([...messagesContainer.value.querySelectorAll('.timestamp')].find(_ => _.innerText === innerText).offsetTop, innerText, 778);
+          const newHeight = [...messagesContainer.value.querySelectorAll('.timestamp')].find(_ => _.innerText === innerText).offsetTop
+          const scrollOffset = newHeight - oldHeight
+          scrollbar.value?.setScrollTop(scrollOffset)
+        }
+      }, 400);
+      // 恢复滚动位置
+     
+    } finally {
+      isLoadingMore.value = false
+    }
+  }
+}, 500, { leading: false, trailing: true })
 /**
  * @param {string} msg
  * @param {string} raw
@@ -178,31 +222,24 @@ function handleScroll({ scrollTop }) {
  * **/
 async function addMessage(msg, raw, type) {
   console.log(msg);
-
-  // 添加indexDB Store配置
-  if (msgUuid.value === '') {
-    msgUuid.value = 'D' + Date.now().toString()
-    await registerStore({
-      name: msgUuid.value,
-      keyPath: 'id'
-    })
-  }
   const newMessage = reactive({
+    id:+new Date(),
     type: type || '',
     rawContent: raw ?? msg,
+    senderId: userStore.token,
+    receiverId:-1, //大模型
     content: msg,
-    timestamp: moment().format('YYYY-MM-DD HH:mm:ss'),
-    role:'user',
-    addToHistory: !taklToHtml.value
+    sortKey: moment().valueOf(),
+    role: 'user',
+    conversationId:msgUuid.value,
+    addToHistory: `${!taklToHtml.value}`
   })
   if (type === 'document') {
     newMessage.content = msg
     newMessage.rawContent = raw
   }
   if (!msg) return
-
   messages.value.push(newMessage)
-  useStore(msgUuid.value).add(cloneDeep(newMessage))
   await nextTick(() => {
     scrollbar.value?.setScrollTop(99999)
   })
@@ -231,21 +268,18 @@ const handleSummary = async () => {
     })
     params = [{
       role: 'user',
-      content: `请根据以下这几内容综合总结出结果:
+      content: `请根据以下这几内容综合总结出结果:
       ${tempStr}要求:
       1. 用简洁清晰的语言提取所有的核心要点
       2. 保持客观中立的语气
       3. 按重要性排序
       4. 返回内容做好换行,以及展示样式
       5. 请以"以下是对该文件内容的总结:"开头,然后用要点的形式列出主要内容。
-      6. 对这几内容进行综合分析及联想`
+      6. 对这几内容进行综合分析及联想`
     }]
   }
   const msg = await addMessage(JSON.stringify(pageInfoList.value), JSON.stringify(params.map(_ => _.content)), 'document')
-  // putChat({
-  //   id: msgUuid.value,
-  //   msg: msg
-  // })
+  putChat(msg)
   if (requestFlowFn) {
     isShowPage.value = false
     taklToHtml.value = false
@@ -279,28 +313,18 @@ function handleCurrentChange(e) {
   }
 }
 
-function handleCurrentData(e) {
+async function handleCurrentData(e) {
   drawerRef.value.drawer = false
   if (!e) {
     addNewDialogue()
     return
   }
-  // 添加indexDB Store配置
+  messages.value = []
+  page.value = 1
   msgUuid.value = e
-  // getChatDetail(e).then(res => {
-  //   messages.value = reds
-  //   nextTick(() => {
-  //     scrollbar.value?.setScrollTop(99999)
-  //   })
-  // }).finally(res => {
-
-  // })
-  useStore(e).getAll().then((res) => {
-    messages.value = res
-    nextTick(() => {
-      scrollbar.value?.setScrollTop(99999)
-    })
-  })
+  chrome.storage.local.set({ msgUuid: msgUuid.value })
+  await msgStore.initMsg()
+  scrollbar.value?.setScrollTop(99999)
 }
 
 async function readClick() {
@@ -314,11 +338,7 @@ async function readClick() {
     ElMessage.warning(`最多添加${import.meta.env.VITE_MAX_FILE_NUMBER}个文件`)
     return
   }
-  console.log(45454);
-  
   const tempPageInfo = await getPageInfo()
-  console.log(tempPageInfo);
-  
   if (AIModel.value.file === true) {
     const blob = new Blob([tempPageInfo.content.mainContent], { type: 'text/plain' })
     const file = new File([blob], tempPageInfo.title + '.txt', { type: 'text/plain' })
@@ -338,19 +358,17 @@ function deletePageInfo(i) {
 }
 
 function addNewDialogue() {
-  // if (messages.value.length === 0) {
-  //   ElMessage.warning('已经是新对话')
-  //   return
-  // }
-  if (msgUuid.value === '') {
+  if (messages.value.length === 0) {
     ElMessage.warning('已经是新对话')
     return
   }
   isShowPage.value = false
   taklToHtml.value = false
   messages.value = []
+  page.value = 1
   pageInfoList.value = []
-  msgUuid.value = ''
+  msgUuid.value = uuidv4()
+  chrome.storage.local.set({ msgUuid: msgUuid.value })
 }
 
 async function handleAsk() {
@@ -358,14 +376,9 @@ async function handleAsk() {
   inputMessage.value = ''
   if (sendLoading.value) return
   const msg = await addMessage(str)
-  // putChat({
-  //   id: msgUuid.value,
-  //   msg: msg
-  // })
+  putChat(msg)
   if (type.value === FunctionList.Intelligent_Form_filling) {
     const res = await fetchRes(str)
-    console.log(res);
-
     if (res.status === 'ok') {
       formInfo.value = res.data
       console.log(res, 55558)
@@ -411,10 +424,7 @@ const handleUpload = async (file) => {
         xlsxData.value[header] = readData[1][i]
       })
       const msg = addMessage(`已上传文件:${file.name}`, buildExcelUnderstandingPrompt(readData[0], file?.name, formInfo.value))
-      // putChat({
-      //   id: msgUuid.value,
-      //   msg: msg
-      // })
+      putChat(msg)
       const { rawContent, status } = await streamRes()
       if (status === 'ok') {
         let form = []
@@ -426,6 +436,7 @@ const handleUpload = async (file) => {
       try {
         sendLoading.value = true
         const msg = await addMessage(`文件上传中`)
+        putChat(msg)
         const data = await getFileValue(file)
         msg.content = `已上传文件:${file.name}`
         msg.rawContent = data
@@ -476,9 +487,11 @@ async function getFileValue(file) {
   const res = await getFileContent(formData)
   return res.data
 }
+let a = null
 // 组件挂载时滚动到底部
-onMounted(() => {
+onMounted(async () => {
   msgStore.updateAIModel(options[0].options[0])
+  await msgStore.initMsg()
   useAutoResizeTextarea(tareRef, inputMessage)
   chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
     if (message.type === 'TO_SIDE_PANEL_PAGE_INFO') {
@@ -501,4 +514,29 @@ onMounted(() => {
 
 <style lang="scss" scoped>
 @use '@/entrypoints/sidepanel/css/chat.scss';
+
+.loading-more-indicator {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 10px 0;
+  color: #909399;
+  font-size: 14px;
+  
+  .loading-spinner {
+    width: 20px;
+    height: 20px;
+    margin-right: 8px;
+    border: 2px solid #e6e6e6;
+    border-top-color: #4d6bfe;
+    border-radius: 50%;
+    animation: spin 1s linear infinite;
+  }
+}
+
+@keyframes spin {
+  to {
+    transform: rotate(360deg);
+  }
+}
 </style>

+ 38 - 151
src/entrypoints/sidepanel/component/historyComponent.vue

@@ -12,9 +12,8 @@ const input = ref('')
 const loading = ref(false)
 const dataList = ref<any[]>([])
 // 获取父组件提供的 Hook 实例
-const { db, useStore, deleteDB } = inject('indexedDBHook') as any
 const emit = defineEmits(['currentData'])
-const { msgUuid, messages } = storeToRefs(useMsgStore())
+const { msgUuid, messages ,page} = storeToRefs(useMsgStore())
 const props = defineProps({
   msgUuid: {
     type: String,
@@ -25,101 +24,16 @@ watch(drawer, (newVal) => {
   if (newVal) {
     // console.log(getChatList)
     loading.value = true
-    // getChatList().then(res => {
-    //   console.log(res);
-    // }).finally(res => loading.value = false)
-    loading.value = true
-    getFirstTwoRecordsFromEachStore(db.value).then((data: any) => {
-      dataList.value = data.filter((item: any) => item !== null).reverse()
-      count.value = dataList.value.length || 0
-      loading.value = false
-      // console.log('First two records from each store:', data);
-    }).catch((error) => {
-      console.error('Error:', error)
-    })
+    getChatList({
+      page:1,
+      size:50
+    }).then(res => {
+      dataList.value = res.list
+      console.log(res.list);
+    }).finally(res => loading.value = false)
   }
 })
-
-function handleDeleteDB() {
-  ElMessageBox.confirm(
-    '此操作无法撤销。',
-    '删除全部?',
-    {
-      confirmButtonText: '确认',
-      cancelButtonText: '取消',
-      showClose: false,
-      type: 'warning',
-      center: true
-    }
-  ).then(() => {
-    deleteDB().then((res: any) => {
-      console.log('Database deleted successfully.', res)
-      // // 重新加载页面
-      // window.location.reload();
-    }).catch((error: any) => {
-      console.error('Error deleting database:', error)
-    })
-  }).catch(() => {
-  })
-}
-
-function getFirstTwoRecordsFromObjectStoreWithCursor(db: any, storeName: any) {
-  return new Promise((resolve, reject) => {
-    const transaction = db.transaction([storeName], 'readonly')
-    const objectStore = transaction.objectStore(storeName)
-    const request = objectStore.openCursor()
-    const results: any[] = []
-    let count = 0
-
-    request.onerror = (event: any) => {
-      reject(`Failed to get data from object store ${storeName}: ${event.target.error}`)
-    }
-
-    request.onsuccess = (event: any) => {
-      const cursor = event.target.result
-      if (cursor && count < 2) {
-        results.push(cursor.value)
-        count++
-        cursor.continue()
-      } else {
-        if (results.length) {
-          resolve({
-            storeName: storeName,
-            data: results
-          })
-        } else {
-          resolve(null)
-        }
-      }
-    }
-  })
-}
-
-async function getFirstTwoRecordsFromEachStore(db: any) {
-  return new Promise((resolve, reject) => {
-    // 获取所有对象存储的名称
-    const storeNames = Array.from(db.objectStoreNames)
-    const result: any[] = []
-
-    // 遍历每个对象存储并获取前 2 条数据
-    const promises = storeNames.map((storeName) => {
-      return getFirstTwoRecordsFromObjectStoreWithCursor(db, storeName)
-    })
-
-    // 等待所有对象存储的数据读取完成
-    Promise.all(promises).then((dataArrays) => {
-      // 将每个对象存储的数据合并到结果数组中
-      dataArrays.forEach((data: any) => {
-        result.push(data)
-      })
-      resolve(result)
-    }).catch((error) => {
-      reject(error)
-    })
-  })
-}
-
-function handleDeleteStore(e: any, item: any) {
+function handleDeleteStore(e: any, item: any,name:string) {
   e.stopPropagation()
   if (dataList.value.length < 2) {
     ElMessage({
@@ -131,7 +45,7 @@ function handleDeleteStore(e: any, item: any) {
   }
   ElMessageBox.confirm(
     '此操作无法撤销。',
-    '要删除此对话吗?',
+    `要删【${name}】对话吗?`,
     {
       confirmButtonText: '确认',
       cancelButtonText: '取消',
@@ -140,83 +54,56 @@ function handleDeleteStore(e: any, item: any) {
       center: true
     }
   ).then(() => {
-    // loading.value = true
-    // deleteChat(item).then((res: any) => {
-    //   loading.value = true
-    //   if(item === msgUuid.value) messages.value = []
-    //   getChatList().then(res => {
-
-    //   }).finally(res => {
-    //     loading.value = false
-    //   })
-    useStore(item).clearAll().then((res: any) => {
+    loading.value = true
+    deleteChat([item]).then((res: any) => {
       loading.value = true
-      getFirstTwoRecordsFromEachStore(db.value).then((data: any) => {
-        dataList.value = data.filter((item: any) => item !== null).reverse()
-        count.value = dataList.value.length || 0
-        loading.value = false
-        // if (item === props.msgUuid) {
-        //   const result = getNextOrPreviousId(dataList.value, item);
-        //   emit('currentData', result)
-        // }
-      }).catch((error) => {
-        console.error('Error:', error)
-      })
+      if (item === msgUuid.value) {
+        messages.value = []
+        page.value = 1
+      }
+      getChatList({
+        page: 1,
+        size: 50
+      }).then(res => {
+        dataList.value = res.list
+      }).finally(res => loading.value = false)
+    }).catch(() => {
     })
-  }).catch(() => {
-  })
 
 
+  })
 }
-
-
-function getNextOrPreviousId(array: any, currentId: any) {
-  const currentIndex = array.findIndex((item: any) => item.storeName === currentId)
-
-  if (currentIndex === -1) {
-    throw new Error('当前 storeName 不在数组中')
-  }
-
-  if (currentIndex < array.length - 1) {
-    return array[currentIndex + 1].storeName // 返回下一个对象的 id
-  } else if (currentIndex > 0) {
-    return array[currentIndex - 1].storeName // 返回上一个对象的 id
-  }
-
-  return null // 没有下一个或上一个对象
-}
-
 defineExpose({
   drawer
 })
 </script>
 
 <template>
-  <el-drawer style="height: 70%" v-model="drawer" direction="btt" :show-close="true"
-             :close-on-click-modal="false" :destroy-on-close="true"
-             :close-on-press-escape="false" class="custom_drawer">
+  <el-drawer style="height: 70%" v-model="drawer" direction="btt" :show-close="true" :close-on-click-modal="false"
+    :destroy-on-close="true" :close-on-press-escape="false" class="custom_drawer">
 
     <template #header>
-      <div class="his_flex"><span class="his_title">历史聊天</span><span class="his_count">({{ count }})</span></div>
+      <div class="his_flex"><span class="his_title">历史聊天</span><span class="his_count">({{ dataList.length }})</span>
+      </div>
     </template>
     <div style="height: 100%;overflow: hidden;" v-loading="loading">
       <div class="his_delete">
         <el-input style="margin-right: 12px" v-model="input" placeholder="搜索" clearable :prefix-icon="Search"
-                  :disabled="true" />
-        <el-tooltip effect="dark" content="删除全部" placement="top">
+          :disabled="true" />
+        <!-- <el-tooltip effect="dark" content="删除全部" placement="top">
           <el-button :icon="Delete" circle @click="handleDeleteDB" />
-        </el-tooltip>
+        </el-tooltip> -->
       </div>
       <div class="his_content">
-        <template v-for="item in dataList" :key="item.storeName">
-          <div :class="`his_list ${msgUuid === item.storeName ? 'his_list_change' : '' }`"
-               @click="emit('currentData',item.storeName)">
-            <p class="ellipsis" style="color:#000000;font-weight: 900;">{{ item.data[0]?.content ?? '--' }}</p>
-            <p class="ellipsis" style="color: #888888">{{ item.data[1] ? item.data[1].content : '--' }}</p>
+        <template v-for="item in dataList" :key="item.conversationId">
+          <div :class="`his_list ${msgUuid === item.conversationId ? 'his_list_change' : '' }`"
+            @click="emit('currentData', item.conversationId)">
+            <p class="ellipsis" style="color:#000000;font-weight: 900;">{{ item?.content ?? '--' }}</p>
             <p class="his_list_op">
-              <span>{{ item.data[0]?.timestamp }}</span>
+              <span style="color: #000">{{ item?.createTime }}</span>
               <el-tooltip effect="dark" content="删除" placement="top">
-                <el-button :icon="Delete" link @click="(e:any)=>handleDeleteStore(e,item.storeName)" />
+                <el-button :disabled="msgUuid === item.conversationId" :icon="Delete" link
+                  @click="(e: any) => handleDeleteStore(e, item.conversationId, item?.content)" />
               </el-tooltip>
             </p>
           </div>

+ 50 - 40
src/entrypoints/sidepanel/hook/useMsg.ts

@@ -3,7 +3,6 @@ import { ref, reactive, nextTick, inject } from 'vue'
 import { storeToRefs } from 'pinia'
 import avator from '@/public/icon/32.png'
 import moment from 'moment'
-import { cloneDeep } from 'lodash'
 import {
   getFormKey,
   hepl,
@@ -16,9 +15,11 @@ import { mockData, mockData2 } from '../mock'
 import { useMsgStore } from '@/store/modules/msg'
 import { FunctionList } from '../mock'
 import { putChat } from '@/api/index.js'
+import { useUserStore } from '@/store/modules/user'
 
 export function useMsg(scrollbar?: any) {
   const { messages, msgUuid } = storeToRefs(useMsgStore())
+  const userStore = useUserStore()
   const indexTemp = ref(0)
   const taklToHtml = ref<any>(false)
   const sendLoading = ref(false)
@@ -26,8 +27,6 @@ export function useMsg(scrollbar?: any) {
   const type = ref(FunctionList.File_Operation)
   const formMap = ref([])
   // 获取父组件提供的 Hook 实例
-  const { useStore } = inject('indexedDBHook') as any
-
   const getFormKeyAndValue = async (file: any, form?: any) => {
     // const obj = reactive({
     //   id: moment(),
@@ -111,13 +110,16 @@ export function useMsg(scrollbar?: any) {
     indexTemp.value = 0
     sendLoading.value = true
     const obj: any = reactive({
-      content: '',
-      rawContent:'',
-      timestamp: moment().format('YYYY-MM-DD HH:mm:ss'),
-      type:'',
-      rolw: 'system',
-      addToHistory: !taklToHtml.value
-    })
+    type: type || '',
+    rawContent: '',
+    senderId: -1,
+    receiverId: userStore.token, //大模型
+    content: '',
+      sortKey: moment().valueOf(),
+    role: 'system',
+    conversationId:msgUuid.value,
+    addToHistory: `${!taklToHtml.value}`
+  })
     messages.value.push(obj)
     msg = msg.split('/智能填表')[1]
     nextTick(() => scrollbar.value?.setScrollTop(99999))
@@ -137,7 +139,6 @@ export function useMsg(scrollbar?: any) {
         )
         const res = await awaitFindForm(obj)
         console.log(res, 34444)
-
         return res
       }
     } catch (error) {
@@ -146,10 +147,10 @@ export function useMsg(scrollbar?: any) {
     } finally {
       sendLoading.value = false
       console.log(messages.value);
-      // putChat({
-      //   id: msgUuid.value,
-      //   msg:obj
-      // })
+      putChat({
+        ...msg,
+        content:'',
+      })
     }
   }
   let str = ''
@@ -226,17 +227,19 @@ export function useMsg(scrollbar?: any) {
     pageInfo.value = await getPageInfo()
     sendLoading.value = true
     const obj = reactive<any>({
+      id: +new Date(),
+      type: type || '',
+      rawContent: '',
+      senderId: -1,
+      receiverId: userStore.token, 
       content: '',
-      type: '', // form 用于展示抽取的内容
-      rawContent: '', // 存储原始内容
-      timestamp: moment().format('YYYY-MM-DD HH:mm:ss'),
-      role:'system',
-      addToHistory: !taklToHtml.value
+      sortKey: moment().valueOf(),
+      role: 'system',
+      conversationId: msgUuid.value,
+      addToHistory: `${!taklToHtml.value}`
     })
+    
     let history = []
-    console.log(taklToHtml.value)
-    console.log(messages.value)
-
     if (taklToHtml.value) {
       if (addHtml) {
         history.push({
@@ -250,20 +253,23 @@ export function useMsg(scrollbar?: any) {
       })
     } else {
       history = messages.value
-        .filter((item: any) => item.addToHistory)
+        .filter((item: any) => item.addToHistory === 'true')
         .slice(-20)
         .map((item: any) => ({
           role: item.role,
           content: item.rawContent
         }))
     }
-
     messages.value.push(obj)
     nextTick(() => {
       scrollbar.value?.setScrollTop(99999)
     })
     try {
+      console.log(history,565666);
+      
       const iterator = await sendMessage(history)
+      console.log(iterator);
+      
       for await (const chunk of iterator) {
         if (chunk) {
           const decodedChunk = chunk.choices[0].delta.content
@@ -281,16 +287,16 @@ export function useMsg(scrollbar?: any) {
       }
       return { rawContent: obj.rawContent, status: 'ok' }
     } catch (error) {
+      console.log(error);
+      
       obj.content = '网络出错'
       return { rawContent: obj.rawContent, status: 'error' }
     } finally {
-      //添加到存储历史
-      useStore(msgUuid.value).add(cloneDeep(obj))
       console.log(messages.value);
-      // putChat({
-      //   id: msgUuid.value,
-      //   msg:obj
-      // })
+      putChat({
+        ...obj,
+        content: '',
+      })
       // 处理最终内容
       sendLoading.value = false
       nextTick(() => {
@@ -308,11 +314,16 @@ export function useMsg(scrollbar?: any) {
   async function requestFlowFn(data: any[]) {
     sendLoading.value = true
     const obj = reactive<any>({
+      id: +new Date(),
+      type: type || '',
+      rawContent: '',
+      senderId: -1,
+      receiverId: userStore.token, //大模型
       content: '',
-      rawContent: '', // 存储原始内容
-      timestamp: moment().format('YYYY-MM-DD HH:mm:ss'),
+      sortKey: moment().valueOf(),
       role: 'system',
-      addToHistory: !taklToHtml.value
+      conversationId: msgUuid.value,
+      addToHistory: `${!taklToHtml.value}`
     })
     messages.value.push(obj)
     scrollbar.value?.setScrollTop(99999)
@@ -336,13 +347,12 @@ export function useMsg(scrollbar?: any) {
         scrollbar.value?.setScrollTop(99999)
       }
     }
-    //添加到存储历史
-    useStore(msgUuid.value).add(cloneDeep(obj))
+
     console.log(messages.value);
-    // putChat({
-    //   id: msgUuid.value,
-    //   msg: obj
-    // })
+    putChat({
+      ...obj,
+      content: '',
+    })
     // 处理最终内容
     sendLoading.value = false
     await nextTick(() => {

+ 2 - 0
src/entrypoints/sidepanel/utils/ai-service.js

@@ -14,7 +14,9 @@ export async function sendMessage(message) {
     throw new Error('No active Pinia instance found')
   }
   const store = useMsgStore(pinia)
+
   const { openai, AIModel } = store
+  
   try {
     const controller = new AbortController()
     controllerList.value.push(controller)

+ 48 - 2
src/store/modules/msg.ts

@@ -1,21 +1,67 @@
 import { defineStore } from 'pinia'
 import OpenAI from 'openai'
-
+import { getChatDetail } from '@/api/index'
+import {
+  formatMessage,
+} from '@/entrypoints/sidepanel/utils/ai-service.js'
 export const useMsgStore = defineStore('msg', {
   state: () => ({
     msgUuid: <string>'',
     messages: <any[]>[],
+    hasNext:true,
+    page:1,
     pageInfoList: [],
     AIModel: <any>{},
     openai: <any>null
   }),
   actions: {
+    async initMsg() {
+      console.log(this.page,this.hasNext,this.messages);
+      const { msgUuid } = await new Promise<any>((resolve) => {
+        chrome.storage.local.get(['msgUuid'], (result) => {
+          resolve(result)
+        })
+      })
+      if (msgUuid) {
+        console.log(85556);
+        this.msgUuid = msgUuid
+        const res = await getChatDetail({
+          page: this.page,
+          size: 10,
+          sort: 'sortKey,desc',
+          conversationId: this.msgUuid
+        })
+        this.messages = res.list.reverse().map(_ => ({
+          id:_.id,
+          type: _.type,
+          rawContent: _.rawContent,
+          senderId: _.senderId,
+          receiverId: _.receiverId,
+          sortKey: _.sortKey,
+          role: _.role,
+          conversationId: _.conversationId,
+          addToHistory: _.addToHistory,
+          content: _.role === 'system' ?  formatMessage(_.rawContent) : _.content
+        })).concat(this.messages)
+        if (this.messages.length < res.total) {
+          this.hasNext = true
+          this.page++
+        }
+        else this.hasNext = false
+      }
+    },
+
+    async changePage() {
+      if (this.hasNext) {
+        await this.initMsg()
+      }
+    },
     updateAIModel(model: any) {
       this.AIModel = model
       this.openai = new OpenAI({
         // apiKey: import.meta.env.VITE_OPENAI_API_KEY_TONG,
         apiKey: import.meta.env.VITE_OPENAI_API_KEY_TONG,
-        baseURL: this.AIModel.url,
+          baseURL: this.AIModel.url,
         // timeout: 5000,
         dangerouslyAllowBrowser: true
       })

+ 11 - 5
src/store/modules/user.ts

@@ -1,6 +1,6 @@
-import { defineStore } from 'pinia'
+import { defineStore, storeToRefs } from 'pinia'
 import { ElMessage } from 'element-plus'
-
+import { useMsgStore } from './msg'
 // 定义用户信息接口
 interface UserInfo {
   id: string
@@ -110,7 +110,7 @@ export const useUserStore = defineStore('user', {
         const res = {
           code: 200,
           data: {
-            token: 'mock_token_' + '1234567890',
+            token: 1234567890,
             userInfo: {
               id: '1',
               username: params.username || '用户' + Math.floor(Math.random() * 1000),
@@ -172,7 +172,6 @@ export const useUserStore = defineStore('user', {
         
         // 清除用户数据
         await this.clearUserData()
-        ElMessage.success('退出登录成功')
         return true
       } catch (error: any) {
         ElMessage.error(error.message || '退出登录出错')
@@ -237,12 +236,19 @@ export const useUserStore = defineStore('user', {
     // 清除用户数据
     async clearUserData() {
       await this.clearToken()
+      const { messages, page, msgUuid } = storeToRefs(useMsgStore())
+      console.log(messages, page, msgUuid);
+      
+      page.value = 1
+      msgUuid.value = ''
       this.userInfo = null
       this.roles = []
       this.permissions = []
+      messages.value = []
+      console.log(6565);
       
       if (typeof chrome !== 'undefined' && chrome.storage) {
-        await chrome.storage.local.remove(['userInfo', 'roles', 'permissions'])
+        await chrome.storage.local.remove(['userInfo', 'roles', 'permissions','msgUuid'])
       } else {
         localStorage.removeItem('userInfo')
         localStorage.removeItem('roles')

+ 25 - 8
src/utils/request.js

@@ -10,6 +10,8 @@ const service = axios.create({
   baseURL: import.meta.env.VITE_APP_BASE_API,
   timeout: 60000
 })
+console.log(import.meta.env.VITE_APP_BASE_API);
+
 let cancel
 // request拦截器
 service.interceptors.request.use(
@@ -21,6 +23,22 @@ service.interceptors.request.use(
       })
     }
 
+    // 处理GET请求,将data参数拼接到URL
+    if (config.method === 'get' && config.data) {
+      // 将data对象转换为URL参数
+      let params = Object.keys(config.data)
+        .map(key => `${encodeURIComponent(key)}=${encodeURIComponent(config.data[key])}`)
+        .join('&');
+      
+      // 拼接到URL
+      if (params) {
+        config.url += (config.url.includes('?') ? '&' : '?') + params;
+      }
+      // 清空data,避免重复发送
+      config.data = undefined;
+    }
+    console.log(config.url);
+
     const {token} = await new Promise((resolve) => {
       chrome.storage.local.get(['token'], (result) => {
         resolve(result)
@@ -57,7 +75,7 @@ service.interceptors.response.use(
       let filename = res.headers['content-disposition'].split('filename=')[1]
       return { fileName: filename, data: res.data }
     }
-    if (code === 401) {
+    if (code === '401') {
       // if (!isRelogin.show) {
       //   isRelogin.show = true;
       //   ElMessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' }).then(() => {
@@ -69,22 +87,21 @@ service.interceptors.response.use(
       //     isRelogin.show = false;
       //   });
       // }
+      ElMessage.error('您的登录状态已过期,请重新登录。')
       const userStore = useUserStore()
-
       userStore.logout()
-
       return Promise.reject('无效的会话,或者会话已过期,请重新登录。')
     } else if (code === 500) {
       ElMessage({ message: msg, type: 'error', grouping: true })
       return Promise.reject(msg)
+    } if (code === '1') {
+      ElMessage({ message: '接口异常', type: 'error', grouping: true })
+      return Promise.reject(msg)
     } else if (code === 601) {
       ElMessage({ message: msg, type: 'warning', grouping: true })
       return Promise.reject(new Error(msg))
-    } else if (code !== 200) {
-      ElNotification.error({ title: msg })
-      return Promise.reject('error')
-    } else {
-      return Promise.resolve(res.data)
+    }  else {
+      return Promise.resolve(res.data.data)
     }
   },
   (error) => {