From 3880399bef4144fa15264f470a0a51034c0253c9 Mon Sep 17 00:00:00 2001
From: houzhongjian <houzhongyi@126.com>
Date: 星期四, 29 五月 2025 14:00:18 +0800
Subject: [PATCH] ai工业大模型代码提交

---
 src/views/ai/dashboard/components/conversation/CommonConversationUpdateForm.vue |  219 +
 src/views/ai/dashboard/components/conversation/ConversationListEmpty.vue        |   78 
 src/components/MarkdownView/index.vue                                           |    5 
 src/assets/ai/zhuanlu/data_bg3.png                                              |    0 
 src/assets/ai/zhuanlu/ldggrqsyc_title.png                                       |    0 
 src/assets/ai/zhuanlu/refresh.png                                               |    0 
 src/views/ai/dashboard/components/message/MessageListEmpty.vue                  |   66 
 src/assets/ai/zhuanlu/content_select.png                                        |    0 
 src/components/Dialog/src/DialogDashboard.vue                                   |  118 
 src/views/ai/dashboard/components/conversation/HistoryConversationList.vue      |  459 +++
 src/components/Dialog/src/DialogHistory.vue                                     |  138 +
 src/api/ai/questiontemplate/index.js                                            |   53 
 src/views/ai/dashboard/components/conversation/CommonConversation.vue           |  964 +++++++
 src/assets/ai/zhuanlu/delete.png                                                |    0 
 src/assets/ai/zhuanlu/edit.png                                                  |    0 
 src/assets/ai/zhuanlu/ldghslyc_title.png                                        |    0 
 src/views/ai/dashboard/components/message/HistoryMessageList.vue                |  290 ++
 src/views/ai/dashboard/components/conversation/CommonConversationList.vue       |  526 ++++
 src/views/ai/questionparamsetting/index.vue                                     |  148 +
 src/assets/ai/zhuanlu/ddtljl_result_title.png                                   |    0 
 src/views/ai/dashboard/components/message/MessageList.vue                       |  132 
 src/api/ai/chat/message/index.ts                                                |   68 
 src/views/ai/questiontemplate/index.vue                                         |  186 +
 src/assets/ai/zhuanlu/data_bg4.png                                              |    0 
 src/router/modules/remaining.ts                                                 |    8 
 src/assets/ai/zhuanlu/icon_conversion.png                                       |    0 
 src/assets/ai/zhuanlu/content.png                                               |    0 
 src/assets/ai/zhuanlu/icon_bg.png                                               |    0 
 src/assets/ai/zhuanlu/left_label.png                                            |    0 
 src/utils/rem.ts                                                                |    9 
 src/views/ai/questionparamsetting/QuestionParamSettingForm.vue                  |  117 
 src/views/ai/questiontemplate/QuestionTemplateForm.vue                          |  148 +
 src/api/ai/questionparamsetting/index.js                                        |   53 
 src/assets/ai/zhuanlu/common_title.png                                          |    0 
 src/assets/ai/zhuanlu/copy.png                                                  |    0 
 /dev/null                                                                       | 1138 --------
 src/assets/ai/zhuanlu/assistant.png                                             |    0 
 src/assets/ai/zhuanlu/user.png                                                  |    0 
 src/views/ai/dashboard/components/conversation/ConversationList.vue             |    1 
 src/assets/ai/zhuanlu/conversation.png                                          |    0 
 src/assets/ai/zhuanlu/right_label.png                                           |    0 
 src/assets/ai/zhuanlu/conversation_big.png                                      |    0 
 src/assets/ai/zhuanlu/scmbyyxzb_title.png                                       |    0 
 src/views/ai/dashboard/components/message/ModelMessageList.vue                  |  193 +
 src/views/ai/dashboard/components/message/HistoryMessageDialog.vue              |  375 ++
 src/assets/ai/zhuanlu/mqhsjhxx_title.png                                        |    0 
 src/views/ai/dashboard/zhuanlu/index.vue                                        | 2347 +++++++++++++++++
 47 files changed, 6,607 insertions(+), 1,232 deletions(-)

diff --git a/src/api/ai/chat/message/index.ts b/src/api/ai/chat/message/index.ts
index 62e82b6..1123524 100644
--- a/src/api/ai/chat/message/index.ts
+++ b/src/api/ai/chat/message/index.ts
@@ -2,6 +2,7 @@
 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 {
@@ -13,6 +14,9 @@
   model: number // 模型标志
   modelId: number // 模型编号
   content: string // 聊天内容
+  thinking: string // 聊天思考
+  thinkingFlag: boolean // 聊天思考
+  conclusion: string // 聊天结论
   tokens: number // 消耗 Token 数量
   segmentIds?: number[] // 段落编号
   segments?: {
@@ -73,34 +77,34 @@
     })
   },
   // 发送 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
-    })
-  },
+  // 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) => {
@@ -114,10 +118,10 @@
     })
   },
 
