houzhongjian
10 天以前 923f49266b00bd05bf8a4037b29ecb706d1306ff
转炉大模型功能完善
已修改6个文件
已复制1个文件
已添加5个文件
已重命名1个文件
819 ■■■■ 文件已修改
src/api/ai/schedulesuggest/index.ts 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/ai/zhuanlu/history_title.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/ai/zhuanlu/suggest_title.png 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Dialog/src/DialogAi.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Dialog/src/DialogSuggest.vue 49 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/ai/dashboard/components/conversation/CommonConversation.vue 23 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/ai/dashboard/components/message/HistoryMessageDialog.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/ai/dashboard/components/message/MessageList.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/ai/dashboard/components/message/ModelMessageList.vue 22 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/ai/dashboard/zhuanlu/index.vue 303 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/ai/suggest/ScheduleSuggestForm.vue 117 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/ai/suggest/index.vue 223 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/ai/utils/utils.ts 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/ai/schedulesuggest/index.ts
对比新文件
@@ -0,0 +1,55 @@
import request from '@/config/axios'
// 大模型调度建议 VO
export interface ScheduleSuggestVO {
  id: number // id
  modelId: number // 模型id
  conversationId: number // 会话id
  messageId: number // 消息id
  content: string // 调度建议
  status: number // 状态(0-未处理 1-已采纳 2-已忽略)
  createTime: Date // 创建时间
}
// 大模型调度建议 API
export const ScheduleSuggestApi = {
  // 查询大模型调度建议分页
  getScheduleSuggestPage: async (params: any) => {
    return await request.get({ url: `/ai/schedule-suggest/page`, params })
  },
  // 查询大模型调度建议详情
  getScheduleSuggest: async (id: number) => {
    return await request.get({ url: `/ai/schedule-suggest/get?id=` + id })
  },
  // 查询大模型调度建议详情
  getTopScheduleSuggests: async (top: number) => {
    return await request.get({ url: `/ai/schedule-suggest/simple-list?top=` + top })
  },
  // 新增大模型调度建议
  createScheduleSuggest: async (data: ScheduleSuggestVO) => {
    return await request.post({ url: `/ai/schedule-suggest/create`, data })
  },
  // 修改大模型调度建议
  updateScheduleSuggest: async (data: ScheduleSuggestVO) => {
    return await request.put({ url: `/ai/schedule-suggest/update`, data })
  },
  // 采纳忽略取消采纳
  operateScheduleSuggest: async (data: ScheduleSuggestVO) => {
    return await request.put({ url: `/ai/schedule-suggest/operate-suggest`, data})
  },
  // 删除大模型调度建议
  deleteScheduleSuggest: async (id: number) => {
    return await request.delete({ url: `/ai/schedule-suggest/delete?id=` + id })
  },
  // 导出大模型调度建议 Excel
  exportScheduleSuggest: async (params) => {
    return await request.download({ url: `/ai/schedule-suggest/export-excel`, params })
  },
}
src/assets/ai/zhuanlu/history_title.png
src/assets/ai/zhuanlu/suggest_title.png
src/components/Dialog/src/DialogAi.vue
文件名从 src/components/Dialog/src/DialogHistory.vue 修改
@@ -1,7 +1,7 @@
<script lang="ts" setup>
import { propTypes } from '@/utils/propTypes'
import { isNumber } from '@/utils/is'
defineOptions({ name: 'DialogHistory' })
defineOptions({ name: 'DialogAi' })
const slots = useSlots()
@@ -111,7 +111,7 @@
      padding: 0;
      margin-right: 0 !important;
      background:
        url("@/assets/ai/zhuanlu/common_title.png") left no-repeat,
        url("@/assets/ai/zhuanlu/history_title.png") left no-repeat,
        linear-gradient(to bottom, #0a1633dd, #0a1633dd); /* 叠加深色遮罩 */
      div {
        color: #73C4FF;
src/components/Dialog/src/DialogSuggest.vue
copy from src/components/Dialog/src/DialogHistory.vue copy to src/components/Dialog/src/DialogSuggest.vue
文件从 src/components/Dialog/src/DialogHistory.vue 复制
@@ -1,7 +1,7 @@
<script lang="ts" setup>
import { propTypes } from '@/utils/propTypes'
import { isNumber } from '@/utils/is'
defineOptions({ name: 'DialogHistory' })
defineOptions({ name: 'Dialog' })
const slots = useSlots()
@@ -9,7 +9,7 @@
  modelValue: propTypes.bool.def(false),
  title: propTypes.string.def('Dialog'),
  fullscreen: propTypes.bool.def(true),
  width: propTypes.oneOfType([String, Number]).def('30%'),
  width: propTypes.oneOfType([String, Number]).def('40%'),
  scroll: propTypes.bool.def(false), // 是否开启滚动条。如果是的话,按照 maxHeight 设置最大高度
  maxHeight: propTypes.oneOfType([String, Number]).def('400px')
})
@@ -50,40 +50,56 @@
  }
)
const dialogStyle = computed(() => {
  return {
    height: unref(dialogHeight)
  }
})
</script>
<template>
  <ElDialog
    v-bind="getBindValue"
    :fullscreen="isFullscreen"
    :close-on-click-modal="true"
    :fullscreen="isFullscreen"
    :width="width"
    destroy-on-close
    lock-scroll
    draggable
    class="history-dialog"
    class="com-dialog"
    :show-close="false"
  >
    <template #header="{ close }">
      <div class="relative h-30px flex items-center justify-between pl-15px pr-15px">
      <div class="relative h-54px 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"
          class="absolute right-15px top-[50%] h-54px 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="var(--el-color-info)"
            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"
            color="var(--el-color-info)"
            @click="close"
          />
        </div>
      </div>
    </template>
    <ElScrollbar v-if="scroll" :style="dialogStyle">
    <slot></slot>
    </ElScrollbar>
    <slot v-else></slot>
    <template v-if="slots.footer" #footer>
      <slot name="footer"></slot>
    </template>
