From 124f894f4e08fd63eae8c7a85babbc19f2cc1829 Mon Sep 17 00:00:00 2001
From: 潘志宝 <979469083@qq.com>
Date: 星期五, 13 六月 2025 09:39:36 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/master'

---
 src/views/model/sche/suggest/suggestOperationRecord.vue                   |   18 +
 src/views/ai/dashboard/components/message/HistoryMessageList.vue          |   33 ++
 src/views/ai/model/template/index.vue                                     |    4 
 src/views/ai/dashboard/components/conversation/CommonConversationList.vue |  116 +++++++---
 src/views/ai/dashboard/components/message/MessageList.vue                 |   16 +
 src/api/ai/chat/message/index.ts                                          |   50 ---
 src/views/ai/dashboard/components/message/HistoryMessageDialog.vue        |  187 +++++++++++++++-
 src/views/model/sche/suggest/suggestSnapshot.vue                          |   12 
 src/components/Dialog/src/DialogHistory.vue                               |   12 -
 src/views/ai/dashboard/zhuanlu/index.vue                                  |   58 +++-
 src/views/ai/dashboard/components/conversation/CommonConversation.vue     |  130 +++++++----
 11 files changed, 443 insertions(+), 193 deletions(-)

diff --git a/src/api/ai/chat/message/index.ts b/src/api/ai/chat/message/index.ts
index 1123524..9ec9dcd 100644
--- a/src/api/ai/chat/message/index.ts
+++ b/src/api/ai/chat/message/index.ts
@@ -2,7 +2,6 @@
 import { fetchEventSource } from '@microsoft/fetch-event-source'
 import { getAccessToken } from '@/utils/auth'
 import { config } from '@/config/axios/config'
-import {refreshToken} from "@/api/login";
 
 // 聊天VO
 export interface ChatMessageVO {
@@ -36,6 +35,14 @@
   getChatMessageListByConversationId: async (conversationId: number | null) => {
     return await request.get({
       url: `/ai/chat/message/list-by-conversation-id?conversationId=${conversationId}`
+    })
+  },
+
+  // 消息列表
+  getChatMessagePageListByConversationId: async (params: number | null) => {
+    return await request.get({
+      url: `/ai/chat/message/page-list-by-conversation-id`,
+      params: params
     })
   },
 
@@ -76,35 +83,6 @@
       signal: ctrl.signal
     })
   },
-  // 发送 Stream 消息 【工业大模型专用】
-  // sendEnergyChatMessageStream: async (
-  //   conversationId: number,
-  //   content: string,
-  //   ctrl,
-  //   enableContext: boolean,
-  //   onMessage,
-  //   onError,
-  //   onClose
-  // ) => {
-  //   const token = getAccessToken()
-  //   return fetchEventSource(`${config.base_url}/ai/chat/message/send-energy-stream`, {
-  //     method: 'post',
-  //     headers: {
-  //       'Content-Type': 'application/json',
-  //       Authorization: `Bearer ${token}`
-  //     },
-  //     openWhenHidden: true,
-  //     body: JSON.stringify({
-  //       conversationId,
-  //       content,
-  //       useContext: enableContext
-  //     }),
-  //     onmessage: onMessage,
-  //     onerror: onError,
-  //     onclose: onClose,
-  //     signal: ctrl.signal
-  //   })
-  // },
 
   // 删除消息
   deleteChatMessage: async (id: string) => {
@@ -115,18 +93,6 @@
   deleteByConversationId: async (conversationId: number) => {
     return await request.delete({
       url: `/ai/chat/message/delete-by-conversation-id?conversationId=${conversationId}`
-    })
-  },
-
-  // // 删除消息【工业大模型专用】
-  // deleteEnergyChatMessage: async (id: string) => {
-  //   return await request.delete({ url: `/ai/chat/message/delete-energy?id=${id}` })
-  // },
-
-  // 删除指定对话的消息【工业大模型专用】
-  deleteEnergyByConversationId: async (conversationId: number) => {
-    return await request.delete({
-      url: `/ai/chat/message/delete-energy-by-conversation-id?conversationId=${conversationId}`
     })
   },
 
diff --git a/src/components/Dialog/src/DialogHistory.vue b/src/components/Dialog/src/DialogHistory.vue
index 0b3dd5a..02dc867 100644
--- a/src/components/Dialog/src/DialogHistory.vue
+++ b/src/components/Dialog/src/DialogHistory.vue
@@ -73,14 +73,6 @@
           class="absolute right-15px top-[50%] h-40px flex translate-y-[-50%] items-center justify-between"
         >
           <Icon
-            v-if="fullscreen"
-            class="is-hover mr-10px cursor-pointer"
-            :icon="isFullscreen ? 'radix-icons:exit-full-screen' : 'radix-icons:enter-full-screen'"
-            color="#73C4FF"
-            hover-color="var(--el-color-primary)"
-            @click="toggleFull"
-          />
-          <Icon
             class="is-hover cursor-pointer"
             icon="ep:close"
             hover-color="var(--el-color-primary)"
@@ -101,9 +93,8 @@
 <style lang="scss">
 .history-dialog {
   height: 90vh;
-  margin-top: 30px;
   color: #73C4FF;
-  overflow: hidden; /* 防止内容溢出 */
+  margin-top: 30px;
   background: rgba(3,29,76,0.79);
   border-radius: 4px 4px 4px 4px;
   border: 1px solid;
@@ -115,7 +106,6 @@
 
   .#{$elNamespace}-dialog {
     margin: 0 !important;
-
     &__header {
       height: 40px;
       padding: 0;
diff --git a/src/views/ai/dashboard/components/conversation/CommonConversation.vue b/src/views/ai/dashboard/components/conversation/CommonConversation.vue
index 2de4e77..04137e7 100644
--- a/src/views/ai/dashboard/components/conversation/CommonConversation.vue
+++ b/src/views/ai/dashboard/components/conversation/CommonConversation.vue
@@ -18,6 +18,9 @@
       ref="sidebarRef">
       <ConversationList
         :active-id="activeConversationId"
+        :quick-access="quickAccessFlag"
+        :model-name="modelName"
+        :default-message="defaultMessage"
         ref="conversationListRef"
         @on-conversation-create="handleConversationCreateSuccess"
         @on-conversation-click="handleConversationClick"
@@ -55,9 +58,8 @@
           <!-- 情况一:消息加载中 -->
           <MessageLoading v-if="activeMessageListLoading" />
           <!-- 情况二:无聊天对话时 -->
-          <MessageNewConversation
+          <MessageListEmpty
             v-if="!activeConversation"
-            @on-new-conversation="handleConversationCreate"
           />
           <!-- 情况三:消息列表为空 -->
           <MessageListEmpty
@@ -89,7 +91,7 @@
               @input="handlePromptInput"
               @compositionstart="onCompositionstart"
               @compositionend="onCompositionend"
-              placeholder="问我任何问题...(Shift+Enter 换行,按下 Enter 发送)"
+              placeholder="请问我问题...(Shift+Enter 换行,按下 Enter 发送)"
             ></textarea>
             <div class="prompt-btns">
               <div class="content">
@@ -141,13 +143,21 @@
 import MessageList from '../message/MessageList.vue'
 import MessageListEmpty from '../message/MessageListEmpty.vue'
 import MessageLoading from '../message/MessageLoading.vue'
-import MessageNewConversation from '../message/MessageNewConversation.vue'
 import { onClickOutside } from '@vueuse/core'
 import * as authUtil from "@/utils/auth";
 import {refreshToken} from "@/api/login";
+import {formatToDateTime} from "@/utils/dateUtil";
+import {ElLoading} from "element-plus";
 
 /** AI 聊天对话 列表 */
 defineOptions({ name: 'NormalConversation' })
+
+const props = defineProps({
+  data: {
+    type: Object,
+    default: () => null
+  }
+})
 
 const route = useRoute() // 路由
 const message = useMessage() // 消息弹窗
@@ -175,8 +185,12 @@
   isCollapsed.value = !isCollapsed.value
 }
 
+const modelName = ref<string>('common') // 对话搜索
+
 // 聊天对话
 const conversationListRef = ref()
+const quickAccessFlag = ref(false)
+const defaultMessage = ref<ChatMessageVO>()
 const activeConversationId = ref<number | null>(null) // 选中的对话编号
 const activeConversation = ref<ChatConversationVO | null>(null) // 选中的 Conversation
 const conversationInProgress = ref(false) // 对话是否正在进行中。目前只有【发送】消息时,会更新为 true,避免切换对话、删除对话等操作
@@ -199,7 +213,6 @@
 // 接收 Stream 消息
 const receiveMessageFullText = ref('')
 const receiveMessageDisplayedText = ref('')
-
 
 // =========== 【聊天对话】相关 ===========
 
@@ -298,7 +311,6 @@
     activeMessageList.value = await ChatMessageApi.getChatMessageListByConversationId(
       activeConversationId.value
     )
-
     // 滚动到最下面
     await nextTick()
     await scrollToBottom()
@@ -335,19 +347,37 @@
   return []
 })
 