-  // 删除消息【工业大模型专用】
-  deleteEnergyChatMessage: async (id: string) => {
-    return await request.delete({ url: `/ai/chat/message/delete-energy?id=${id}` })
-  },
+  // // 删除消息【工业大模型专用】
+  // deleteEnergyChatMessage: async (id: string) => {
+  //   return await request.delete({ url: `/ai/chat/message/delete-energy?id=${id}` })
+  // },
 
   // 删除指定对话的消息【工业大模型专用】
   deleteEnergyByConversationId: async (conversationId: number) => {
diff --git a/src/api/ai/questionparamsetting/index.js b/src/api/ai/questionparamsetting/index.js
new file mode 100644
index 0000000..6c337b3
--- /dev/null
+++ b/src/api/ai/questionparamsetting/index.js
@@ -0,0 +1,53 @@
+import request from '@/utils/request'
+
+// 创建大模型问题设置参数
+export function createQuestionParamSetting(data) {
+  return request({
+    url: '/ai/question-param-setting/create',
+    method: 'post',
+    data: data
+  })
+}
+
+// 更新大模型问题设置参数
+export function updateQuestionParamSetting(data) {
+  return request({
+    url: '/ai/question-param-setting/update',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除大模型问题设置参数
+export function deleteQuestionParamSetting(id) {
+  return request({
+    url: '/ai/question-param-setting/delete?id=' + id,
+    method: 'delete'
+  })
+}
+
+// 获得大模型问题设置参数
+export function getQuestionParamSetting(id) {
+  return request({
+    url: '/ai/question-param-setting/get?id=' + id,
+    method: 'get'
+  })
+}
+
+// 获得大模型问题设置参数分页
+export function getQuestionParamSettingPage(params) {
+  return request({
+    url: '/ai/question-param-setting/page',
+    method: 'get',
+    params
+  })
+}
+// 导出大模型问题设置参数 Excel
+export function exportQuestionParamSettingExcel(params) {
+  return request({
+    url: '/ai/question-param-setting/export-excel',
+    method: 'get',
+    params,
+    responseType: 'blob'
+  })
+}
\ No newline at end of file
diff --git a/src/api/ai/questiontemplate/index.js b/src/api/ai/questiontemplate/index.js
new file mode 100644
index 0000000..520ba3f
--- /dev/null
+++ b/src/api/ai/questiontemplate/index.js
@@ -0,0 +1,53 @@
+import request from '@/utils/request'
+
+// 创建大模型问题模板
+export function createQuestionTemplate(data) {
+  return request({
+    url: '/ai/question-template/create',
+    method: 'post',
+    data: data
+  })
+}
+
+// 更新大模型问题模板
+export function updateQuestionTemplate(data) {
+  return request({
+    url: '/ai/question-template/update',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除大模型问题模板
+export function deleteQuestionTemplate(id) {
+  return request({
+    url: '/ai/question-template/delete?id=' + id,
+    method: 'delete'
+  })
+}
+
+// 获得大模型问题模板
+export function getQuestionTemplate(id) {
+  return request({
+    url: '/ai/question-template/get?id=' + id,
+    method: 'get'
+  })
+}
+
+// 获得大模型问题模板分页
+export function getQuestionTemplatePage(params) {
+  return request({
+    url: '/ai/question-template/page',
+    method: 'get',
+    params
+  })
+}
+// 导出大模型问题模板 Excel
+export function exportQuestionTemplateExcel(params) {
+  return request({
+    url: '/ai/question-template/export-excel',
+    method: 'get',
+    params,
+    responseType: 'blob'
+  })
+}
\ No newline at end of file
diff --git a/src/assets/ai/zhuanlu/assistant.png b/src/assets/ai/zhuanlu/assistant.png
new file mode 100644
index 0000000..6ac16ef
--- /dev/null
+++ b/src/assets/ai/zhuanlu/assistant.png
Binary files differ
diff --git a/src/assets/ai/zhuanlu/common_title.png b/src/assets/ai/zhuanlu/common_title.png
new file mode 100644
index 0000000..7670b91
--- /dev/null
+++ b/src/assets/ai/zhuanlu/common_title.png
Binary files differ
diff --git a/src/assets/ai/zhuanlu/content.png b/src/assets/ai/zhuanlu/content.png
new file mode 100644
index 0000000..52e59ce
--- /dev/null
+++ b/src/assets/ai/zhuanlu/content.png
Binary files differ
diff --git a/src/assets/ai/zhuanlu/content_select.png b/src/assets/ai/zhuanlu/content_select.png
new file mode 100644
index 0000000..d6b928f
--- /dev/null
+++ b/src/assets/ai/zhuanlu/content_select.png
Binary files differ
diff --git a/src/assets/ai/zhuanlu/conversation.png b/src/assets/ai/zhuanlu/conversation.png
new file mode 100644
index 0000000..48b840f
--- /dev/null
+++ b/src/assets/ai/zhuanlu/conversation.png
Binary files differ
diff --git a/src/assets/ai/zhuanlu/conversation_big.png b/src/assets/ai/zhuanlu/conversation_big.png
new file mode 100644
index 0000000..b51c940
--- /dev/null
+++ b/src/assets/ai/zhuanlu/conversation_big.png
Binary files differ
diff --git a/src/assets/ai/zhuanlu/copy.png b/src/assets/ai/zhuanlu/copy.png
new file mode 100644
index 0000000..f148cba
--- /dev/null
+++ b/src/assets/ai/zhuanlu/copy.png
Binary files differ
diff --git a/src/assets/ai/zhuanlu/data_bg3.png b/src/assets/ai/zhuanlu/data_bg3.png
new file mode 100644
index 0000000..bdcbe84
--- /dev/null
+++ b/src/assets/ai/zhuanlu/data_bg3.png
Binary files differ
diff --git a/src/assets/ai/zhuanlu/data_bg4.png b/src/assets/ai/zhuanlu/data_bg4.png
new file mode 100644
index 0000000..1f5f878
--- /dev/null
+++ b/src/assets/ai/zhuanlu/data_bg4.png
Binary files differ
diff --git a/src/assets/ai/zhuanlu/ddtljl_result_title.png b/src/assets/ai/zhuanlu/ddtljl_result_title.png
new file mode 100644
index 0000000..2162562
--- /dev/null
+++ b/src/assets/ai/zhuanlu/ddtljl_result_title.png
Binary files differ
diff --git a/src/assets/ai/zhuanlu/delete.png b/src/assets/ai/zhuanlu/delete.png
new file mode 100644
index 0000000..18f3848
--- /dev/null
+++ b/src/assets/ai/zhuanlu/delete.png
Binary files differ
diff --git a/src/assets/ai/zhuanlu/edit.png b/src/assets/ai/zhuanlu/edit.png
new file mode 100644
index 0000000..10254bb
--- /dev/null
+++ b/src/assets/ai/zhuanlu/edit.png
Binary files differ
diff --git a/src/assets/ai/zhuanlu/icon_bg.png b/src/assets/ai/zhuanlu/icon_bg.png
new file mode 100644
index 0000000..01c8235
--- /dev/null
+++ b/src/assets/ai/zhuanlu/icon_bg.png
Binary files differ
diff --git a/src/assets/ai/zhuanlu/icon_conversion.png b/src/assets/ai/zhuanlu/icon_conversion.png
new file mode 100644
index 0000000..48b840f
--- /dev/null
+++ b/src/assets/ai/zhuanlu/icon_conversion.png
Binary files differ
diff --git a/src/assets/ai/zhuanlu/ldggrqsyc_title.png b/src/assets/ai/zhuanlu/ldggrqsyc_title.png
new file mode 100644
index 0000000..4619d97
--- /dev/null
+++ b/src/assets/ai/zhuanlu/ldggrqsyc_title.png
Binary files differ
diff --git a/src/assets/ai/zhuanlu/ldghslyc_title.png b/src/assets/ai/zhuanlu/ldghslyc_title.png
new file mode 100644
index 0000000..d561e77
--- /dev/null
+++ b/src/assets/ai/zhuanlu/ldghslyc_title.png
Binary files differ
diff --git a/src/assets/ai/zhuanlu/left_label.png b/src/assets/ai/zhuanlu/left_label.png
new file mode 100644
index 0000000..2807509
--- /dev/null
+++ b/src/assets/ai/zhuanlu/left_label.png
Binary files differ
diff --git a/src/assets/ai/zhuanlu/mqhsjhxx_title.png b/src/assets/ai/zhuanlu/mqhsjhxx_title.png
new file mode 100644
index 0000000..b5f2526
--- /dev/null
+++ b/src/assets/ai/zhuanlu/mqhsjhxx_title.png
Binary files differ
diff --git a/src/assets/ai/zhuanlu/refresh.png b/src/assets/ai/zhuanlu/refresh.png
new file mode 100644
index 0000000..96e2c9b
--- /dev/null
+++ b/src/assets/ai/zhuanlu/refresh.png
Binary files differ
diff --git a/src/assets/ai/zhuanlu/right_label.png b/src/assets/ai/zhuanlu/right_label.png
new file mode 100644
index 0000000..a74da5b
--- /dev/null
+++ b/src/assets/ai/zhuanlu/right_label.png
Binary files differ
diff --git a/src/assets/ai/zhuanlu/scmbyyxzb_title.png b/src/assets/ai/zhuanlu/scmbyyxzb_title.png
new file mode 100644
index 0000000..912ee9b
--- /dev/null
+++ b/src/assets/ai/zhuanlu/scmbyyxzb_title.png
Binary files differ
diff --git a/src/assets/ai/zhuanlu/user.png b/src/assets/ai/zhuanlu/user.png
new file mode 100644
index 0000000..e55f54f
--- /dev/null
+++ b/src/assets/ai/zhuanlu/user.png
Binary files differ
diff --git a/src/components/Dialog/src/DialogDashboard.vue b/src/components/Dialog/src/DialogDashboard.vue
new file mode 100644
index 0000000..358931b
--- /dev/null
+++ b/src/components/Dialog/src/DialogDashboard.vue
@@ -0,0 +1,118 @@
+<script lang="ts" setup>
+import { propTypes } from '@/utils/propTypes'
+import { isNumber } from '@/utils/is'
+defineOptions({ name: 'DialogDashboard' })
+
+const slots = useSlots()
+
+const props = defineProps({
+  modelValue: propTypes.bool.def(false),
+  title: propTypes.string.def('Dialog'),
+  fullscreen: propTypes.bool.def(true),
+  width: propTypes.oneOfType([String, Number]).def('30%'),
+  scroll: propTypes.bool.def(false), // 是否开启滚动条。如果是的话,按照 maxHeight 设置最大高度
+  maxHeight: propTypes.oneOfType([String, Number]).def('400px')
+})
+
+const getBindValue = computed(() => {
+  const delArr: string[] = ['fullscreen', 'title', 'maxHeight', 'appendToBody']
+  const attrs = useAttrs()
+  const obj = { ...attrs, ...props }
+  for (const key in obj) {
+    if (delArr.indexOf(key) !== -1) {
+      delete obj[key]
+    }
+  }
+  return obj
+})
+
+</script>
+
+<template>
+  <ElDialog
+    v-bind="getBindValue"
+    :close-on-click-modal="true"
+    :width="width"
+    destroy-on-close
+    lock-scroll
+    draggable
+    class="dashboard-dialog"
+    :show-close="false"
+  >
+    <template #header="{ close }">
+      <div class="relative h-30px flex items-center justify-between pl-15px pr-15px">
+        <slot name="title">
+          {{ title }}
+        </slot>
+        <div
+          class="absolute right-15px top-[50%] h-40px flex translate-y-[-50%] items-center justify-between"
+        >
+          <Icon
+            class="is-hover cursor-pointer"
+            icon="ep:close"
+            hover-color="var(--el-color-primary)"
+            color="#73C4FF"
+            @click="close"
+          />
+        </div>
+      </div>
+    </template>
+
+    <slot></slot>
+    <template v-if="slots.footer" #footer>
+      <slot name="footer"></slot>
+    </template>
+  </ElDialog>
+</template>
+
+<style lang="scss">
+.el-dialog.is-draggable .el-dialog__header {
+  color: white !important;
+}
+::v-deep .el-input {
+  background: rgba(0,194,255,0.08) !important;
+}
+::v-deep .el-input__inner {
+  color: #8FD6FE;
+  background: rgba(0,194,255,0.08) !important;
+  border: 1px solid #1D9FE8
+}
+.dashboard-dialog {
+  color: #73C4FF;
+  background: rgba(3,29,76,0.79);
+  border-radius: 4px 4px 4px 4px;
+  border: 1px solid;
+  .#{$elNamespace}-overlay-dialog {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+  }
+
+  .#{$elNamespace}-dialog {
+    margin: 0 !important;
+
+    &__header {
+      height: 40px;
+      padding: 0;
+      margin-right: 0 !important;
+      margin-bottom: 20px;
+      background:
+        url("@/assets/ai/zhuanlu/common_title.png") no-repeat,
+        linear-gradient(to bottom, #0a1633dd, #0a1633dd); /* 叠加深色遮罩 */
+      div {
+        color: #73C4FF;
+        margin-left: 20px;
+      }
+    }
+
+    &__body {
+      padding: 15px !important;
+    }
+
+    &__headerbtn {
+      color: #73C4FF;
+      top: 0;
+    }
+  }
+}
+</style>
diff --git a/src/components/Dialog/src/DialogHistory.vue b/src/components/Dialog/src/DialogHistory.vue
new file mode 100644
index 0000000..0b3dd5a
--- /dev/null
+++ b/src/components/Dialog/src/DialogHistory.vue
@@ -0,0 +1,138 @@
+<script lang="ts" setup>
+import { propTypes } from '@/utils/propTypes'
+import { isNumber } from '@/utils/is'
+defineOptions({ name: 'DialogHistory' })
+
+const slots = useSlots()
+
+const props = defineProps({
+  modelValue: propTypes.bool.def(false),
+  title: propTypes.string.def('Dialog'),
+  fullscreen: propTypes.bool.def(true),
+  width: propTypes.oneOfType([String, Number]).def('30%'),
+  scroll: propTypes.bool.def(false), // 是否开启滚动条。如果是的话,按照 maxHeight 设置最大高度
+  maxHeight: propTypes.oneOfType([String, Number]).def('400px')
+})
+
+const getBindValue = computed(() => {
+  const delArr: string[] = ['fullscreen', 'title', 'maxHeight', 'appendToBody']
+  const attrs = useAttrs()
+  const obj = { ...attrs, ...props }
+  for (const key in obj) {
+    if (delArr.indexOf(key) !== -1) {
+      delete obj[key]
+    }
+  }
+  return obj
+})
+
+const isFullscreen = ref(false)
+
+const toggleFull = () => {
+  isFullscreen.value = !unref(isFullscreen)
+}
+
+const dialogHeight = ref(isNumber(props.maxHeight) ? `${props.maxHeight}px` : props.maxHeight)
+
+watch(
+  () => isFullscreen.value,
+  async (val: boolean) => {
+    await nextTick()
+    if (val) {
+      const windowHeight = document.documentElement.offsetHeight
+      dialogHeight.value = `${windowHeight - 55 - 60 - (slots.footer ? 63 : 0)}px`
+    } else {
+      dialogHeight.value = isNumber(props.maxHeight) ? `${props.maxHeight}px` : props.maxHeight
+    }
+  },
+  {
+    immediate: true
+  }
+)
+
+</script>
+
+<template>
+  <ElDialog
+    v-bind="getBindValue"
+    :fullscreen="isFullscreen"
+    :close-on-click-modal="true"
+    :width="width"
+    destroy-on-close
+    lock-scroll
+    draggable
+    class="history-dialog"
+    :show-close="false"
+  >
+    <template #header="{ close }">
+      <div class="relative h-30px flex items-center justify-between pl-15px pr-15px">
+        <slot name="title">
+          {{ title }}
+        </slot>
+        <div
+          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)"
+            color="#73C4FF"
+            @click="close"
+          />
+        </div>
+      </div>
+    </template>
+
+    <slot></slot>
+    <template v-if="slots.footer" #footer>
+      <slot name="footer"></slot>
+    </template>
+  </ElDialog>
+</template>
+
+<style lang="scss">
+.history-dialog {
+  height: 90vh;
+  margin-top: 30px;
+  color: #73C4FF;
+  overflow: hidden; /* 防止内容溢出 */
+  background: rgba(3,29,76,0.79);
+  border-radius: 4px 4px 4px 4px;
+  border: 1px solid;
+  .#{$elNamespace}-overlay-dialog {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+  }
+
+  .#{$elNamespace}-dialog {
+    margin: 0 !important;
+
+    &__header {
+      height: 40px;
+      padding: 0;
+      margin-right: 0 !important;
+      background:
+        url("@/assets/ai/zhuanlu/common_title.png") left no-repeat,
+        linear-gradient(to bottom, #0a1633dd, #0a1633dd); /* 叠加深色遮罩 */
+      div {
+        color: #73C4FF;
+        margin-left: 20px;
+      }
+    }
+
+    &__headerbtn {
+      color: #73C4FF;
+      top: 0;
+    }
+  }
+}
+</style>
diff --git a/src/components/MarkdownView/index.vue b/src/components/MarkdownView/index.vue
index 0b1837f..65541f6 100644
--- a/src/components/MarkdownView/index.vue
+++ b/src/components/MarkdownView/index.vue
@@ -39,7 +39,10 @@
 
 /** 保留换行符 */
 const formatContent = (text) => {
-  return text.replace(/\n/g, '<br>')
+  if (text) {
+    return text.replace(/\n/g, '<br>')
+  }
+  return text
 }
 
 /** 初始化 **/
diff --git a/src/router/modules/remaining.ts b/src/router/modules/remaining.ts
index 8e594fd..2e808c4 100644
--- a/src/router/modules/remaining.ts
+++ b/src/router/modules/remaining.ts
@@ -1,6 +1,8 @@
 import {Layout} from '@/utils/routerHelper'
+import {meta} from "eslint-plugin-prettier";
 
 const { t } = useI18n()
+
 /**
  * redirect: noredirect        当设置 noredirect 的时候该路由在面包屑导航中不可被点击
  * name:'router-name'          设定路由的名字,一定要填写不然使用<keep-alive>时会出现各种问题
@@ -576,11 +578,11 @@
   {
     path: '/ai/zhuanlu',
     name: 'Zhuanlu',
-    component: () => import('@/views/ai/dashboard/zhuanlu/Index.vue'),
+    component: () => import('@/views/ai/dashboard/zhuanlu/index.vue'),
     meta: {
       hidden: true,
-      noTagsView: true
-    },
+      noTagsView: true,
+    }
   },
   {
     path: '/model/analysis',
diff --git a/src/utils/rem.ts b/src/utils/rem.ts
new file mode 100644
index 0000000..00be2a1
--- /dev/null
+++ b/src/utils/rem.ts
@@ -0,0 +1,9 @@
+// utils/rem.js
+const baseWidth = 1920 // 设计稿基准宽度
+const baseFontSize = 16 // 基准字体大小
+
+export const setRem = () => {
+  const scale = document.documentElement.clientWidth / baseWidth
+  document.documentElement.style.fontSize =
+    `${Math.min(scale, 1.5) * baseFontSize}px` // 限制最大缩放比例
+}
diff --git a/src/views/ai/dashboard/components/conversation/CommonConversation.vue b/src/views/ai/dashboard/components/conversation/CommonConversation.vue
new file mode 100644
index 0000000..2de4e77
--- /dev/null
+++ b/src/views/ai/dashboard/components/conversation/CommonConversation.vue
@@ -0,0 +1,964 @@
+<template>
+  <!-- 整体容器添加相对定位 -->
+  <div class="container-wrapper" ref="containerRef">
+    <!-- 折叠按钮 -->
+    <div
+      class="sidebar-toggle"
+      :style="{ left: toggleLeft }"
+      @click.stop="toggleSidebar"
+      ref="toggleRef">
+      <Icon :icon="isCollapsed ? 'ep:caret-right' : 'ep:caret-left'" />
+    </div>
+
+    <!-- 左侧对话列表 -->
+    <div
+      class="conversation-wrapper"
+      :class="{ collapsed: isCollapsed }"
+      :style="{ width: sidebarWidth }"
+      ref="sidebarRef">
+      <ConversationList
+        :active-id="activeConversationId"
+        ref="conversationListRef"
+        @on-conversation-create="handleConversationCreateSuccess"
+        @on-conversation-click="handleConversationClick"
+        @on-conversation-clear="handleConversationClear"
+        @on-conversation-delete="handlerConversationDelete"
+      />
+    </div>
+    <!-- 右侧:对话详情 -->
+    <el-container class="detail-container">
+      <el-header class="header">
+        <div class="title">
+          {{ activeConversation?.title ? activeConversation?.title : '对话' }}
+          <span v-if="activeMessageList.length">({{ activeMessageList.length }})</span>
+        </div>
+        <div class="btns" v-if="activeConversation">
+          <el-button type="primary" bg plain size="small" @click="openChatConversationUpdateForm">
+            <span v-html="activeConversation?.modelName"></span>
+            <Icon icon="ep:setting" class="ml-10px" />
+          </el-button>
+          <el-button size="small" class="btn" @click="handlerMessageClear">
+            <Icon icon="heroicons-outline:archive-box-x-mark" color="#73C4FF" />
+          </el-button>
+          <el-button size="small" class="btn" @click="handleGoBottomMessage">
+            <Icon icon="ep:download" color="#73C4FF" />
+          </el-button>
+          <el-button size="small" class="btn" @click="handleGoTopMessage">
+            <Icon icon="ep:top" color="#73C4FF" />
+          </el-button>
+        </div>
+      </el-header>
+
+      <!-- main:消息列表 -->
+      <el-main class="main-container">
+        <div class="message-container">
+          <!-- 情况一:消息加载中 -->
+          <MessageLoading v-if="activeMessageListLoading" />
+          <!-- 情况二:无聊天对话时 -->
+          <MessageNewConversation
+            v-if="!activeConversation"
+            @on-new-conversation="handleConversationCreate"
+          />
+          <!-- 情况三:消息列表为空 -->
+          <MessageListEmpty
+            v-if="!activeMessageListLoading && messageList.length === 0 && activeConversation"
+            @on-prompt="doSendMessage"
+          />
+          <!-- 情况四:消息列表不为空 -->
+          <MessageList
+            v-if="!activeMessageListLoading && messageList.length > 0"
+            ref="messageRef"
+            :conversation="activeConversation"
+            :list="messageList"
+            @on-delete-success="handleMessageDelete"
+            @on-edit="handleMessageEdit"
+            @on-refresh="handleMessageRefresh"
+          />
+        </div>
+      </el-main>
+
+      <!-- 底部 -->
+      <el-footer class="footer-container">
+        <!-- 输入框 -->
+        <div class="input-container">
+          <form class="prompt-from">
+            <textarea
+              class="prompt-input"
+              v-model="prompt"
+              @keydown="handleSendByKeydown"
+              @input="handlePromptInput"
+              @compositionstart="onCompositionstart"
+              @compositionend="onCompositionend"
+              placeholder="问我任何问题...(Shift+Enter 换行,按下 Enter 发送)"
+            ></textarea>
+            <div class="prompt-btns">
+              <div class="content">
+                <el-button
+                  :class="{ 'active-button': enableContext }"
+                  @click="enableContext = !enableContext"
+                >
+                  <el-icon class="content-icon" />
+                  上下文
+                </el-button>
+              </div>
+              <div class="message">
+                <el-button
+                  type="primary"
+                  size="default"
+                  @click="handleSendByButton"
+                  :loading="conversationInProgress"
+                  v-if="conversationInProgress == false"
+                >
+                  {{ conversationInProgress ? '进行中' : '发消息' }}
+                </el-button>
+                <el-button
+                  type="danger"
+                  size="default"
+                  @click="stopStream()"
+                  v-if="conversationInProgress == true"
+                >
+                  停止
+                </el-button>
+              </div>
+            </div>
+          </form>
+        </div>
+      </el-footer>
+    </el-container>
+  </div>
+  <!-- 更新对话 Form -->
+  <ConversationUpdateForm
+    ref="conversationUpdateFormRef"
+    @success="handleConversationUpdateSuccess"
+  />
+</template>
+
+<script setup lang="ts">
+import { ChatMessageApi, ChatMessageVO } from '@/api/ai/chat/message'
+import { ChatConversationApi, ChatConversationVO } from '@/api/ai/chat/conversation'
+import ConversationList from './CommonConversationList.vue'
+import ConversationUpdateForm from './CommonConversationUpdateForm.vue'
+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";
+
+/** AI 聊天对话 列表 */
+defineOptions({ name: 'NormalConversation' })
+
+const route = useRoute() // 路由
+const message = useMessage() // 消息弹窗
+
+const isCollapsed = ref(true)
+const sidebarWidth = ref('270px')
+const toggleLeft = computed(() => isCollapsed.value ? '0' : sidebarWidth.value)
+
+// 新增DOM引用用于会话列表的展开和收缩
+const containerRef = ref<HTMLElement>()
+const sidebarRef = ref<HTMLElement>()
+const toggleRef = ref<HTMLElement>()
+
+// 点击外部区域处理
+onClickOutside(sidebarRef, (event) => {
+  if (!isCollapsed.value &&
+    !sidebarRef.value?.contains(event.target) &&
+    !toggleRef.value?.contains(event.target)) {
+    isCollapsed.value = true
+  }
+})
+
+// 切换侧边栏
+const toggleSidebar = () => {
+  isCollapsed.value = !isCollapsed.value
+}
+
+// 聊天对话
+const conversationListRef = ref()
+const activeConversationId = ref<number | null>(null) // 选中的对话编号
+const activeConversation = ref<ChatConversationVO | null>(null) // 选中的 Conversation
+const conversationInProgress = ref(false) // 对话是否正在进行中。目前只有【发送】消息时,会更新为 true,避免切换对话、删除对话等操作
+
+// 消息列表
+const messageRef = ref()
+const activeMessageList = ref<ChatMessageVO[]>([]) // 选中对话的消息列表
+const activeMessageListLoading = ref<boolean>(false) // activeMessageList 是否正在加载中
+const activeMessageListLoadingTimer = ref<any>() // activeMessageListLoading Timer 定时器。如果加载速度很快,就不进入加载中
+// 消息滚动
+const textSpeed = ref<number>(50) // Typing speed in milliseconds
+const textRoleRunning = ref<boolean>(false) // Typing speed in milliseconds
+
+// 发送消息输入框
+const isComposing = ref(false) // 判断用户是否在输入
+const conversationInAbortController = ref<any>() // 对话进行中 abort 控制器(控制 stream 对话)
+const inputTimeout = ref<any>() // 处理输入中回车的定时器
+const prompt = ref<string>() // prompt
+const enableContext = ref<boolean>(true) // 是否开启上下文
+// 接收 Stream 消息
+const receiveMessageFullText = ref('')
+const receiveMessageDisplayedText = ref('')
+
+
+// =========== 【聊天对话】相关 ===========
+
+/** 获取对话信息 */
+const getConversation = async (id: number | null) => {
+  if (!id) {
+    return
+  }
+  const conversation: ChatConversationVO = await ChatConversationApi.getChatConversationMy(id)
+  if (!conversation) {
+    return
+  }
+  activeConversation.value = conversation
+  activeConversationId.value = conversation.id
+}
+
+/**
+ * 点击某个对话
+ *
+ * @param conversation 选中的对话
+ * @return 是否切换成功
+ */
+const handleConversationClick = async (conversation: ChatConversationVO) => {
+  // 对话进行中,不允许切换
+  if (conversationInProgress.value) {
+    message.alert('对话中,不允许切换!')
+    return false
+  }
+
+  // 更新选中的对话 id
+  activeConversationId.value = conversation.id
+  activeConversation.value = conversation
+  // 刷新 message 列表
+  await getMessageList()
+  // 滚动底部
+  scrollToBottom(true)
+  // 清空输入框
+  prompt.value = ''
+  return true
+}
+
+/** 删除某个对话*/
+const handlerConversationDelete = async (delConversation: ChatConversationVO) => {
+  // 删除的对话如果是当前选中的,那么就重置
+  if (activeConversationId.value === delConversation.id) {
+    await handleConversationClear()
+  }
+}
+/** 清空选中的对话 */
+const handleConversationClear = async () => {
+  // 对话进行中,不允许切换
+  if (conversationInProgress.value) {
+    message.alert('对话中,不允许切换!')
+    return false
+  }
+  activeConversationId.value = null
+  activeConversation.value = null
+  activeMessageList.value = []
+}
+
+/** 修改聊天对话 */
+const conversationUpdateFormRef = ref()
+const openChatConversationUpdateForm = async () => {
+  conversationUpdateFormRef.value.open(activeConversationId.value)
+}
+const handleConversationUpdateSuccess = async () => {
+  // 对话更新成功,刷新最新信息
+  await getConversation(activeConversationId.value)
+}
+
+/** 处理聊天对话的创建成功 */
+const handleConversationCreate = async () => {
+  // 创建对话
+  await conversationListRef.value.createConversation()
+}
+/** 处理聊天对话的创建成功 */
+const handleConversationCreateSuccess = async () => {
+  // 创建新的对话,清空输入框
+  prompt.value = ''
+}
+
+// =========== 【消息列表】相关 ===========
+
+/** 获取消息 message 列表 */
+const getMessageList = async () => {
+  try {
+    if (activeConversationId.value === null) {
+      return
+    }
+    // Timer 定时器,如果加载速度很快,就不进入加载中
+    activeMessageListLoadingTimer.value = setTimeout(() => {
+      activeMessageListLoading.value = true
+    }, 60)
+
+    // 获取消息列表
+    activeMessageList.value = await ChatMessageApi.getChatMessageListByConversationId(
+      activeConversationId.value
+    )
+
+    // 滚动到最下面
+    await nextTick()
+    await scrollToBottom()
+  } finally {
+    // time 定时器,如果加载速度很快,就不进入加载中
+    if (activeMessageListLoadingTimer.value) {
+      clearTimeout(activeMessageListLoadingTimer.value)
+    }
+    // 加载结束
+    activeMessageListLoading.value = false
+  }
+}
+
+/**
+ * 消息列表
+ *
+ * 和 {@link #getMessageList()} 的差异是,把 systemMessage 考虑进去
+ */
+const messageList = computed(() => {
+  if (activeMessageList.value.length > 0) {
+    dealResult(activeMessageList.value)
+    return activeMessageList.value
+  }
+  // 没有消息时,如果有 systemMessage 则展示它
+  if (activeConversation.value?.systemMessage) {
+    return [
+      {
+        id: 0,
+        type: 'system',
+        content: activeConversation.value.systemMessage
+      }
+    ]
+  }
+  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]
+    }
+  })
+}
+
+/** 处理删除 message 消息 */
+const handleMessageDelete = () => {
+  if (conversationInProgress.value) {
+    message.alert('回答中,不能删除!')
+    return
+  }
+  // 刷新 message 列表
+  getMessageList()
+}
+
+/** 处理 message 清空 */
+const handlerMessageClear = async () => {
+  if (!activeConversationId.value) {
+    return
+  }
+  try {
+    // 确认提示
+    await message.delConfirm('确认清空对话消息?')
+    // 清空对话
+    await ChatMessageApi.deleteByConversationId(activeConversationId.value)
+    // 刷新 message 列表
+    activeMessageList.value = []
+  } catch {}
+}
+
+/** 回到 message 列表的顶部 */
+const handleGoTopMessage = () => {
+  messageRef.value.handlerGoTop()
+}
+
+/** 回到 message 列表的底部 */
+const handleGoBottomMessage = () => {
+  messageRef.value.handleGoBottom()
+}
+
+// =========== 【发送消息】相关 ===========
+
+/** 处理来自 keydown 的发送消息 */
+const handleSendByKeydown = async (event) => {
+  // 判断用户是否在输入
+  if (isComposing.value) {
+    return
+  }
+  // 进行中不允许发送
+  if (conversationInProgress.value) {
+    return
+  }
+  const content = prompt.value?.trim() as string
+  if (event.key === 'Enter') {
+    if (event.shiftKey) {
+      // 插入换行
+      prompt.value += '\r\n'
+      event.preventDefault() // 防止默认的换行行为
+    } else {
+      // 发送消息
+      await doSendMessage(content)
+      event.preventDefault() // 防止默认的提交行为
+    }
+  }
+}
+
+/** 处理来自【发送】按钮的发送消息 */
+const handleSendByButton = () => {
+  doSendMessage(prompt.value?.trim() as string)
+}
+
+/** 处理 prompt 输入变化 */
+const handlePromptInput = (event) => {
+  // 非输入法 输入设置为 true
+  if (!isComposing.value) {
+    // 回车 event data 是 null
+    if (event.data == null) {
+      return
+    }
+    isComposing.value = true
+  }
+  // 清理定时器
+  if (inputTimeout.value) {
+    clearTimeout(inputTimeout.value)
+  }
+  // 重置定时器
+  inputTimeout.value = setTimeout(() => {
+    isComposing.value = false
+  }, 400)
+}
+// TODO @芋艿:是不是可以通过 @keydown.enter、@keydown.shift.enter 来实现,回车发送、shift+回车换行;主要看看,是不是可以简化 isComposing 相关的逻辑
+const onCompositionstart = () => {
+  isComposing.value = true
+}
+const onCompositionend = () => {
+  // console.log('输入结束...')
+  setTimeout(() => {
+    isComposing.value = false
+  }, 200)
+}
+
+/** 真正执行【发送】消息操作 */
+const doSendMessage = async (content: string) => {
+  // 校验
+  if (content.length < 1) {
+    message.error('发送失败,原因:内容为空!')
+    return
+  }
+  if (activeConversationId.value == null) {
+    message.error('还没创建对话,不能发送!')
+    return
+  }
+  // 清空输入框
+  prompt.value = ''
+  // 发送请求时如果accessToken过期,无法中断请求,暂时增加请求前刷新token
+  authUtil.setToken(await refreshToken())
+  // 执行发送
+  await doSendMessageStream({
+    conversationId: activeConversationId.value,
+    content: content
+  } as ChatMessageVO)
+}
+
+/** 真正执行【发送】消息操作 */
+const doSendMessageStream = async (userMessage: ChatMessageVO) => {
+  // 创建 AbortController 实例,以便中止请求
+  conversationInAbortController.value = new AbortController()
+  // 标记对话进行中
+  conversationInProgress.value = true
+  // 设置为空
+  receiveMessageFullText.value = ''
+
+  try {
+    // 1.1 先添加两个假数据,等 stream 返回再替换
+    activeMessageList.value.push({
+      id: -1,
+      conversationId: activeConversationId.value,
+      type: 'user',
+      content: userMessage.content,
+      createTime: new Date()
+    } as ChatMessageVO)
+    activeMessageList.value.push({
+      id: -2,
+      conversationId: activeConversationId.value,
+      type: 'assistant',
+      content: '思考中...',
+      createTime: new Date()
+    } as ChatMessageVO)
+    // 1.2 滚动到最下面
+    await nextTick()
+    await scrollToBottom() // 底部
+    // 1.3 开始滚动
+    textRoll()
+
+    // 2. 发送 event stream
+    let isFirstChunk = true // 是否是第一个 chunk 消息段
+    await ChatMessageApi.sendChatMessageStream(
+      userMessage.conversationId,
+      userMessage.content,
+      conversationInAbortController.value,
+      enableContext.value,
+      async (res) => {
+        const { code, data, msg } = JSON.parse(res.data)
+        if (code !== 0) {
+          message.alert(`对话异常! ${msg}`)
+          return
+        }
+
+        // 如果内容为空,就不处理。
+        if (data.receive.content === '') {
+          return
+        }
+        // 首次返回需要添加一个 message 到页面,后面的都是更新
+        if (isFirstChunk) {
+          isFirstChunk = false
+          // 弹出两个假数据
+          activeMessageList.value.pop()
+          activeMessageList.value.pop()
+          // 更新返回的数据
+          activeMessageList.value.push(data.send)
+          activeMessageList.value.push(data.receive)
+        }
+        // debugger
+        receiveMessageFullText.value = receiveMessageFullText.value + data.receive.content
+        // 滚动到最下面
+        await scrollToBottom()
+      },
+      (error) => {
+        message.alert(`对话异常! ${error}`)
+        stopStream()
+      },
+      () => {
+        stopStream()
+      }
+    )
+  } catch {}
+}
+
+/** 停止 stream 流式调用 */
+const stopStream = async () => {
+  // tip:如果 stream 进行中的 message,就需要调用 controller 结束
+  if (conversationInAbortController.value) {
+    conversationInAbortController.value.abort()
+  }
+  // 设置为 false
+  conversationInProgress.value = false
+}
+
+/** 编辑 message:设置为 prompt,可以再次编辑 */
+const handleMessageEdit = (message: ChatMessageVO) => {
+  prompt.value = message.content
+}
+
+/** 刷新 message:基于指定消息,再次发起对话 */
+const handleMessageRefresh = (message: ChatMessageVO) => {
+  doSendMessage(message.content)
+}
+
+// ============== 【消息滚动】相关 =============
+
+/** 滚动到 message 底部 */
+const scrollToBottom = async (isIgnore?: boolean) => {
+  await nextTick()
+  if (messageRef.value) {
+    messageRef.value.scrollToBottom(isIgnore)
+  }
+}
+
+/** 自提滚动效果 */
+const textRoll = async () => {
+  let index = 0
+  try {
+    // 只能执行一次
+    if (textRoleRunning.value) {
+      return
+    }
+    // 设置状态
+    textRoleRunning.value = true
+    receiveMessageDisplayedText.value = ''
+    const task = async () => {
+      // 调整速度
+      const diff =
+        (receiveMessageFullText.value.length - receiveMessageDisplayedText.value.length) / 10
+      if (diff > 5) {
+        textSpeed.value = 10
+      } else if (diff > 2) {
+        textSpeed.value = 30
+      } else if (diff > 1.5) {
+        textSpeed.value = 50
+      } else {
+        textSpeed.value = 100
+      }
+      // 对话结束,就按 30 的速度
+      if (!conversationInProgress.value) {
+        textSpeed.value = 10
+      }
+
+      if (index < receiveMessageFullText.value.length) {
+        receiveMessageDisplayedText.value += receiveMessageFullText.value[index]
+        index++
+
+        // 更新 message
+        const lastMessage = activeMessageList.value[activeMessageList.value.length - 1]
+        lastMessage.content = receiveMessageDisplayedText.value
+        // 滚动到住下面
+        await scrollToBottom()
+        // 重新设置任务
+        timer = setTimeout(task, textSpeed.value)
+      } else {
+        // 不是对话中可以结束
+        if (!conversationInProgress.value) {
+          textRoleRunning.value = false
+          clearTimeout(timer)
+        } else {
+          // 重新设置任务
+          timer = setTimeout(task, textSpeed.value)
+        }
+      }
+    }
+    let timer = setTimeout(task, textSpeed.value)
+  } catch {}
+}
+
+/** 初始化 **/
+onMounted(async () => {
+  // 如果有 conversationId 参数,则默认选中
+  if (route.query.conversationId) {
+    const id = route.query.conversationId as unknown as number
+    activeConversationId.value = id
+    await getConversation(id)
+  }
+
+  // 获取列表数据
+  activeMessageListLoading.value = true
+  await getMessageList()
+})
+</script>
+
+<style lang="scss" scoped>
+
+.container-wrapper {
+  position: relative;  // 关键定位容器
+  width: 100%;
+  height: 100%;
+}
+
+.sidebar-toggle {
+  position: absolute;
+  left: 300px;  // 初始展开位置
+  top: 40%;
+  z-index: 1000;
+  width: 20px;
+  height: 80px;
+  background: rgba(115, 196, 255, 0.5);
+  border-radius: 0 8px 8px 0;
+  cursor: pointer;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  transition: all 0.3s ease;
+  color: rgba(255,215,0);
+  transition: left 0.3s ease, background 0.2s ease;
+
+  &:hover {
+    background: #409EFF;
+    left: 304px;  // 悬停微调
+    transition: left 0.3s ease, background 0.2s ease;
+  }
+}
+
+.conversation-wrapper {
+  position: absolute;
+  left: 0;
+  top: 0;
+  bottom: 0;
+  width: 300px;
+  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;
+
+  &.collapsed {
+    transform: translateX(-100%);
+    opacity: 0;
+    pointer-events: none;
+  }
+}
+
+// 头部
+.detail-container {
+  width: 100%;
+  height: 885px;
+  margin-left: 5px;
+  background-color: rgba(0, 0, 0, 0); /* 透明背景 */
+  transition: margin 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+  z-index: 1;
+  .header {
+    display: flex;
+    flex-direction: row;
+    align-items: center;
+    justify-content: space-between;
+    box-shadow: 0 0 0 0 #dcdfe6;
+
+    .title {
+      font-size: 18px;
+      font-weight: bold;
+      color: gold;
+    }
+
+    .btns {
+      display: flex;
+      width: 300px;
+      flex-direction: row;
+      justify-content: flex-end;
+
+      .btn {
+        padding: 10px;
+      }
+
+      /* 所有状态通用透明背景 */
+      :deep(.el-button) {
+        background: transparent !important;
+        border-color: currentColor; /* 保持与文字同色 */
+        color: #409EFF; /* 蓝色文字 */
+      }
+
+      /* 悬停状态 */
+      :deep(.el-button:hover) {
+        background: rgba(0, 0, 0, 0.05) !important; /* 轻微悬停反馈 */
+      }
+
+      /* 点击状态 */
+      :deep(.el-button:active) {
+        background: rgba(0, 0, 0, 0.1) !important;
+      }
+
+      /* 禁用状态 */
+      :deep(.el-button.is-disabled) {
+        opacity: 0.6;
+        background: transparent !important;
+      }
+    }
+  }
+
+  &[style*="0"] {
+    margin-left: 0 !important;
+  }
+}
+
+// main 容器
+.main-container {
+  margin-left: 10px;
+  padding: 0;
+  position: relative;
+  height: 500px;
+  width: 100%;
+
+  .message-container {
+    position: absolute;
+    top: 0;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    overflow-y: hidden;
+    padding: 0;
+    margin: 0;
+    /* 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); }
+    }
+  }
+}
+
+// 底部
+.footer-container {
+  display: flex;
+  flex-direction: column;
+  height: 114px;
+  margin-left: 10px;
+  padding: 0;
+
+  // 输入框
+  .input-container {
+    display: flex;
+    flex-direction: column;
+    height: auto;
+    width: 876px;
+    margin: 0;
+    padding: 0;
+    overflow-y: auto;        /* 垂直方向溢出时显示滚动条 */
+    overflow-x: hidden;      /* 水平方向隐藏滚动条 */
+    /* 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); }
+    }
+
+    .prompt-from {
+      display: flex;
+      flex-direction: column;
+      padding: 9px 10px;
+      width: 876px;
+      height: 114px;
+      background: rgba(115,196,255,0.05);
+      border-radius: 4px 4px 4px 4px;
+      border: 1px solid #73C4FF;
+    }
+
+    textarea::placeholder {
+      color: #DBEEFF;
+    }
+
+    .prompt-input {
+      width: 876px;
+      height: 113.55px;
+      font-weight: 400;
+      font-size: 14px;
+      background-color: rgba(219,238,255,0);
+      line-height: 21px;
+      text-align: left;
+      font-style: normal;
+      text-transform: none;
+      border: 0;
+      color: #73C4FF;
+    }
+
+    .prompt-input:focus {
+      outline: none;
+    }
+
+    .prompt-btns {
+      display: flex;
+      justify-content: space-between;
+      padding-bottom: 0;
+      padding-top: 5px;
+
+      .content {
+        /* 默认状态 */
+        .el-button {
+          background: transparent !important;
+          border-color: rgba(115, 196, 255, 0.5);
+          color: #73C4FF;
+          border-radius: 15px !important;
+        }
+
+        /* 上下文图标处理 */
+        .content-icon {
+          color: blue;  /* 图标颜色 */
+          font-size: 18px;
+          margin-right: 10px;
+          background: url("@/assets/ai/zhuanlu/content.png");
+          vertical-align: middle;
+        }
+
+        /* 选中状态 */
+        .active-button {
+          background: #409eff !important;
+          border-color: #409eff !important;
+          color: white !important;
+          .content-icon {
+            background: url("@/assets/ai/zhuanlu/content_select.png");
+            vertical-align: middle;
+          }
+        }
+
+        /* 按钮组间距处理 */
+        .button-group .el-button {
+          margin-left: 0;
+          border-radius: 4px;
+        }
+
+        /* 悬停效果 */
+        .el-button:not(.active-button):hover {
+          border-color: rgba(115,196,255,0.5);
+          color: #409eff;
+        }
+      }
+      .message {
+        /* 所有状态通用透明背景 */
+        :deep(.el-button) {
+          background: rgba(73, 254, 210, 0.8) !important;
+          border-color: currentColor; /* 保持与文字同色 */
+          font-family: Alimama ShuHeiTi, Alimama ShuHeiTi;
+          font-weight: bold;
+          font-size: 16px;
+          color: #123C4E;
+          clip-path: polygon(
+              0 0,
+              100% 0,
+              100% 100%,
+              10px 100%,          /* 右下方向留出10px */
+              0 calc(100% - 10px) /* 左上方向留出10px */
+          );
+          position: relative;
+          padding-left: 15px; /* 增加右侧留白 */
+        }
+
+        /* 悬停状态 */
+        :deep(.el-button:hover) {
+          background: rgba(73, 254, 210, 0.6) !important; /* 轻微悬停反馈 */
+        }
+
+        /* 点击状态 */
+        :deep(.el-button:active) {
+          background: rgba(73, 254, 210, 1) !important;
+        }
+
+        /* 禁用状态 */
+        :deep(.el-button.is-disabled) {
+          opacity: 0.6;
+          background: transparent !important;
+        }
+
+        /* 核心样式覆盖 */
+        :deep(.el-switch__core) {
+          background: transparent !important;
+          border-radius: 0 0 15px 0 !important;
+          border: none !important;
+          height: 40px !important;
+        }
+
+        /* 按钮内容容器 */
+        .button-content {
+          display: flex;
+          align-items: center;
+          padding: 0 15px;
+          height: 100%;
+        }
+      }
+    }
+  }
+}
+</style>
diff --git a/src/views/ai/dashboard/components/conversation/CommonConversationList.vue b/src/views/ai/dashboard/components/conversation/CommonConversationList.vue
new file mode 100644
index 0000000..df1c641
--- /dev/null
+++ b/src/views/ai/dashboard/components/conversation/CommonConversationList.vue
@@ -0,0 +1,526 @@
+<!--  AI 对话  -->
+<template>
+  <el-aside width="260px" class="conversation-container h-100%">
+    <!-- 左顶部:对话 -->
+    <div class="h-100%">
+      <div class="conversation-title">
+        <img
+          src="@/assets/ai/zhuanlu/conversation_big.png"
+          class="mr-3px w-[1.2em] h-[1.2em]"
+          alt="icon"
+        />
+        对话列表
+      </div>
+<!--      <hr class="line"/>-->
+      <el-button class="w-1/1 btn-new-conversation" type="primary" @click="createConversation">
+        <img
+          src="@/assets/ai/zhuanlu/conversation_big.png"
+          class="mr-8px w-[1.5em] h-[1.5em]"
+          alt="icon"
+        />
+        开始新对话
+      </el-button>
+
+      <!-- 左顶部:搜索对话 -->
+      <el-input
+        v-model="searchName"
+        size="large"
+        class="mt-10px search-input"
+        placeholder="搜索历史记录"
+        @keyup="searchConversation"
+      >
+        <template #prefix>
+          <Icon icon="ep:search" />
+        </template>
+      </el-input>
+
+      <!-- 左中间:对话列表 -->
+      <div class="conversation-list">
+        <!-- 情况一:加载中 -->
+        <el-empty v-if="loading" description="." :v-loading="loading" />
+        <!-- 情况二:按照 group 分组,展示聊天会话 list 列表 -->
+        <div v-for="conversationKey in Object.keys(conversationMap)" :key="conversationKey">
+          <div
+            class="conversation-item classify-title"
+            v-if="conversationMap[conversationKey].length"
+          >
+            <el-text class="mx-1" size="small" tag="b">{{ conversationKey }}</el-text>
+          </div>
+          <div
+            class="conversation-item"
+            v-for="conversation in conversationMap[conversationKey]"
+            :key="conversation.id"
+            @click="handleConversationClick(conversation.id)"
+            @mouseover="hoverConversationId = conversation.id"
+            @mouseout="hoverConversationId = ''"
+          >
+            <div
+              :class="
+                conversation.id === activeConversationId ? 'conversation active' : 'conversation'
+              "
+            >
+              <div class="title-wrapper">
+                <img class="avatar" :src="conversation.roleAvatar || roleAvatarDefaultImg" />
+                <span class="title">{{ conversation.title }}</span>
+              </div>
+              <div class="button-wrapper" v-show="hoverConversationId === conversation.id">
+                <el-button class="btn" link @click.stop="handleTop(conversation)">
+                  <el-icon title="置顶" v-if="!conversation.pinned"><Top /></el-icon>
+                  <el-icon title="置顶" v-if="conversation.pinned"><Bottom /></el-icon>
+                </el-button>
+                <el-button class="btn" link @click.stop="updateConversationTitle(conversation)">
+                  <el-icon title="编辑">
+                    <Icon icon="ep:edit" />
+                  </el-icon>
+                </el-button>
+                <el-button class="btn" link @click.stop="deleteChatConversation(conversation)">
+                  <el-icon title="删除对话">
+                    <Icon icon="ep:delete" />
+                  </el-icon>
+                </el-button>
+              </div>
+            </div>
+          </div>
+        </div>
+        <!-- 底部占位  -->
+        <div class="h-160px w-100%"></div>
+      </div>
+    </div>
+
+    <!-- 左底部:工具栏 -->
+    <div class="tool-box">
+      <div @click="handleClearConversation">
+        <Icon icon="ep:delete" />
+        <el-text size="small">清空未置顶对话</el-text>
+      </div>
+    </div>
+
+  </el-aside>
+</template>
+
+<script setup lang="ts">
+import { ChatConversationApi, ChatConversationVO } from '@/api/ai/chat/conversation'
+import { Bottom, Top } from '@element-plus/icons-vue'
+import roleAvatarDefaultImg from '@/assets/ai/zhuanlu/assistant.png'
+
+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[]) // 对话列表
+const conversationMap = ref<any>({}) // 对话分组 (置顶、今天、三天前、一星期前、一个月前)
+const loading = ref<boolean>(false) // 加载中
+const loadingTime = ref<any>() // 加载中定时器
+
+// 定义组件 props
+const props = defineProps({
+  activeId: {
+    type: String || null,
+    required: true
+  }
+})
+
+// 定义钩子
+const emits = defineEmits([
+  'onConversationCreate',
+  'onConversationClick',
+  'onConversationClear',
+  'onConversationDelete'
+])
+
+/** 搜索对话 */
+const searchConversation = async (e) => {
+  // 恢复数据
+  if (!searchName.value.trim().length) {
+    conversationMap.value = await getConversationGroupByCreateTime(conversationList.value)
+  } else {
+    // 过滤
+    const filterValues = conversationList.value.filter((item) => {
+      return item.title.includes(searchName.value.trim())
+    })
+    conversationMap.value = await getConversationGroupByCreateTime(filterValues)
+  }
+}
+
+/** 点击对话 */
+const handleConversationClick = async (id: number) => {
+  // 过滤出选中的对话
+  const filterConversation = conversationList.value.filter((item) => {
+    return item.id === id
+  })
+  // 回调 onConversationClick
+  // noinspection JSVoidFunctionReturnValueUsed
+  const success = emits('onConversationClick', filterConversation[0])
+  // 切换对话
+  if (success) {
+    activeConversationId.value = id
+  }
+}
+
+/** 获取对话列表 */
+const getChatConversationList = async () => {
+  try {
+    // 加载中
+    loadingTime.value = setTimeout(() => {
+      loading.value = true
+    }, 50)
+
+    // 1.1 获取 对话数据
+    conversationList.value = await ChatConversationApi.getChatConversationEnergyList(modelName.value)
+    if(conversationList.value.length == 0) {
+      await createConversation()
+    }
+    // 1.2 排序
+    conversationList.value.sort((a, b) => {
+      return b.createTime - a.createTime
+    })
+    // 1.3 没有任何对话情况
+    if (conversationList.value.length === 0) {
+      activeConversationId.value = null
+      conversationMap.value = {}
+      return
+    }
+
+    // 2. 对话根据时间分组(置顶、今天、一天前、三天前、七天前、30 天前)
+    conversationMap.value = await getConversationGroupByCreateTime(conversationList.value)
+  } finally {
+    // 清理定时器
+    if (loadingTime.value) {
+      clearTimeout(loadingTime.value)
+    }
+    // 加载完成
+    loading.value = false
+  }
+}
+
+/** 按照 creteTime 创建时间,进行分组 */
+const getConversationGroupByCreateTime = async (list: ChatConversationVO[]) => {
+  // 排序、指定、时间分组(今天、一天前、三天前、七天前、30天前)
+  // noinspection NonAsciiCharacters
+  const groupMap = {
+    置顶: [],
+    今天: [],
+    一天前: [],
+    三天前: [],
+    七天前: [],
+    三十天前: []
+  }
+  // 当前时间的时间戳
+  const now = Date.now()
+  // 定义时间间隔常量(单位:毫秒)
+  const oneDay = 24 * 60 * 60 * 1000
+  const threeDays = 3 * oneDay
+  const sevenDays = 7 * oneDay
+  const thirtyDays = 30 * oneDay
+  for (const conversation of list) {
+    // 置顶
+    if (conversation.pinned) {
+      groupMap['置顶'].push(conversation)
+      continue
+    }
+    // 计算时间差(单位:毫秒)
+    const diff = now - conversation.createTime
+    // 根据时间间隔判断
+    if (diff < oneDay) {
+      groupMap['今天'].push(conversation)
+    } else if (diff < threeDays) {
+      groupMap['一天前'].push(conversation)
+    } else if (diff < sevenDays) {
+      groupMap['三天前'].push(conversation)
+    } else if (diff < thirtyDays) {
+      groupMap['七天前'].push(conversation)
+    } else {
+      groupMap['三十天前'].push(conversation)
+    }
+  }
+  return groupMap
+}
+
+/** 新建对话 */
+const createConversation = async () => {
+  // 1. 新建对话
+  const conversationId = await ChatConversationApi.createChatConversationEnergy(
+    {modelName: modelName.value} as unknown as ChatConversationVO
+  )
+  // 2. 获取对话内容
+  await getChatConversationList()
+  // 3. 选中对话
+  await handleConversationClick(conversationId)
+  // 4. 回调
+  emits('onConversationCreate')
+}
+
+/** 修改对话的标题 */
+const updateConversationTitle = async (conversation: ChatConversationVO) => {
+  // 1. 二次确认
+  const { value } = await ElMessageBox.prompt('修改标题', {
+    inputPattern: /^[\s\S]*.*\S[\s\S]*$/, // 判断非空,且非空格
+    inputErrorMessage: '标题不能为空',
+    inputValue: conversation.title
+  })
+  // 2. 发起修改
+  await ChatConversationApi.updateChatConversationMy({
+    id: conversation.id,
+    title: value
+  } as ChatConversationVO)
+  message.success('重命名成功')
+  // 3. 刷新列表
+  await getChatConversationList()
+  // 4. 过滤当前切换的
+  const filterConversationList = conversationList.value.filter((item) => {
+    return item.id === conversation.id
+  })
+  if (filterConversationList.length > 0) {
+    // tip:避免切换对话
+    if (activeConversationId.value === filterConversationList[0].id) {
+      emits('onConversationClick', filterConversationList[0])
+    }
+  }
+}
+
+/** 删除聊天对话 */
+const deleteChatConversation = async (conversation: ChatConversationVO) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm(`是否确认删除对话 - ${conversation.title}?`)
+    // 发起删除
+    await ChatConversationApi.deleteChatConversationMy(conversation.id)
+    message.success('对话已删除')
+    // 刷新列表
+    await getChatConversationList()
+    // 回调
+    emits('onConversationDelete', conversation)
+  } catch {}
+}
+
+/** 清空对话 */
+const handleClearConversation = async () => {
+  try {
+    await message.confirm('确认后对话会全部清空,置顶的对话除外。')
+    await ChatConversationApi.deleteChatConversationMyByUnpinned()
+    ElMessage({
+      message: '操作成功!',
+      type: 'success'
+    })
+    // 清空 对话 和 对话内容
+    activeConversationId.value = null
+    // 获取 对话列表
+    await getChatConversationList()
+    // 回调 方法
+    emits('onConversationClear')
+  } catch {}
+}
+
+/** 对话置顶 */
+const handleTop = async (conversation: ChatConversationVO) => {
+  // 更新对话置顶
+  conversation.pinned = !conversation.pinned
+  await ChatConversationApi.updateChatConversationMy(conversation)
+  // 刷新对话
+  await getChatConversationList()
+}
+
+// ============ 角色仓库 ============
+
+/** 角色仓库抽屉 */
+const roleRepositoryOpen = ref<boolean>(false) // 角色仓库是否打开
+const handleRoleRepository = async () => {
+  roleRepositoryOpen.value = !roleRepositoryOpen.value
+}
+
+/** 监听选中的对话 */
+const { activeId } = toRefs(props)
+watch(activeId, async (newValue, oldValue) => {
+  activeConversationId.value = newValue as string
+})
+
+// 定义 public 方法
+defineExpose({ createConversation })
+
+/** 初始化 */
+onMounted(async () => {
+  // 获取 对话列表
+  await getChatConversationList()
+  // 默认选中
+  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])
+    }
+  }
+})
+</script>
+
+<style scoped lang="scss">
+.conversation-container {
+  position: relative;
+  display: flex;
+  flex-direction: column;
+  justify-content: space-between;
+  padding: 10px 10px 0;
+  overflow: hidden;
+
+  .conversation-title {
+    color: #73C4FF;
+    line-height: 25px;
+    margin-bottom: 8px;
+    border-bottom: 1px solid rgba(69,133,255,0.2);
+    img {
+      padding: 6px 0 0 5px;
+    }
+  }
+  .line {
+    margin: 5px 0;
+  }
+
+  .btn-new-conversation {
+    border-radius: 4px;
+    border: 1px solid rgba(69,133,255,0.6);
+    background: rgba(69,133,255,0.4);
+    color: #73C4FF;
+  }
+
+  .search-input {
+    height: 30px;
+    border-radius: 4px;
+    border: 1px solid rgba(69,133,255,0.6);
+    background: rgba(69,133,255,0.4);
+  }
+
+  .conversation-list {
+    overflow: auto;
+    height: 100%;
+
+    .classify-title {
+      padding-top: 10px;
+      b {
+        color: white;
+      }
+    }
+
+    .conversation-item {
+      margin-top: 5px;
+    }
+
+    .conversation {
+      display: flex;
+      flex-direction: row;
+      justify-content: space-between;
+      flex: 1;
+      padding: 0 5px;
+      cursor: pointer;
+      border-radius: 5px;
+      align-items: center;
+      line-height: 30px;
+      background-color: rgba(69,133,255,0.1);
+      &.active {
+        background-color: rgba(69,133,255,0.5);
+        .button {
+          display: inline-block;
+        }
+        .title-wrapper {
+          > span {
+            font-weight: bold;
+            color: rgba(115, 196, 255);
+          }
+        }
+      }
+
+      .title-wrapper {
+        display: flex;
+        flex-direction: row;
+        align-items: center;
+        > span {
+          color: rgba(115, 196, 255, 0.5);
+        }
+      }
+
+      .title {
+        padding: 2px 10px;
+        max-width: 220px;
+        font-size: 14px;
+        font-weight: 400;
+        color: rgba(0, 0, 0, 0.77);
+        overflow: hidden;
+        white-space: nowrap;
+        text-overflow: ellipsis;
+      }
+
+      .avatar {
+        width: 25px;
+        height: 25px;
+        border-radius: 5px;
+        display: flex;
+        flex-direction: row;
+        justify-items: center;
+      }
+
+      // 对话编辑、删除
+      .button-wrapper {
+        right: 2px;
+        display: flex;
+        flex-direction: row;
+        justify-items: center;
+        color: #606266;
+
+        .btn {
+          margin: 0;
+          color: #73C4FF;
+        }
+      }
+    }
+  }
+
+  // 角色仓库、清空未设置对话
+  .tool-box {
+    bottom: 0;
+    padding: 0 20px;
+    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);
+
+    div {
+      display: flex;
+      margin-left: 20%;
+      align-items: center;
+      color: #73C4FF;
+      padding: 0;
+      cursor: pointer;
+      > span {
+        color: #73C4FF;
+        margin-left: 5px;
+      }
+    }
+  }
+}
+
+/* 移除所有输入框边框 */
+:deep(.el-form-item .el-input__wrapper) {
+  border: none !important;
+  box-shadow: none !important;
+  background: rgba(255,255,255,0.1) !important; /* 保留浅色背景 */
+}
+
+/* 移除输入框边框 */
+:deep(.el-input .el-input__wrapper) {
+  border: 1px solid #1E5A86 !important;
+  box-shadow: none !important;
+}
+
+:deep(.el-input__inner) {
+  color: #73C4FF;
+}
+:deep(.el-input__wrapper) {
+  background: rgba(0,194,255,0.08) !important;
+}
+</style>
diff --git a/src/views/ai/dashboard/components/conversation/CommonConversationUpdateForm.vue b/src/views/ai/dashboard/components/conversation/CommonConversationUpdateForm.vue
new file mode 100644
index 0000000..afa5279
--- /dev/null
+++ b/src/views/ai/dashboard/components/conversation/CommonConversationUpdateForm.vue
@@ -0,0 +1,219 @@
+<template>
+  <DialogDashboard title="模型设定" v-model="dialogVisible">
+    <el-form
+      ref="formRef"
+      :model="formData"
+      :rules="formRules"
+      label-width="130px"
+      v-loading="formLoading"
+    >
+      <el-form-item label="模型" prop="modelId">
+        <el-select v-model="formData.modelId" disabled>
+          <el-option
+            v-for="model in models"
+            :key="model.id"
+            :label="model.name"
+            :value="model.id"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="温度参数" prop="temperature">
+        <el-input-number
+          v-model="formData.temperature"
+          placeholder="请输入温度参数"
+          :min="0"
+          :max="2"
+          :precision="2"
+          class="!w-1/1"
+        />
+      </el-form-item>
+      <el-form-item label="回复数 Token 数" prop="maxTokens">
+        <el-input-number
+          v-model="formData.maxTokens"
+          placeholder="请输入回复数 Token 数"
+          :min="0"
+          :max="8192"
+          class="!w-1/1"
+        />
+      </el-form-item>
+      <el-form-item label="上下文数量" prop="maxContexts">
+        <el-input-number
+          v-model="formData.maxContexts"
+          placeholder="请输入上下文数量"
+          :min="0"
+          :max="20"
+          class="!w-1/1"
+        />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
+      <el-button @click="dialogVisible = false">取 消</el-button>
+    </template>
+  </DialogDashboard>
+</template>
+<script setup lang="ts">
+import { ModelApi, ModelVO } from '@/api/ai/model/model'
+import { ChatConversationApi, ChatConversationVO } from '@/api/ai/chat/conversation'
+import { AiModelTypeEnum } from '@/views/ai/utils/constants'
+
+/** AI 聊天对话的更新表单 */
+defineOptions({ name: 'ChatConversationUpdateForm' })
+
+const message = useMessage() // 消息弹窗
+
+const dialogVisible = ref(false) // 弹窗的是否展示
+const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+const formData = ref({
+  id: undefined,
+  systemMessage: undefined,
+  modelId: undefined,
+  temperature: undefined,
+  maxTokens: undefined,
+  maxContexts: undefined
+})
+const formRules = reactive({
+  modelId: [{ required: true, message: '模型不能为空', trigger: 'blur' }],
+  status: [{ required: true, message: '状态不能为空', trigger: 'blur' }],
+  temperature: [{ required: true, message: '温度参数不能为空', trigger: 'blur' }],
+  maxTokens: [{ required: true, message: '回复数 Token 数不能为空', trigger: 'blur' }],
+  maxContexts: [{ required: true, message: '上下文数量不能为空', trigger: 'blur' }]
+})
+const formRef = ref() // 表单 Ref
+const models = ref([] as ModelVO[]) // 聊天模型列表
+
+/** 打开弹窗 */
+const open = async (id: number) => {
+  dialogVisible.value = true
+  resetForm()
+  // 修改时,设置数据
+  if (id) {
+    formLoading.value = true
+    try {
+      const data = await ChatConversationApi.getChatConversationMy(id)
+      formData.value = Object.keys(formData.value).reduce((obj, key) => {
+        if (data.hasOwnProperty(key)) {
+          obj[key] = data[key]
+        }
+        return obj
+      }, {})
+    } finally {
+      formLoading.value = false
+    }
+  }
+  // 获得下拉数据
+  models.value = await ModelApi.getModelSimpleList(AiModelTypeEnum.CHAT)
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+/** 提交表单 */
+const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
+const submitForm = async () => {
+  // 校验表单
+  await formRef.value.validate()
+  // 提交请求
+  formLoading.value = true
+  try {
+    const data = formData.value as unknown as ChatConversationVO
+    await ChatConversationApi.updateChatConversationMy(data)
+    message.success('对话配置已更新')
+    dialogVisible.value = false
+    // 发送操作成功的事件
+    emit('success')
+  } finally {
+    formLoading.value = false
+  }
+}
+
+/** 重置表单 */
+const resetForm = () => {
+  formData.value = {
+    id: undefined,
+    systemMessage: undefined,
+    modelId: undefined,
+    temperature: undefined,
+    maxTokens: undefined,
+    maxContexts: undefined
+  }
+  formRef.value?.resetFields()
+}
+</script>
+
+<style lang="scss" scoped>
+
+:deep(.el-form-item__label) {
+  color: #73C4FF;
+}
+:deep(.el-form-item__content) {
+  .el-input-number {
+    border: black solid 1px !important;
+  }
+}
+/* 移除所有输入框边框 */
+:deep(.el-form-item .el-input__wrapper) {
+  border: none !important;
+  box-shadow: none !important;
+  background: rgba(255,255,255,0.1) !important; /* 保留浅色背景 */
+}
+
+/* 移除数字输入框边框 */
+:deep(.el-input-number .el-input__wrapper) {
+  border: 1px solid #1E5A86 !important;
+  box-shadow: none !important;
+}
+
+/* 下拉组件 */
+:deep(.el-select) {
+  /* 下拉箭头 */
+  .el-select__caret {
+    color: #73C4FF !important; /* 匹配图中的浅蓝箭头 */
+    font-size: 16px !important;
+  }
+}
+
+/* 深度选择器调整边框细节 */
+:deep(.el-select__wrapper) {
+  border-radius: 6px;        /* 圆角大小 */
+  border-width: 1.5px;        /* 边框粗细 */
+  box-shadow: 0 0 0 1px #1E5A86 !important; /* 聚焦阴影 */
+}
+
+/* 移除按钮组边框(增减按钮) */
+:deep(.el-input-number__decrease),
+:deep(.el-input-number__increase) {
+  border: 1px solid #1E5A86;
+  background: transparent !important;
+  i {
+    color: #73C4FF;
+  }
+}
+:deep(.el-loading-mask) {
+  background: black !important;
+  border-radius: 10px;
+}
+:deep(.el-input__inner) {
+  color: #73C4FF;
+}
+:deep(.el-select__selected-item ) {
+  color: #73C4FF !important;
+}
+:deep(.el-select__wrapper) {
+  background: rgba(0,194,255,0.08) !important;
+}
+:deep(.el-input-number span) {
+  background: rgba(0,194,255,0.08) !important;
+}
+:deep(.el-input__wrapper) {
+  background: rgba(0,194,255,0.08) !important;
+}
+.el-dialog__footer {
+  button {
+    background: rgba(0,194,255,0.08) !important;
+    border-color: rgba(0,194,255,0.8) !important;
+  }
+  button:first-child {
+    color: #73C4FF;
+  }
+}
+
+</style>
diff --git a/src/views/ai/dashboard/components/conversation/ConversationList.vue b/src/views/ai/dashboard/components/conversation/ConversationList.vue
index 01df587..c66ff0a 100644
--- a/src/views/ai/dashboard/components/conversation/ConversationList.vue
+++ b/src/views/ai/dashboard/components/conversation/ConversationList.vue
@@ -149,7 +149,6 @@
 
     // 1.1 获取 对话数据
     conversationList.value = await ChatConversationApi.getChatConversationEnergyList(modelName.value)
-    console.log(conversationList.value)
     if(conversationList.value.length == 0) {
       await createConversation()
     }
diff --git a/src/views/ai/dashboard/components/conversation/ConversationListEmpty.vue b/src/views/ai/dashboard/components/conversation/ConversationListEmpty.vue
new file mode 100644
index 0000000..d7fef8c
--- /dev/null
+++ b/src/views/ai/dashboard/components/conversation/ConversationListEmpty.vue
@@ -0,0 +1,78 @@
+<!-- 无聊天对话时,在 message 区域-->
+<template>
+  <div class="conversation-empty">
+    <div class="center-container">
+      <div class="title no-data">暂无数据</div>
+      <div class="title">欢迎来到转炉煤气调度大模型</div>
+    </div>
+  </div>
+</template>
+<script setup lang="ts">
+const emits = defineEmits(['onNewConversation'])
+
+import roleAvatarDefaultImg from '@/assets/ai/zhuanlu/assistant.png'
+
+const roleAvatar = roleAvatarDefaultImg
+
+</script>
+<style scoped lang="scss">
+.conversation-empty {
+  display: flex;
+  flex-direction: row;
+  justify-content: center;
+  width: 100%;
+  height: 100%;
+
+  .box-center {
+    margin-top: 35%;
+    margin-left: 12%;
+    display: inline-block;
+
+    .tip {
+      width: 120px;
+      height: 40px;
+      padding: 2px 0 0 10px;
+      border-radius: 2px;
+      font-family: Alimama ShuHeiTi, Alimama ShuHeiTi;
+      font-weight: bold;
+      font-size: 24px;
+      text-align: left;
+      font-style: normal;
+      text-transform: none;
+      background: linear-gradient(0deg, #49FFD3 0%, #4585FF 100%);
+    }
+  }
+
+  .conversation-empty {
+    position: relative;
+    display: flex;
+    flex-direction: row;
+    justify-content: center;
+    width: 100%;
+    height: 100%;
+
+    .center-container {
+      display: flex;
+      flex-direction: column;
+      justify-content: center;
+
+      .title {
+        width: 120px;
+        height: 40px;
+        padding: 2px 0 0 10px;
+        border-radius: 2px;
+        font-family: Alimama ShuHeiTi, Alimama ShuHeiTi;
+        font-weight: bold;
+        font-size: 24px;
+        text-align: left;
+        font-style: normal;
+        text-transform: none;
+        background: linear-gradient(0deg, #49FFD3 0%, #4585FF 100%);
+      }
+      .no-data {
+        color: rgba(143,214,254,0.8) !important;
+      }
+    }
+  }
+}
+</style>
diff --git a/src/views/ai/dashboard/components/conversation/HistoryConversationList.vue b/src/views/ai/dashboard/components/conversation/HistoryConversationList.vue
new file mode 100644
index 0000000..fd1ec4f
--- /dev/null
+++ b/src/views/ai/dashboard/components/conversation/HistoryConversationList.vue
@@ -0,0 +1,459 @@
+<!--  AI 对话  -->
+<template>
+  <el-aside width="260px" class="conversation-container h-100%">
+    <!-- 左顶部:对话 -->
+    <div class="h-100%">
+      <el-button class="w-1/1 btn-new-conversation" type="primary" @click="createConversation">
+        <Icon icon="ep:plus" class="mr-5px" />
+        新建对话
+      </el-button>
+
+      <!-- 左顶部:搜索对话 -->
+      <el-input
+        v-model="searchName"
+        size="large"
+        class="mt-10px search-input"
+        placeholder="搜索历史记录"
+        @keyup="searchConversation"
+      >
+        <template #prefix>
+          <Icon icon="ep:search" />
+        </template>
+      </el-input>
+
+      <!-- 左中间:对话列表 -->
+      <div class="conversation-list">
+        <!-- 情况一:加载中 -->
+        <el-empty v-if="loading" description="." :v-loading="loading" />
+        <!-- 情况二:按照 group 分组,展示聊天会话 list 列表 -->
+        <div v-for="conversationKey in Object.keys(conversationMap)" :key="conversationKey">
+          <div
+            class="conversation-item classify-title"
+            v-if="conversationMap[conversationKey].length"
+          >
+            <el-text class="mx-1" size="small" tag="b">{{ conversationKey }}</el-text>
+          </div>
+          <div
+            class="conversation-item"
+            v-for="conversation in conversationMap[conversationKey]"
+            :key="conversation.id"
+            @click="handleConversationClick(conversation.id)"
+            @mouseover="hoverConversationId = conversation.id"
+            @mouseout="hoverConversationId = ''"
+          >
+            <div
+              :class="
+                conversation.id === activeConversationId ? 'conversation active' : 'conversation'
+              "
+            >
+              <div class="title-wrapper">
+                <img class="avatar" :src="conversation.roleAvatar || roleAvatarDefaultImg" />
+                <span class="title">{{ conversation.title }}</span>
+              </div>
+              <div class="button-wrapper" v-show="hoverConversationId === conversation.id">
+                <el-button class="btn" link @click.stop="handleTop(conversation)">
+                  <el-icon title="置顶" v-if="!conversation.pinned"><Top /></el-icon>
+                  <el-icon title="置顶" v-if="conversation.pinned"><Bottom /></el-icon>
+                </el-button>
+                <el-button class="btn" link @click.stop="updateConversationTitle(conversation)">
+                  <el-icon title="编辑">
+                    <Icon icon="ep:edit" />
+                  </el-icon>
+                </el-button>
+                <el-button class="btn" link @click.stop="deleteChatConversation(conversation)">
+                  <el-icon title="删除对话">
+                    <Icon icon="ep:delete" />
+                  </el-icon>
+                </el-button>
+              </div>
+            </div>
+          </div>
+        </div>
+        <!-- 底部占位  -->
+        <div class="h-160px w-100%"></div>
+      </div>
+    </div>
+
+  </el-aside>
+</template>
+
+<script setup lang="ts">
+import { ChatConversationApi, ChatConversationVO } from '@/api/ai/chat/conversation'
+import { Bottom, Top } from '@element-plus/icons-vue'
+import roleAvatarDefaultImg from '@/assets/ai/gpt.svg'
+
+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[]) // 对话列表
+const conversationMap = ref<any>({}) // 对话分组 (置顶、今天、三天前、一星期前、一个月前)
+const loading = ref<boolean>(false) // 加载中
+const loadingTime = ref<any>() // 加载中定时器
+
+// 定义组件 props
+const props = defineProps({
+  activeId: {
+    type: String || null,
+    required: true
+  }
+})
+
+// 定义钩子
+const emits = defineEmits([
+  'onConversationCreate',
+  'onConversationClick',
+  'onConversationClear',
+  'onConversationDelete'
+])
+
+/** 搜索对话 */
+const searchConversation = async (e) => {
+  // 恢复数据
+  if (!searchName.value.trim().length) {
+    conversationMap.value = await getConversationGroupByCreateTime(conversationList.value)
+  } else {
+    // 过滤
+    const filterValues = conversationList.value.filter((item) => {
+      return item.title.includes(searchName.value.trim())
+    })
+    conversationMap.value = await getConversationGroupByCreateTime(filterValues)
+  }
+}
+
+/** 点击对话 */
+const handleConversationClick = async (id: number) => {
+  // 过滤出选中的对话
+  const filterConversation = conversationList.value.filter((item) => {
+    return item.id === id
+  })
+  // 回调 onConversationClick
+  // noinspection JSVoidFunctionReturnValueUsed
+  const success = emits('onConversationClick', filterConversation[0])
+  // 切换对话
+  if (success) {
+    activeConversationId.value = id
+  }
+}
+
+/** 获取对话列表 */
+const getChatConversationList = async () => {
+  try {
+    // 加载中
+    loadingTime.value = setTimeout(() => {
+      loading.value = true
+    }, 50)
+
+    // 1.1 获取 对话数据
+    conversationList.value = await ChatConversationApi.getChatConversationEnergyList(modelName.value)
+    if(conversationList.value.length == 0) {
+      await createConversation()
+    }
+    // 1.2 排序
+    conversationList.value.sort((a, b) => {
+      return b.createTime - a.createTime
+    })
+    // 1.3 没有任何对话情况
+    if (conversationList.value.length === 0) {
+      activeConversationId.value = null
+      conversationMap.value = {}
+      return
+    }
+
+    // 2. 对话根据时间分组(置顶、今天、一天前、三天前、七天前、30 天前)
+    conversationMap.value = await getConversationGroupByCreateTime(conversationList.value)
+  } finally {
+    // 清理定时器
+    if (loadingTime.value) {
+      clearTimeout(loadingTime.value)
+    }
+    // 加载完成
+    loading.value = false
+  }
+}
+
+/** 按照 creteTime 创建时间,进行分组 */
+const getConversationGroupByCreateTime = async (list: ChatConversationVO[]) => {
+  // 排序、指定、时间分组(今天、一天前、三天前、七天前、30天前)
+  // noinspection NonAsciiCharacters
+  const groupMap = {
+    置顶: [],
+    今天: [],
+    一天前: [],
+    三天前: [],
+    七天前: [],
+    三十天前: []
+  }
+  // 当前时间的时间戳
+  const now = Date.now()
+  // 定义时间间隔常量(单位:毫秒)
+  const oneDay = 24 * 60 * 60 * 1000
+  const threeDays = 3 * oneDay
+  const sevenDays = 7 * oneDay
+  const thirtyDays = 30 * oneDay
+  for (const conversation of list) {
+    // 置顶
+    if (conversation.pinned) {
+      groupMap['置顶'].push(conversation)
+      continue
+    }
+    // 计算时间差(单位:毫秒)
+    const diff = now - conversation.createTime
+    // 根据时间间隔判断
+    if (diff < oneDay) {
+      groupMap['今天'].push(conversation)
+    } else if (diff < threeDays) {
+      groupMap['一天前'].push(conversation)
+    } else if (diff < sevenDays) {
+      groupMap['三天前'].push(conversation)
+    } else if (diff < thirtyDays) {
+      groupMap['七天前'].push(conversation)
+    } else {
+      groupMap['三十天前'].push(conversation)
+    }
+  }
+  return groupMap
+}
+
+/** 新建对话 */
+const createConversation = async () => {
+  // 1. 新建对话
+  const conversationId = await ChatConversationApi.createChatConversationEnergy(
+    {modelName: modelName.value} as unknown as ChatConversationVO
+  )
+  // 2. 获取对话内容
+  await getChatConversationList()
+  // 3. 选中对话
+  await handleConversationClick(conversationId)
+  // 4. 回调
+  emits('onConversationCreate')
+}
+
+/** 修改对话的标题 */
+const updateConversationTitle = async (conversation: ChatConversationVO) => {
+  // 1. 二次确认
+  const { value } = await ElMessageBox.prompt('修改标题', {
+    inputPattern: /^[\s\S]*.*\S[\s\S]*$/, // 判断非空,且非空格
+    inputErrorMessage: '标题不能为空',
+    inputValue: conversation.title
+  })
+  // 2. 发起修改
+  await ChatConversationApi.updateChatConversationMy({
+    id: conversation.id,
+    title: value
+  } as ChatConversationVO)
+  message.success('重命名成功')
+  // 3. 刷新列表
+  await getChatConversationList()
+  // 4. 过滤当前切换的
+  const filterConversationList = conversationList.value.filter((item) => {
+    return item.id === conversation.id
+  })
+  if (filterConversationList.length > 0) {
+    // tip:避免切换对话
+    if (activeConversationId.value === filterConversationList[0].id) {
+      emits('onConversationClick', filterConversationList[0])
+    }
+  }
+}
+
+/** 删除聊天对话 */
+const deleteChatConversation = async (conversation: ChatConversationVO) => {
+  try {
+    // 删除的二次确认
+    await message.delConfirm(`是否确认删除对话 - ${conversation.title}?`)
+    // 发起删除
+    await ChatConversationApi.deleteChatConversationMy(conversation.id)
+    message.success('对话已删除')
+    // 刷新列表
+    await getChatConversationList()
+    // 回调
+    emits('onConversationDelete', conversation)
+  } catch {}
+}
+
+/** 清空对话 */
+const handleClearConversation = async () => {
+  try {
+    await message.confirm('确认后对话会全部清空,置顶的对话除外。')
+    await ChatConversationApi.deleteChatConversationMyByUnpinned()
+    ElMessage({
+      message: '操作成功!',
+      type: 'success'
+    })
+    // 清空 对话 和 对话内容
+    activeConversationId.value = null
+    // 获取 对话列表
+    await getChatConversationList()
+    // 回调 方法
+    emits('onConversationClear')
+  } catch {}
+}
+
+/** 对话置顶 */
+const handleTop = async (conversation: ChatConversationVO) => {
+  // 更新对话置顶
+  conversation.pinned = !conversation.pinned
+  await ChatConversationApi.updateChatConversationMy(conversation)
+  // 刷新对话
+  await getChatConversationList()
+}
+
+// ============ 角色仓库 ============
+
+/** 角色仓库抽屉 */
+const roleRepositoryOpen = ref<boolean>(false) // 角色仓库是否打开
+const handleRoleRepository = async () => {
+  roleRepositoryOpen.value = !roleRepositoryOpen.value
+}
+
+/** 监听选中的对话 */
+const { activeId } = toRefs(props)
+watch(activeId, async (newValue, oldValue) => {
+  activeConversationId.value = newValue as string
+})
+
+// 定义 public 方法
+defineExpose({ createConversation })
+
+/** 初始化 */
+onMounted(async () => {
+  // 获取 对话列表
+  await getChatConversationList()
+  // 默认选中
+  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])
+    }
+  }
+})
+</script>
+
+<style scoped lang="scss">
+.conversation-container {
+  position: relative;
+  display: flex;
+  flex-direction: column;
+  justify-content: space-between;
+  padding: 10px 10px 0;
+  overflow: hidden;
+
+  .btn-new-conversation {
+    padding: 18px 0;
+  }
+
+  .search-input {
+    margin-top: 20px;
+  }
+
+  .conversation-list {
+    overflow: auto;
+    height: 100%;
+
+    .classify-title {
+      padding-top: 10px;
+    }
+
+    .conversation-item {
+      margin-top: 5px;
+    }
+
+    .conversation {
+      display: flex;
+      flex-direction: row;
+      justify-content: space-between;
+      flex: 1;
+      padding: 0 5px;
+      cursor: pointer;
+      border-radius: 5px;
+      align-items: center;
+      line-height: 30px;
+
+      &.active {
+        background-color: #e6e6e6;
+
+        .button {
+          display: inline-block;
+        }
+      }
+
+      .title-wrapper {
+        display: flex;
+        flex-direction: row;
+        align-items: center;
+      }
+
+      .title {
+        padding: 2px 10px;
+        max-width: 220px;
+        font-size: 14px;
+        font-weight: 400;
+        color: rgba(0, 0, 0, 0.77);
+        overflow: hidden;
+        white-space: nowrap;
+        text-overflow: ellipsis;
+      }
+
+      .avatar {
+        width: 25px;
+        height: 25px;
+        border-radius: 5px;
+        display: flex;
+        flex-direction: row;
+        justify-items: center;
+      }
+
+      // 对话编辑、删除
+      .button-wrapper {
+        right: 2px;
+        display: flex;
+        flex-direction: row;
+        justify-items: center;
+        color: #606266;
+
+        .btn {
+          margin: 0;
+        }
+      }
+    }
+  }
+
+  // 角色仓库、清空未设置对话
+  .tool-box {
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    //width: 100%;
+    padding: 0 20px;
+    background-color: #f4f4f4;
+    box-shadow: 0 0 1px 1px rgba(228, 228, 228, 0.8);
+    line-height: 35px;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    color: var(--el-text-color);
+
+    > div {
+      display: flex;
+      align-items: center;
+      color: #606266;
+      padding: 0;
+      margin: 0;
+      cursor: pointer;
+
+      > span {
+        margin-left: 5px;
+      }
+    }
+  }
+}
+</style>
diff --git a/src/views/ai/dashboard/components/message/HistoryMessageDialog.vue b/src/views/ai/dashboard/components/message/HistoryMessageDialog.vue
new file mode 100644
index 0000000..68b1a73
--- /dev/null
+++ b/src/views/ai/dashboard/components/message/HistoryMessageDialog.vue
@@ -0,0 +1,375 @@
+<template>
+  <DialogHistory title="历史建议" v-model="dialogVisible" width="1200">
+    <!-- 左侧:对话列表 -->
+    <ConversationList
+      v-show="false"
+      :active-id="activeConversationId"
+      ref="conversationListRef"
+    />
+    <!-- 右侧:对话详情 -->
+    <el-container class="detail-container">
+      <el-header class="header">
+        <div class="title">
+          {{ activeConversation?.title ? activeConversation?.title : '' }}
+          <span v-if="activeMessageList.length">({{ activeMessageList.length }})</span>
+        </div>
+        <div class="btns" v-if="activeConversation">
+          <el-button size="small" class="btn" @click="handlerMessageClear">
+            <Icon icon="heroicons-outline:archive-box-x-mark" color="#73C4FF" />
+          </el-button>
+          <el-button size="small" class="btn" @click="handleGoBottomMessage">
+            <Icon icon="ep:download" color="#73C4FF" />
+          </el-button>
+          <el-button size="small" class="btn" @click="handleGoTopMessage">
+            <Icon icon="ep:top" color="#73C4FF" />
+          </el-button>
+        </div>
+      </el-header>
+
+      <!-- main:消息列表 -->
+      <el-main class="main-container">
+        <div class="message-container">
+          <!-- 情况一:无聊天对话时 -->
+          <ConversationListEmpty
+            v-if="!activeConversation"
+          />
+          <!-- 情况二:消息列表为空 -->
+          <MessageListEmpty
+            v-if="activeMessageList.length === 0 && activeConversation"
+          />
+          <!-- 情况三:消息列表不为空 -->
+          <HistoryMessageList
+            v-if="activeMessageList.length > 0"
+            ref="messageRef"
+            :conversation="activeConversation"
+            :list="activeMessageList"
+          />
+        </div>
+      </el-main>
+    </el-container>
+
+  </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'
+
+/** AI 聊天对话 列表 */
+defineOptions({ name: 'HistoryMessageDialog' })
+
+const route = useRoute() // 路由
+const message = useMessage() // 消息弹窗
+
+const dialogVisible = ref(false) // 弹窗的是否展示
+
+// 聊天对话
+const conversationListRef = ref()
+const activeConversation = ref<ChatConversationVO | null>(null) // 选中的 Conversation
+
+// 消息列表
+const messageRef = ref()
+const activeMessageList = ref<ChatMessageVO[]>([]) // 选中对话的消息列表
+
+
+/** 打开弹窗 */
+const open = async (messages: ChatMessageVO[], conversation: ChatConversationVO) => {
+  dialogVisible.value = true
+  await nextTick() // 等待弹窗DOM挂载
+  activeMessageList.value = messages
+  activeConversation.value = conversation
+}
+
+defineExpose({ open }) // 提供方法给 parent 调用
+
+/** 回到 message 列表的顶部 */
+const handleGoTopMessage = () => {
+  messageRef.value.handlerGoTop()
+}
+
+/** 回到 message 列表的底部 */
+const handleGoBottomMessage = () => {
+  messageRef.value.handleGoBottom()
+}
+
+/** 处理 message 清空 */
+const handlerMessageClear = async () => {
+  if (!activeConversation.value) {
+    return
+  }
+  try {
+    // 确认提示
+    await message.delConfirm('确认清空对话消息?')
+    // 清空对话
+    await ChatMessageApi.deleteByConversationId(activeConversation.value.id)
+    // 刷新 message 列表
+    activeMessageList.value = []
+  } catch {}
+}
+
+/** 初始化 **/
+onMounted(async () => {
+})
+</script>
+
+<style lang="scss" scoped>
+
+// 头部
+.detail-container {
+  display: flex;
+  flex-direction: column;
+  width: 100%;
+  height: 820px;
+  background-color: rgba(0, 0, 0, 0); /* 透明背景 */
+  z-index: 1;
+  .header {
+    display: flex;
+    flex-direction: row;
+    align-items: center;
+    justify-content: space-between;
+    box-shadow: 0 0 0 0 #dcdfe6;
+
+    .title {
+      font-size: 18px;
+      font-weight: bold;
+      color: gold;
+    }
+
+    .btns {
+      display: flex;
+      width: 300px;
+      flex-direction: row;
+      justify-content: flex-end;
+
+      .btn {
+        padding: 10px;
+      }
+
+      /* 所有状态通用透明背景 */
+      :deep(.el-button) {
+        background: transparent !important;
+        border-color: currentColor; /* 保持与文字同色 */
+        color: #409EFF; /* 蓝色文字 */
+      }
+
+      /* 悬停状态 */
+      :deep(.el-button:hover) {
+        background: rgba(0, 0, 0, 0.05) !important; /* 轻微悬停反馈 */
+      }
+
+      /* 点击状态 */
+      :deep(.el-button:active) {
+        background: rgba(0, 0, 0, 0.1) !important;
+      }
+
+      /* 禁用状态 */
+      :deep(.el-button.is-disabled) {
+        opacity: 0.6;
+        background: transparent !important;
+      }
+    }
+  }
+}
+
+// main 容器
+.main-container {
+  padding: 0;
+  position: relative;
+  overflow: hidden;          /* 隐藏外层滚动条 */
+  width: 100%;
+
+  .message-container {
+    position: absolute;
+    top: 0;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    overflow-y: hidden;
+    /* 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); }
+    }
+  }
+}
+
+// 底部
+.footer-container {
+  display: flex;
+  flex-direction: column;
+  height: 114px;
+  margin-left: 10px;
+  padding: 0;
+
+  // 输入框
+  .input-container {
+    display: flex;
+    flex-direction: column;
+    height: auto;
+    width: 876px;
+    margin: 0;
+    padding: 0;
+    overflow-y: auto;        /* 垂直方向溢出时显示滚动条 */
+    overflow-x: hidden;      /* 水平方向隐藏滚动条 */
+    /* 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); }
+    }
+
+    .prompt-from {
+      display: flex;
+      flex-direction: column;
+      padding: 9px 10px;
+      width: 876px;
+      height: 114px;
+      background: rgba(115,196,255,0.05);
+      border-radius: 4px 4px 4px 4px;
+      border: 1px solid #73C4FF;
+    }
+
+    .prompt-input {
+      width: 876px;
+      height: 113.55px;
+      font-weight: 400;
+      font-size: 14px;
+      background-color: rgba(219,238,255,0);
+      line-height: 21px;
+      text-align: left;
+      font-style: normal;
+      text-transform: none;
+      border: 0;
+      color: rgba(219,238,255,0.6);
+    }
+
+    .prompt-input:focus {
+      outline: none;
+    }
+
+    .prompt-btns {
+      display: flex;
+      justify-content: space-between;
+      padding-bottom: 0;
+      padding-top: 5px;
+
+      .content {
+        /* 默认状态 */
+        .el-button {
+          background: transparent !important;
+          border-color: rgba(115, 196, 255, 0.5);
+          color: #73C4FF;
+          border-radius: 15px !important;
+        }
+
+        /* 上下文图标处理 */
+        .content-icon {
+          color: blue;  /* 图标颜色 */
+          font-size: 18px;
+          margin-right: 10px;
+          background: url("@/assets/ai/zhuanlu/content.png");
+          vertical-align: middle;
+        }
+
+        /* 选中状态 */
+        .active-button {
+          background: #409eff !important;
+          border-color: #409eff !important;
+          color: white !important;
+          .content-icon {
+            background: url("@/assets/ai/zhuanlu/content_select.png");
+            vertical-align: middle;
+          }
+        }
+
+        /* 按钮组间距处理 */
+        .button-group .el-button {
+          margin-left: 0;
+          border-radius: 4px;
+        }
+
+        /* 悬停效果 */
+        .el-button:not(.active-button):hover {
+          border-color: rgba(115,196,255,0.5);
+          color: #409eff;
+        }
+      }
+      .message {
+        /* 所有状态通用透明背景 */
+        :deep(.el-button) {
+          background: rgba(73, 254, 210, 0.8) !important;
+          border-color: currentColor; /* 保持与文字同色 */
+          font-family: Alimama ShuHeiTi, Alimama ShuHeiTi;
+          font-weight: bold;
+          font-size: 16px;
+          color: #123C4E;
+          clip-path: polygon(
+              0 0,
+              100% 0,
+              100% 100%,
+              10px 100%,          /* 右下方向留出10px */
+              0 calc(100% - 10px) /* 左上方向留出10px */
+          );
+          position: relative;
+          padding-left: 15px; /* 增加右侧留白 */
+        }
+
+        /* 悬停状态 */
+        :deep(.el-button:hover) {
+          background: rgba(73, 254, 210, 0.6) !important; /* 轻微悬停反馈 */
+        }
+
+        /* 点击状态 */
+        :deep(.el-button:active) {
+          background: rgba(73, 254, 210, 1) !important;
+        }
+
+        /* 禁用状态 */
+        :deep(.el-button.is-disabled) {
+          opacity: 0.6;
+          background: transparent !important;
+        }
+
+        /* 核心样式覆盖 */
+        :deep(.el-switch__core) {
+          background: transparent !important;
+          border-radius: 0 0 15px 0 !important;
+          border: none !important;
+          height: 40px !important;
+        }
+
+        /* 按钮内容容器 */
+        .button-content {
+          display: flex;
+          align-items: center;
+          padding: 0 15px;
+          height: 100%;
+        }
+      }
+    }
+  }
+}
+</style>
diff --git a/src/views/ai/dashboard/components/message/HistoryMessageList.vue b/src/views/ai/dashboard/components/message/HistoryMessageList.vue
new file mode 100644
index 0000000..91371e1
--- /dev/null
+++ b/src/views/ai/dashboard/components/message/HistoryMessageList.vue
@@ -0,0 +1,290 @@
+<template>
+  <div ref="messageContainer" class="h-100% overflow-y-auto relative">
+    <div class="chat-list" v-for="(item, index) in list" :key="index">
+      <!-- 靠左 message:system、assistant 类型 -->
+      <div class="left-message message-item" v-if="item.type !== 'user'">
+        <div class="avatar">
+          <el-avatar :src="roleAvatar" />
+        </div>
+        <div class="message">
+          <div>
+            <el-text class="time">{{ formatDate(item.createTime) }}</el-text>
+          </div>
+          <div v-if="item.thinkingFlag" class="left-text-container-thinking" ref="markdownViewRef">
+            <MarkdownView v-if="item.thinking" class="left-text thinking" :content="item.thinking" />
+            <MarkdownView v-else class="left-text thinking" :content="item.content" />
+          </div>
+          <div v-else-if="item.thinking" class="left-text-container-thinking" ref="markdownViewRef">
+            <MarkdownView class="left-text thinking" :content="item.thinking" />
+          </div>
+          <div v-else class="left-text-container-conclusion" ref="markdownViewRef">
+            <MarkdownView class="left-text" :content="item.content" />
+          </div>
+          <div class="left-text-container-conclusion" ref="markdownViewRef">
+            <MarkdownView class="left-text" :content="item.conclusion" />
+          </div>
+          <div class="right-btns">
+            <el-button class="btn-cus" link @click="copyContent(item.content)">
+              <img class="btn-image" src="@/assets/ai/zhuanlu/copy.png" />
+            </el-button>
+            <!-- 暂时不能删除,随意删除会影响首页echarts图表展示 -->
+<!--            <el-button v-if="item.id > 0" class="btn-cus" link @click="onDelete(item.id)">-->
+<!--              <img class="btn-image h-17px" src="@/assets/ai/zhuanlu/delete.png" />-->
+<!--            </el-button>-->
+          </div>
+        </div>
+      </div>
+      <!-- 靠右 message:user 类型 -->
+      <div class="left-message message-item" v-if="item.type === 'user'">
+        <div class="avatar">
+          <el-avatar :src="userAvatar" />
+        </div>
+        <div class="message">
+          <div>
+            <el-text class="time">{{ formatDate(item.createTime) }}</el-text>
+          </div>
+          <div class="right-text-container">
+            <div class="right-text">{{ item.content }}</div>
+          </div>
+          <div class="right-btns">
+            <el-button class="btn-cus" link @click="copyContent(item.content)">
+              <img class="btn-image" src="@/assets/ai/zhuanlu/copy.png" />
+            </el-button>
+            <el-button class="btn-cus" link @click="onDelete(item.id)">
+              <img class="btn-image h-17px mr-12px" src="@/assets/ai/zhuanlu/delete.png" />
+            </el-button>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+  <!-- 回到底部 -->
+  <div v-if="isScrolling" class="to-bottom" @click="handleGoBottom">
+    <el-button :icon="ArrowDownBold" circle />
+  </div>
+</template>
+<script setup lang="ts">
+import { PropType } from 'vue'
+import { formatDate } from '@/utils/formatTime'
+import MarkdownView from '@/components/MarkdownView/index.vue'
+import { useClipboard } from '@vueuse/core'
+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)
+const isScrolling = ref(false) //用于判断用户是否在滚动
+
+const userAvatar = computed(() => userAvatarDefaultImg)
+const roleAvatar = computed(() => props.conversation.roleAvatar ?? roleAvatarDefaultImg)
+
+// 定义 props
+const props = defineProps({
+  conversation: {
+    type: Object as PropType<ChatConversationVO>,
+    required: true
+  },
+  list: {
+    type: Array as PropType<ChatMessageVO[]>,
+    required: true
+  }
+})
+
+const { list } = toRefs(props) // 消息列表
+
+const emits = defineEmits(['onDeleteSuccess']) // 定义 emits
+
+// ============ 处理对话滚动 ==============
+
+/** 滚动到底部 */
+const scrollToBottom = async (isIgnore?: boolean) => {
+  // 注意要使用 nextTick 以免获取不到 dom
+  await nextTick()
+  if (isIgnore || !isScrolling.value) {
+    messageContainer.value.scrollTop =
+      messageContainer.value.scrollHeight - messageContainer.value.offsetHeight
+  }
+}
+
+function handleScroll() {
+  const scrollContainer = messageContainer.value
+  const scrollTop = scrollContainer.scrollTop
+  const scrollHeight = scrollContainer.scrollHeight
+  const offsetHeight = scrollContainer.offsetHeight
+  if (scrollTop + offsetHeight < scrollHeight - 100) {
+    // 用户开始滚动并在最底部之上,取消保持在最底部的效果
+    isScrolling.value = true
+  } else {
+    // 用户停止滚动并滚动到最底部,开启保持到最底部的效果
+    isScrolling.value = false
+  }
+}
+
+/** 回到底部 */
+const handleGoBottom = async () => {
+  const scrollContainer = messageContainer.value
+  scrollContainer.scrollTop = scrollContainer.scrollHeight
+}
+
+/** 回到顶部 */
+const handlerGoTop = async () => {
+  const scrollContainer = messageContainer.value
+  scrollContainer.scrollTop = 0
+}
+
+defineExpose({ scrollToBottom, handlerGoTop, handleGoBottom }) // 提供方法给 parent 调用
+
+// ============ 处理消息操作 ==============
+
+/** 复制 */
+const copyContent = async (content) => {
+  await copy(content)
+  message.success('复制成功!')
+}
+
+/** 删除 */
+const onDelete = async (id) => {
+  // 删除 message
+  await ChatMessageApi.deleteChatMessage(id)
+  message.success('删除成功!')
+  // 回调
+  emits('onDeleteSuccess')
+}
+
+/** 初始化 */
+onMounted(async () => {
+  messageContainer.value.addEventListener('scroll', handleScroll)
+})
+</script>
+
+<style scoped lang="scss">
+/* 添加或修改以下样式 */
+div[ref="messageContainer"] {
+  height: 100%;          /* 继承父容器高度 */
+  overflow-y: auto;      /* 启用垂直滚动 */
+  max-height: 720px;     /* 或根据实际需求调整 */
+  padding-bottom: 20px; /* 避免底部内容被截断 */
+}
+// 中间
+.chat-list {
+  display: flex;
+  flex-direction: column;
+  height: auto;
+  padding: 0 20px;
+
+  .left-message {
+    display: flex;
+    flex-direction: row;
+  }
+
+  .message {
+    display: flex;
+    flex-direction: column;
+    text-align: left;
+    margin: 0 15px;
+
+    .time {
+      text-align: left;
+      line-height: 30px;
+    }
+
+    .left-text-container-thinking {
+      position: relative;
+      display: flex;
+      flex-direction: column;
+      overflow-wrap: break-word;
+      background: rgba(115,196,255,0.05);
+      border-radius: 4px 4px 4px 4px;
+      border-left: 1px solid #73C4FF;
+      padding: 10px 10px 5px 10px;
+      .left-text {
+        color: rgba(219,238,255,0.5);
+        font-size: 0.85rem;
+      }
+    }
+
+    .left-text-container-conclusion {
+      position: relative;
+      display: flex;
+      flex-direction: column;
+      overflow-wrap: break-word;
+      background: rgba(115,196,255,0);
+      border-radius: 4px 4px 4px 4px;
+      padding: 0 10px 5px 0;
+      .left-text {
+        color: rgba(219,238,255,0.8);
+        font-size: 1rem;
+      }
+    }
+
+    .right-text-container {
+      display: flex;
+      flex-direction: row-reverse;
+
+      .right-text {
+        font-size: 0.95rem;
+        color: #DBEEFF;
+        display: inline;
+        background: rgba(40,139,255,0.1);
+        box-shadow: 0 0 0 0 rgba(40,139,255,0.3);
+        border-radius: 10px;
+        padding: 10px;
+        width: auto;
+        overflow-wrap: break-word;
+        white-space: pre-wrap;
+      }
+    }
+
+    .left-btns {
+      display: flex;
+      flex-direction: row;
+      margin-top: 8px;
+    }
+
+    .right-btns {
+      display: flex;
+      flex-direction: row-reverse;
+      margin-top: 8px;
+    }
+  }
+
+  // 复制、删除按钮
+  .btn-cus {
+    display: flex;
+    background-color: transparent;
+    align-items: center;
+
+    .btn-image {
+      height: 20px;
+    }
+  }
+
+  .btn-cus:hover {
+    cursor: pointer;
+    background-color: #f6f6f6;
+  }
+}
+
+// 回到底部
+.to-bottom {
+  position: absolute;
+  z-index: 1000;
+  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);
+  }
+}
+</style>
diff --git a/src/views/ai/dashboard/components/message/MessageList.vue b/src/views/ai/dashboard/components/message/MessageList.vue
index 0caa248..616b9f4 100644
--- a/src/views/ai/dashboard/components/message/MessageList.vue
+++ b/src/views/ai/dashboard/components/message/MessageList.vue
@@ -3,9 +3,58 @@
     <div class="chat-list" v-for="(item, index) in list" :key="index">
       <!-- 靠左 message:system、assistant 类型 -->
       <div class="left-message message-item" v-if="item.type !== 'user'">