@@ -91,10 +107,9 @@
</template>
<style lang="scss">
.history-dialog {
  height: 90vh;
.com-dialog {
  height: 62vh;
  color: #73C4FF;
  margin-top: 30px;
  background: rgba(3,29,76,0.79);
  border-radius: 4px 4px 4px 4px;
  border: 1px solid;
@@ -111,7 +126,7 @@
      padding: 0;
      margin-right: 0 !important;
      background:
        url("@/assets/ai/zhuanlu/common_title.png") left no-repeat,
        url("@/assets/ai/zhuanlu/suggest_title.png") left no-repeat,
        linear-gradient(to bottom, #0a1633dd, #0a1633dd); /* 叠加深色遮罩 */
      div {
        color: #73C4FF;
@@ -123,6 +138,18 @@
      color: #73C4FF;
      top: 0;
    }
    &__body {
      padding: 15px !important;
    }
    &__footer {
      border-top: 1px solid var(--el-border-color);
    }
    &__headerbtn {
      top: 0;
    }
  }
}
</style>
src/views/ai/dashboard/components/conversation/CommonConversation.vue
@@ -147,7 +147,7 @@
import * as authUtil from "@/utils/auth";
import {refreshToken} from "@/api/login";
import {formatToDateTime} from "@/utils/dateUtil";
import {ElLoading} from "element-plus";
import { formatReasoningContent } from '@/views/ai/utils/utils'
/** AI 聊天对话 列表 */
defineOptions({ name: 'NormalConversation' })
@@ -347,23 +347,6 @@
  return []
})
// //处理调度推理结论(deepSeek)
// const dealResult = (conversations: any) => {
//   const regex = /<think>(\n*)([\s\S]*?)(\n*)<\/think>(\n*)([\s\S]*)/;
//   conversations.forEach((conversation) => {
//     if(conversation.content.includes('<\/think>')) {
//       conversation.thinkingFlag = false
//     } else {
//       conversation.thinkingFlag = true
//     }
//     const match = conversation.content.match(regex);
//     if(match) {
//       conversation.thinking = match[2];
//       conversation.conclusion = match[5]
//     }
//   })
// }
//处理调度推理结论(微调大模型)
const dealResult = (messages: any) => {
  messages.forEach((message) => {
@@ -378,6 +361,8 @@
      } else {
        message.thinking = message.content
      }
      // 处理推理思路内容
      message.thinking = formatReasoningContent(message.thinking);
    }
  })
}
@@ -691,7 +676,7 @@
  position: absolute;
  left: 320px;  // 初始展开位置
  top: 40%;
  z-index: 1000;
  z-index: 1;
  width: 20px;
  height: 80px;
  background: rgba(115, 196, 255, 0.5);