-//处理调度推理结论
-const dealResult = (conversations: any) => {
-  const regex = /<think>(\n*)([\s\S]*?)(\n*)<\/think>(\n*)([\s\S]*)/;
-  conversations.forEach((conversation) => {
-    if(conversation.content.includes('<\/think>')) {
-      conversation.thinkingFlag = false
-    } else {
-      conversation.thinkingFlag = true
-    }
-    const match = conversation.content.match(regex);
-    if(match) {
-      conversation.thinking = match[2];
-      conversation.conclusion = match[5]
+// //处理调度推理结论(deepSeek)
+// const dealResult = (conversations: any) => {
+//   const regex = /<think>(\n*)([\s\S]*?)(\n*)<\/think>(\n*)([\s\S]*)/;
+//   conversations.forEach((conversation) => {
+//     if(conversation.content.includes('<\/think>')) {
+//       conversation.thinkingFlag = false
+//     } else {
+//       conversation.thinkingFlag = true
+//     }
+//     const match = conversation.content.match(regex);
+//     if(match) {
+//       conversation.thinking = match[2];
+//       conversation.conclusion = match[5]
+//     }
+//   })
+// }
+
+//处理调度推理结论(微调大模型)
+const dealResult = (messages: any) => {
+  messages.forEach((message) => {
+    if(message.type === 'assistant') {
+      const spliceText = message.content.includes("总结:") ? "总结:" : "结论:";
+      // 创建同时捕获前后内容的正则表达式
+      const regex = new RegExp(`^([\\s\\S]*?)${spliceText}([\\s\\S]*)$`);
+      const match = message.content.match(regex);
+      if(match) {
+        message.thinking = match[1];
+        message.conclusion = match[2]
+      } else {
+        message.thinking = message.content
+      }
     }
   })
 }
@@ -455,19 +485,20 @@
     message.error('发送失败,原因:内容为空!')
     return
   }
+  // 发送请求时如果accessToken过期,无法中断请求,暂时增加请求前刷新token
+  authUtil.setToken(await refreshToken())
   if (activeConversationId.value == null) {
-    message.error('还没创建对话,不能发送!')
-    return
+    await conversationListRef.value.createConversation(props.data?formatToDateTime(new Date(props.data.createTime)):null)
   }
   // 清空输入框
   prompt.value = ''
-  // 发送请求时如果accessToken过期,无法中断请求,暂时增加请求前刷新token
-  authUtil.setToken(await refreshToken())
-  // 执行发送
-  await doSendMessageStream({
-    conversationId: activeConversationId.value,
-    content: content
-  } as ChatMessageVO)
+  setTimeout(() => {
+    // 执行发送
+    doSendMessageStream({
+      conversationId: activeConversationId.value,
+      content: content
+    } as ChatMessageVO)
+  }, 400)
 }
 
 /** 真正执行【发送】消息操作 */
@@ -478,7 +509,6 @@
   conversationInProgress.value = true
   // 设置为空
   receiveMessageFullText.value = ''
-
   try {
     // 1.1 先添加两个假数据,等 stream 返回再替换
     activeMessageList.value.push({
@@ -499,8 +529,7 @@
     await nextTick()
     await scrollToBottom() // 底部
     // 1.3 开始滚动
-    textRoll()
-
+    await textRoll()
     // 2. 发送 event stream
     let isFirstChunk = true // 是否是第一个 chunk 消息段
     await ChatMessageApi.sendChatMessageStream(
@@ -542,7 +571,9 @@
         stopStream()
       }
     )
-  } catch {}
+  } catch {
+    console.log('sendStream Exception')
+  }
 }
 
 /** 停止 stream 流式调用 */
@@ -632,16 +663,19 @@
 
 /** 初始化 **/
 onMounted(async () => {
-  // 如果有 conversationId 参数,则默认选中
-  if (route.query.conversationId) {
-    const id = route.query.conversationId as unknown as number
-    activeConversationId.value = id
-    await getConversation(id)
+  defaultMessage.value = props.data
+  if(defaultMessage.value) {
+    prompt.value = defaultMessage.value.content
+    quickAccessFlag.value = true
+  } else {
+    // 获取列表数据
+    activeMessageListLoading.value = true
+    await getMessageList()
   }
+})
 
-  // 获取列表数据
-  activeMessageListLoading.value = true
-  await getMessageList()
+onUnmounted(() => {
+  stopStream()
 })
 </script>
 
@@ -655,7 +689,7 @@
 
 .sidebar-toggle {
   position: absolute;
-  left: 300px;  // 初始展开位置
+  left: 320px;  // 初始展开位置
   top: 40%;
   z-index: 1000;
   width: 20px;
@@ -682,12 +716,12 @@
   left: 0;
   top: 0;
   bottom: 0;
-  width: 300px;
+  width: 320px;
   background: rgba(13,28,58,0.9);
   box-shadow: 2px 0 8px rgba(0,0,0,0.1);
   transition: transform 0.3s ease, opacity 0.2s ease;
   z-index: 999;
-  overflow: hidden;
+  overflow-x: hidden;
 
   &.collapsed {
     transform: translateX(-100%);
@@ -699,7 +733,7 @@
 // 头部
 .detail-container {
   width: 100%;
-  height: 885px;
+  height: 910px;
   margin-left: 5px;
   background-color: rgba(0, 0, 0, 0); /* 透明背景 */
   transition: margin 0.3s cubic-bezier(0.4, 0, 0.2, 1);
@@ -796,7 +830,7 @@
 .footer-container {
   display: flex;
   flex-direction: column;
-  height: 114px;
+  height: 205px;
   margin-left: 10px;
   padding: 0;
 
@@ -831,7 +865,7 @@
       flex-direction: column;
       padding: 9px 10px;
       width: 876px;
-      height: 114px;
+      height: 205px;
       background: rgba(115,196,255,0.05);
       border-radius: 4px 4px 4px 4px;
       border: 1px solid #73C4FF;
@@ -842,8 +876,8 @@
     }
 
     .prompt-input {
-      width: 876px;
-      height: 113.55px;
+      width: 860px;
+      height: 203px;
       font-weight: 400;
       font-size: 14px;
       background-color: rgba(219,238,255,0);
diff --git a/src/views/ai/dashboard/components/conversation/CommonConversationList.vue b/src/views/ai/dashboard/components/conversation/CommonConversationList.vue
index df1c641..590e88c 100644
--- a/src/views/ai/dashboard/components/conversation/CommonConversationList.vue
+++ b/src/views/ai/dashboard/components/conversation/CommonConversationList.vue
@@ -1,8 +1,8 @@
 <!--  AI 对话  -->
 <template>
-  <el-aside width="260px" class="conversation-container h-100%">
+  <el-aside width="280px" class="conversation-container h-100%">
     <!-- 左顶部:对话 -->
-    <div class="h-100%">
+    <div class="h-80%">
       <div class="conversation-title">
         <img
           src="@/assets/ai/zhuanlu/conversation_big.png"
@@ -12,7 +12,7 @@
         对话列表
       </div>
 <!--      <hr class="line"/>-->
-      <el-button class="w-1/1 btn-new-conversation" type="primary" @click="createConversation">
+      <el-button class="w-1/1 btn-new-conversation" type="primary" @click="createNewConversation">
         <img
           src="@/assets/ai/zhuanlu/conversation_big.png"
           class="mr-8px w-[1.5em] h-[1.5em]"
@@ -82,8 +82,6 @@
             </div>
           </div>
         </div>
-        <!-- 底部占位  -->
-        <div class="h-160px w-100%"></div>
       </div>
     </div>
 
@@ -102,12 +100,13 @@
 import { ChatConversationApi, ChatConversationVO } from '@/api/ai/chat/conversation'
 import { Bottom, Top } from '@element-plus/icons-vue'
 import roleAvatarDefaultImg from '@/assets/ai/zhuanlu/assistant.png'
+import {ChatMessageVO} from "@/api/ai/chat/message";
+import {formatToDate, formatToDateTime} from "@/utils/dateUtil";
 
 const message = useMessage() // 消息弹窗
 
 // 定义属性
 const searchName = ref<string>('') // 对话搜索
-const modelName = ref<string>('common') // 对话搜索
 const activeConversationId = ref<number | null>(null) // 选中的对话,默认为 null
 const hoverConversationId = ref<number | null>(null) // 悬浮上去的对话
 const conversationList = ref([] as ChatConversationVO[]) // 对话列表
@@ -120,7 +119,16 @@
   activeId: {
     type: String || null,
     required: true
-  }
+  },
+  modelName: {
+    type: String || null,
+    required: true
+  },
+  quickAccess: {
+    type: Boolean || null,
+    required: true
+  },
+  defaultMessage: {}
 })
 
 // 定义钩子
@@ -169,10 +177,7 @@
     }, 50)
 
     // 1.1 获取 对话数据
-    conversationList.value = await ChatConversationApi.getChatConversationEnergyList(modelName.value)
-    if(conversationList.value.length == 0) {
-      await createConversation()
-    }
+    conversationList.value = await ChatConversationApi.getChatConversationEnergyList(props.modelName)
     // 1.2 排序
     conversationList.value.sort((a, b) => {
       return b.createTime - a.createTime
@@ -184,7 +189,7 @@
       return
     }
 
-    // 2. 对话根据时间分组(置顶、今天、一天前、三天前、七天前、30 天前)
+    // 2. 对话根据时间分组(置顶、今天、昨天、三天前、七天前、30 天前)
     conversationMap.value = await getConversationGroupByCreateTime(conversationList.value)
   } finally {
     // 清理定时器
@@ -203,7 +208,7 @@
   const groupMap = {
     置顶: [],
     今天: [],
-    一天前: [],
+    昨天: [],
     三天前: [],
     七天前: [],
     三十天前: []
@@ -212,9 +217,11 @@
   const now = Date.now()
   // 定义时间间隔常量(单位:毫秒)
   const oneDay = 24 * 60 * 60 * 1000
-  const threeDays = 3 * oneDay
   const sevenDays = 7 * oneDay
   const thirtyDays = 30 * oneDay
+  //今天
+  const today = formatToDate(new Date())
+  const yesterday = formatToDate(new Date().setDate(new Date().getDate() - 1))
   for (const conversation of list) {
     // 置顶
     if (conversation.pinned) {
@@ -223,11 +230,13 @@
     }
     // 计算时间差(单位:毫秒)
     const diff = now - conversation.createTime
+    let conversationDate = formatToDate(conversation.createTime)
+    let titleDate = conversation.title.split(' ')[0]
     // 根据时间间隔判断
-    if (diff < oneDay) {
+    if (titleDate == today) {
       groupMap['今天'].push(conversation)
-    } else if (diff < threeDays) {
-      groupMap['一天前'].push(conversation)
+    } else if (titleDate == yesterday) {
+      groupMap['昨天'].push(conversation)
     } else if (diff < sevenDays) {
       groupMap['三天前'].push(conversation)
     } else if (diff < thirtyDays) {
@@ -240,17 +249,22 @@
 }
 
 /** 新建对话 */
-const createConversation = async () => {
+const createNewConversation = async () => {
+  await createConversation(null)
+}
+
+/** 新建对话 */
+const createConversation = async (title: String) => {
   // 1. 新建对话
   const conversationId = await ChatConversationApi.createChatConversationEnergy(
-    {modelName: modelName.value} as unknown as ChatConversationVO
+    {modelName: props.modelName, title: title} as unknown as ChatConversationVO
   )
   // 2. 获取对话内容
   await getChatConversationList()
   // 3. 选中对话
   await handleConversationClick(conversationId)
-  // 4. 回调
-  emits('onConversationCreate')
+  // // 4. 回调
+  // emits('onConversationCreate')
 }
 
 /** 修改对话的标题 */
@@ -344,15 +358,28 @@
 onMounted(async () => {
   // 获取 对话列表
   await getChatConversationList()
-  // 默认选中
-  if (props.activeId) {
-    activeConversationId.value = props.activeId
-  } else {
-    // 首次默认选中第一个
+  if(!props.quickAccess) {
+    // 默认选中
+    if (props.activeId) {
+      activeConversationId.value = props.activeId
+    } else {
+      // 首次默认选中第一个
+      if (conversationList.value.length) {
+        activeConversationId.value = conversationList.value[0].id
+        // 回调 onConversationClick
+        await emits('onConversationClick', conversationList.value[0])
+      }
+    }
+  } else if(props.defaultMessage) {
+    let tempTitle = formatToDateTime(new Date(props.defaultMessage.createTime))
     if (conversationList.value.length) {
-      activeConversationId.value = conversationList.value[0].id
-      // 回调 onConversationClick
-      await emits('onConversationClick', conversationList.value[0])
+      conversationList.value.forEach((item) => {
+        if(item.title === tempTitle) {
+          activeConversationId.value = item.id
+          // 回调 onConversationClick
+          emits('onConversationClick', item)
+        }
+      })
     }
   }
 })
@@ -395,16 +422,30 @@
   }
 
   .conversation-list {
-    overflow: auto;
+    overflow-y: auto;
     height: 100%;
+    margin-top: 10px;
+    /* Firefox */
+    scrollbar-width: thin;
+    scrollbar-color: rgba(0, 0, 0, 0.15) transparent;
 
+    /* WebKit */
+    &::-webkit-scrollbar {
+      width: 6px;
+      background: transparent;
+    }
+    &::-webkit-scrollbar-thumb {
+      border-radius: 4px;
+      background: rgba(0, 0, 0, 0.15);
+      transition: background 0.3s;
+      &:hover { background: rgba(0, 0, 0, 0.25); }
+    }
     .classify-title {
       padding-top: 10px;
       b {
         color: white;
       }
     }
-
     .conversation-item {
       margin-top: 5px;
     }
@@ -443,10 +484,9 @@
       }
 
       .title {
-        padding: 2px 10px;
+        padding: 2px 5px;
         max-width: 220px;
-        font-size: 14px;
-        font-weight: 400;
+        font-size: 13px;
         color: rgba(0, 0, 0, 0.77);
         overflow: hidden;
         white-space: nowrap;
@@ -478,17 +518,19 @@
     }
   }
 
-  // 角色仓库、清空未设置对话
+  // 清空未设置对话
   .tool-box {
-    bottom: 0;
+    display: flex;
     padding: 0 20px;
+    width: 90%;
+    margin-left: 5%;
     background-color: rgba(69,133,255,0.2);
     box-shadow: 0 0 1px 1px rgba(69,133,255,0.4);
     line-height: 35px;
     justify-content: space-between;
     align-items: center;
     color: var(--el-text-color);
-
+    border-radius: 2px;
     div {
       display: flex;
       margin-left: 20%;
diff --git a/src/views/ai/dashboard/components/message/HistoryMessageDialog.vue b/src/views/ai/dashboard/components/message/HistoryMessageDialog.vue
index 68b1a73..51999c2 100644
--- a/src/views/ai/dashboard/components/message/HistoryMessageDialog.vue
+++ b/src/views/ai/dashboard/components/message/HistoryMessageDialog.vue
@@ -1,17 +1,41 @@
 <template>
-  <DialogHistory title="历史建议" v-model="dialogVisible" width="1200">
-    <!-- 左侧:对话列表 -->
-    <ConversationList
-      v-show="false"
-      :active-id="activeConversationId"
-      ref="conversationListRef"
-    />
-    <!-- 右侧:对话详情 -->
+  <DialogHistory title="历史建议" v-model="dialogVisible" width="1200" custom-class="transparent-dialog">
+    <!-- 搜索工作栏 -->
+    <el-form
+      class="-mb-15px query-area"
+      :model="queryParams"
+      ref="queryFormRef"
+      :inline="true"
+      label-width="68px"
+    >
+      <el-form-item label="对话时间" prop="createTime">
+        <el-date-picker
+          v-model="queryParams.createTime"
+          value-format="YYYY-MM-DD HH:mm:ss"
+          type="datetimerange"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
+          class="!w-360px transparent-date-picker-popper"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-button @click="handleQuery">
+          <Icon icon="ep:search" class="mr-5px"/>
+          搜索
+        </el-button>
+        <el-button @click="resetQuery">
+          <Icon icon="ep:refresh" class="mr-5px"/>
+          重置
+        </el-button>
+      </el-form-item>
+    </el-form>
+    <!-- 对话详情 -->
     <el-container class="detail-container">
       <el-header class="header">
         <div class="title">
           {{ activeConversation?.title ? activeConversation?.title : '' }}
-          <span v-if="activeMessageList.length">({{ activeMessageList.length }})</span>
+          <span v-if="total">({{ total }})</span>
         </div>
         <div class="btns" v-if="activeConversation">
           <el-button size="small" class="btn" @click="handlerMessageClear">
@@ -43,32 +67,55 @@
             ref="messageRef"
             :conversation="activeConversation"
             :list="activeMessageList"
+            :gotoManualMethod="gotoManual"
           />
         </div>
       </el-main>
     </el-container>
-
+    <!-- 分页 -->
+    <Pagination
+      :total="total"
+      v-model:page="queryParams.pageNo"
+      v-model:limit="queryParams.pageSize"
+      @pagination="handleQuery"
+    />
   </DialogHistory>
 </template>
 
 <script setup lang="ts">
 import {ChatMessageApi, ChatMessageVO} from '@/api/ai/chat/message'
 import { ChatConversationVO } from '@/api/ai/chat/conversation'
-import ConversationList from '../conversation/HistoryConversationList.vue'
 import HistoryMessageList from './HistoryMessageList.vue'
 import MessageListEmpty from './MessageListEmpty.vue'
 import ConversationListEmpty from '../conversation/ConversationListEmpty.vue'
+import {formatDate} from "@vueuse/core";
+import {ref} from "vue";
 
 /** AI 聊天对话 列表 */
 defineOptions({ name: 'HistoryMessageDialog' })
 
-const route = useRoute() // 路由
+// 接收父组件传递的方法
+const props = defineProps({
+  parentMethod: Function,
+  gotoManualMethod: Function
+});
+
+// 定义发射事件
+const emit = defineEmits(['gotoManualMethod'])
+
 const message = useMessage() // 消息弹窗
+const total = ref(0) // 历史建议列表
 
 const dialogVisible = ref(false) // 弹窗的是否展示
 
+const queryFormRef = ref() // 搜索的表单
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  createTime: [],
+})
+
 // 聊天对话
-const conversationListRef = ref()
 const activeConversation = ref<ChatConversationVO | null>(null) // 选中的 Conversation
 
 // 消息列表
@@ -77,14 +124,24 @@
 
 
 /** 打开弹窗 */
-const open = async (messages: ChatMessageVO[], conversation: ChatConversationVO) => {
+const open = async (messages: ChatMessageVO[], conversation: ChatConversationVO, activeHistoryMessageTotal: number) => {
   dialogVisible.value = true
+  total.value = activeHistoryMessageTotal
   await nextTick() // 等待弹窗DOM挂载
   activeMessageList.value = messages
   activeConversation.value = conversation
 }
 
-defineExpose({ open }) // 提供方法给 parent 调用
+/** 处理查询时间段 */
+const dealDate = async () => {
+  const currentDate = new Date();
+  const previousDate = new Date(currentDate.getTime() -  2 * 60 * 60 * 1000);
+  queryParams.createTime[0] = formatDate(previousDate, 'YYYY-MM-DD HH:mm:ss');
+  queryParams.createTime[1] = formatDate(currentDate, 'YYYY-MM-DD HH:mm:ss');
+  return queryParams;
+}
+
+defineExpose({ open, dealDate }) // 提供方法给 parent 调用
 
 /** 回到 message 列表的顶部 */
 const handleGoTopMessage = () => {
@@ -111,19 +168,77 @@
   } catch {}
 }
 
+const gotoManual = async (item: ChatMessageVO) => {
+  emit('gotoManualMethod', item) // 发送数据给父组件
+}
+
+/** 搜索按钮操作 */
+const handleQuery = async () => {
+  if (props.parentMethod) {
+    // props.parentMethod({ data: queryParams }); // 可传递参数
+    let pageResult = await props.parentMethod(queryParams)
+    activeMessageList.value = pageResult.list
+    total.value = pageResult.total
+  }
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value.resetFields()
+  queryParams.pageNo = 1
+  handleQuery()
+}
+
 /** 初始化 **/
 onMounted(async () => {
+  // await dealDate()
 })
 </script>
 
 <style lang="scss" scoped>
+
+.query-area {
+  margin-top: 10px;
+  float: right;
+  :deep(.el-form-item__label) {
+    color: #73C4FF;
+  }
+  :deep(.el-date-editor .el-icon) {
+    color: #DBEEFF;
+  }
+  :deep(.el-date-editor .el-range-input) {
+    color: rgba(219, 238, 255, 0.5);
+  }
+  /* 移除所有输入框边框 */
+  :deep(.el-form-item .el-input__wrapper) {
+    border: none !important;
+    box-shadow: none !important;
+    background: rgba(255,255,255,0.1) !important; /* 保留浅色背景 */
+  }
+  /* 所有状态通用透明背景 */
+  :deep(.el-button) {
+    background: transparent !important;
+    border-color: currentColor; /* 保持与文字同色 */
+    color: #409EFF; /* 蓝色文字 */
+  }
+
+  /* 悬停状态 */
+  :deep(.el-button:hover) {
+    background: rgba(0, 0, 0, 0.5) !important; /* 轻微悬停反馈 */
+  }
+
+  /* 点击状态 */
+  :deep(.el-button:active) {
+    background: rgba(0, 0, 0, 0.8) !important;
+  }
+}
 
 // 头部
 .detail-container {
   display: flex;
   flex-direction: column;
   width: 100%;
-  height: 820px;
+  height: 75vh;
   background-color: rgba(0, 0, 0, 0); /* 透明背景 */
   z-index: 1;
   .header {
@@ -165,7 +280,6 @@
       :deep(.el-button:active) {
         background: rgba(0, 0, 0, 0.1) !important;
       }
-
       /* 禁用状态 */
       :deep(.el-button.is-disabled) {
         opacity: 0.6;
@@ -370,6 +484,45 @@
         }
       }
     }
+
+    /* 下拉组件 */
+    :deep(.el-select) {
+      /* 下拉箭头 */
+      .el-select__caret {
+        color: #73C4FF !important; /* 匹配图中的浅蓝箭头 */
+        font-size: 16px !important;
+      }
+    }
+
+    /* 深度选择器调整边框细节 */
+    :deep(.el-select__wrapper) {
+      background-color: transparent !important;
+      border-radius: 6px;        /* 圆角大小 */
+      border-width: 1.5px;        /* 边框粗细 */
+      box-shadow: 0 0 0 1px #1E5A86 !important; /* 聚焦阴影 */
+    }
+  }
+}
+
+.el-pagination {
+  //--el-pagination-button-bg-color: transparent;
+  opacity: 0.6;
+  :deep(.el-pagination__total) {
+    color: white;
+  }
+  :deep(.el-pager) {
+    color: rgba(3,27,21);
+    font-weight: bold;
+  }
+  :deep(.el-pagination__jump) {
+    color: white;
+  }
+  :deep(.el-select__popper) {
+    background-color: transparent;
+  }
+  :deep(.el-scrollbar) {
+    --el-scrollbar-opacity: 0.8;
+    --el-scrollbar-bg-color: transparent;
   }
 }
 </style>
diff --git a/src/views/ai/dashboard/components/message/HistoryMessageList.vue b/src/views/ai/dashboard/components/message/HistoryMessageList.vue
index 91371e1..b4ebf44 100644
--- a/src/views/ai/dashboard/components/message/HistoryMessageList.vue
+++ b/src/views/ai/dashboard/components/message/HistoryMessageList.vue
@@ -43,7 +43,7 @@
           <div>
             <el-text class="time">{{ formatDate(item.createTime) }}</el-text>
           </div>
-          <div class="right-text-container">
+          <div class="right-text-container question" @click="gotoManual(item)">
             <div class="right-text">{{ item.content }}</div>
           </div>
           <div class="right-btns">
@@ -71,15 +71,12 @@
 import { ArrowDownBold } from '@element-plus/icons-vue'
 import { ChatMessageApi, ChatMessageVO } from '@/api/ai/chat/message'
 import { ChatConversationVO } from '@/api/ai/chat/conversation'
-import { useUserStore } from '@/store/modules/user'
 import userAvatarDefaultImg from '@/assets/ai/zhuanlu/user.png'
 import roleAvatarDefaultImg from '@/assets/ai/zhuanlu/assistant.png'
 
-const dialogVisible = ref(false) // 弹窗的是否展示
 
 const message = useMessage() // 消息弹窗
 const { copy } = useClipboard() // 初始化 copy 到粘贴板
-const userStore = useUserStore()
 
 // 判断“消息列表”滚动的位置(用于判断是否需要滚动到消息最下方)
 const messageContainer: any = ref(null)
@@ -87,6 +84,7 @@
 
 const userAvatar = computed(() => userAvatarDefaultImg)
 const roleAvatar = computed(() => props.conversation.roleAvatar ?? roleAvatarDefaultImg)
+
 
 // 定义 props
 const props = defineProps({
@@ -97,7 +95,9 @@
   list: {
     type: Array as PropType<ChatMessageVO[]>,
     required: true
-  }
+  },
+
+  gotoManualMethod: Function
 })
 
 const { list } = toRefs(props) // 消息列表
@@ -146,6 +146,12 @@
 
 // ============ 处理消息操作 ==============
 
+const gotoManual = async (item: ChatMessageVO) => {
+  if(props.gotoManualMethod) {
+    props.gotoManualMethod(item)
+  }
+}
+
 /** 复制 */
 const copyContent = async (content) => {
   await copy(content)
@@ -192,6 +198,11 @@
     flex-direction: column;
     text-align: left;
     margin: 0 15px;
+
+    .question:hover {
+      cursor: pointer;
+      background: rgba(40, 139, 255, 0.3);
+    }
 
     .time {
       text-align: left;
@@ -282,9 +293,15 @@
   bottom: 0;
   right: 50%;
   .el-button {
-    background: rgba(255,215,0,0.2);
-    border: solid 1px rgba(255,215,0,0.8);
-    color: rgba(255,215,0,0.8);
+    background: rgba(255,255,255,0.1);
+    border: solid 1px rgba(255,215,0,0.6);
+    color: rgba(255,215,0,0.5);
+  }
+  .el-button:hover {
+    cursor: pointer;
+    background-color: rgba(255,255,255,0.4);
+    border: solid 2px rgba(255,215,0);
+    color: rgba(255,215,0);
   }
 }
 </style>
diff --git a/src/views/ai/dashboard/components/message/MessageList.vue b/src/views/ai/dashboard/components/message/MessageList.vue
index 616b9f4..5ecf1bc 100644
--- a/src/views/ai/dashboard/components/message/MessageList.vue
+++ b/src/views/ai/dashboard/components/message/MessageList.vue
@@ -240,7 +240,7 @@
       overflow-wrap: break-word;
       background: rgba(115,196,255,0);
       border-radius: 4px 4px 4px 4px;
-      padding: 20px 10px 5px 0;
+      padding: 0 10px 0 0;
       .left-text {
         color: rgba(219,238,255,0.8);
         font-size: 1rem;
@@ -295,11 +295,23 @@
   }
 }
 
-// 回到底部
 .to-bottom {
   position: absolute;
   z-index: 1000;
   bottom: 0;
   right: 50%;
+
+  .el-button {
+    background: rgba(255, 255, 255, 0.1);
+    border: solid 1px rgba(255, 215, 0, 0.6);
+    color: rgba(255, 215, 0, 0.5);
+  }
+
+  .el-button:hover {
+    cursor: pointer;
+    background-color: rgba(255, 255, 255, 0.4);
+    border: solid 2px rgba(255, 215, 0);
+    color: rgba(255, 215, 0);
+  }
 }
 </style>
diff --git a/src/views/ai/dashboard/zhuanlu/index.vue b/src/views/ai/dashboard/zhuanlu/index.vue
index 0d0be46..9feb5d0 100644
--- a/src/views/ai/dashboard/zhuanlu/index.vue
+++ b/src/views/ai/dashboard/zhuanlu/index.vue
@@ -62,7 +62,7 @@
 
     <div class="gas-scheduling-center">
       <div class="mode-switch">
-        <el-radio-group v-model="tabPosition" class="custom-radio-group">
+        <el-radio-group v-model="tabPosition" @change="handleChange" class="custom-radio-group">
           <el-radio-button label="model">大模型模式</el-radio-button>
           <el-radio-button label="conversation">对话模式</el-radio-button>
         </el-radio-group>
@@ -164,12 +164,15 @@
         <!-- 历史建议 -->
         <HistoryMessageDialog
           ref="historyMessageRef"
-          :conversation="activeConversation"
+          :parentMethod="queryHistoryMessage"
+          @gotoManualMethod="gotoManual"
         />
       </div>
 
       <div v-else>
-        <NormalConversation />
+        <NormalConversation
+          :data="defaultMessage"
+        />
       </div>
     </div>
 
@@ -265,6 +268,7 @@
 import {round} from "lodash-es";
 import {ArrowUpBold} from "@element-plus/icons-vue";
 import * as authUtil from "@/utils/auth";
+import HistoryMessageList from "@/views/ai/dashboard/components/message/HistoryMessageList.vue";
 
 const mqhsList = ref([
   {
@@ -548,6 +552,7 @@
 const messageRef = ref()
 const activeMessageList = ref<ChatMessageVO[]>([]) // 选中对话的消息列表
 const activeHistoryMessageList = ref<ChatMessageVO[]>([]) // 历史建议列表
+const activeHistoryMessageTotal = ref(0) // 历史建议总数
 const activeMessageListLoading = ref<boolean>(false) // activeMessageList 是否正在加载中
 const activeMessageListLoadingTimer = ref<any>() // activeMessageListLoading Timer 定时器。如果加载速度很快,就不进入加载中
 // 消息滚动
@@ -573,8 +578,30 @@
 const historyMessageRef = ref()
 const openHistoryMessage = async () => {
   // 刷新 message 列表
-  await getHistoryMessageList()
-  historyMessageRef.value.open(activeHistoryMessageList.value, activeConversation.value)
+  let resDate = await historyMessageRef.value.dealDate()
+  await getHistoryMessageList(resDate)
+  historyMessageRef.value.open(activeHistoryMessageList.value, activeConversation.value, activeHistoryMessageTotal.value)
+}
+
+const queryHistoryMessage = async (queryParams: ChatMessageVO) => {
+  return await getHistoryMessageList(queryParams)
+}
+
+//切换对话模式判断
+const handleChange = async () => {
+  // 对话进行中,不允许切换
+  if (conversationInProgress.value) {
+    message.alert('对话中,不允许切换!')
+    return false
+  }
+}
+
+// 默认选中消息
+const defaultMessage = ref<ChatMessageVO>()
+
+const gotoManual = async (item: ChatMessageVO) => {
+  defaultMessage.value = item
+  tabPosition.value = 'conversation'
 }
 
 // =========== 【聊天对话】相关 ===========
@@ -671,22 +698,23 @@
 }
 
 /** 获取消息 message 列表 */
-const getHistoryMessageList = async () => {
+const getHistoryMessageList = async (params: any) => {
   if (activeConversationId.value === null) {
     return
   }
+  params.conversationId = activeConversationId.value
   // 获取消息列表
-  activeHistoryMessageList.value = await ChatMessageApi.getChatMessageListByConversationId(
-    activeConversationId.value
-  )
-  if (activeHistoryMessageList.value.length > 0) {
+  let pageResult = await ChatMessageApi.getChatMessagePageListByConversationId(params)
+  activeHistoryMessageList.value = pageResult.list
+  activeHistoryMessageTotal.value = pageResult.total
+  if (activeHistoryMessageList.value != null && activeHistoryMessageList.value.length > 0) {
     activeHistoryMessageList.value.forEach((message: ChatMessageVO) => {
       if(message.type != 'user') {
         dealResult(message)
       }
     })
-    return activeHistoryMessageList.value
   }
+  return pageResult
 }
 //处理调度推理结论
 const dealResult = (message: any) => {
@@ -709,7 +737,6 @@
 const messageList = computed(() => {
   if (activeMessageList.value.length > 0) {
     activeMessageList.value[1].thinking = dealResultAndData(activeMessageList.value[1].content)
-    console.log(activeMessageList.value)
     return activeMessageList.value
   }
   // 没有消息时,如果有 systemMessage 则展示它
@@ -1143,11 +1170,11 @@
   let returnValue = 0;
   if(type == 'max') {
     returnValue = computed(() => {
-      return Math.max(...tank) + 20
+      return Number((Math.max(...tank) + 20).toFixed(0))
     })
   } else if(type == 'min') {
     returnValue = computed(() => {
-      return Math.min(...tank) - 60
+      return Number((Math.min(...tank) - 60).toFixed(0))
     })
   } else if(type == 'average') {
     returnValue = computed(() => {
@@ -1155,7 +1182,7 @@
       tank.forEach((item) => {
         sum += item[0]
       })
-      return (sum / tank.length).toFixed(0);
+      return Number((sum / tank.length).toFixed(0));
     })
   }
   return returnValue.value
@@ -1582,6 +1609,7 @@
 
 // 清理监听
 onUnmounted(() => {
+  console.log('stopStream')
   const events = ['fullscreenchange', 'webkitfullscreenchange', 'msfullscreenchange'];
   events.forEach(event => {
     document.removeEventListener(event, handleFullscreenChange);
diff --git a/src/views/ai/model/template/index.vue b/src/views/ai/model/template/index.vue
index fa84bdb..b6de43b 100644
--- a/src/views/ai/model/template/index.vue
+++ b/src/views/ai/model/template/index.vue
@@ -109,9 +109,7 @@
 
 </template>
 <script lang="ts" setup>
-  import {DICT_TYPE, getIntDictOptions} from '@/utils/dict'
-  import {dateFormatter} from '@/utils/formatTime'
-  import download from '@/utils/download'
+  import { DICT_TYPE } from '@/utils/dict'
   import * as AiQuestionTemplateApi from '@/api/ai/questiontemplate'
   import TemplateForm from './templateForm.vue'
   import * as AiModelApi from "@/api/ai/model/model";
diff --git a/src/views/model/sche/suggest/suggestOperationRecord.vue b/src/views/model/sche/suggest/suggestOperationRecord.vue
index b0ff44b..c66c2aa 100644
--- a/src/views/model/sche/suggest/suggestOperationRecord.vue
+++ b/src/views/model/sche/suggest/suggestOperationRecord.vue
@@ -21,12 +21,13 @@
           label="结果code"
           header-align="center"
           align="left"
-          min-width="150"
+          min-width="50"
         />
         <el-table-column
           prop="resultData"
           label="结果数据"
           header-align="center"
+          min-width="150"
           align="center"
         />
         <el-table-column
@@ -34,21 +35,29 @@
           label="操作"
           header-align="center"
           align="center"
-          min-width="150"
+          min-width="50"
+        />
+        <el-table-column
+          prop="reason"
+          label="原因"
+          header-align="center"
+          align="center"
+          min-width="100"
         />
         <el-table-column
           prop="handler"
           label="处理人"
           header-align="center"
           align="center"
-          min-width="150"
+          min-width="100"
         />
         <el-table-column
           prop="handleTime"
           label="处理时间"
+          :formatter="dateFormatter"
           header-align="center"
           align="center"
-          min-width="150"
+          min-width="100"
         />
       </el-table>
       <!-- 分页 -->
@@ -67,6 +76,7 @@
   import type {DrawerProps} from 'element-plus'
   import { getSuggestOperationRecordPage } from '@/api/model/sche/suggest/suggestOperationRecord';
   import SuggestSnapshot from './suggestSnapshot.vue'
+  import {dateFormatter} from '@/utils/formatTime'
   import {ref} from "vue";
 
   defineOptions({name: 'SuggestOperationRecord'})
diff --git a/src/views/model/sche/suggest/suggestSnapshot.vue b/src/views/model/sche/suggest/suggestSnapshot.vue
index fd8272f..801a654 100644
--- a/src/views/model/sche/suggest/suggestSnapshot.vue
+++ b/src/views/model/sche/suggest/suggestSnapshot.vue
@@ -16,10 +16,10 @@
     </el-checkbox-group>
 
     <div
-      v-for="(chart, index) in charts"
+      v-for="chart in charts"
       :key="chart.id"
       class="chart-container"
-      :ref="el => chartDoms[index] = el"
+      :ref="el => chartDoms[chart.id] = el"
       v-loading="loading"
     ></div>
   </el-dialog>
@@ -34,8 +34,8 @@
   const visible = ref(false)
   const dataList = ref([])
   const selectedData = ref([])
-  const charts = ref([])
-  const chartDoms = ref([])
+  const charts = ref()
+  const chartDoms = ref({})
   const chartInstances = ref([])
   const loading = ref(false)
 
@@ -106,10 +106,10 @@
 
   /** 渲染图表 */
   const renderCharts = () => {
-    chartInstances.value = chartDoms.value.map((dom, index) => {
+    chartInstances.value = charts.value.map((chartInfo, index) => {
+      const dom = chartDoms.value[chartInfo.id]
       if (!dom) return null
       const chart = echarts.init(dom)
-      const chartInfo = charts.value[index]
 
       if (!chartInfo) return chart
 

--
Gitblit v1.9.3