+        <div class="avatar">
+          <el-avatar :src="roleAvatar" />
+        </div>
         <div class="message">
-          <div class="left-text-container" ref="markdownViewRef">
-            <MarkdownView class="left-text" :content="item.content" />
+          <div>
+            <el-text class="time">{{ formatDate(item.createTime) }}</el-text>
+          </div>
+          <div v-if="item.thinkingFlag" class="left-text-container-thinking" ref="markdownViewRef">
+            <MarkdownView v-if="item.thinking" class="left-text thinking" :content="item.thinking" />
+            <MarkdownView v-else class="left-text thinking" :content="item.content" />
+          </div>
+          <div v-else-if="item.thinking" class="left-text-container-thinking" ref="markdownViewRef">
+            <MarkdownView class="left-text thinking" :content="item.thinking" />
+          </div>
+          <div class="left-text-container-conclusion" ref="markdownViewRef">
+            <MarkdownView class="left-text" :content="item.conclusion" />
+          </div>
+          <div class="left-btns">
+            <el-button class="btn-cus" link @click="copyContent(item.content)">
+              <img class="btn-image" src="@/assets/ai/zhuanlu/copy.png" />
+            </el-button>
+            <el-button v-if="item.id > 0" class="btn-cus" link @click="onDelete(item.id)">
+              <img class="btn-image h-17px" src="@/assets/ai/zhuanlu/delete.png" />
+            </el-button>
+          </div>
+        </div>
+      </div>
+      <!-- 靠右 message:user 类型 -->
+      <div class="right-message message-item" v-if="item.type === 'user'">
+        <div class="avatar">
+          <el-avatar :src="userAvatar" />
+        </div>
+        <div class="message">
+          <div>
+            <el-text class="time">{{ formatDate(item.createTime) }}</el-text>
+          </div>
+          <div class="right-text-container">
+            <div class="right-text">{{ item.content }}</div>
+          </div>
+          <div class="right-btns">
+            <el-button class="btn-cus" link @click="copyContent(item.content)">
+              <img class="btn-image" src="@/assets/ai/zhuanlu/copy.png" />
+            </el-button>
+            <el-button class="btn-cus" link @click="onDelete(item.id)">
+              <img class="btn-image h-17px mr-12px" src="@/assets/ai/zhuanlu/delete.png" />
+            </el-button>
+            <el-button class="btn-cus" link @click="onRefresh(item)">
+              <img class="btn-image h-17px mr-12px" src="@/assets/ai/zhuanlu/refresh.png" />
+            </el-button>
+            <el-button class="btn-cus" link @click="onEdit(item)">
+              <img class="btn-image h-17px mr-12px" src="@/assets/ai/zhuanlu/edit.png" />
+            </el-button>
           </div>
         </div>
       </div>