src/views/ai/dashboard/components/message/HistoryMessageDialog.vue
@@ -1,5 +1,5 @@
<template>
  <DialogHistory title="历史建议" v-model="dialogVisible" width="1200" custom-class="transparent-dialog">
  <DialogAi title="" v-model="dialogVisible" width="1200" custom-class="transparent-dialog">
    <!-- 搜索工作栏 -->
    <el-form
      class="-mb-15px query-area"
@@ -79,7 +79,7 @@
      v-model:limit="queryParams.pageSize"
      @pagination="handleQuery"
    />
  </DialogHistory>
  </DialogAi>
</template>
<script setup lang="ts">
src/views/ai/dashboard/components/message/MessageList.vue
@@ -134,14 +134,12 @@
/** 回到底部 */
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
}
src/views/ai/dashboard/components/message/ModelMessageList.vue
@@ -1,5 +1,5 @@
<template>
  <div ref="messageContainer" class="h-100%">
  <div ref="messageContainer" class="h-100% 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'">
@@ -21,10 +21,9 @@
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 { 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 到粘贴板
@@ -88,24 +87,9 @@
  scrollContainer.scrollTop = 0
}
defineExpose({ scrollToBottom, handlerGoTop }) // 提供方法给 parent 调用
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')
}
/** 刷新 */
const onRefresh = async (message: ChatMessageVO) => {
src/views/ai/dashboard/zhuanlu/index.vue
@@ -26,9 +26,15 @@
      <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 class="content1">
            <div class="value" v-if="item.type == 1">
              <div class="item" v-for="(list, i) in item.lists" :key="`dynamics-${i}`">
                <span>{{list.no}}</span><span>{{list.value}}</span>
              </div>
            </div>
            <div class="value" v-else>
              <span v-if="item.value == '进行'" style="color: #49FFD3; font-size: 14px; font-weight: bold;">{{item.value}}</span>
              <span v-else style="color: #FFAE81; font-size: 14px; font-weight: bold;">{{item.value}}</span>
            </div>
            <div class="name">
              {{item.name}}
@@ -179,11 +185,11 @@
    <div class="gas-scheduling-right">
      <div id="ldghslyc">
        <div class="title"></div>
        <div ref="LDGHSLYCEhartContainer" style="width: 100%; height: 180px"></div>
        <div ref="LDGHSLYCEhartContainer" style="width: 100%; height: 140px"></div>
      </div>
      <div id="ldggrqsyc">
        <div class="title"></div>
        <div ref="LDGGRYCEhartContainer" style="width: 100%; height: 180px"></div>
        <div ref="LDGGRYCEhartContainer" style="width: 100%; height: 140px"></div>
      </div>
      <div id="mqhsjhxx">
        <div class="title"></div>
@@ -248,6 +254,32 @@
          <div class="item right-label"></div>
        </div>
      </div>
      <div class="schedule-suggest">
        <div class="result-title">
          <span>推理结论</span><el-button @click="openSuggest" size="small" class="result-button" :icon="ArrowRight">查看更多</el-button>
        </div>
        <div class="result-content">
          <div class="content-item" v-for="(item, index) in topSuggests" :key="`dynamics-${index}`">
            <div class="time">
              <span>{{formatDate(item.createTime, 'MM-DD HH:mm')}}</span>
            </div>
            <el-tooltip
              effect="dark"
              :content="item.content"
              placement="top"
              :disabled="!isOverflow"
            >
              <div class="content" ref="contentRef">
                {{ item.content }}
              </div>
            </el-tooltip>
          </div>
        </div>
        <!-- 历史建议 -->
        <ScheduleSuggestDialog
          ref="scheduleSuggestRef"
        />
      </div>
    </div>
  </div>
</template>
@@ -264,11 +296,13 @@
import HistoryMessageDialog from "../components/message/HistoryMessageDialog.vue"
import * as echarts from "echarts";
import {formatToDateTime} from "@/utils/dateUtil";
import { formatReasoningContent } from '@/views/ai/utils/utils'
import {refreshToken} from "@/api/login";
import {round} from "lodash-es";
import {ArrowUpBold} from "@element-plus/icons-vue";
import {ArrowRight, ArrowUpBold} from "@element-plus/icons-vue";
import * as authUtil from "@/utils/auth";
import HistoryMessageList from "@/views/ai/dashboard/components/message/HistoryMessageList.vue";
import {ScheduleSuggestApi, ScheduleSuggestVO} from "@/api/ai/schedulesuggest";
import {formatDate} from "@/utils/formatTime";
const mqhsList = ref([
  {
@@ -306,33 +340,60 @@
const tsxxList = ref([
  {
    name: '各高炉出铁水信号',
    value: '进行',
    unit: ''
    type: 1,
    lists: [
      {
        no: '1#',
        value: '不进行',
      },
      {
        no: '2#',
        value: '不进行',
      }
    ]
  },
  {
    name: '各高炉出铁量',
    value: 5000,
    unit: '吨'
    type: 1,
    lists: [
      {
        no: '1#',
        value: '500t',
      },
      {
        no: '2#',
        value: '600t',
      }
    ]
  },
  {
    name: '各高炉铁水装入鱼雷罐车信号',
    value: '进行',
    unit: 'm³/h'
    type: 1,
    lists: [
      {
        no: '1#',
        value: '不进行',
      },
      {
        no: '2#',
        value: '不进行',
      }
    ]
  },
  {
    name: '鱼雷罐车等待信号',
    value: '进行',
    unit: 'm³/h'
    type: 2,
    value: '进行'
  },
  {
    name: '铁水倒入铁水包信号',
    value: '不进行',
    unit: 'm³/h'
    type: 2,
    value: '不进行'
  },
  {
    name: '铁产量计划',
    value: 6000,
    unit: '吨'
    type: 3,
    value: '6000t',
  },
])
@@ -440,14 +501,12 @@
const mqhsjhxxList = ref([
  {
    name: '转炉总炉数\n' +
      '日计划',
    name: '转炉总炉数日计划',
    value: 567,
    unit: '炉'
  },
  {
    name: '转炉入炉铁水量\n' +
      '日计划',
    name: '转炉入炉铁水量日计划',
    value: 200,
    unit: '吨'
  },
@@ -536,6 +595,11 @@
  }
])
const topSuggests = ref<ScheduleSuggestVO[]>([])
const contentRef = ref([]);
const isOverflow = ref([]);
const ddtlResult = ref('')
@@ -725,10 +789,14 @@
    message.thinking = match[2];
    message.conclusion = match[4]
  }
  message.thinking = formatReasoningContent(message.thinking)
  return message
}
/** 调度建议 */
const scheduleSuggestRef = ref()
const openSuggest = async () => {
  scheduleSuggestRef.value.open()
}
/**
 * 消息列表
 *
@@ -736,8 +804,22 @@
 */
const messageList = computed(() => {
  if (activeMessageList.value.length > 0) {
    activeMessageList.value[1].thinking = dealResultAndData(activeMessageList.value[1].content)
    return activeMessageList.value
    // 对AI返回的消息进行格式化处理
    const formattedList = activeMessageList.value.map(msg => {
      if (msg.type === 'assistant') {
        // 复制消息对象以避免修改原始数据
        const formattedMsg = {...msg};
        // 处理推理思路内容
        formattedMsg.content = formatReasoningContent(msg.content);
        return formattedMsg;
      }
      return msg;
    });
    // 处理调度推理结论及数据
    formattedList[1].thinking = dealResultAndData(formattedList[1].content);
    return formattedList;
  }
  // 没有消息时,如果有 systemMessage 则展示它
  if (activeConversation.value?.systemMessage) {
@@ -773,6 +855,15 @@
  }
  initLDGGRQSYCChart()
  return content
}
const getScheduleResult = (content: string) => {
  const spliceText = content.includes("总结:") ? "总结:" : "结论:";
  const regex = new RegExp(`^([\\s\\S]*?)${spliceText}([\\s\\S]*)$`);
  const match = content.match(regex);
  console.log(match)
  const result = match ? match[2].trim() : '';
  return result
}
const extractRecoveryDetails = (text, consume, gui, totalMinutes = 60) => {
@@ -945,6 +1036,10 @@
    conversationId: activeConversationId.value,
    content: content
  } as ChatMessageVO)
  // 保存调度建议
  setTimeout(async () => {
    await createSuggest()
  }, 1000)
}
/** 真正执行【发送】消息操作 */
@@ -1108,6 +1203,31 @@
  } catch {}
}
const suggestData = ref({
  id: undefined,
  modelId: undefined,
  conversationId: undefined,
  messageId: undefined,
  content: undefined,
  status: undefined,
})
const createSuggest = async () => {
  const suggestParam = suggestData.value as unknown as ScheduleSuggestVO
  let assistantMessage = activeMessageList.value[1]
  suggestParam.content = getScheduleResult(assistantMessage.content)
  if(suggestParam.content != '') {
    suggestParam.modelId = activeConversation.value.modelId
    suggestParam.conversationId = activeConversation.value.id
    suggestParam.messageId = assistantMessage.id
    suggestParam.createTime = assistantMessage.createTime
    suggestParam.status = 0
    await ScheduleSuggestApi.createScheduleSuggest(suggestParam)
    // 刷新首页推理结果列表
    await getTopSuggest()
  }
}
const LDGHSLYCEhartContainer = ref();
  // 生成未来60秒的时间标签(LDG回收量预测)
@@ -1148,7 +1268,7 @@
  const max = LDGMaxTotalValue()
  const schedule = modelData.value.schedule[type]
  // 返回对象格式数据,包含原始值和基准值
  const baseline = round(max, 0) + (6 - 2 * type)
  const baseline = round(max, 0) + (4.5 - 2 * type)
  return schedule.map(item => ({
    value: item + baseline, // 显示值 = 原始值 + 基准值
    original: item        // 原始值
@@ -1232,15 +1352,15 @@
    },
    grid: {
      left: 25,
      right: 25,
      right: 5,
      bottom: 10,
      top: 30,
      containLabel: true
    },
    legend: {
      top: 10,
      top: 5,
      right: 10,
      data: ['1#转炉', '2#转炉', '3#转炉', '总回收量'],
      data: ['1#转炉', '2#转炉', '3#转炉'],
      textStyle: {
        color: '#8FD6FE'
      },
@@ -1293,6 +1413,7 @@
          focus: 'series'
        },
        lineStyle: {
          width: 1,
          color: '#FF7686' // 粉色
        }
      },
@@ -1306,6 +1427,7 @@
          focus: 'series'
        },
        lineStyle: {
          width: 1,
          color: '#49FFD3' // 绿色
        }
      },
@@ -1320,6 +1442,7 @@
          focus: 'series'
        },
        lineStyle: {
          width: 1,
          color: '#FFAE81' // 橙色
        },
      },