@@ -18,12 +67,15 @@
 </template>
 <script setup lang="ts">
 import { PropType } from 'vue'
+import { formatDate } from '@/utils/formatTime'
 import MarkdownView from '@/components/MarkdownView/index.vue'
 import { useClipboard } from '@vueuse/core'
 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 message = useMessage() // 消息弹窗
 const { copy } = useClipboard() // 初始化 copy 到粘贴板
@@ -32,6 +84,10 @@
 // 判断“消息列表”滚动的位置(用于判断是否需要滚动到消息最下方)
 const messageContainer: any = ref(null)
 const isScrolling = ref(false) //用于判断用户是否在滚动
+
+const userAvatar = computed(() => userAvatarDefaultImg)
+// const userAvatar = computed(() => userStore.user.avatar || userAvatarDefaultImg)
+const roleAvatar = computed(() => props.conversation.roleAvatar ?? roleAvatarDefaultImg)
 
 // 定义 props
 const props = defineProps({
@@ -78,16 +134,18 @@
 /** 回到底部 */
 const handleGoBottom = async () => {
   const scrollContainer = messageContainer.value
+  console.log(scrollContainer.scrollHeight)
   scrollContainer.scrollTop = scrollContainer.scrollHeight
 }
 
 /** 回到顶部 */
 const handlerGoTop = async () => {
   const scrollContainer = messageContainer.value
+  console.log(scrollContainer.scrollHeight)
   scrollContainer.scrollTop = 0
 }
 
-defineExpose({ scrollToBottom, handlerGoTop }) // 提供方法给 parent 调用
+defineExpose({ scrollToBottom, handlerGoTop, handleGoBottom }) // 提供方法给 parent 调用
 
 // ============ 处理消息操作 ==============
 
@@ -100,7 +158,7 @@
 /** 删除 */
 const onDelete = async (id) => {
   // 删除 message
-  await ChatMessageApi.deleteEnergyChatMessage(id)
+  await ChatMessageApi.deleteChatMessage(id)
   message.success('删除成功!')
   // 回调
   emits('onDeleteSuccess')
@@ -143,15 +201,24 @@
     flex-direction: row;
   }
 
+  .right-message {
+    display: flex;
+    flex-direction: row-reverse;
+    justify-content: flex-start;
+  }
+
   .message {
     display: flex;
     flex-direction: column;
     text-align: left;
-    height: 462px;
+    margin: 0 15px;
 
-    .left-text-container {
-      width: 855px;
-      height: 462px;
+    .time {
+      text-align: left;
+      line-height: 30px;
+    }
+
+    .left-text-container-thinking {
       position: relative;
       display: flex;
       flex-direction: column;
@@ -160,14 +227,55 @@
       border-radius: 4px 4px 4px 4px;
       border-left: 1px solid #73C4FF;
       padding: 10px 10px 5px 10px;
-
       .left-text {
-        font-weight: 400;
-        font-size: 14px;
-        color: rgba(219,238,255,0.6);
+        color: rgba(219,238,255,0.5);
+        font-size: 0.85rem;
       }
     }
 
+    .left-text-container-conclusion {
+      position: relative;
+      display: flex;
+      flex-direction: column;
+      overflow-wrap: break-word;
+      background: rgba(115,196,255,0);
+      border-radius: 4px 4px 4px 4px;
+      padding: 20px 10px 5px 0;
+      .left-text {
+        color: rgba(219,238,255,0.8);
+        font-size: 1rem;
+      }
+    }
+
+    .right-text-container {
+      display: flex;
+      flex-direction: row-reverse;
+
+      .right-text {
+        font-size: 0.95rem;
+        color: #DBEEFF;
+        display: inline;
+        background: rgba(40,139,255,0.1);
+        box-shadow: 0 0 0 1px rgba(40,139,255,0.3);
+        border-radius: 10px;
+        padding: 10px;
+        width: auto;
+        overflow-wrap: break-word;
+        white-space: pre-wrap;
+      }
+    }
+
+    .left-btns {
+      display: flex;
+      flex-direction: row;
+      margin-top: 8px;
+    }
+
+    .right-btns {
+      display: flex;
+      flex-direction: row-reverse;
+      margin-top: 8px;
+    }
   }
 
   // 复制、删除按钮
diff --git a/src/views/ai/dashboard/components/message/MessageListEmpty.vue b/src/views/ai/dashboard/components/message/MessageListEmpty.vue
index 5d30a87..e746f74 100644
--- a/src/views/ai/dashboard/components/message/MessageListEmpty.vue
+++ b/src/views/ai/dashboard/components/message/MessageListEmpty.vue
@@ -3,26 +3,15 @@
   <div class="chat-empty">
     <!-- title -->
     <div class="center-container">
-      <div class="title">工业大模型 AI</div>
+      <div class="avatar"><img src="@/assets/ai/zhuanlu/assistant.png" /></div>
+      <div class="gradient-text">欢迎来到转炉煤气调度大模型</div>
     </div>
   </div>
 </template>
 <script setup lang="ts">
-const promptList = [
-  {
-    prompt: '今天气怎么样?'
-  },
-  {
-    prompt: '写一首好听的诗歌?'
-  }
-] // prompt 列表
 
 const emits = defineEmits(['onPrompt'])
 
-/** 选中 prompt 点击 */
-const handlerPromptClick = async ({ prompt }) => {
-  emits('onPrompt', prompt)
-}
 </script>
 <style scoped lang="scss">
 .chat-empty {
@@ -32,42 +21,29 @@
   justify-content: center;
   width: 100%;
   height: 100%;
-
   .center-container {
-    display: flex;
-    flex-direction: column;
-    justify-content: center;
+    margin-top: 30%;
+    display: inline-block;
 
-    .title {
-      font-size: 28px;
-      font-weight: bold;
-      text-align: center;
-      color: #8FD6FE;
+    div {
+      float: left;
     }
 
-    .role-list {
-      display: flex;
-      flex-direction: row;
-      flex-wrap: wrap;
-      align-items: center;
-      justify-content: center;
-      width: 460px;
-      margin-top: 20px;
-
-      .role-item {
-        display: flex;
-        justify-content: center;
-        width: 180px;
-        line-height: 50px;
-        border: 1px solid #e4e4e4;
-        border-radius: 10px;
-        margin: 10px;
-        cursor: pointer;
-      }
-
-      .role-item:hover {
-        background-color: rgba(243, 243, 243, 0.73);
-      }
+    .avatar {
+      background: transparent;
+      width: 50px;
+      height: 50px;
+      position: relative;
+    }
+    /* 渐变文字样式 */
+    .gradient-text {
+      font-family: Alimama ShuHeiTi, Alimama ShuHeiTi;
+      font-weight: bold;
+      font-size: 24px;
+      background: linear-gradient(0deg, #49FFD3 0%, #4585FF 100%);
+      -webkit-background-clip: text;
+      color: transparent;
+      margin: 4px 0 0 10px;
     }
   }
 }
diff --git a/src/views/ai/dashboard/components/message/ModelMessageList.vue b/src/views/ai/dashboard/components/message/ModelMessageList.vue
new file mode 100644
index 0000000..f46b93e
--- /dev/null
+++ b/src/views/ai/dashboard/components/message/ModelMessageList.vue
@@ -0,0 +1,193 @@
+<template>
+  <div ref="messageContainer" class="h-100%">
+    <div class="chat-list" v-for="(item, index) in list" :key="index">
+      <!-- 靠左 message:system、assistant 类型 -->
+      <div class="left-message message-item" v-if="item.type !== 'user'">
+        <div class="message">
+          <div class="left-text-container" ref="markdownViewRef">
+            <MarkdownView class="left-text" :content="item.thinking" />
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+  <!-- 回到底部 -->
+  <div v-if="isScrolling" class="to-bottom" @click="handleGoBottom">
+    <el-button :icon="ArrowDownBold" circle />
+  </div>
+</template>
+<script setup lang="ts">
+import { PropType } from 'vue'
+import MarkdownView from '@/components/MarkdownView/index.vue'
+import { useClipboard } from '@vueuse/core'
+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 {formatDate} from "@/utils/formatTime";
+
+const message = useMessage() // 消息弹窗
+const { copy } = useClipboard() // 初始化 copy 到粘贴板
+const userStore = useUserStore()
+
+// 判断“消息列表”滚动的位置(用于判断是否需要滚动到消息最下方)
+const messageContainer: any = ref(null)
+const isScrolling = ref(false) //用于判断用户是否在滚动
+
+// 定义 props
+const props = defineProps({
+  conversation: {
+    type: Object as PropType<ChatConversationVO>,
+    required: true
+  },
+  list: {
+    type: Array as PropType<ChatMessageVO[]>,
+    required: true
+  }
+})
+
+const { list } = toRefs(props) // 消息列表
+
+const emits = defineEmits(['onDeleteSuccess', 'onRefresh', 'onEdit']) // 定义 emits
+
+// ============ 处理对话滚动 ==============
+
+/** 滚动到底部 */
+const scrollToBottom = async (isIgnore?: boolean) => {
+  // 注意要使用 nextTick 以免获取不到 dom
+  await nextTick()
+  if (isIgnore || !isScrolling.value) {
+    messageContainer.value.scrollTop =
+      messageContainer.value.scrollHeight - messageContainer.value.offsetHeight
+  }
+}
+
+function handleScroll() {
+  const scrollContainer = messageContainer.value
+  const scrollTop = scrollContainer.scrollTop
+  const scrollHeight = scrollContainer.scrollHeight
+  const offsetHeight = scrollContainer.offsetHeight
+  if (scrollTop + offsetHeight < scrollHeight - 100) {
+    // 用户开始滚动并在最底部之上,取消保持在最底部的效果
+    isScrolling.value = true
+  } else {
+    // 用户停止滚动并滚动到最底部,开启保持到最底部的效果
+    isScrolling.value = false
+  }
+}
+
+/** 回到底部 */
+const handleGoBottom = async () => {
+  const scrollContainer = messageContainer.value
+  scrollContainer.scrollTop = scrollContainer.scrollHeight
+}
+
+/** 回到顶部 */
+const handlerGoTop = async () => {
+  const scrollContainer = messageContainer.value
+  scrollContainer.scrollTop = 0
+}
+
+defineExpose({ scrollToBottom, handlerGoTop }) // 提供方法给 parent 调用
+
+// ============ 处理消息操作 ==============
+
+/** 复制 */
+const copyContent = async (content) => {
+  await copy(content)
+  message.success('复制成功!')
+}
+
+/** 删除 */
+const onDelete = async (id) => {
+  // 删除 message
+  await ChatMessageApi.deleteChatMessage(id)
+  message.success('删除成功!')
+  // 回调
+  emits('onDeleteSuccess')
+}
+
+/** 刷新 */
+const onRefresh = async (message: ChatMessageVO) => {
+  emits('onRefresh', message)
+}
+
+/** 编辑 */
+const onEdit = async (message: ChatMessageVO) => {
+  emits('onEdit', message)
+}
+
+/** 初始化 */
+onMounted(async () => {
+  messageContainer.value.addEventListener('scroll', handleScroll)
+})
+</script>
+
+<style scoped lang="scss">
+.message-container {
+  position: relative;
+  overflow-y: scroll;
+}
+
+// 中间
+.chat-list {
+  display: flex;
+  flex-direction: column;
+  overflow-y: hidden;
+  padding: 0 20px;
+  .message-item {
+    margin-top: 30px;
+  }
+
+  .message {
+    display: flex;
+    flex-direction: column;
+    text-align: left;
+    height: 480px;
+
+    .left-text-container {
+      width: 855px;
+      height: 450px;
+      position: relative;
+      display: flex;
+      flex-direction: column;
+      overflow-wrap: break-word;
+      background: rgba(115,196,255,0.05);
+      border-radius: 4px 4px 4px 4px;
+      border-left: 1px solid #73C4FF;
+      padding: 10px 10px 5px 10px;
+
+      .left-text {
+        font-weight: 400;
+        font-size: 14px;
+        color: rgba(219,238,255,0.6);
+      }
+    }
+
+  }
+
+  // 复制、删除按钮
+  .btn-cus {
+    display: flex;
+    background-color: transparent;
+    align-items: center;
+
+    .btn-image {
+      height: 20px;
+    }
+  }
+
+  .btn-cus:hover {
+    cursor: pointer;
+    background-color: #f6f6f6;
+  }
+}
+
+// 回到底部
+.to-bottom {
+  position: absolute;
+  z-index: 1000;
+  bottom: 0;
+  right: 50%;
+}
+</style>
diff --git a/src/views/ai/dashboard/zhuanlu/Index.vue b/src/views/ai/dashboard/zhuanlu/Index.vue
deleted file mode 100644
index 89bed03..0000000
--- a/src/views/ai/dashboard/zhuanlu/Index.vue
+++ /dev/null
@@ -1,1138 +0,0 @@
-<template>
-  <div class="gas-scheduling-container">
-    <div class="gas-scheduling-left">
-      <div id="mqhsssxx">
-        <div class="title"></div>
-        <div class="data1-item" v-for="(item, index) in mqhsList" :key="`dynamics-${index}`">
-          <div class="content">
-            <div class="value">
-              <span>{{item.value}}</span> <span>{{item.unit}}</span>
-            </div>
-            <div class="name">
-              {{item.name}}
-            </div>
-          </div>
-        </div>
-      </div>
-      <div id="tsxx">
-        <div class="title"></div>
-        <div class="data1-item" v-for="(item, index) in tsxxList" :key="`dynamics-${index}`">
-          <div class="content">
-            <div class="value">
-              <span>{{item.value}}</span> <span>{{item.unit}}</span>
-            </div>
-            <div class="name">
-              {{item.name}}
-            </div>
-          </div>
-        </div>
-      </div>
-      <div id="zlxx">
-        <div class="title"></div>
-        <el-table :data="zlxxList" class="transparent-table">
-          <el-table-column prop="name" label="控制器名称" header-class-name="custom-header" width="150"/>
-          <el-table-column prop="zl1" label="1#转炉" header-class-name="custom-header" />
-          <el-table-column prop="zl2" label="2#转炉" header-class-name="custom-header" />
-          <el-table-column prop="zl3" label="3#转炉" header-class-name="custom-header" />
-        </el-table>
-      </div>
-      <div id="mqxhssxx">
-        <div class="title"></div>
-        <div class="data2-item" v-for="(item, index) in mqxhssxxList" :key="`dynamics-${index}`">
-          <div class="content2">
-            <div class="name">
-              {{item.name}}
-            </div>
-            <div class="value">
-              <span>{{item.value}}</span> <span>{{item.unit}}</span>
-            </div>
-          </div>
-        </div>
-      </div>
-    </div>
-    <!-- 对话列表 -->
-    <ConversationList
-      v-show="false"
-      :active-id="activeConversationId"
-      ref="conversationListRef"
-      @on-conversation-click="handleConversationClick"
-      @on-conversation-clear="handleConversationClear"
-      @on-conversation-delete="handlerConversationDelete"
-    />
-    <div class="detail-container">
-      <!-- 输入框 -->
-      <div class="input-container">
-        <form class="prompt-from">
-          <textarea
-            class="prompt-input"
-            v-model="prompt"
-            @keydown="handleSendByKeydown"
-            @input="handlePromptInput"
-            @compositionstart="onCompositionstart"
-            @compositionend="onCompositionend"
-            placeholder="问我任何问题...(Shift+Enter 换行,按下 Enter 发送)"
-          ></textarea>
-          <div class="prompt-btns">
-            <div>
-              <el-switch v-model="enableContext" />
-              <span class="ml-5px text-14px text-#8f8f8f">上下文</span>
-            </div>
-            <el-button
-              type="primary"
-              size="default"
-              @click="handleSendByButton"
-              :loading="conversationInProgress"
-              v-if="conversationInProgress == false"
-            >
-              {{ conversationInProgress ? '进行中' : '发送' }}
-            </el-button>
-            <el-button
-              type="danger"
-              size="default"
-              @click="stopStream()"
-              v-if="conversationInProgress == true"
-            >
-              停止
-            </el-button>
-          </div>
-        </form>
-      </div>
-
-      <!-- main:消息列表 -->
-      <el-main class="main-container">
-        <div class="title">
-          <span>工业能源大模型思考</span>
-        </div>
-        <div>
-          <div class="message-container">
-            <!-- 情况一:消息加载中 -->
-            <MessageLoading v-if="activeMessageListLoading" />
-            <!-- 情况二:无聊天对话时 -->
-<!--            <MessageNewConversation-->
-<!--              v-if="!activeConversation"-->
-<!--              @on-new-conversation="handleConversationCreate"-->
-<!--            />-->
-            <!-- 情况三:消息列表为空 -->
-            <MessageListEmpty
-              v-if="!activeMessageListLoading && messageList.length === 0 && activeConversation"
-              @on-prompt="doSendMessage"
-            />
-            <!-- 情况四:消息列表不为空 -->
-            <MessageList
-              v-if="!activeMessageListLoading && messageList.length > 0"
-              ref="messageRef"
-              :conversation="activeConversation"
-              :list="messageList"
-              @on-delete-success="handleMessageDelete"
-              @on-edit="handleMessageEdit"
-              @on-refresh="handleMessageRefresh"
-            />
-          </div>
-        </div>
-      </el-main>
-    </div>
-
-    <!-- 更新对话 Form -->
-    <ConversationUpdateForm
-      ref="conversationUpdateFormRef"
-      @success="handleConversationUpdateSuccess"
-    />
-  </div>
-</template>
-
-<script setup lang="ts">
-import { ref, onMounted } from 'vue'
-import {ChatConversationApi, ChatConversationVO} from "@/api/ai/chat/conversation";
-import {ChatMessageApi, ChatMessageVO} from "@/api/ai/chat/message";
-import MessageList from '../components/message/MessageList.vue'
-import MessageListEmpty from '../components/message/MessageListEmpty.vue'
-import MessageLoading from '../components/message/MessageLoading.vue'
-import ConversationUpdateForm
-  from "../components/conversation/ConversationUpdateForm.vue";
-import ConversationList from "../components/conversation/ConversationList.vue";
-
-const mqhsList = ref([
-  {
-    name: '单转炉煤气回收流量',
-    value: 130,
-    unit: 'km³/h'
-  },
-  {
-    name: '转炉煤气 O 含量',
-    value: 618,
-    unit: '%'
-  },
-  {
-    name: '转炉煤气 CO 含量',
-    value: 15,
-    unit: '%'
-  },
-  {
-    name: '转炉铁水碳含量',
-    value: 20,
-    unit: '%'
-  },
-  {
-    name: '三通阀信号',
-    value: 0,
-    unit: ''
-  },
-  {
-    name: '单转炉吹氧流量',
-    value: 400,
-    unit: 'kNm³/h'
-  }
-])
-
-const tsxxList = ref([
-  {
-    name: '各高炉出铁水信号',
-    value: '进行',
-    unit: ''
-  },
-  {
-    name: '各高炉出铁量',
-    value: 5000,
-    unit: '吨'
-  },
-  {
-    name: '各高炉铁水装入鱼雷罐车信号',
-    value: '进行',
-    unit: 'm³/h'
-  },
-  {
-    name: '鱼雷罐车等待信号',
-    value: '进行',
-    unit: 'm³/h'
-  },
-  {
-    name: '铁水倒入铁水包信号',
-    value: '不进行',
-    unit: 'm³/h'
-  },
-  {
-    name: '铁产量计划',
-    value: 6000,
-    unit: '吨'
-  },
-])
-
-const zlxxList = ref([
-  {
-    name: '吹炼状态',
-    zl1: '正在吹炼',
-    zl2: '正在吹炼',
-    zl3: '正在吹炼'
-  },
-  {
-    name: '当前状态持续时间',
-    zl1: '10min',
-    zl2: '10min',
-    zl3: '10min'
-  },
-  {
-    name: '当前炉吹炼开始时刻',
-    zl1: '18:40',
-    zl2: '18:40',
-    zl3: '18:40'
-  },
-  {
-    name: '当前炉吹炼结束时刻',
-    zl1: '18:40',
-    zl2: '18:40',
-    zl3: '18:40'
-  },
-  {
-    name: '前一炉吹炼开始时刻',
-    zl1: '18:40',
-    zl2: '18:40',
-    zl3: '18:40'
-  },
-  {
-    name: '前一炉吹炼结束时刻',
-    zl1: '18:40',
-    zl2: '18:40',
-    zl3: '18:40'
-  }
-])
-
-const mqxhssxxList = ref([
-  {
-    name: '去棒三混合站',
-    value: 57.1,
-    unit: 'km³/h'
-  },
-  {
-    name: '东区掺混 LDG',
-    value: 49.4,
-    unit: 'km³/h'
-  },
-  {
-    name: '去焦化方向',
-    value: 67.4,
-    unit: 'm³/h'
-  },
-  {
-    name: '西区掺混 LDG',
-    value: 70,
-    unit: 'm³/h'
-  },
-  {
-    name: '送 BFG 管网',
-    value: 50.1,
-    unit: 'm³/h'
-  },
-  {
-    name: '去热卷二',
-    value: 72.2,
-    unit: 'km³/h'
-  },
-  {
-    name: '热卷一',
-    value: 13.9,
-    unit: 'km³/h'
-  },
-  {
-    name: '转底炉 1',
-    value: 7.0,
-    unit: 'km³/h'
-  },
-  {
-    name: '超薄带',
-    value: 67.4,
-    unit: 'km³/h'
-  },
-  {
-    name: '转底炉 2',
-    value: 13.5,
-    unit: 'km³/h'
-  },
-  {
-    name: '135MW 1',
-    value: 45.3,
-    unit: 'km³/h'
-  },
-  {
-    name: '135MW 2',
-    value: 36.2,
-    unit: 'km³/h'
-  },
-])
-
-const route = useRoute() // 路由
-const message = useMessage() // 消息弹窗
-
-// 聊天对话
-const conversationListRef = ref()
-const activeConversationId = ref<number | null>(null) // 选中的对话编号
-const activeConversation = ref<ChatConversationVO | null>(null) // 选中的 Conversation
-const conversationInProgress = ref(false) // 对话是否正在进行中。目前只有【发送】消息时,会更新为 true,避免切换对话、删除对话等操作
-
-// 消息列表
-const messageRef = ref()
-const activeMessageList = ref<ChatMessageVO[]>([]) // 选中对话的消息列表
-const activeMessageListLoading = ref<boolean>(false) // activeMessageList 是否正在加载中
-const activeMessageListLoadingTimer = ref<any>() // activeMessageListLoading Timer 定时器。如果加载速度很快,就不进入加载中
-// 消息滚动
-const textSpeed = ref<number>(50) // Typing speed in milliseconds
-const textRoleRunning = ref<boolean>(false) // Typing speed in milliseconds
-
-// 发送消息输入框
-const isComposing = ref(false) // 判断用户是否在输入
-const conversationInAbortController = ref<any>() // 对话进行中 abort 控制器(控制 stream 对话)
-const inputTimeout = ref<any>() // 处理输入中回车的定时器
-const prompt = ref<string>() // prompt
-const enableContext = ref<boolean>(false) // 是否开启上下文
-// 接收 Stream 消息
-const receiveMessageFullText = ref('')
-const receiveMessageDisplayedText = ref('')
-
-// =========== 【聊天对话】相关 ===========
-
-/** 获取对话信息 */
-const getConversation = async (id: number | null) => {
-  if (!id) {
-    return
-  }
-  const conversation: ChatConversationVO = await ChatConversationApi.getChatConversationMy(id)
-  if (!conversation) {
-    return
-  }
-  activeConversation.value = conversation
-  activeConversationId.value = conversation.id
-}
-
-/**
- * 点击某个对话
- *
- * @param conversation 选中的对话
- * @return 是否切换成功
- */
-const handleConversationClick = async (conversation: ChatConversationVO) => {
-  // 对话进行中,不允许切换
-  if (conversationInProgress.value) {
-    message.alert('对话中,不允许切换!')
-    return false
-  }
-
-  // 更新选中的对话 id
-  activeConversationId.value = conversation.id
-  activeConversation.value = conversation
-  // 刷新 message 列表
-  await getMessageList()
-  // 滚动底部
-  scrollToBottom(true)
-  // 清空输入框
-  prompt.value = ''
-  return true
-}
-
-/** 删除某个对话*/
-const handlerConversationDelete = async (delConversation: ChatConversationVO) => {
-  // 删除的对话如果是当前选中的,那么就重置
-  if (activeConversationId.value === delConversation.id) {
-    await handleConversationClear()
-  }
-}
-/** 清空选中的对话 */
-const handleConversationClear = async () => {
-  // 对话进行中,不允许切换
-  if (conversationInProgress.value) {
-    message.alert('对话中,不允许切换!')
-    return false
-  }
-  activeConversationId.value = null
-  activeConversation.value = null
-  activeMessageList.value = []
-}
-
-/** 修改聊天对话 */
-const conversationUpdateFormRef = ref()
-const openChatConversationUpdateForm = async () => {
-  conversationUpdateFormRef.value.open(activeConversationId.value)
-}
-const handleConversationUpdateSuccess = async () => {
-  // 对话更新成功,刷新最新信息
-  await getConversation(activeConversationId.value)
-}
-
-// =========== 【消息列表】相关 ===========
-
-/** 获取消息 message 列表 */
-const getMessageList = async () => {
-  try {
-    if (activeConversationId.value === null) {
-      return
-    }
-    // Timer 定时器,如果加载速度很快,就不进入加载中
-    activeMessageListLoadingTimer.value = setTimeout(() => {
-      activeMessageListLoading.value = true
-    }, 60)
-
-    // 获取消息列表
-    activeMessageList.value = await ChatMessageApi.getEnergyChatMessageListByConversationId(
-      activeConversationId.value
-    )
-    if(activeMessageList.value.length != 0) {
-      console.log(22222222)
-      console.log(activeMessageList.value[0].content)
-      prompt.value = activeMessageList.value[0].content
-    }
-
-    // 滚动到最下面
-    await nextTick()
-    await scrollToBottom()
-  } finally {
-    // time 定时器,如果加载速度很快,就不进入加载中
-    if (activeMessageListLoadingTimer.value) {
-      clearTimeout(activeMessageListLoadingTimer.value)
-    }
-    // 加载结束
-    activeMessageListLoading.value = false
-  }
-}
-
-/**
- * 消息列表
- *
- * 和 {@link #getMessageList()} 的差异是,把 systemMessage 考虑进去
- */
-const messageList = computed(() => {
-  if (activeMessageList.value.length > 0) {
-    return activeMessageList.value
-  }
-  // 没有消息时,如果有 systemMessage 则展示它
-  if (activeConversation.value?.systemMessage) {
-    return [
-      {
-        id: 0,
-        type: 'system',
-        content: activeConversation.value.systemMessage
-      }
-    ]
-  }
-  return []
-})
-
-/** 处理删除 message 消息 */
-const handleMessageDelete = () => {
-  if (conversationInProgress.value) {
-    message.alert('回答中,不能删除!')
-    return
-  }
-  // 刷新 message 列表
-  getMessageList()
-}
-
-/** 处理 message 清空 */
-const handlerMessageClear = async () => {
-  if (!activeConversationId.value) {
-    return
-  }
-  try {
-    // 确认提示
-    // await message.delConfirm('确认清空历史对话结果?')
-    // 清空对话
-    await ChatMessageApi.deleteEnergyByConversationId(activeConversationId.value)
-    // 刷新 message 列表
-    activeMessageList.value = []
-  } catch {}
-}
-
-/** 回到 message 列表的顶部 */
-const handleGoTopMessage = () => {
-  messageRef.value.handlerGoTop()
-}
-
-// =========== 【发送消息】相关 ===========
-
-/** 处理来自 keydown 的发送消息 */
-const handleSendByKeydown = async (event) => {
-  // 判断用户是否在输入
-  if (isComposing.value) {
-    return
-  }
-  // 进行中不允许发送
-  if (conversationInProgress.value) {
-    return
-  }
-  const content = prompt.value?.trim() as string
-  if (event.key === 'Enter') {
-    if (event.shiftKey) {
-      // 插入换行
-      prompt.value += '\r\n'
-      event.preventDefault() // 防止默认的换行行为
-    } else {
-      // 发送消息
-      await doSendMessage(content)
-      event.preventDefault() // 防止默认的提交行为
-    }
-  }
-}
-
-/** 处理来自【发送】按钮的发送消息 */
-const handleSendByButton = () => {
-  doSendMessage(prompt.value?.trim() as string)
-}
-
-/** 处理 prompt 输入变化 */
-const handlePromptInput = (event) => {
-  // 非输入法 输入设置为 true
-  if (!isComposing.value) {
-    // 回车 event data 是 null
-    if (event.data == null) {
-      return
-    }
-    isComposing.value = true
-  }
-  // 清理定时器
-  if (inputTimeout.value) {
-    clearTimeout(inputTimeout.value)
-  }
-  // 重置定时器
-  inputTimeout.value = setTimeout(() => {
-    isComposing.value = false
-  }, 400)
-}
-// TODO @芋艿:是不是可以通过 @keydown.enter、@keydown.shift.enter 来实现,回车发送、shift+回车换行;主要看看,是不是可以简化 isComposing 相关的逻辑
-const onCompositionstart = () => {
-  isComposing.value = true
-}
-const onCompositionend = () => {
-  // console.log('输入结束...')
-  setTimeout(() => {
-    isComposing.value = false
-  }, 200)
-}
-
-/** 真正执行【发送】消息操作 */
-const doSendMessage = async (content: string) => {
-  // 校验
-  if (content.length < 1) {
-    message.error('发送失败,原因:内容为空!')
-    return
-  }
-  if (activeConversationId.value == null) {
-    message.error('还没创建对话,不能发送!')
-    return
-  }
-  // 执行发送
-  await doSendMessageStream({
-    conversationId: activeConversationId.value,
-    content: content
-  } as ChatMessageVO)
-}
-
-/** 真正执行【发送】消息操作 */
-const doSendMessageStream = async (userMessage: ChatMessageVO) => {
-  // 创建 AbortController 实例,以便中止请求
-  conversationInAbortController.value = new AbortController()
-  // 标记对话进行中
-  conversationInProgress.value = true
-  // 设置为空
-  // receiveMessageFullText.value = ''
-
-  try {
-    // 1.0 每次发送消息前先将消息记录chat message清空
-    await handlerMessageClear()
-    // 1.1 先添加两个假数据,等 stream 返回再替换
-    activeMessageList.value.push({
-      id: -1,
-      conversationId: activeConversationId.value,
-      type: 'user',
-      content: userMessage.content,
-      createTime: new Date()
-    } as ChatMessageVO)
-    activeMessageList.value.push({
-      id: -2,
-      conversationId: activeConversationId.value,
-      type: 'assistant',
-      content: '思考中...',
-      createTime: new Date()
-    } as ChatMessageVO)
-    // 1.2 滚动到最下面
-    await nextTick()
-    await scrollToBottom() // 底部
-    // 1.3 开始滚动
-    textRoll()
-
-    // 2. 发送 event stream
-    let isFirstChunk = true // 是否是第一个 chunk 消息段
-    await ChatMessageApi.sendEnergyChatMessageStream(
-      userMessage.conversationId,
-      userMessage.content,
-      conversationInAbortController.value,
-      enableContext.value,
-      async (res) => {
-        const { code, data, msg } = JSON.parse(res.data)
-        if (code !== 0) {
-          message.alert(`对话异常! ${msg}`)
-          return
-        }
-
-        // 如果内容为空,就不处理。
-        if (data.receive.content === '') {
-          return
-        }
-        // 首次返回需要添加一个 message 到页面,后面的都是更新
-        if (isFirstChunk) {
-          isFirstChunk = false
-          // 弹出两个假数据
-          activeMessageList.value.pop()
-          activeMessageList.value.pop()
-          // 更新返回的数据
-          activeMessageList.value.push(data.send)
-          activeMessageList.value.push(data.receive)
-        }
-        // debugger
-        receiveMessageFullText.value = receiveMessageFullText.value + data.receive.content
-        // 滚动到最下面
-        await scrollToBottom()
-      },
-      (error) => {
-        message.alert(`对话异常! ${error}`)
-        stopStream()
-      },
-      () => {
-        stopStream()
-      }
-    )
-  } catch {}
-}
-
-/** 停止 stream 流式调用 */
-const stopStream = async () => {
-  // tip:如果 stream 进行中的 message,就需要调用 controller 结束
-  if (conversationInAbortController.value) {
-    conversationInAbortController.value.abort()
-  }
-  // 设置为 false
-  conversationInProgress.value = false
-}
-
-/** 编辑 message:设置为 prompt,可以再次编辑 */
-const handleMessageEdit = (message: ChatMessageVO) => {
-  prompt.value = message.content
-}
-
-/** 刷新 message:基于指定消息,再次发起对话 */
-const handleMessageRefresh = (message: ChatMessageVO) => {
-  doSendMessage(message.content)
-}
-
-// ============== 【消息滚动】相关 =============
-
-/** 滚动到 message 底部 */
-const scrollToBottom = async (isIgnore?: boolean) => {
-  await nextTick()
-  if (messageRef.value) {
-    messageRef.value.scrollToBottom(isIgnore)
-  }
-}
-
-/** 自提滚动效果 */
-const textRoll = async () => {
-  let index = 0
-  try {
-    // 只能执行一次
-    if (textRoleRunning.value) {
-      return
-    }
-    // 设置状态
-    textRoleRunning.value = true
-    receiveMessageDisplayedText.value = ''
-    const task = async () => {
-      // 调整速度
-      const diff =
-        (receiveMessageFullText.value.length - receiveMessageDisplayedText.value.length) / 10
-      if (diff > 5) {
-        textSpeed.value = 10
-      } else if (diff > 2) {
-        textSpeed.value = 30
-      } else if (diff > 1.5) {
-        textSpeed.value = 50
-      } else {
-        textSpeed.value = 100
-      }
-      // 对话结束,就按 30 的速度
-      if (!conversationInProgress.value) {
-        textSpeed.value = 10
-      }
-
-      if (index < receiveMessageFullText.value.length) {
-        receiveMessageDisplayedText.value += receiveMessageFullText.value[index]
-        index++
-
-        // 更新 message
-        const lastMessage = activeMessageList.value[activeMessageList.value.length - 1]
-        lastMessage.content = receiveMessageDisplayedText.value
-        // 滚动到住下面
-        await scrollToBottom()
-        // 重新设置任务
-        timer = setTimeout(task, textSpeed.value)
-      } else {
-        // 不是对话中可以结束
-        if (!conversationInProgress.value) {
-          textRoleRunning.value = false
-          clearTimeout(timer)
-        } else {
-          // 重新设置任务
-          timer = setTimeout(task, textSpeed.value)
-        }
-      }
-    }
-    let timer = setTimeout(task, textSpeed.value)
-  } catch {}
-}
-
-/** 初始化 **/
-onMounted(async () => {
-  // 如果有 conversationId 参数,则默认选中
-  if (route.query.conversationId) {
-    const id = route.query.conversationId as unknown as number
-    activeConversationId.value = id
-    await getConversation(id)
-  }
-
-  // 获取列表数据
-  activeMessageListLoading.value = true
-  await getMessageList()
-})
-
-</script>
-
-<style lang="scss" scoped>
-.gas-scheduling-container {
-  display: flex;
-  font-family: Microsoft YaHei, Microsoft YaHei;
-  /* 背景层容器 */
-  &::before {
-    content: '';
-    position: fixed;
-    top: 0;
-    left: 0;
-    width: 100%;
-    height: 100%;
-    z-index: -1; /* 置于内容层下方 */
-    background:
-      url("@/assets/ai/zhuanlu/bg.png") center/cover no-repeat,
-      linear-gradient(to bottom, #0a1633dd, #0a1633dd); /* 叠加深色遮罩 */
-    pointer-events: none; /* 防止遮挡交互 */
-  }
-  .gas-scheduling-left {
-    width: 22%;
-    height: 89%;
-    margin-left: 32px;
-    margin-top: 30px;
-    z-index: 1;
-    background-color: rgba(0, 0, 0, 0); /* 透明背景 */
-    .data1-item {
-      height: 41px;
-      width: 182px;
-      display: inline-block;
-      margin: 8px 10px ;
-      background: url("@/assets/ai/zhuanlu/data_bg1.png");
-    }
-    .data2-item {
-      height: 30px;
-      width: 192px;
-      display: inline-block;
-      margin: 6px 8px;
-      background: url("@/assets/ai/zhuanlu/data_bg2.png");
-    }
-    .content {
-      margin-left: 16px;
-      .value {
-        span:nth-child(1){
-          height: 19px;
-          font-weight: bold;
-          font-size: 16px;
-          color: #FFAE81;
-          line-height: 19px;
-        }
-        span:nth-child(2) {
-          height: 16px;
-          font-weight: 400;
-          font-size: 12px;
-          color: #C7E7FF;
-        }
-      }
-      .name {
-        height: 16px;
-        font-weight: 400;
-        font-size: 12px;
-        color: #C7E7FF;
-      }
-    }
-    .content2 {
-      display: flex;
-      width: 192px;
-      margin-left: 10px;
-      .name {
-        width: 95px;
-        height: 18px;
-        font-weight: 400;
-        font-size: 14px;
-        color: #C7E7FF;
-      }
-      .value {
-        margin-left: auto;
-        margin-right: 5px;
-        span:nth-child(1){
-          height: 15px;
-          font-weight: bold;
-          font-size: 15px;
-          color: #FFAE81;
-          line-height: 15px;
-        }
-        span:nth-child(2) {
-          height: 15px;
-          font-weight: 400;
-          font-size: 12px;
-          color: #C7E7FF;
-        }
-      }
-    }
-    #mqhsssxx {
-      .title {
-        height: 30px;
-        background:
-          url("@/assets/ai/zhuanlu/mqhsssxx_title.png") center/cover no-repeat; /* 叠加深色遮罩 */
-      }
-    }
-    #tsxx {
-      .title {
-        height: 30px;
-        background:
-          url("@/assets/ai/zhuanlu/tsxx_title.png") center/cover no-repeat; /* 叠加深色遮罩 */
-      }
-    }
-    #zlxx {
-      .title {
-        height: 30px;
-        background:
-          url("@/assets/ai/zhuanlu/zlxx_title.png") center/cover no-repeat; /* 叠加深色遮罩 */
-      }
-      :deep(.el-table){
-        font-weight: 400;
-        font-size: 14px;
-        color: #DBEEFF;
-        text-align: left;
-      }
-      .transparent-table{
-        margin-top: 14px;
-      }
-      /* 设置列头背景图片 */
-      :deep(.el-table .el-table__inner-wrapper .el-table__header-wrapper) {
-        background:
-          url("@/assets/ai/zhuanlu/table_header_bg.png") center/cover no-repeat !important; /* 叠加深色遮罩 */
-      }
-      :deep(.el-table .even-row){
-        background-color: rgba(0,194,255,0.5);
-      }
-      :deep(.el-table .odd-row) {
-        background-color: rgba(0,194,255,0.8);
-      }
-      :deep(.current-row>td){
-        background-color: rgba(16,198,255,0.2);
-      }
-      :deep(.el-table th) {
-        color: #8FD6FE;
-        background: linear-gradient( 180deg, rgba(16,198,255,0) 0%, rgba(17,198,255,0.14) 100%);
-        border: 1px solid rgba(16,198,255,0.2);
-      }
-      :deep(.el-table thead){
-        color: #8FD6FE;
-        font-weight: 500;
-        border-radius: 4px 4px 4px 4px;
-      }
-      :deep(.el-table tr) {
-        color: #8FD6FE;
-        background: rgba(2, 16, 36, 0.94);
-        border-radius: 4px 4px 4px 4px;
-      }
-      :deep(.el-table td,
-      .building-top .el-table th.is-leaf) {
-        border-bottom: 0px solid #FAFAFB;
-      }
-      :deep(.el-table .el-table__inner-wrapper:before) {
-        background-color: transparent !important;
-      }
-    }
-    #mqxhssxx {
-      .title {
-        height: 30px;
-        background:
-          url("@/assets/ai/zhuanlu/mqxhssxx_title.png") center/cover no-repeat; /* 叠加深色遮罩 */
-      }
-    }
-  }
-
-  // 头部
-  .detail-container {
-    width: 876px;
-    height: 885px;
-    margin-left: 55px;
-    margin-top: 100px;
-    background-color: rgba(0, 0, 0, 0); /* 透明背景 */
-    z-index: 1;
-    .header {
-      display: flex;
-      flex-direction: row;
-      align-items: center;
-      justify-content: space-between;
-      box-shadow: 0 0 0 0 #dcdfe6;
-
-      .title {
-        font-size: 18px;
-        font-weight: bold;
-      }
-
-      .btns {
-        display: flex;
-        width: 300px;
-        flex-direction: row;
-        justify-content: flex-end;
-        //justify-content: space-between;
-
-        .btn {
-          padding: 10px;
-        }
-      }
-    }
-  }
-
-  .conversation-container {
-    z-index: 1;
-    position: relative;
-    display: flex;
-    flex-direction: column;
-    justify-content: space-between;
-    padding: 10px 10px 0;
-    .btn-new-conversation {
-      padding: 18px 0;
-    }
-
-    .search-input {
-      margin-top: 20px;
-    }
-
-    .conversation-list {
-      margin-top: 20px;
-
-      .conversation {
-        display: flex;
-        flex-direction: row;
-        justify-content: space-between;
-        flex: 1;
-        padding: 0 5px;
-        margin-top: 10px;
-        cursor: pointer;
-        border-radius: 5px;
-        align-items: center;
-        line-height: 30px;
-
-        &.active {
-          background-color: #e6e6e6;
-
-          .button {
-            display: inline-block;
-          }
-        }
-
-        .title-wrapper {
-          display: flex;
-          flex-direction: row;
-          align-items: center;
-        }
-
-        .title {
-          padding: 5px 10px;
-          max-width: 220px;
-          font-size: 14px;
-          overflow: hidden;
-          white-space: nowrap;
-          text-overflow: ellipsis;
-        }
-
-        .avatar {
-          width: 28px;
-          height: 28px;
-          display: flex;
-          flex-direction: row;
-          justify-items: center;
-        }
-
-        // 对话编辑、删除
-        .button-wrapper {
-          right: 2px;
-          display: flex;
-          flex-direction: row;
-          justify-items: center;
-          color: #606266;
-          .el-icon {
-            margin-right: 5px;
-          }
-        }
-      }
-    }
-  }
-
-  // main 容器
-  .main-container {
-    margin: 0;
-    padding: 0;
-    position: relative;
-    height: 100%;
-    width: 100%;
-
-    .message-container {
-      position: absolute;
-      top: 0;
-      bottom: 0;
-      left: 0;
-      right: 0;
-      overflow-y: hidden;
-      padding: 0;
-      margin: 0;
-    }
-    .title {
-      background: url("@/assets/ai/zhuanlu/think_bg.png") center/cover no-repeat;
-      width: auto;
-      height: 30px;
-      font-weight: 400;
-      font-size: 14px;
-      color: #8FD6FE;
-      text-align: left;
-      font-style: normal;
-      text-transform: none;
-      span {
-        margin-left: 30px;
-      }
-    }
-  }
-
-  // 输入框
-  .input-container {
-    display: flex;
-    flex-direction: column;
-    height: auto;
-    margin: 0;
-    padding: 0;
-    overflow-y: auto;        /* 垂直方向溢出时显示滚动条 */
-    overflow-x: hidden;      /* 水平方向隐藏滚动条 */
-    /* 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); }
-    }
-
-    .prompt-from {
-      display: flex;
-      flex-direction: column;
-      margin: 10px 20px 20px 20px;
-      padding: 9px 10px;
-      width: 855px;
-      height: 225px;
-      background: rgba(115,196,255,0.05);
-      border-radius: 4px 4px 4px 4px;
-      border: 1px solid #73C4FF;
-    }
-
-    .prompt-input {
-      width: 851px;
-      height: 168px;
-      font-weight: 400;
-      font-size: 14px;
-      background-color: rgba(219,238,255,0);
-      line-height: 21px;
-      text-align: left;
-      font-style: normal;
-      text-transform: none;
-      border: 0;
-      color: rgba(219,238,255,0.6);
-    }
-
-    .prompt-input:focus {
-      outline: none;
-    }
-
-    .prompt-btns {
-      display: flex;
-      justify-content: space-between;
-      padding-bottom: 0;
-      padding-top: 5px;
-    }
-  }
-}
-</style>
diff --git a/src/views/ai/dashboard/zhuanlu/index.vue b/src/views/ai/dashboard/zhuanlu/index.vue
new file mode 100644
index 0000000..0d0be46
--- /dev/null
+++ b/src/views/ai/dashboard/zhuanlu/index.vue
@@ -0,0 +1,2347 @@
+<template>
+  <div class="gas-scheduling-container">
+    <el-button size="small" class="fullscreen-btn" @click="toggleFullscreen">
+      <Icon
+        class="is-hover mr-12px cursor-pointer"
+        :icon="isFullscreen ? 'radix-icons:exit-full-screen' : 'radix-icons:enter-full-screen'"
+        color="#8FD6FE"
+        hover-color="var(--el-color-primary)"
+      />
+      {{ isFullscreen ? '退出全屏' : '全屏' }}
+    </el-button>
+    <div class="gas-scheduling-left">
+      <div id="mqhsssxx">
+        <div class="title"></div>
+        <div class="data1-item" v-for="(item, index) in mqhsList" :key="`dynamics-${index}`">
+          <div class="content">
+            <div class="value">
+              <span>{{item.value}}</span> <span>{{item.unit}}</span>
+            </div>
+            <div class="name">
+              {{item.name}}
+            </div>
+          </div>
+        </div>
+      </div>
+      <div id="tsxx">
+        <div class="title"></div>
+        <div class="data1-item" v-for="(item, index) in tsxxList" :key="`dynamics-${index}`">
+          <div class="content">
+            <div class="value">
+              <span>{{item.value}}</span> <span>{{item.unit}}</span>
+            </div>
+            <div class="name">
+              {{item.name}}
+            </div>
+          </div>
+        </div>
+      </div>
+      <div id="zlxx">
+        <div class="title"></div>
+        <el-table :data="zlxxList" class="transparent-table">
+          <el-table-column prop="name" label="控制器名称" header-class-name="custom-header" width="150"/>
+          <el-table-column prop="zl1" label="1#转炉" header-class-name="custom-header" />
+          <el-table-column prop="zl2" label="2#转炉" header-class-name="custom-header" />
+          <el-table-column prop="zl3" label="3#转炉" header-class-name="custom-header" />
+        </el-table>
+      </div>
+      <div id="mqxhssxx">
+        <div class="title"></div>
+        <div class="data2-item" v-for="(item, index) in mqxhssxxList" :key="`dynamics-${index}`">
+          <div class="content2">
+            <div class="name">
+              {{item.name}}
+            </div>
+            <div class="value">
+              <span>{{item.value}}</span> <span>{{item.unit}}</span>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <div class="gas-scheduling-center">
+      <div class="mode-switch">
+        <el-radio-group v-model="tabPosition" class="custom-radio-group">
+          <el-radio-button label="model">大模型模式</el-radio-button>
+          <el-radio-button label="conversation">对话模式</el-radio-button>
+        </el-radio-group>
+      </div>
+
+      <div v-if="tabPosition === 'model'">
+        <!-- 对话列表 -->
+        <ConversationList
+          v-show="false"
+          :active-id="activeConversationId"
+          ref="conversationListRef"
+          @on-conversation-click="handleConversationClick"
+          @on-conversation-clear="handleConversationClear"
+          @on-conversation-delete="handlerConversationDelete"
+        />
+        <div class="detail-container">
+          <!-- 输入框 -->
+          <div class="input-container">
+            <form class="prompt-from">
+              <textarea
+                class="prompt-input"
+                v-model="prompt"
+                @keydown="handleSendByKeydown"
+                @input="handlePromptInput"
+                @compositionstart="onCompositionstart"
+                @compositionend="onCompositionend"
+                placeholder="问我任何问题...(Shift+Enter 换行,按下 Enter 发送)"
+              ></textarea>
+              <div class="prompt-btns">
+                <div class="content">
+                  <el-button
+                    :class="{ 'active-button': enableContext }"
+                    @click="enableContext = !enableContext"
+                  >
+                    <el-icon class="content-icon" />
+                    上下文
+                  </el-button>
+                </div>
+                <div class="message">
+                  <el-button
+                    type="primary"
+                    size="default"
+                    @click="handleSendByButton"
+                    :loading="conversationInProgress"
+                    v-if="conversationInProgress == false"
+                  >
+                    {{ conversationInProgress ? '进行中' : '发消息' }}
+                  </el-button>
+                  <el-button
+                    type="danger"
+                    size="default"
+                    @click="stopStream()"
+                    v-if="conversationInProgress == true"
+                  >
+                    停止
+                  </el-button>
+                </div>
+              </div>
+            </form>
+          </div>
+
+          <!-- main:消息列表 -->
+          <el-main class="main-container">
+            <div class="title">
+              <span>工业能源大模型思考</span>
+            </div>
+            <div>
+              <div class="message-container">
+                <!-- 情况一:消息加载中 -->
+                <MessageLoading v-if="activeMessageListLoading" />
+                <!-- 情况二:消息列表为空 -->
+                <MessageListEmpty
+                  v-if="!activeMessageListLoading && messageList.length === 0 && activeConversation"
+                  @on-prompt="doSendMessage"
+                />
+                <!-- 情况三:消息列表不为空 -->
+                <ModelMessageList
+                  v-if="!activeMessageListLoading && messageList.length > 0"
+                  ref="messageRef"
+                  :conversation="activeConversation"
+                  :list="messageList"
+                  @on-delete-success="handleMessageDelete"
+                  @on-edit="handleMessageEdit"
+                  @on-refresh="handleMessageRefresh"
+                />
+              </div>
+            </div>
+          </el-main>
+          <!-- main:调度推理结论 -->
+          <div class="result-container-title">
+            <span>调度推理结论</span><el-button @click="openHistoryMessage" size="small" class="history-button" :icon="ArrowUpBold">历史建议</el-button>
+          </div>
+          <el-main class="result-container">
+            <div class="result">
+              <textarea class="result-content" v-model="ddtlResult"></textarea>
+            </div>
+          </el-main>
+        </div>
+        <!-- 历史建议 -->
+        <HistoryMessageDialog
+          ref="historyMessageRef"
+          :conversation="activeConversation"
+        />
+      </div>
+
+      <div v-else>
+        <NormalConversation />
+      </div>
+    </div>
+
+    <div class="gas-scheduling-right">
+      <div id="ldghslyc">
+        <div class="title"></div>
+        <div ref="LDGHSLYCEhartContainer" style="width: 100%; height: 180px"></div>
+      </div>
+      <div id="ldggrqsyc">
+        <div class="title"></div>
+        <div ref="LDGGRYCEhartContainer" style="width: 100%; height: 180px"></div>
+      </div>
+      <div id="mqhsjhxx">
+        <div class="title"></div>
+        <div class="time-content" v-for="(item, index) in mqhsjhTimeList" :key="`dynamics-${index}`">
+          <div class="time-content-item">
+            <div class="name">
+              <span>{{item.name}}</span>
+            </div>
+            <div class="time">
+              <div class="in-pot"></div><div class="in">装入{{item.inTime}}</div>
+              <div class="out-pot"></div><div class="out">结束{{item.outTime}}</div>
+            </div>
+          </div>
+        </div>
+        <div class="data2-item" v-for="(item, index) in mqhsjhxxList" :key="`dynamics-${index}`">
+          <div class="content">
+            <div class="name">
+              {{item.name}}
+            </div>
+            <div class="value">
+              <span>{{item.value}}</span> <span>{{item.unit}}</span>
+            </div>
+          </div>
+        </div>
+      </div>
+      <div id="scmbyyxzb">
+        <div class="title"></div>
+        <div class="little-title">生产目标/班</div>
+        <div class="data3-item" v-for="(item, index) in scmbList" :key="`dynamics-${index}`">
+          <div class="content2">
+            <div class="name">
+              {{item.name}}
+            </div>
+            <el-progress
+              :percentage="percentage(item)"
+              :stroke-width="12"
+              :text-inside="true"
+              :color="customColor"
+              :show-text="false"
+            />
+            <div class="value">
+              <span>{{item.current}}/{{item.total}}</span>
+            </div>
+            <div class="value-content">
+              <span>已吹炼/总炉数</span>
+            </div>
+          </div>
+        </div>
+        <div class="little-title">运行指标/天</div>
+        <div class="zb-content">
+          <div class="item left-label"></div>
+          <div class="item data4-item" v-for="(item, index) in yxzbList" :key="`dynamics-${index}`">
+            <div class="content">
+              <div class="value">
+                <span>{{item.value}}</span> <span>{{item.unit}}</span>
+              </div>
+              <div class="name">
+                {{item.name}}
+              </div>
+            </div>
+          </div>
+          <div class="item right-label"></div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted, reactive } from 'vue'
+import {ChatConversationApi, ChatConversationVO} from "@/api/ai/chat/conversation";
+import {ChatMessageApi, ChatMessageVO} from "@/api/ai/chat/message";
+import ModelMessageList from '../components/message/ModelMessageList.vue'
+import NormalConversation from '../components/conversation/CommonConversation.vue'
+import MessageListEmpty from '../components/message/MessageListEmpty.vue'
+import MessageLoading from '../components/message/MessageLoading.vue'
+import ConversationList from "../components/conversation/ConversationList.vue";
+import HistoryMessageDialog from "../components/message/HistoryMessageDialog.vue"
+import * as echarts from "echarts";
+import {formatToDateTime} from "@/utils/dateUtil";
+import {refreshToken} from "@/api/login";
+import {round} from "lodash-es";
+import {ArrowUpBold} from "@element-plus/icons-vue";
+import * as authUtil from "@/utils/auth";
+
+const mqhsList = ref([
+  {
+    name: '单转炉煤气回收流量',
+    value: 130,
+    unit: 'km³/h'
+  },
+  {
+    name: '转炉煤气 O 含量',
+    value: 618,
+    unit: '%'
+  },
+  {
+    name: '转炉煤气 CO 含量',
+    value: 15,
+    unit: '%'
+  },
+  {
+    name: '转炉铁水碳含量',
+    value: 20,
+    unit: '%'
+  },
+  {
+    name: '三通阀信号',
+    value: 0,
+    unit: ''
+  },
+  {
+    name: '单转炉吹氧流量',
+    value: 400,
+    unit: 'kNm³/h'
+  }
+])
+
+const tsxxList = ref([
+  {
+    name: '各高炉出铁水信号',
+    value: '进行',
+    unit: ''
+  },
+  {
+    name: '各高炉出铁量',
+    value: 5000,
+    unit: '吨'
+  },
+  {
+    name: '各高炉铁水装入鱼雷罐车信号',
+    value: '进行',
+    unit: 'm³/h'
+  },
+  {
+    name: '鱼雷罐车等待信号',
+    value: '进行',
+    unit: 'm³/h'
+  },
+  {
+    name: '铁水倒入铁水包信号',
+    value: '不进行',
+    unit: 'm³/h'
+  },
+  {
+    name: '铁产量计划',
+    value: 6000,
+    unit: '吨'
+  },
+])
+
+const zlxxList = ref([
+  {
+    name: '吹炼状态',
+    zl1: '正在吹炼',
+    zl2: '正在吹炼',
+    zl3: '正在吹炼'
+  },
+  {
+    name: '当前状态持续时间',
+    zl1: '10min',
+    zl2: '10min',
+    zl3: '10min'
+  },
+  {
+    name: '当前炉吹炼开始时刻',
+    zl1: '18:40',
+    zl2: '18:40',
+    zl3: '18:40'
+  },
+  {
+    name: '当前炉吹炼结束时刻',
+    zl1: '18:40',
+    zl2: '18:40',
+    zl3: '18:40'
+  },
+  {
+    name: '前一炉吹炼开始时刻',
+    zl1: '18:40',
+    zl2: '18:40',
+    zl3: '18:40'
+  },
+  {
+    name: '前一炉吹炼结束时刻',
+    zl1: '18:40',
+    zl2: '18:40',
+    zl3: '18:40'
+  }
+])
+
+const mqxhssxxList = ref([
+  {
+    name: '去棒三混合站',
+    value: 57.1,
+    unit: 'km³/h'
+  },
+  {
+    name: '东区掺混 LDG',
+    value: 49.4,
+    unit: 'km³/h'
+  },
+  {
+    name: '去焦化方向',
+    value: 67.4,
+    unit: 'm³/h'
+  },
+  {
+    name: '西区掺混 LDG',
+    value: 70,
+    unit: 'm³/h'
+  },
+  {
+    name: '送 BFG 管网',
+    value: 50.1,
+    unit: 'm³/h'
+  },
+  {
+    name: '去热卷二',
+    value: 72.2,
+    unit: 'km³/h'
+  },
+  {
+    name: '热卷一',
+    value: 13.9,
+    unit: 'km³/h'
+  },
+  {
+    name: '转底炉 1',
+    value: 7.0,
+    unit: 'km³/h'
+  },
+  {
+    name: '超薄带',
+    value: 67.4,
+    unit: 'km³/h'
+  },
+  {
+    name: '转底炉 2',
+    value: 13.5,
+    unit: 'km³/h'
+  },
+  {
+    name: '135MW 1',
+    value: 45.3,
+    unit: 'km³/h'
+  },
+  {
+    name: '135MW 2',
+    value: 36.2,
+    unit: 'km³/h'
+  },
+])
+
+const mqhsjhxxList = ref([
+  {
+    name: '转炉总炉数\n' +
+      '日计划',
+    value: 567,
+    unit: '炉'
+  },
+  {
+    name: '转炉入炉铁水量\n' +
+      '日计划',
+    value: 200,
+    unit: '吨'
+  },
+  {
+    name: '转炉检修计划',
+    value: '未进行',
+    unit: ''
+  },
+  {
+    name: '钢产量日计划',
+    value: 300,
+    unit: '吨'
+  },
+  {
+    name: '转炉加入废钢总量',
+    value: 500,
+    unit: '吨'
+  },
+  {
+    name: '转炉实绩钢产量',
+    value: 100,
+    unit: '吨'
+  }
+])
+
+const mqhsjhTimeList = ref([
+  {
+    name: '兑铁',
+    inTime: '04-23 03:19',
+    outTime: '04-28 14:54',
+  },
+  {
+    name: '吹炼',
+    inTime: '04-23 03:19',
+    outTime: '04-28 14:54',
+  },
+  {
+    name: '出钢',
+    inTime: '04-23 03:19',
+    outTime: '04-28 14:54',
+  }
+])
+
+const scmbList = ref([
+  {
+    id: 1,
+    name: '1#转炉',
+    current: 20,
+    total: 30
+  },
+  {
+    id: 2,
+    name: '2#转炉',
+    current: 25,
+    total: 100
+  },
+  {
+    id: 3,
+    name: '3#转炉',
+    current: 4,
+    total: 29
+  }
+])
+
+// 自定义进度条颜色(可选)
+const customColor = '#409EFF';
+
+const percentage = (item) => {
+  return Math.round((item.current / item.total) * 100);
+};
+
+const yxzbList = ref([
+  {
+    name: '昨日LDG拒收时间',
+    value: 0.00,
+    unit: 'min'
+  },
+  {
+    name: '昨日吨钢回收量',
+    value: 86.08,
+    unit: 'm³/t'
+  },{
+    name: '昨日LDG混入累积量',
+    value: 2687.25,
+    unit: 'km³'
+  }
+
+])
+
+const ddtlResult = ref('')
+
+const route = useRoute() // 路由
+const message = useMessage() // 消息弹窗
+
+// 聊天对话
+const conversationListRef = ref()
+const activeConversationId = ref<number | null>(null) // 选中的对话编号
+const activeConversation = ref<ChatConversationVO | null>(null) // 选中的 Conversation
+const conversationInProgress = ref(false) // 对话是否正在进行中。目前只有【发送】消息时,会更新为 true,避免切换对话、删除对话等操作
+
+// 消息列表
+const messageRef = ref()
+const activeMessageList = ref<ChatMessageVO[]>([]) // 选中对话的消息列表
+const activeHistoryMessageList = ref<ChatMessageVO[]>([]) // 历史建议列表
+const activeMessageListLoading = ref<boolean>(false) // activeMessageList 是否正在加载中
+const activeMessageListLoadingTimer = ref<any>() // activeMessageListLoading Timer 定时器。如果加载速度很快,就不进入加载中
+// 消息滚动
+const textSpeed = ref<number>(50) // Typing speed in milliseconds
+const textRoleRunning = ref<boolean>(false) // Typing speed in milliseconds
+
+// 发送消息输入框
+const isComposing = ref(false) // 判断用户是否在输入
+const conversationInAbortController = ref<any>() // 对话进行中 abort 控制器(控制 stream 对话)
+const inputTimeout = ref<any>() // 处理输入中回车的定时器
+const prompt = ref<string>() // prompt
+const enableContext = ref<boolean>(false) // 是否开启上下文
+// 接收 Stream 消息
+const receiveMessageFullText = ref('')
+const receiveMessageDisplayedText = ref('')
+
+const tabPosition = ref('model')
+
+// 模型数据
+const modelData = ref()
+
+/** 历史建议 */
+const historyMessageRef = ref()
+const openHistoryMessage = async () => {
+  // 刷新 message 列表
+  await getHistoryMessageList()
+  historyMessageRef.value.open(activeHistoryMessageList.value, activeConversation.value)
+}
+
+// =========== 【聊天对话】相关 ===========
+
+/** 获取对话信息 */
+const getConversation = async (id: number | null) => {
+  if (!id) {
+    return
+  }
+  const conversation: ChatConversationVO = await ChatConversationApi.getChatConversationMy(id)
+  if (!conversation) {
+    return
+  }
+  activeConversation.value = conversation
+  activeConversationId.value = conversation.id
+}
+
+/**
+ * 点击某个对话
+ *
+ * @param conversation 选中的对话
+ * @return 是否切换成功
+ */
+const handleConversationClick = async (conversation: ChatConversationVO) => {
+  // 对话进行中,不允许切换
+  if (conversationInProgress.value) {
+    message.alert('对话中,不允许切换!')
+    return false
+  }
+
+  // 更新选中的对话 id
+  activeConversationId.value = conversation.id
+  activeConversation.value = conversation
+  // 刷新 message 列表
+  await getMessageList()
+  // 滚动底部
+  scrollToBottom(true)
+  // 清空输入框
+  // prompt.value = ''
+  return true
+}
+
+/** 删除某个对话*/
+const handlerConversationDelete = async (delConversation: ChatConversationVO) => {
+  // 删除的对话如果是当前选中的,那么就重置
+  if (activeConversationId.value === delConversation.id) {
+    await handleConversationClear()
+  }
+}
+/** 清空选中的对话 */
+const handleConversationClear = async () => {
+  // 对话进行中,不允许切换
+  if (conversationInProgress.value) {
+    message.alert('对话中,不允许切换!')
+    return false
+  }
+  activeConversationId.value = null
+  activeConversation.value = null
+  activeMessageList.value = []
+}
+
+// =========== 【消息列表】相关 ===========
+
+/** 获取消息 message 列表 */
+const getMessageList = async () => {
+  try {
+    if (activeConversationId.value === null) {
+      return
+    }
+    // Timer 定时器,如果加载速度很快,就不进入加载中
+    activeMessageListLoadingTimer.value = setTimeout(() => {
+      activeMessageListLoading.value = true
+    }, 60)
+
+    // 获取消息列表
+    activeMessageList.value = await ChatMessageApi.getEnergyChatMessageListByConversationId(
+      activeConversationId.value
+    )
+    if(activeMessageList.value.length != 0) {
+      prompt.value = activeMessageList.value[0].content
+    }
+
+    // 滚动到最下面
+    await nextTick()
+    await scrollToBottom()
+  } finally {
+    // time 定时器,如果加载速度很快,就不进入加载中
+    if (activeMessageListLoadingTimer.value) {
+      clearTimeout(activeMessageListLoadingTimer.value)
+    }
+    // 加载结束
+    activeMessageListLoading.value = false
+  }
+}
+
+/** 获取消息 message 列表 */
+const getHistoryMessageList = async () => {
+  if (activeConversationId.value === null) {
+    return
+  }
+  // 获取消息列表
+  activeHistoryMessageList.value = await ChatMessageApi.getChatMessageListByConversationId(
+    activeConversationId.value
+  )
+  if (activeHistoryMessageList.value.length > 0) {
+    activeHistoryMessageList.value.forEach((message: ChatMessageVO) => {
+      if(message.type != 'user') {
+        dealResult(message)
+      }
+    })
+    return activeHistoryMessageList.value
+  }
+}
+//处理调度推理结论
+const dealResult = (message: any) => {
+  const spliceText = message.content.includes("总结:") ? "总结:" : "结论:";
+  const regex = new RegExp('(\\n*)([\\s\\S]*?)(\\n*)' + spliceText + '([\\s\\S]*)');
+  const match = message.content.match(regex);
+  if(match) {
+    message.thinking = match[2];
+    message.conclusion = match[4]
+  }
+  return message
+}
+
+
+/**
+ * 消息列表
+ *
+ * 和 {@link #getMessageList()} 的差异是,把 systemMessage 考虑进去
+ */
+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 则展示它
+  if (activeConversation.value?.systemMessage) {
+    return [
+      {
+        id: 0,
+        type: 'system',
+        content: activeConversation.value.systemMessage
+      }
+    ]
+  }
+  return []
+})
+
+//处理调度推理结论及数据
+const dealResultAndData = (content: string) => {
+  const spliceText = content.includes("总结:") ? "总结:" : "结论:";
+// 创建同时捕获前后内容的正则表达式
+  const regex = new RegExp(`^([\\s\\S]*?)${spliceText}([\\s\\S]*)$`);
+  const match = content.match(regex);
+
+// 获取前面段落(优先返回匹配结果,若无匹配返回全文)
+  content = match ? match[1].trim() : content;
+// 已存在的后面段落获取方式
+  const result = match ? match[2].trim() : '';
+  ddtlResult.value = result
+  const dataRegex = /转炉煤气回收情况:\s*((?:.*?)(?=\n\d\.|\n|$))/s;
+  const dataMatch = content.match(dataRegex);
+  const dataContent = dataMatch ? dataMatch[1] : '';
+  modelData.value = extractRecoveryDetails(dataContent, 78, 90);
+  if(modelData.value.schedule.length === 3) {
+    initLDGHSLYCChart()
+  }
+  initLDGGRQSYCChart()
+  return content
+}
+
+const extractRecoveryDetails = (text, consume, gui, totalMinutes = 60) => {
+  // 正则表达式匹配转炉数据块
+  const furnaceBlocks = Array.from(text.matchAll(/(\d+#转炉.*?)(?=\d+#转炉|$)/gs))
+    .map(match => match[0]);
+
+  // 初始化数据结构
+  const state = reactive({
+    allSchedule: [],
+    result: {},
+    totalRecovery: Array.from({length: totalMinutes}, () => [0]),
+    tankLevels: Array.from({length: totalMinutes + 1}, () => [0])
+  });
+
+  // 解析每个转炉的数据
+  furnaceBlocks.forEach(block => {
+    // 提取转炉编号
+    const furnaceNum = parseInt(block.match(/(\d+)#/)[1]);
+
+    // 解析时间段和回收量
+    const periods = Array.from(block.matchAll(/第(\d+)-(\d+)[^\d]+?(\d+\.?\d*)km³/gs))
+      .map(match => [
+        parseInt(match[1]),
+        parseInt(match[2]),
+        parseFloat(match[3])
+      ]);
+
+    // 存储到结果
+    state.result[furnaceNum] = periods;
+
+    // 计算每分钟回收量
+    periods.forEach(([start, end, amount]) => {
+      const duration = end - start;
+      const perMin = amount / duration;
+      for(let t = start; t < end; t++) {
+        state.totalRecovery[t][0] += perMin;
+      }
+    });
+  });
+
+  // 生成0/1序列
+  furnaceBlocks.forEach(block => {
+    const schedule = new Array(totalMinutes).fill(0);
+    Array.from(block.matchAll(/第(\d+)-(\d+)/gs)).forEach(match => {
+      const start = parseInt(match[1]) - 1;
+      const end = parseInt(match[2]) - 1;
+      for(let i = start; i <= end; i++) {
+        if(i >= 0 && i < totalMinutes) schedule[i] = 1;
+      }
+    });
+    state.allSchedule.push(schedule);
+  });
+
+  // 计算柜位
+  let cumulative = 0;
+  const consumptionRate = consume / 60;
+  state.tankLevels = Array.from({length: totalMinutes + 1}, (_, t) => {
+    if(t > 0) cumulative += state.totalRecovery[t-1][0];
+    const consumed = consumptionRate * t;
+    return [Number((gui + cumulative - consumed).toFixed(2))];
+  });
+
+  // 格式化输出
+  return {
+    schedule: state.allSchedule,
+    result: state.result,
+    totalRecovery: state.totalRecovery.map(v => [Number(v[0].toFixed(2))]),
+    tankLevels: state.tankLevels
+  }
+}
+
+/** 处理删除 message 消息 */
+const handleMessageDelete = () => {
+  if (conversationInProgress.value) {
+    message.alert('回答中,不能删除!')
+    return
+  }
+  // 刷新 message 列表
+  getMessageList()
+}
+
+/** 处理 message 清空 */
+const handlerMessageClear = async () => {
+  if (!activeConversationId.value) {
+    return
+  }
+  try {
+    // 刷新 message 列表
+    activeMessageList.value = []
+  } catch {}
+}
+
+// =========== 【发送消息】相关 ===========
+
+/** 处理来自 keydown 的发送消息 */
+const handleSendByKeydown = async (event) => {
+  // 判断用户是否在输入
+  if (isComposing.value) {
+    return
+  }
+  // 进行中不允许发送
+  if (conversationInProgress.value) {
+    return
+  }
+  const content = prompt.value?.trim() as string
+  if (event.key === 'Enter') {
+    if (event.shiftKey) {
+      // 插入换行
+      prompt.value += '\r\n'
+      event.preventDefault() // 防止默认的换行行为
+    } else {
+      // 发送消息
+      await doSendMessage(content)
+      event.preventDefault() // 防止默认的提交行为
+    }
+  }
+}
+
+/** 处理来自【发送】按钮的发送消息 */
+const handleSendByButton = () => {
+  doSendMessage(prompt.value?.trim() as string)
+}
+
+/** 处理 prompt 输入变化 */
+const handlePromptInput = (event) => {
+  // 非输入法 输入设置为 true
+  if (!isComposing.value) {
+    // 回车 event data 是 null
+    if (event.data == null) {
+      return
+    }
+    isComposing.value = true
+  }
+  // 清理定时器
+  if (inputTimeout.value) {
+    clearTimeout(inputTimeout.value)
+  }
+  // 重置定时器
+  inputTimeout.value = setTimeout(() => {
+    isComposing.value = false
+  }, 400)
+}
+// TODO :是不是可以通过 @keydown.enter、@keydown.shift.enter 来实现,回车发送、shift+回车换行;主要看看,是不是可以简化 isComposing 相关的逻辑
+const onCompositionstart = () => {
+  isComposing.value = true
+}
+const onCompositionend = () => {
+  // console.log('输入结束...')
+  setTimeout(() => {
+    isComposing.value = false
+  }, 200)
+}
+
+/** 真正执行【发送】消息操作 */
+const doSendMessage = async (content: string) => {
+  // 校验
+  if (content.length < 1) {
+    message.error('发送失败,原因:内容为空!')
+    return
+  }
+  if (activeConversationId.value == null) {
+    message.error('还没创建对话,不能发送!')
+    return
+  }
+  // 发送请求时如果accessToken过期,无法中断请求,暂时增加请求前刷新token
+  authUtil.setToken(await refreshToken())
+  // 执行发送
+  await doSendMessageStream({
+    conversationId: activeConversationId.value,
+    content: content
+  } as ChatMessageVO)
+}
+
+/** 真正执行【发送】消息操作 */
+const doSendMessageStream = async (userMessage: ChatMessageVO) => {
+  // 创建 AbortController 实例,以便中止请求
+  conversationInAbortController.value = new AbortController()
+  // 标记对话进行中
+  conversationInProgress.value = true
+  // 设置为空
+  receiveMessageFullText.value = ''
+
+  try {
+    // 1.0 每次发送消息前先将消息记录chat message清空
+    await handlerMessageClear()
+    // 1.1 先添加两个假数据,等 stream 返回再替换
+    activeMessageList.value.push({
+      id: -1,
+      conversationId: activeConversationId.value,
+      type: 'user',
+      content: userMessage.content,
+      createTime: new Date()
+    } as ChatMessageVO)
+    activeMessageList.value.push({
+      id: -2,
+      conversationId: activeConversationId.value,
+      type: 'assistant',
+      content: '思考中...',
+      createTime: new Date()
+    } as ChatMessageVO)
+    // 1.2 滚动到最下面
+    await nextTick()
+    await scrollToBottom() // 底部
+    // 1.3 开始滚动
+    textRoll()
+
+    // 2. 发送 event stream
+    let isFirstChunk = true // 是否是第一个 chunk 消息段
+    await ChatMessageApi.sendChatMessageStream(
+      userMessage.conversationId,
+      userMessage.content,
+      conversationInAbortController.value,
+      enableContext.value,
+      async (res) => {
+        const { code, data, msg } = JSON.parse(res.data)
+        if (code !== 0) {
+          message.alert(`对话异常! ${msg}`)
+        }
+
+        // 如果内容为空,就不处理。
+        if (data.receive.content === '') {
+          return
+        }
+        // 首次返回需要添加一个 message 到页面,后面的都是更新
+        if (isFirstChunk) {
+          isFirstChunk = false
+          // 弹出两个假数据
+          activeMessageList.value.pop()
+          activeMessageList.value.pop()
+          // 更新返回的数据
+          activeMessageList.value.push(data.send)
+          activeMessageList.value.push(data.receive)
+        }
+        // debugger
+        receiveMessageFullText.value = receiveMessageFullText.value + data.receive.content
+        // 滚动到最下面
+        await scrollToBottom()
+      },
+      (error) => {
+        message.alert(`对话异常! ${error}`)
+        stopStream()
+      },
+      () => {
+        stopStream()
+      }
+    )
+  } catch {}
+}
+
+/** 停止 stream 流式调用 */
+const stopStream = async () => {
+  // tip:如果 stream 进行中的 message,就需要调用 controller 结束
+  if (conversationInAbortController.value) {
+    conversationInAbortController.value.abort()
+  }
+  // 设置为 false
+  conversationInProgress.value = false
+}
+
+/** 编辑 message:设置为 prompt,可以再次编辑 */
+const handleMessageEdit = (message: ChatMessageVO) => {
+  prompt.value = message.content
+}
+
+/** 刷新 message:基于指定消息,再次发起对话 */
+const handleMessageRefresh = (message: ChatMessageVO) => {
+  doSendMessage(message.content)
+}
+
+// ============== 【消息滚动】相关 =============
+
+/** 滚动到 message 底部 */
+const scrollToBottom = async (isIgnore?: boolean) => {
+  await nextTick()
+  if (messageRef.value) {
+    messageRef.value.scrollToBottom(isIgnore)
+  }
+}
+
+/** 自提滚动效果 */
+const textRoll = async () => {
+  let index = 0
+  try {
+    // 只能执行一次
+    if (textRoleRunning.value) {
+      return
+    }
+    // 设置状态
+    textRoleRunning.value = true
+    receiveMessageDisplayedText.value = ''
+    const task = async () => {
+      // 调整速度
+      const diff =
+        (receiveMessageFullText.value.length - receiveMessageDisplayedText.value.length) / 10
+      if (diff > 5) {
+        textSpeed.value = 10
+      } else if (diff > 2) {
+        textSpeed.value = 30
+      } else if (diff > 1.5) {
+        textSpeed.value = 50
+      } else {
+        textSpeed.value = 100
+      }
+      // 对话结束,就按 30 的速度
+      if (!conversationInProgress.value) {
+        textSpeed.value = 10
+      }
+
+      if (index < receiveMessageFullText.value.length) {
+        receiveMessageDisplayedText.value += receiveMessageFullText.value[index]
+        index++
+
+        // 更新 message
+        const lastMessage = activeMessageList.value[activeMessageList.value.length - 1]
+        lastMessage.content = receiveMessageDisplayedText.value
+        // 滚动到住下面
+        await scrollToBottom()
+        // 重新设置任务
+        timer = setTimeout(task, textSpeed.value)
+      } else {
+        // 不是对话中可以结束
+        if (!conversationInProgress.value) {
+          textRoleRunning.value = false
+          clearTimeout(timer)
+        } else {
+          // 重新设置任务
+          timer = setTimeout(task, textSpeed.value)
+        }
+      }
+    }
+    let timer = setTimeout(task, textSpeed.value)
+  } catch {}
+}
+
+const LDGHSLYCEhartContainer = ref();
+
+  // 生成未来60秒的时间标签(LDG回收量预测)
+const generateLDGHSLYCTimeLabels = () => {
+    const labels = [];
+    for (let i = 0; i < 60; i++) {
+      labels.push(i);
+    }
+    return labels;
+  };
+
+// 生成未来60秒和过去60秒的时间标签
+const generateLDGGRQSYCTimeLabels = () => {
+  const labels = [];
+  const now = new Date();
+  // 补零函数
+  const padZero = num => num.toString().padStart(2, '0')
+  for (let i = 0; i < 121; i++) {
+    const date = new Date(now.getTime() + (i-60) * 1000);
+    const formatted = `${date.getFullYear()}-${padZero(date.getMonth() + 1)}-${padZero(date.getDate())} ` +
+      `${padZero(date.getHours())}:${padZero(date.getMinutes())}:${padZero(date.getSeconds())}`;
+    labels.push(formatted);
+  }
+  return labels;
+};
+
+// 数据格式转换示例(LDG回收量预测)
+const seriesHSLYCDataConverter = () => {
+  const recovery = modelData.value.totalRecovery
+  const totalRecovery = []
+  recovery.forEach(item => {
+    totalRecovery.push(item[0])
+  })
+  return totalRecovery;
+};
+
+const seriesHSLYCDataSchedule = (type) => {
+  const max = LDGMaxTotalValue()
+  const schedule = modelData.value.schedule[type]
+  // 返回对象格式数据,包含原始值和基准值
+  const baseline = round(max, 0) + (6 - 2 * type)
+  return schedule.map(item => ({
+    value: item + baseline, // 显示值 = 原始值 + 基准值
+    original: item        // 原始值
+  }));
+};
+
+// 计算总回收量的最大值
+const LDGMaxTotalValue = () => {
+  const total = modelData.value.totalRecovery
+  let returnValue = computed(() => {
+    return Math.max(...total)
+  })
+  return returnValue.value
+};
+
+// 计算柜容预测趋势的最大值和最小值,用于上下界限显示
+const LDGComputedValue = (type) => {
+  const tank = modelData.value.tankLevels
+  let returnValue = 0;
+  if(type == 'max') {
+    returnValue = computed(() => {
+      return Math.max(...tank) + 20
+    })
+  } else if(type == 'min') {
+    returnValue = computed(() => {
+      return Math.min(...tank) - 60
+    })
+  } else if(type == 'average') {
+    returnValue = computed(() => {
+      let sum = 0
+      tank.forEach((item) => {
+        sum += item[0]
+      })
+      return (sum / tank.length).toFixed(0);
+    })
+  }
+  return returnValue.value
+};
+
+// 数据格式转换示例(LDG柜容趋势预测)
+const seriesGRQSYCDataConverter = () => {
+  const tank = modelData.value.tankLevels
+  const tankLevels = []
+  tank.forEach(item => {
+    tankLevels.push(item[0])
+  })
+  return tankLevels;
+};
+
+// 图表配置
+const initLDGHSLYCChart = () => {
+  const LDGHSLYCChart = echarts.init(LDGHSLYCEhartContainer.value);
+  const option = {
+    tooltip: {
+      trigger: 'axis',
+      formatter: function (params) {
+        let tooltipContent = params[0].name + '<br/>'; // 时间标签
+        params.forEach(param => {
+          const seriesName = param.seriesName;
+          let originalValue;
+
+          // 判断是否为转炉系列
+          if (seriesName.includes('转炉')) {
+            // 直接从数据项中获取原始值
+            originalValue = param.data.original;
+            tooltipContent += `
+              ${param.marker}
+              ${seriesName}: ${originalValue.toFixed(0)}<br/>
+            `;
+          } else {
+            // 总回收量直接显示值
+            originalValue = param.value;
+            tooltipContent += `
+              ${param.marker}
+              ${seriesName}: ${originalValue.toFixed(2)}<br/>
+            `;
+          }
+        });
+        return tooltipContent;
+      }
+    },
+    grid: {
+      left: 25,
+      right: 25,
+      bottom: 10,
+      top: 30,
+      containLabel: true
+    },
+    legend: {
+      top: 10,
+      right: 10,
+      data: ['1#转炉', '2#转炉', '3#转炉', '总回收量'],
+      textStyle: {
+        color: '#8FD6FE'
+      },
+      itemWidth: 20, // 图例标记的宽度
+      itemHeight: 0, // 图例标记的高度,设为较小值使其更像线条
+    },
+    xAxis: {
+      type: 'category',
+      boundaryGap: false,
+      data: generateLDGHSLYCTimeLabels(),
+      axisTick: {
+        show: false
+      },
+      axisLine: {
+        lineStyle: {
+          color: '#C7E7FF'
+        }
+      },
+      axisLabel: {
+        color: '#fff'
+      }
+    },
+    yAxis: {
+      type: 'value',
+      min: 0,
+      axisLine: {
+        show: true,
+        lineStyle: {
+          color: '#C7E7FF',
+        }
+      },
+      axisTick: {
+        show: false
+      },
+      axisLabel: {
+        show: false
+      },
+      splitLine: {
+        show: false // Y轴网格线
+      }
+    },
+    series: [
+      {
+        name: '1#转炉',
+        type: 'line',
+        step: 'start',
+        data: seriesHSLYCDataSchedule(0),
+        showSymbol: false, // 取消数据点
+        emphasis: {
+          focus: 'series'
+        },
+        lineStyle: {
+          color: '#FF7686' // 粉色
+        }
+      },
+      {
+        name: '2#转炉',
+        type: 'line',
+        step: 'start',
+        data: seriesHSLYCDataSchedule(1),
+        showSymbol: false, // 取消数据点
+        emphasis: {
+          focus: 'series'
+        },
+        lineStyle: {
+          color: '#49FFD3' // 绿色
+        }
+      },
+      {
+        name: '3#转炉',
+        type: 'line',
+        step: 'start',
+        showSymbol: false, // 取消数据点
+        data: seriesHSLYCDataSchedule(2),
+        color: '#FAC858',
+        emphasis: {
+          focus: 'series'
+        },
+        lineStyle: {
+          color: '#FFAE81' // 橙色
+        },
+      },
+      {
+        name: '总回收量',
+        type: 'line',
+        step: 'start',
+        showSymbol: false, // 取消数据点
+        data: seriesHSLYCDataConverter(),
+        emphasis: {
+          focus: 'series'
+        },
+        lineStyle: {
+          color: 'white'
+        },
+      }
+    ]
+  };
+  LDGHSLYCChart.setOption(option);
+};
+
+const LDGGRYCEhartContainer = ref();
+
+/** 带预测的转炉数据阶梯图配置 */
+const initLDGGRQSYCChart = () => {
+  const labels = generateLDGGRQSYCTimeLabels()
+  const tankLevels = seriesGRQSYCDataConverter()
+  const fullData = [];
+  for(let i = 0; i < 121; i ++ ) {
+    let value = 90
+    if(i >= 60) {
+      value = tankLevels[i - 60]
+    }
+    fullData.push([labels[i], value]);
+  }
+
+  const splitTime = formatToDateTime(new Date()); // 分割时间点(当前时间)
+  const upperLimit = LDGComputedValue('max');
+  const lowerLimit = LDGComputedValue('min');
+  const averageValue = LDGComputedValue('average');
+
+  // 分割真实数据和预测数据
+  const splitIndex = fullData.findIndex(item => item[0] === splitTime);
+  const realData = fullData.slice(0, splitIndex + 1);
+  const predictData = fullData.slice(splitIndex);
+
+  // 创建纯净的上下限数据数组
+  const upperLimitData = [
+    [labels[0], upperLimit],
+    [labels[labels.length - 1], upperLimit]
+  ];
+
+  const lowerLimitData = [
+    [labels[0], lowerLimit],
+    [labels[labels.length - 1], lowerLimit]
+  ];
+
+  const LDGGRQSYCChart = echarts.init(LDGGRYCEhartContainer.value);
+  const option = {
+    grid: {
+      left: 0,
+      right: 0,
+      bottom: 10,
+      top: 20,
+      containLabel: true
+    },
+    tooltip: {
+      trigger: 'axis'
+    },
+    xAxis: {
+      type: 'time',
+      axisLabel: {
+        formatter: (value) => {
+          return echarts.time.format(value, '{mm}:{ss}', false)
+        },
+        color: '#C7E7FF'
+      },
+      axisLine: {
+        show: true,
+        lineStyle: {
+          color: '#C7E7FF'
+        }
+      },
+      axisTick: {
+        show: false
+      },
+      splitLine: {
+        show: false
+      }
+    },
+    yAxis: {
+      type: 'value',
+      min: 0,
+      max: LDGComputedValue('max') + 30,
+      axisLine: {
+        show: true,
+        lineStyle: {
+          color: '#C7E7FF'
+        }
+      },
+      axisTick: {
+        show: false
+      },
+      splitLine: {
+        show: false
+      }
+    },
+    series: [
+      // 真实数据(实线)
+      {
+        type: 'line',
+        data: realData,
+        smooth: true,
+        symbol: 'none',
+        lineStyle: { color: '#95E6FF' },
+        markLine: {
+          symbol: ['none', 'none'],
+          label: {
+            show: false
+          },
+          data: [{
+            xAxis: splitTime,
+            lineStyle: {
+              type: 'solid',
+              color: '#5DFF9E',
+              width: 1
+            }
+          }]
+        }
+      },
+      // 预测数据(虚线)
+      {
+        type: 'line',
+        data: predictData,
+        smooth: true,
+        symbol: 'none',
+        lineStyle: {
+          type: 'dashed',
+          color: '#E76666',
+          dashOffset: 5
+        }
+      },
+      // 上限填充
+      {
+        type: 'line',
+        data: upperLimitData,
+        lineStyle: { width: 0 },
+        areaStyle: {
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            { offset: 0, color: 'rgba(255,0,0,0.2)' },   // 顶部颜色
+            { offset: 1, color: 'rgba(255,0,0,0.5)' }      // 底部颜色(到upperLimit)
+          ]),
+          origin: 'end' // 关键:从线条向上填充
+        },
+        silent: true
+      },
+      // 下限填充
+      {
+        type: 'line',
+        data: lowerLimitData,
+        lineStyle: { width: 0 },
+        areaStyle: {
+          color: new echarts.graphic.LinearGradient(0, 1, 0, 0, [
+            { offset: 0, color: 'rgba(0,0,255,0.2)' },
+            { offset: 1, color: 'rgba(0,0,255,0.5)' }
+          ])
+        },
+        silent: true
+      },
+      // 辅助线系列
+      {
+        type: 'line',
+        symbol: 'none', // 关闭辅助线系列自身的数据点
+        markLine: {
+          symbol: ['none', 'none'], // 全局隐藏标记点
+          data: [
+            // 上限辅助线配置部分
+            {
+              yAxis: upperLimit,
+              symbol: 'none',
+              label: { show: false },
+              emphasis: {
+                // 这里添加让高亮时标记点也不显示
+                itemStyle: {
+                  borderWidth: 0,
+                  borderColor: 'transparent',
+                  color: 'transparent'
+                },
+                label: { show: false }
+              },
+              lineStyle: { color: 'rgba(255,0,0)', width: 1, type: 'solid' }
+            },
+            // 下限辅助线配置部分
+            {
+              yAxis: lowerLimit,
+              symbol: 'none',
+              label: { show: false },
+              emphasis: {
+                // 这里添加让高亮时标记点也不显示
+                itemStyle: {
+                  borderWidth: 0,
+                  borderColor: 'transparent',
+                  color: 'transparent'
+                },
+                label: { show: false }
+              },
+              lineStyle: { color: 'rgba(0,0,255)', width: 1, type: 'solid' }
+            },
+            {
+              yAxis: averageValue,
+              label: { show: false },
+              lineStyle: { type: 'dashed', color: 'rgba(0,194,255,0.52)', width: 1 }
+            }
+          ]
+        }
+      }
+    ]
+  };
+  LDGGRQSYCChart.setOption(option);
+}
+
+const isFullscreen = ref(false);
+
+// 核心:统一检测全屏状态
+const updateFullscreenStatus = () => {
+  // 同时检测 API 全屏和 F11 全屏(近似)
+  isFullscreen.value = !!document.fullscreenElement || window.outerHeight === screen.height;
+};
+
+// 监听全屏 API 变化
+const handleFullscreenChange = () => {
+  updateFullscreenStatus();
+};
+
+// 监听 F11 按键(兜底)
+const handleKeyPress = (e) => {
+  if (e.key === 'F11') {
+    e.preventDefault(); // 尝试阻止默认行为(部分浏览器允许)
+    setTimeout(updateFullscreenStatus, 100); // 延迟确保状态更新
+  }
+};
+
+// 监听窗口大小变化(F11 全屏会触发)
+const handleResize = () => {
+  updateFullscreenStatus();
+};
+
+// 切换全屏(API 方式)
+const toggleFullscreen = async () => {
+  if (!document.fullscreenElement) {
+    await document.documentElement.requestFullscreen();
+  } else {
+    await document.exitFullscreen();
+  }
+};
+
+//初始化全屏信息
+const initFullscreen = async () => {
+  // Fullscreen API 事件
+  const events = ['fullscreenchange', 'webkitfullscreenchange', 'msfullscreenchange'];
+  events.forEach(event => {
+    document.addEventListener(event, handleFullscreenChange);
+  });
+
+  // 窗口变化 + 键盘事件兜底
+  window.addEventListener('resize', handleResize);
+  window.addEventListener('keydown', handleKeyPress);
+
+  // 初始状态检测
+  updateFullscreenStatus();
+}
+
+/** 初始化 **/
+onMounted(async () => {
+  await initFullscreen()
+  // 如果有 conversationId 参数,则默认选中
+  if (route.query.conversationId) {
+    const id = route.query.conversationId as unknown as number
+    activeConversationId.value = id
+    await getConversation(id)
+  }
+
+  // 获取列表数据
+  activeMessageListLoading.value = true
+  await getMessageList()
+})
+
+// 清理监听
+onUnmounted(() => {
+  const events = ['fullscreenchange', 'webkitfullscreenchange', 'msfullscreenchange'];
+  events.forEach(event => {
+    document.removeEventListener(event, handleFullscreenChange);
+  });
+  window.removeEventListener('resize', handleResize);
+  window.removeEventListener('keydown', handleKeyPress);
+});
+
+</script>
+
+<style lang="scss" scoped>
+.gas-scheduling-container {
+  display: flex;
+  font-family: Microsoft YaHei, Microsoft YaHei;
+  .fullscreen-btn {
+    background-color: transparent;
+    border: 1px solid rgba(115, 196, 255, 0.5);
+    position: fixed;
+    margin-top: 3.5%;
+    margin-left: 28%;
+    color: rgba(115, 196, 255, 0.8);
+    font-size: 12px;
+  }
+  /* 背景层容器 */
+  &::before {
+    content: '';
+    position: fixed;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    z-index: -1; /* 置于内容层下方 */
+    background:
+      url("@/assets/ai/zhuanlu/bg.png") center/cover no-repeat,
+      linear-gradient(to bottom, #0a1633dd, #0a1633dd); /* 叠加深色遮罩 */
+    pointer-events: none; /* 防止遮挡交互 */
+  }
+  .gas-scheduling-left {
+    width: 23%;
+    height: 89%;
+    margin: 2rem 2rem 0 1.8rem;
+    z-index: 1;
+    background-color: rgba(0, 0, 0, 0); /* 透明背景 */
+    .data1-item {
+      height: 2.6rem;
+      width: 42%;
+      display: inline-block;
+      margin: 8px 10px ;
+      background: url("@/assets/ai/zhuanlu/data_bg1.png") center/cover no-repeat;
+    }
+    .data2-item {
+      height: 30px;
+      width: 42%;
+      display: inline-block;
+      margin: 6px 8px;
+      background: url("@/assets/ai/zhuanlu/data_bg2.png") center/cover no-repeat;
+    }
+    .content {
+      margin-left: 16px;
+      .value {
+        span:nth-child(1){
+          height: 19px;
+          font-weight: bold;
+          font-size: 16px;
+          color: #FFAE81;
+          line-height: 19px;
+        }
+        span:nth-child(2) {
+          height: 16px;
+          font-weight: 400;
+          font-size: 12px;
+          color: #C7E7FF;
+        }
+      }
+      .name {
+        height: 16px;
+        font-weight: 400;
+        font-size: 12px;
+        color: #C7E7FF;
+      }
+    }
+    .content2 {
+      display: flex;
+      width: 11rem;
+      margin-left: 10px;
+      .name {
+        width: 95px;
+        height: 18px;
+        font-weight: 400;
+        font-size: 14px;
+        color: #C7E7FF;
+      }
+      .value {
+        margin-left: auto;
+        margin-right: 5px;
+        span:nth-child(1){
+          height: 15px;
+          font-weight: bold;
+          font-size: 15px;
+          color: #FFAE81;
+          line-height: 15px;
+        }
+        span:nth-child(2) {
+          height: 15px;
+          font-weight: 400;
+          font-size: 12px;
+          color: #C7E7FF;
+        }
+      }
+    }
+    #mqhsssxx {
+      .title {
+        height: 2rem;
+        background:
+          url("@/assets/ai/zhuanlu/mqhsssxx_title.png") center/cover no-repeat; /* 叠加深色遮罩 */
+      }
+    }
+    #tsxx {
+      .title {
+        margin-top: 5px;
+        height: 2rem;
+        background:
+          url("@/assets/ai/zhuanlu/tsxx_title.png") center/cover no-repeat; /* 叠加深色遮罩 */
+      }
+    }
+    #zlxx {
+      .title {
+        margin-top: 5px;
+        height: 2rem;
+        background:
+          url("@/assets/ai/zhuanlu/zlxx_title.png") center/cover no-repeat; /* 叠加深色遮罩 */
+      }
+      :deep(.el-table) {
+        font-weight: 400;
+        font-size: 14px;
+        color: #DBEEFF;
+        text-align: left;
+        background-color: transparent !important;
+      }
+
+      .transparent-table {
+        margin-top: 14px;
+      }
+
+      /* 行样式 */
+      :deep(.el-table .el-table__body tr) {
+        border: 1px solid rgba(16, 198, 255, 0.3);
+        margin-bottom: 4px;
+        border-radius: 4px;
+        overflow: hidden;
+        background-color: transparent;
+      }
+
+      /* 行内容样式 */
+      :deep(.el-table .el-table__body td) {
+        padding: 4px 0;
+        color: #FFAA5D;
+      }
+
+      /* 列头样式 */
+      :deep(.el-table th) {
+        color: #8FD6FE;
+        background:
+          url("@/assets/ai/zhuanlu/table_header_bg.png") center/cover no-repeat !important; /* 叠加深色遮罩 */
+        border: none;
+      }
+
+      :deep(.el-table tr) {
+        background: transparent;
+      }
+
+      /* 行头样式 */
+      :deep(.el-table .el-table__body td:first-child) {
+        color: #8FD6FE;
+        font-weight: 500;
+        background-color: transparent;
+      }
+
+      :deep(.el-table .el-table__body tr:nth-child(even) td) {
+        background-color: rgba(0, 194, 255, 0.1);
+      }
+
+      :deep(.el-table .el-table__body tr:nth-child(odd) td) {
+        background-color: rgba(0, 194, 255, 0.2);
+      }
+
+      /* 移除表格内部边框线 */
+      :deep(.el-table td, .el-table th.is-leaf) {
+        border-bottom: none;
+      }
+
+      :deep(.el-table .el-table__inner-wrapper:before) {
+        background-color: transparent !important;
+      }
+    }
+    #mqxhssxx {
+      .title {
+        height: 30px;
+        margin-top: 15px;
+        background:
+          url("@/assets/ai/zhuanlu/mqxhssxx_title.png") center/cover no-repeat; /* 叠加深色遮罩 */
+      }
+    }
+  }
+
+  .gas-scheduling-center {
+    margin-top: 2.6rem;
+    width: 55.5rem !important;
+    .mode-switch {
+      margin-top: 20px;
+      margin-left: 43rem;
+      font-weight: 400;
+      font-size: 14px;
+      color: #73C4FF;
+      width: 40%;
+      /* 必须穿透到组件内部层级 */
+      :deep(.custom-radio-group) {
+        --el-color-primary: red !important; /* 强制修改主题色变量 */
+      }
+
+      /* 所有按钮基础样式 */
+      :deep(.custom-radio-group .el-radio-button__inner) {
+        background: black !important; /* 未选中黑色背景 */
+        border: 1px solid rgba(173, 216, 230, 0.3) !important; /* 蓝色边框 */
+        font-weight: 300;
+        font-size: 14px;
+        color: #DBEEFF;
+        transition: all 0.3s;
+      }
+
+      /* 选中状态 */
+      :deep(.custom-radio-group .el-radio-button.is-active .el-radio-button__inner) {
+        background: #b92220 !important;
+        color: gold !important;
+        font-weight: bolder;
+      }
+
+      /* 强制覆盖原生选中状态 */
+      :deep(.custom-radio-group .el-radio-button__orig-radio:checked + .el-radio-button__inner) {
+        background: inherit !important; /* 继承上层样式 */
+      }
+    }
+
+    // 头部
+    .detail-container {
+      margin-left: 5px;
+      background-color: rgba(0, 0, 0, 0); /* 透明背景 */
+      z-index: 1;
+      .header {
+        display: flex;
+        flex-direction: row;
+        align-items: center;
+        justify-content: space-between;
+        box-shadow: 0 0 0 0 #dcdfe6;
+
+        .title {
+          font-size: 18px;
+          font-weight: bold;
+        }
+
+        .btns {
+          display: flex;
+          width: 300px;
+          flex-direction: row;
+          justify-content: flex-end;
+          //justify-content: space-between;
+
+          .btn {
+            padding: 10px;
+          }
+        }
+      }
+    }
+
+    // main 容器
+    .main-container {
+      margin: 0;
+      padding: 0;
+      position: relative;
+      height: 30rem;
+
+      .message-container {
+        position: absolute;
+        top: 0;
+        bottom: 0;
+        left: 0;
+        right: 0;
+        overflow-y: hidden;
+        padding: 0;
+        margin: 0;
+      }
+      .title {
+        background: url("@/assets/ai/zhuanlu/think_bg.png") center/cover no-repeat;
+        width: auto;
+        height: 1.8rem;
+        font-weight: 400;
+        font-size: 14px;
+        color: #8FD6FE;
+        text-align: left;
+        font-style: normal;
+        text-transform: none;
+        span {
+          margin-left: 30px;
+        }
+      }
+    }
+
+    .result-container-title {
+      margin-top: 15px;
+      background: url("@/assets/ai/zhuanlu/ddtljl_result_title.png") center/cover no-repeat;
+      width: auto;
+      height: 1.8rem;
+      font-weight: 400;
+      font-size: 14px;
+      color: #8FD6FE;
+      text-align: left;
+      font-style: normal;
+      text-transform: none;
+      span {
+        margin-left: 30px;
+      }
+      .history-button {
+        color: rgba(143, 214, 254);
+        font-weight: bold;
+        float: right;
+        margin-right: 5px;
+        background-color: rgba(0, 255, 255, 0.1);
+        border-radius: 3px;
+        padding: 0 5px;
+        border: none;
+        cursor: pointer
+      }
+      .history-button:hover {
+        color: rgba(143, 214, 254, 0.5);
+      }
+    }
+    // main 容器
+    .result-container {
+      padding: 0;
+      position: relative;
+      width: 100%;
+      /* 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); }
+      }
+      .result {
+        margin-top: 10px;
+        margin-left: 18px;
+        border-left: 1px solid #73C4FF;
+        .result-content {
+          width: 53rem;
+          height: 6rem;
+          margin-left: 10px;
+          font-weight: 400;
+          font-size: 14px;
+          background-color: rgba(219,238,255,0);
+          line-height: 21px;
+          text-align: left;
+          font-style: normal;
+          text-transform: none;
+          border: 0;
+          color: rgba(219,238,255,0.6);
+        }
+        .result-content:focus {
+          outline: none;
+        }
+      }
+    }
+
+    // 输入框
+    .input-container {
+      display: flex;
+      flex-direction: column;
+      height: auto;
+      margin: 0;
+      padding: 0;
+      overflow-y: auto;        /* 垂直方向溢出时显示滚动条 */
+      overflow-x: hidden;      /* 水平方向隐藏滚动条 */
+      /* 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); }
+      }
+
+      .prompt-from {
+        display: flex;
+        flex-direction: column;
+        margin: 10px 20px 20px 20px;
+        padding: 9px 10px;
+        width: 53.9rem;
+        background: rgba(115,196,255,0.05);
+        border-radius: 4px 4px 4px 4px;
+        border: 1px solid #73C4FF;
+      }
+
+      .prompt-input {
+        width: 53rem;
+        height: 11rem;
+        font-weight: 400;
+        font-size: 14px;
+        background-color: rgba(219,238,255,0);
+        line-height: 21px;
+        text-align: left;
+        font-style: normal;
+        text-transform: none;
+        border: 0;
+        color: rgba(219,238,255,0.6);
+      }
+
+      .prompt-input:focus {
+        outline: none;
+      }
+
+      .prompt-btns {
+        display: flex;
+        justify-content: space-between;
+        padding-bottom: 0;
+        padding-top: 5px;
+        .content {
+          /* 默认状态 */
+          .el-button {
+            background: transparent !important;
+            border-color: rgba(115, 196, 255, 0.5);
+            color: #73C4FF;
+            border-radius: 15px !important;
+          }
+
+          /* 上下文图标处理 */
+          .content-icon {
+            color: blue;  /* 图标颜色 */
+            font-size: 18px;
+            margin-right: 10px;
+            background: url("@/assets/ai/zhuanlu/content.png");
+            vertical-align: middle;
+          }
+
+          /* 选中状态 */
+          .active-button {
+            background: #409eff !important;
+            border-color: #409eff !important;
+            color: white !important;
+            .content-icon {
+              background: url("@/assets/ai/zhuanlu/content_select.png");
+              vertical-align: middle;
+            }
+          }
+
+          /* 按钮组间距处理 */
+          .button-group .el-button {
+            margin-left: 0;
+            border-radius: 4px;
+          }
+
+          /* 悬停效果 */
+          .el-button:not(.active-button):hover {
+            border-color: rgba(115,196,255,0.5);
+            color: #409eff;
+          }
+        }
+        .message {
+          /* 所有状态通用透明背景 */
+          :deep(.el-button) {
+            background: rgba(73, 254, 210, 0.8) !important;
+            border-color: currentColor; /* 保持与文字同色 */
+            font-family: Alimama ShuHeiTi, Alimama ShuHeiTi;
+            font-weight: bold;
+            font-size: 16px;
+            color: #123C4E;
+            clip-path: polygon(
+                0 0,
+                100% 0,
+                100% 100%,
+                10px 100%,          /* 右下方向留出10px */
+                0 calc(100% - 10px) /* 左上方向留出10px */
+            );
+            position: relative;
+            padding-left: 15px; /* 增加右侧留白 */
+          }
+
+          /* 悬停状态 */
+          :deep(.el-button:hover) {
+            background: rgba(73, 254, 210, 0.6) !important; /* 轻微悬停反馈 */
+          }
+
+          /* 点击状态 */
+          :deep(.el-button:active) {
+            background: rgba(73, 254, 210, 1) !important;
+          }
+
+          /* 禁用状态 */
+          :deep(.el-button.is-disabled) {
+            opacity: 0.6;
+            background: transparent !important;
+          }
+
+          /* 核心样式覆盖 */
+          :deep(.el-switch__core) {
+            background: transparent !important;
+            border-radius: 0 0 15px 0 !important;
+            border: none !important;
+            height: 40px !important;
+          }
+
+          /* 按钮内容容器 */
+          .button-content {
+            display: flex;
+            align-items: center;
+            padding: 0 15px;
+            height: 100%;
+          }
+        }
+      }
+    }
+  }
+
+  .gas-scheduling-right {
+    width: 22%;
+    height: 89%;
+    margin-left: 4.3rem;
+    margin-top: 2.8rem;
+    z-index: 1;
+    background-color: rgba(0, 0, 0, 0); /* 透明背景 */
+
+    #ldghslyc {
+      .title {
+        height: 1.8rem;
+        background:
+          url("@/assets/ai/zhuanlu/ldghslyc_title.png") center/cover no-repeat; /* 叠加深色遮罩 */
+      }
+    }
+    #ldggrqsyc {
+      .title {
+        height: 1.8rem;
+        background:
+          url("@/assets/ai/zhuanlu/ldggrqsyc_title.png") center/cover no-repeat; /* 叠加深色遮罩 */
+      }
+    }
+    #mqhsjhxx {
+      .title {
+        height: 1.8rem;
+        background:
+          url("@/assets/ai/zhuanlu/mqhsjhxx_title.png") center/cover no-repeat; /* 叠加深色遮罩 */
+      }
+      .time-content {
+        display: inline-block;
+        width: 32%;
+        height: 2.9rem;
+        margin: 10px 0 5px 5px;
+        font-weight: 400;
+        font-size: 12px;
+        color: #C7E7FF;
+        .time-content-item {
+          display: flex;
+          .name {
+            width: 1.6rem;
+            height: 3rem;
+            padding: 8px 3px;
+            background: linear-gradient( 180deg, rgba(115,196,255,0.1) 0%, rgba(255,136,69,0.1) 100%);
+            border-radius: 2px 2px 2px 2px;
+            border: 1px solid rgba(255,255,255,0.15);
+            opacity: 0.9;
+          }
+          .name span {
+            height: 1.8rem;
+            font-family: Alimama ShuHeiTi, Alimama ShuHeiTi;
+            font-weight: bold;
+            font-size: 14px;
+            color: #C7E7FF;
+            line-height: 15px;
+            text-align: left;
+            font-style: normal;
+            text-transform: none;
+          }
+          .time {
+            width: 105px;
+            height: 38px;
+            margin: 4px;
+            display: inline-block;
+            .in-pot{
+              width: 4px;
+              height: 4px;
+              background: #49FFD3;
+              border-radius: 80px 80px 80px 80px;
+              margin-top: 7px;
+              margin-right: 3px;
+            }
+            .out-pot{
+              width: 4px;
+              height: 4px;
+              background: #FFAE81;
+              border-radius: 80px 80px 80px 80px;
+              margin-top: 7px;
+              margin-right: 3px;
+            }
+          }
+          .time > div {
+            float: left;
+            height: 25px;
+          }
+        }
+      }
+      .data2-item {
+        height: 2.8rem;
+        width: 45%;
+        display: inline-block;
+        margin: 6px 8px;
+        background: url("@/assets/ai/zhuanlu/data_bg3.png") no-repeat;
+      }
+      .content {
+        display: flex;
+        width: 192px;
+        margin-left: 10px;
+
+        .name {
+          width: 95px;
+          height: 18px;
+          font-weight: 400;
+          font-size: 14px;
+          color: #C7E7FF;
+        }
+
+        .value {
+          margin-top: 10px;
+          margin-left: auto;
+          margin-right: 5px;
+          span:nth-child(1) {
+            height: 15px;
+            font-weight: bold;
+            font-size: 15px;
+            color: #FFAE81;
+            line-height: 15px;
+          }
+
+          span:nth-child(2) {
+            height: 15px;
+            font-weight: 400;
+            font-size: 12px;
+            color: #C7E7FF;
+          }
+        }
+      }
+    }
+    #scmbyyxzb {
+      margin-top: 10px;
+      .title {
+        height: 1.8rem;
+        background:
+          url("@/assets/ai/zhuanlu/scmbyyxzb_title.png") center/cover no-repeat; /* 叠加深色遮罩 */
+      }
+      .little-title {
+        font-size: 14px;
+        color: #8FD6FE;
+        margin: 10px;
+      }
+      .data3-item {
+        height: 5.2rem;
+        width: 30%;
+        display: inline-block;
+        margin: 0 6px 6px 6px;
+        background: url("@/assets/ai/zhuanlu/data_bg4.png") center/cover no-repeat;
+        .name {
+          font-family: Alimama ShuHeiTi, Alimama ShuHeiTi;
+          font-weight: bold;
+          font-size: 16px;
+          color: #FFFFFF;
+          margin-left: 3px;
+        }
+        .value {
+          color: #DBEEFF;
+          font-size: 14px;
+          margin-left: 3px;
+        }
+        .value-content {
+          color: #8FD6FE;
+          font-size: 12px;
+          margin-left: 3px;
+        }
+      }
+      .zb-content {
+        display: inline-block;
+        .item {
+          float: left;
+        }
+        .data4-item {
+          height: 2.2rem;
+          width: 7.8rem;
+          display: inline-block;
+          margin: 5px 0;
+          .content {
+            margin-left: 16px;
+            .value {
+              text-align: center;
+              span:nth-child(1){
+                height: 19px;
+                font-weight: bold;
+                font-size: 16px;
+                color: #FFAE81;
+                line-height: 19px;
+              }
+              span:nth-child(2) {
+                height: 16px;
+                font-weight: 400;
+                font-size: 12px;
+                color: #C7E7FF;
+              }
+            }
+            .name {
+              font-weight: 400;
+              font-size: 12px;
+              color: #C7E7FF;
+            }
+          }
+          .content div {
+            height: 25px;
+          }
+        }
+        .left-label {
+          width: 1.2rem;
+          height: 3.5rem;
+          background: url("@/assets/ai/zhuanlu/left_label.png") center/cover no-repeat;
+        }
+        .right-label {
+          width: 1.2rem;
+          height: 3.5rem;
+          background: url("@/assets/ai/zhuanlu/right_label.png") center/cover no-repeat;
+        }
+      }
+    }
+  }
+}
+
+/* 背景颜色修改 */
+:deep(.el-progress .el-progress-bar .el-progress-bar__outer) {
+  width: 90%;
+  margin: 5px 0 2px 5px;
+  background-color: rgba(64, 158, 255, 0.3) !important;
+  border-radius: 2px;
+}
+
+/* 进度条填充颜色 */
+:deep(.el-progress-bar__inner) {
+  border-radius: 2px;
+  transition: all 0.4s cubic-bezier(0.08, 0.82, 0.17, 1);
+}
+
+</style>
diff --git a/src/views/ai/questionparamsetting/QuestionParamSettingForm.vue b/src/views/ai/questionparamsetting/QuestionParamSettingForm.vue
new file mode 100644
index 0000000..869dc3f
--- /dev/null
+++ b/src/views/ai/questionparamsetting/QuestionParamSettingForm.vue
@@ -0,0 +1,117 @@
+<template>
+  <div class="app-container">
+    <!-- 对话框(添加 / 修改) -->
+    <el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="45%" v-dialogDrag
+               append-to-body>
+      <el-form ref="formRef" :model="formData" :rules="formRules" v-loading="formLoading"
+               label-width="100px">
+        <el-form-item label="问题模板id" prop="templateId">
+          <el-input v-model="formData.templateId" placeholder="请输入问题模板id"/>
+        </el-form-item>
+        <el-form-item label="key" prop="settingKey">
+          <el-input v-model="formData.settingKey" placeholder="请输入key"/>
+        </el-form-item>
+        <el-form-item label="参数名称" prop="settingName">
+          <el-input v-model="formData.settingName" placeholder="请输入参数名称"/>
+        </el-form-item>
+        <el-form-item label="参数默认值" prop="settingValue">
+          <el-input v-model="formData.settingValue" placeholder="请输入参数默认值"/>
+        </el-form-item>
+        <el-form-item label="排序" prop="sort">
+          <el-input v-model="formData.sort" placeholder="请输入排序"/>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm" :disabled="formLoading">确 定</el-button>
+        <el-button @click="dialogVisible = false">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import * as QuestionParamSettingApi from '@/api/ai/questionparamsetting';
+
+export default {
+  name: "QuestionParamSettingForm",
+  components: {},
+  data() {
+    return {
+      // 弹出层标题
+      dialogTitle: "",
+      // 是否显示弹出层
+      dialogVisible: false,
+      // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+      formLoading: false,
+      // 表单参数
+      formData: {
+        id: undefined,
+        templateId: undefined,
+        settingKey: undefined,
+        settingName: undefined,
+        settingValue: undefined,
+        sort: undefined,
+      },
+      // 表单校验
+      formRules: {
+        templateId: [{required: true, message: '问题模板id不能为空', trigger: 'blur'}],
+      },
+    };
+  },
+  methods: {
+    /** 打开弹窗 */
+    async open(id) {
+      this.dialogVisible = true;
+      this.reset();
+      // 修改时,设置数据
+      if (id) {
+        this.formLoading = true;
+        try {
+          const res = await QuestionParamSettingApi.getQuestionParamSetting(id);
+          this.formData = res.data;
+          this.title = "修改大模型问题设置参数";
+        } finally {
+          this.formLoading = false;
+        }
+      }
+      this.title = "新增大模型问题设置参数";
+    },
+    /** 提交按钮 */
+    async submitForm() {
+      // 校验主表
+      await this.$refs["formRef"].validate();
+      this.formLoading = true;
+      try {
+        const data = this.formData;
+        // 修改的提交
+        if (data.id) {
+          await QuestionParamSettingApi.updateQuestionParamSetting(data);
+          this.$modal.msgSuccess("修改成功");
+          this.dialogVisible = false;
+          this.$emit('success');
+          return;
+        }
+        // 添加的提交
+        await QuestionParamSettingApi.createQuestionParamSetting(data);
+        this.$modal.msgSuccess("新增成功");
+        this.dialogVisible = false;
+        this.$emit('success');
+      } finally {
+        this.formLoading = false;
+      }
+    },
+    /** 表单重置 */
+    reset() {
+      this.formData = {
+        id: undefined,
+        templateId: undefined,
+        settingKey: undefined,
+        settingName: undefined,
+        settingValue: undefined,
+        sort: undefined,
+      };
+      this.resetForm("formRef");
+    }
+  }
+};
+</script>
diff --git a/src/views/ai/questionparamsetting/index.vue b/src/views/ai/questionparamsetting/index.vue
new file mode 100644
index 0000000..e71a3e2
--- /dev/null
+++ b/src/views/ai/questionparamsetting/index.vue
@@ -0,0 +1,148 @@
+<template>
+  <div class="app-container">
+    <!-- 搜索工作栏 -->
+    <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch"
+             label-width="68px">
+      <el-form-item label="问题模板id" prop="templateId">
+        <el-input v-model="queryParams.templateId" placeholder="请输入问题模板id" clearable
+                  @keyup.enter.native="handleQuery"/>
+      </el-form-item>
+      <el-form-item label="key" prop="settingKey">
+        <el-input v-model="queryParams.settingKey" placeholder="请输入key" clearable
+                  @keyup.enter.native="handleQuery"/>
+      </el-form-item>
+      <el-form-item label="参数名称" prop="settingName">
+        <el-input v-model="queryParams.settingName" placeholder="请输入参数名称" clearable
+                  @keyup.enter.native="handleQuery"/>
+      </el-form-item>
+      <el-form-item label="参数默认值" prop="settingValue">
+        <el-input v-model="queryParams.settingValue" placeholder="请输入参数默认值" clearable
+                  @keyup.enter.native="handleQuery"/>
+      </el-form-item>
+      <el-form-item label="排序" prop="sort">
+        <el-input v-model="queryParams.sort" placeholder="请输入排序" clearable
+                  @keyup.enter.native="handleQuery"/>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <!-- 操作工具栏 -->
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button type="primary" plain icon="el-icon-plus" size="mini" @click="openForm(undefined)"
+                   v-hasPermi="['ai:question-param-setting:create']">新增
+        </el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
+      <el-table-column label="id" align="center" prop="id"/>
+      <el-table-column label="问题模板id" align="center" prop="templateId"/>
+      <el-table-column label="key" align="center" prop="settingKey"/>
+      <el-table-column label="参数名称" align="center" prop="settingName"/>
+      <el-table-column label="参数默认值" align="center" prop="settingValue"/>
+      <el-table-column label="排序" align="center" prop="sort"/>
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template v-slot="scope">
+          <el-button size="mini" type="text" icon="el-icon-edit" @click="openForm(scope.row.id)"
+                     v-hasPermi="['ai:question-param-setting:update']">修改
+          </el-button>
+          <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
+                     v-hasPermi="['ai:question-param-setting:delete']">删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页组件 -->
+    <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo"
+                :limit.sync="queryParams.pageSize"
+                @pagination="getList"/>
+    <!-- 对话框(添加 / 修改) -->
+    <QuestionParamSettingForm ref="formRef" @success="getList"/>
+  </div>
+</template>
+
+<script>
+import * as QuestionParamSettingApi from '@/api/ai/questionparamsetting';
+import QuestionParamSettingForm from './QuestionParamSettingForm.vue';
+
+export default {
+  name: "QuestionParamSetting",
+  components: {
+    QuestionParamSettingForm,
+  },
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 大模型问题设置参数列表
+      list: [],
+      // 是否展开,默认全部展开
+      isExpandAll: true,
+      // 重新渲染表格状态
+      refreshTable: true,
+      // 选中行
+      currentRow: {},
+      // 查询参数
+      queryParams: {
+        pageNo: 1,
+        pageSize: 10,
+        templateId: null,
+        settingKey: null,
+        settingName: null,
+        settingValue: null,
+        sort: null,
+      },
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    /** 查询列表 */
+    async getList() {
+      try {
+        this.loading = true;
+        const res = await QuestionParamSettingApi.getQuestionParamSettingPage(this.queryParams);
+        this.list = res.data.list;
+        this.total = res.data.total;
+      } finally {
+        this.loading = false;
+      }
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNo = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    /** 添加/修改操作 */
+    openForm(id) {
+      this.$refs["formRef"].open(id);
+    },
+    /** 删除按钮操作 */
+    async handleDelete(row) {
+      const id = row.id;
+      await this.$modal.confirm('是否确认删除大模型问题设置参数编号为"' + id + '"的数据项?')
+      try {
+        await QuestionParamSettingApi.deleteQuestionParamSetting(id);
+        await this.getList();
+        this.$modal.msgSuccess("删除成功");
+      } catch {
+      }
+    },
+  }
+};
+</script>
diff --git a/src/views/ai/questiontemplate/QuestionTemplateForm.vue b/src/views/ai/questiontemplate/QuestionTemplateForm.vue
new file mode 100644
index 0000000..acc6048
--- /dev/null
+++ b/src/views/ai/questiontemplate/QuestionTemplateForm.vue
@@ -0,0 +1,148 @@
+<template>
+  <div class="app-container">
+    <!-- 对话框(添加 / 修改) -->
+    <el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="45%" v-dialogDrag
+               append-to-body>
+      <el-form ref="formRef" :model="formData" :rules="formRules" v-loading="formLoading"
+               label-width="100px">
+        <el-form-item label="模型id" prop="modelId">
+          <el-input v-model="formData.modelId" placeholder="请输入模型id"/>
+        </el-form-item>
+        <el-form-item label="问题编号" prop="questionCode">
+          <el-input v-model="formData.questionCode" placeholder="请输入问题编号"/>
+        </el-form-item>
+        <el-form-item label="问题名称" prop="questionName">
+          <el-input v-model="formData.questionName" placeholder="请输入问题名称"/>
+        </el-form-item>
+        <el-form-item label="问题内容">
+          <Editor v-model="formData.questionContent" :min-height="192"/>
+        </el-form-item>
+        <el-form-item label="输入个数" prop="dataLength">
+          <el-input v-model="formData.dataLength" placeholder="请输入输入个数"/>
+        </el-form-item>
+        <el-form-item label="是否启用(0禁用 1启用)" prop="isEnable">
+          <el-input v-model="formData.isEnable" placeholder="请输入是否启用(0禁用 1启用)"/>
+        </el-form-item>
+        <el-form-item label="备注" prop="remark">
+          <el-input v-model="formData.remark" placeholder="请输入备注"/>
+        </el-form-item>
+        <el-form-item label="创建时间" prop="createDate">
+          <el-date-picker clearable v-model="formData.createDate" type="date"
+                          value-format="timestamp" placeholder="选择创建时间"/>
+        </el-form-item>
+        <el-form-item label="更新者" prop="updator">
+          <el-input v-model="formData.updator" placeholder="请输入更新者"/>
+        </el-form-item>
+        <el-form-item label="更新时间" prop="updateDate">
+          <el-date-picker clearable v-model="formData.updateDate" type="date"
+                          value-format="timestamp" placeholder="选择更新时间"/>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm" :disabled="formLoading">确 定</el-button>
+        <el-button @click="dialogVisible = false">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import * as QuestionTemplateApi from '@/api/ai/questiontemplate';
+import Editor from '@/components/Editor';
+
+export default {
+  name: "QuestionTemplateForm",
+  components: {
+    Editor,
+  },
+  data() {
+    return {
+      // 弹出层标题
+      dialogTitle: "",
+      // 是否显示弹出层
+      dialogVisible: false,
+      // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
+      formLoading: false,
+      // 表单参数
+      formData: {
+        id: undefined,
+        modelId: undefined,
+        questionCode: undefined,
+        questionName: undefined,
+        questionContent: undefined,
+        dataLength: undefined,
+        isEnable: undefined,
+        remark: undefined,
+        createDate: undefined,
+        updator: undefined,
+        updateDate: undefined,
+      },
+      // 表单校验
+      formRules: {
+        modelId: [{required: true, message: '模型id不能为空', trigger: 'blur'}],
+        questionCode: [{required: true, message: '问题编号不能为空', trigger: 'blur'}],
+      },
+    };
+  },
+  methods: {
+    /** 打开弹窗 */
+    async open(id) {
+      this.dialogVisible = true;
+      this.reset();
+      // 修改时,设置数据
+      if (id) {
+        this.formLoading = true;
+        try {
+          const res = await QuestionTemplateApi.getQuestionTemplate(id);
+          this.formData = res.data;
+          this.title = "修改大模型问题模板";
+        } finally {
+          this.formLoading = false;
+        }
+      }
+      this.title = "新增大模型问题模板";
+    },
+    /** 提交按钮 */
+    async submitForm() {
+      // 校验主表
+      await this.$refs["formRef"].validate();
+      this.formLoading = true;
+      try {
+        const data = this.formData;
+        // 修改的提交
+        if (data.id) {
+          await QuestionTemplateApi.updateQuestionTemplate(data);
+          this.$modal.msgSuccess("修改成功");
+          this.dialogVisible = false;
+          this.$emit('success');
+          return;
+        }
+        // 添加的提交
+        await QuestionTemplateApi.createQuestionTemplate(data);
+        this.$modal.msgSuccess("新增成功");
+        this.dialogVisible = false;
+        this.$emit('success');
+      } finally {
+        this.formLoading = false;
+      }
+    },
+    /** 表单重置 */
+    reset() {
+      this.formData = {
+        id: undefined,
+        modelId: undefined,
+        questionCode: undefined,
+        questionName: undefined,
+        questionContent: undefined,
+        dataLength: undefined,
+        isEnable: undefined,
+        remark: undefined,
+        createDate: undefined,
+        updator: undefined,
+        updateDate: undefined,
+      };
+      this.resetForm("formRef");
+    }
+  }
+};
+</script>
diff --git a/src/views/ai/questiontemplate/index.vue b/src/views/ai/questiontemplate/index.vue
new file mode 100644
index 0000000..d8078ea
--- /dev/null
+++ b/src/views/ai/questiontemplate/index.vue
@@ -0,0 +1,186 @@
+<template>
+  <div class="app-container">
+    <!-- 搜索工作栏 -->
+    <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch"
+             label-width="68px">
+      <el-form-item label="模型id" prop="modelId">
+        <el-input v-model="queryParams.modelId" placeholder="请输入模型id" clearable
+                  @keyup.enter.native="handleQuery"/>
+      </el-form-item>
+      <el-form-item label="问题编号" prop="questionCode">
+        <el-input v-model="queryParams.questionCode" placeholder="请输入问题编号" clearable
+                  @keyup.enter.native="handleQuery"/>
+      </el-form-item>
+      <el-form-item label="问题名称" prop="questionName">
+        <el-input v-model="queryParams.questionName" placeholder="请输入问题名称" clearable
+                  @keyup.enter.native="handleQuery"/>
+      </el-form-item>
+      <el-form-item label="输入个数" prop="dataLength">
+        <el-input v-model="queryParams.dataLength" placeholder="请输入输入个数" clearable
+                  @keyup.enter.native="handleQuery"/>
+      </el-form-item>
+      <el-form-item label="是否启用(0禁用 1启用)" prop="isEnable">
+        <el-input v-model="queryParams.isEnable" placeholder="请输入是否启用(0禁用 1启用)" clearable
+                  @keyup.enter.native="handleQuery"/>
+      </el-form-item>
+      <el-form-item label="备注" prop="remark">
+        <el-input v-model="queryParams.remark" placeholder="请输入备注" clearable
+                  @keyup.enter.native="handleQuery"/>
+      </el-form-item>
+      <el-form-item label="创建时间" prop="createDate">
+        <el-date-picker v-model="queryParams.createDate" style="width: 240px"
+                        value-format="yyyy-MM-dd HH:mm:ss" type="daterange"
+                        range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期"
+                        :default-time="['00:00:00', '23:59:59']"/>
+      </el-form-item>
+      <el-form-item label="更新者" prop="updator">
+        <el-input v-model="queryParams.updator" placeholder="请输入更新者" clearable
+                  @keyup.enter.native="handleQuery"/>
+      </el-form-item>
+      <el-form-item label="更新时间" prop="updateDate">
+        <el-date-picker v-model="queryParams.updateDate" style="width: 240px"
+                        value-format="yyyy-MM-dd HH:mm:ss" type="daterange"
+                        range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期"
+                        :default-time="['00:00:00', '23:59:59']"/>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <!-- 操作工具栏 -->
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button type="primary" plain icon="el-icon-plus" size="mini" @click="openForm(undefined)"
+                   v-hasPermi="['ai:question-template:create']">新增
+        </el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
+      <el-table-column label="id" align="center" prop="id"/>
+      <el-table-column label="模型id" align="center" prop="modelId"/>
+      <el-table-column label="问题编号" align="center" prop="questionCode"/>
+      <el-table-column label="问题名称" align="center" prop="questionName"/>
+      <el-table-column label="问题内容" align="center" prop="questionContent"/>
+      <el-table-column label="输入个数" align="center" prop="dataLength"/>
+      <el-table-column label="是否启用(0禁用 1启用)" align="center" prop="isEnable"/>
+      <el-table-column label="备注" align="center" prop="remark"/>
+      <el-table-column label="创建时间" align="center" prop="createDate" width="180">
+        <template v-slot="scope">
+          <span>{{ parseTime(scope.row.createDate) }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="更新者" align="center" prop="updator"/>
+      <el-table-column label="更新时间" align="center" prop="updateDate" width="180">
+        <template v-slot="scope">
+          <span>{{ parseTime(scope.row.updateDate) }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template v-slot="scope">
+          <el-button size="mini" type="text" icon="el-icon-edit" @click="openForm(scope.row.id)"
+                     v-hasPermi="['ai:question-template:update']">修改
+          </el-button>
+          <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
+                     v-hasPermi="['ai:question-template:delete']">删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页组件 -->
+    <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo"
+                :limit.sync="queryParams.pageSize"
+                @pagination="getList"/>
+    <!-- 对话框(添加 / 修改) -->
+    <QuestionTemplateForm ref="formRef" @success="getList"/>
+  </div>
+</template>
+
+<script>
+import * as QuestionTemplateApi from '@/api/ai/questiontemplate';
+import QuestionTemplateForm from './QuestionTemplateForm.vue';
+
+export default {
+  name: "QuestionTemplate",
+  components: {
+    QuestionTemplateForm,
+  },
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 大模型问题模板列表
+      list: [],
+      // 是否展开,默认全部展开
+      isExpandAll: true,
+      // 重新渲染表格状态
+      refreshTable: true,
+      // 选中行
+      currentRow: {},
+      // 查询参数
+      queryParams: {
+        pageNo: 1,
+        pageSize: 10,
+        modelId: null,
+        questionCode: null,
+        questionName: null,
+        questionContent: null,
+        dataLength: null,
+        isEnable: null,
+        remark: null,
+        createDate: [],
+        updator: null,
+        updateDate: [],
+      },
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    /** 查询列表 */
+    async getList() {
+      try {
+        this.loading = true;
+        const res = await QuestionTemplateApi.getQuestionTemplatePage(this.queryParams);
+        this.list = res.data.list;
+        this.total = res.data.total;
+      } finally {
+        this.loading = false;
+      }
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNo = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    /** 添加/修改操作 */
+    openForm(id) {
+      this.$refs["formRef"].open(id);
+    },
+    /** 删除按钮操作 */
+    async handleDelete(row) {
+      const id = row.id;
+      await this.$modal.confirm('是否确认删除大模型问题模板编号为"' + id + '"的数据项?')
+      try {
+        await QuestionTemplateApi.deleteQuestionTemplate(id);
+        await this.getList();
+        this.$modal.msgSuccess("删除成功");
+      } catch {
+      }
+    },
+  }
+};
+</script>

--
Gitblit v1.9.3