@@ -1333,6 +1456,7 @@
          focus: 'series'
        },
        lineStyle: {
          width: 1,
          color: 'white'
        },
      }
@@ -1383,7 +1507,7 @@
      left: 0,
      right: 0,
      bottom: 10,
      top: 20,
      top: 10,
      containLabel: true
    },
    tooltip: {
@@ -1434,7 +1558,10 @@
        data: realData,
        smooth: true,
        symbol: 'none',
        lineStyle: { color: '#95E6FF' },
        lineStyle: {
          color: '#95E6FF',
          width: 1
        },
        markLine: {
          symbol: ['none', 'none'],
          label: {
@@ -1459,6 +1586,7 @@
        lineStyle: {
          type: 'dashed',
          color: '#E76666',
          width: 1,
          dashOffset: 5
        }
      },
@@ -1591,6 +1719,11 @@
  // 初始状态检测
  updateFullscreenStatus();
}
/** 查询列表 */
const getTopSuggest = async () => {
  const data = await ScheduleSuggestApi.getTopScheduleSuggests(5)
  topSuggests.value = data
}
/** 初始化 **/
onMounted(async () => {
@@ -1605,11 +1738,11 @@
  // 获取列表数据
  activeMessageListLoading.value = true
  await getMessageList()
  await getTopSuggest()
})
// 清理监听
onUnmounted(() => {
  console.log('stopStream')
  const events = ['fullscreenchange', 'webkitfullscreenchange', 'msfullscreenchange'];
  events.forEach(event => {
    document.removeEventListener(event, handleFullscreenChange);
@@ -1682,6 +1815,35 @@
          font-weight: 400;
          font-size: 12px;
          color: #C7E7FF;
        }
      }
      .name {
        height: 16px;
        font-weight: 400;
        font-size: 12px;
        color: #C7E7FF;
      }
    }
    .content1 {
      margin-left: 16px;
      .value {
        span:nth-child(1) {
          height: 16px;
          font-weight: 400;
          font-size: 12px;
          color: #C7E7FF;
        }
        span:nth-child(2){
          padding-left: 3px;
          height: 19px;
          font-weight: bold;
          font-size: 14px;
          color: #FFAE81;
          line-height: 19px;
        }
        .item {
          display: inline-block;
          width: 45%;
        }
      }
      .name {
@@ -2229,8 +2391,8 @@
        }
      }
      .data2-item {
        height: 2.8rem;
        width: 45%;
        height: 1.4rem;
        width: 46%;
        display: inline-block;
        margin: 6px 8px;
        background: url("@/assets/ai/zhuanlu/data_bg3.png") no-repeat;
@@ -2241,15 +2403,13 @@
        margin-left: 10px;
        .name {
          width: 95px;
          height: 18px;
          width: 140px;
          font-weight: 400;
          font-size: 14px;
          color: #C7E7FF;
        }
        .value {
          margin-top: 10px;
          margin-left: auto;
          margin-right: 5px;
          span:nth-child(1) {
@@ -2279,13 +2439,13 @@
      .little-title {
        font-size: 14px;
        color: #8FD6FE;
        margin: 10px;
        margin: 5px 10px 5px 10px;
      }
      .data3-item {
        height: 5.2rem;
        width: 30%;
        display: inline-block;
        margin: 0 6px 6px 6px;
        margin: 0 6px 0 6px;
        background: url("@/assets/ai/zhuanlu/data_bg4.png") center/cover no-repeat;
        .name {
          font-family: Alimama ShuHeiTi, Alimama ShuHeiTi;
@@ -2355,6 +2515,67 @@
        }
      }
    }
    .schedule-suggest {
      .result-title {
        margin-top: 10px;
        background: url("@/assets/ai/zhuanlu/ddtljl_result_title.png") no-repeat;
        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-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);
        }
      }
      .result-content {
        margin-top: 5px;
        display: inline-block;
        font-weight: 400;
        font-size: 14px;
        color: rgba(130,202,255,0.89);
        text-align: left;
        font-style: normal;
        text-transform: none;
        .content-item {
          height: 28px;
          background: rgba(69,133,255,0.2);
          border-radius: 2px 2px 2px 2px;
          padding-left: 3px;
          margin: 3px 0;
          display: flex;
          align-items: center;
          overflow: hidden;
        }
        .time {
          flex-shrink: 0;
          margin-right: 12px;
        }
        .content {
          width: 350px;
          flex: 1;
          white-space: nowrap;
          overflow: hidden;
          text-overflow: ellipsis;
        }
      }
    }
  }
}
src/views/ai/suggest/ScheduleSuggestForm.vue
对比新文件
@@ -0,0 +1,117 @@
<template>
  <Dialog :title="dialogTitle" v-model="dialogVisible">
    <el-form
      ref="formRef"
      :model="formData"
      :rules="formRules"
      label-width="100px"
      v-loading="formLoading"
    >
      <el-form-item label="模型id" prop="modelId">
        <el-input v-model="formData.modelId" placeholder="请输入模型id" />
      </el-form-item>
      <el-form-item label="会话id" prop="conversationId">
        <el-input v-model="formData.conversationId" placeholder="请输入会话id" />
      </el-form-item>
      <el-form-item label="消息id" prop="messageId">
        <el-input v-model="formData.messageId" placeholder="请输入消息id" />
      </el-form-item>
      <el-form-item label="调度建议" prop="content">
        <Editor v-model="formData.content" height="150px" />
      </el-form-item>
      <el-form-item label="状态(0-未处理 1-已采纳 2-已忽略)" prop="status">
        <el-radio-group v-model="formData.status">
          <el-radio label="1">请选择字典生成</el-radio>
        </el-radio-group>
      </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>
  </Dialog>
</template>
<script setup lang="ts">
import { ScheduleSuggestApi, ScheduleSuggestVO } from '@/api/ai/schedulesuggest'
/** 大模型调度建议 表单 */
defineOptions({ name: 'ScheduleSuggestForm' })
const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗
const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') // 弹窗的标题
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
const formType = ref('') // 表单的类型:create - 新增;update - 修改
const formData = ref({
  id: undefined,
  modelId: undefined,
  conversationId: undefined,
  messageId: undefined,
  content: undefined,
  status: undefined,
})
const formRules = reactive({
  modelId: [{ required: true, message: '模型id不能为空', trigger: 'blur' }],
  conversationId: [{ required: true, message: '会话id不能为空', trigger: 'blur' }],
  messageId: [{ required: true, message: '消息id不能为空', trigger: 'blur' }],
  status: [{ required: true, message: '状态(0-未处理 1-已采纳 2-已忽略)不能为空', trigger: 'blur' }],
})
const formRef = ref() // 表单 Ref
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
  dialogVisible.value = true
  dialogTitle.value = t('action.' + type)
  formType.value = type
  resetForm()
  // 修改时,设置数据
  if (id) {
    formLoading.value = true
    try {
      formData.value = await ScheduleSuggestApi.getScheduleSuggest(id)
    } finally {
      formLoading.value = false
    }
  }
}
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 ScheduleSuggestVO
    if (formType.value === 'create') {
      await ScheduleSuggestApi.createScheduleSuggest(data)
      message.success(t('common.createSuccess'))
    } else {
      await ScheduleSuggestApi.updateScheduleSuggest(data)
      message.success(t('common.updateSuccess'))
    }
    dialogVisible.value = false
    // 发送操作成功的事件
    emit('success')
  } finally {
    formLoading.value = false
  }
}
/** 重置表单 */
const resetForm = () => {
  formData.value = {
    id: undefined,
    modelId: undefined,
    conversationId: undefined,
    messageId: undefined,
    content: undefined,
    status: undefined,
  }
  formRef.value?.resetFields()
}
</script>
src/views/ai/suggest/index.vue
对比新文件
@@ -0,0 +1,223 @@
<template>
  <ContentWrap>
    <!-- 搜索工作栏 -->
    <el-form
      class="-mb-15px"
      :model="queryParams"
      ref="queryFormRef"
      :inline="true"
      label-width="68px"
    >
      <el-form-item label="模型id" prop="modelId">
        <el-input
          v-model="queryParams.modelId"
          placeholder="请输入模型id"
          clearable
          @keyup.enter="handleQuery"
          class="!w-240px"
        />
      </el-form-item>
      <el-form-item label="会话id" prop="conversationId">
        <el-input
          v-model="queryParams.conversationId"
          placeholder="请输入会话id"
          clearable
          @keyup.enter="handleQuery"
          class="!w-240px"
        />
      </el-form-item>
      <el-form-item label="消息id" prop="messageId">
        <el-input
          v-model="queryParams.messageId"
          placeholder="请输入消息id"
          clearable
          @keyup.enter="handleQuery"
          class="!w-240px"
        />
      </el-form-item>
      <el-form-item label="状态(0-未处理 1-已采纳 2-已忽略)" prop="status">
        <el-select
          v-model="queryParams.status"
          placeholder="请选择状态(0-未处理 1-已采纳 2-已忽略)"
          clearable
          class="!w-240px"
        >
          <el-option label="请选择字典生成" value="" />
        </el-select>
      </el-form-item>
      <el-form-item label="创建时间" prop="createTime">
        <el-date-picker
          v-model="queryParams.createTime"
          value-format="YYYY-MM-DD HH:mm:ss"
          type="daterange"
          start-placeholder="开始日期"
          end-placeholder="结束日期"
          :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
          class="!w-240px"
        />
      </el-form-item>
      <el-form-item>
        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
        <el-button
          type="primary"
          plain
          @click="openForm('create')"
          v-hasPermi="['ai:schedule-suggest:create']"
        >
          <Icon icon="ep:plus" class="mr-5px" /> 新增
        </el-button>
        <el-button
          type="success"
          plain
          @click="handleExport"
          :loading="exportLoading"
          v-hasPermi="['ai:schedule-suggest:export']"
        >
          <Icon icon="ep:download" class="mr-5px" /> 导出
        </el-button>
      </el-form-item>
    </el-form>
  </ContentWrap>
  <!-- 列表 -->
  <ContentWrap>
    <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="会话id" align="center" prop="conversationId" />
      <el-table-column label="消息id" align="center" prop="messageId" />
      <el-table-column label="调度建议" align="center" prop="content" />
      <el-table-column label="状态(0-未处理 1-已采纳 2-已忽略)" align="center" prop="status" />
      <el-table-column
        label="创建时间"
        align="center"
        prop="createTime"
        :formatter="dateFormatter"
        width="180px"
      />
      <el-table-column label="操作" align="center">
        <template #default="scope">
          <el-button
            link
            type="primary"
            @click="openForm('update', scope.row.id)"
            v-hasPermi="['ai:schedule-suggest:update']"
          >
            编辑
          </el-button>
          <el-button
            link
            type="danger"
            @click="handleDelete(scope.row.id)"
            v-hasPermi="['ai:schedule-suggest:delete']"
          >
            删除
          </el-button>
        </template>
      </el-table-column>
    </el-table>
    <!-- 分页 -->
    <Pagination
      :total="total"
      v-model:page="queryParams.pageNo"
      v-model:limit="queryParams.pageSize"
      @pagination="getList"
    />
  </ContentWrap>
  <!-- 表单弹窗:添加/修改 -->
  <ScheduleSuggestForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts">
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import { ScheduleSuggestApi, ScheduleSuggestVO } from '@/api/ai/schedulesuggest'
import ScheduleSuggestForm from './ScheduleSuggestForm.vue'
/** 大模型调度建议 列表 */
defineOptions({ name: 'ScheduleSuggest' })
const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化
const loading = ref(true) // 列表的加载中
const list = ref<ScheduleSuggestVO[]>([]) // 列表的数据
const total = ref(0) // 列表的总页数
const queryParams = reactive({
  pageNo: 1,
  pageSize: 10,
  modelId: undefined,
  conversationId: undefined,
  messageId: undefined,
  content: undefined,
  status: undefined,
  createTime: [],
})
const queryFormRef = ref() // 搜索的表单
const exportLoading = ref(false) // 导出的加载中
/** 查询列表 */
const getList = async () => {
  loading.value = true
  try {
    const data = await ScheduleSuggestApi.getScheduleSuggestPage(queryParams)
    list.value = data.list
    total.value = data.total
  } finally {
    loading.value = false
  }
}
/** 搜索按钮操作 */
const handleQuery = () => {
  queryParams.pageNo = 1
  getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
  queryFormRef.value.resetFields()
  handleQuery()
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
  formRef.value.open(type, id)
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
  try {
    // 删除的二次确认
    await message.delConfirm()
    // 发起删除
    await ScheduleSuggestApi.deleteScheduleSuggest(id)
    message.success(t('common.delSuccess'))
    // 刷新列表
    await getList()
  } catch {}
}
/** 导出按钮操作 */
const handleExport = async () => {
  try {
    // 导出的二次确认
    await message.exportConfirm()
    // 发起导出
    exportLoading.value = true
    const data = await ScheduleSuggestApi.exportScheduleSuggest(queryParams)
    download.excel(data, '大模型调度建议.xls')
  } catch {
  } finally {
    exportLoading.value = false
  }
}
/** 初始化 **/
onMounted(() => {
  getList()
})
</script>
src/views/ai/utils/utils.ts
@@ -11,3 +11,20 @@
export const hasChinese = (str: string) => {
  return /[\u4e00-\u9fa5]/.test(str)
}
export const formatReasoningContent = (content: string) => {
  // 匹配 "数字" + "." + ("中文"或"空格") + "其他内容" + ":"
  const stepRegex = /(\d+\.(?:[\u4e00-\u9fa5]|\s)[^:]*:)(\s*)/g;
  // 替换逻辑:
  // - 如果标题后没有换行(即 `$2` 是空或只有空格),则添加 `<br>`
  // - 如果标题后已有换行(如 `\n` 或 `<br>`),则不额外添加
  return content.replace(
    stepRegex,
    (match, title, whitespace) => {
      const hasNewline = whitespace.includes('\\n') || whitespace.includes('<br>');
      const lineBreak = hasNewline ? '' : '<br>';
      return `<strong style="font-size: 16px; line-height: 32px; color: #FFFFFF;">${title}</strong>${lineBreak}`;
    }
  );
}