已修改16个文件
已复制1个文件
已添加7个文件
已重命名1个文件
1808 ■■■■ 文件已修改
src/api/ai/model/model/index.ts 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/ai/schedulesuggest/index.ts 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/model/mpk/mpk.ts 4 ●●●● 补丁 | 查看 | 原始文档 | 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 51 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/ai/chat/index/components/conversation/ConversationUpdateForm.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/ai/dashboard/components/conversation/CommonConversation.vue 23 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/ai/dashboard/components/conversation/CommonConversationList.vue 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/ai/dashboard/components/conversation/CommonConversationUpdateForm.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/ai/dashboard/components/conversation/ConversationUpdateForm.vue 2 ●●● 补丁 | 查看 | 原始文档 | 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/components/suggest/ScheduleSuggestDialog.vue 417 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/ai/dashboard/components/suggest/ScheduleSuggestList.vue 307 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/ai/dashboard/zhuanlu/index.vue 338 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/ai/model/template/index.vue 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/ai/model/template/templateForm.vue 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/ai/suggest/ScheduleSuggestForm.vue 117 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/ai/suggest/index.vue 223 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/ai/utils/constants.ts 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/ai/utils/utils.ts 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/model/sche/model/ScheduleModelForm.vue 194 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/ai/model/model/index.ts
@@ -23,7 +23,7 @@
  },
  // 获得模型列表
  getModelSimpleList: async (type?: number) => {
  getModelSimpleList: async (type?: string) => {
    return await request.get({
      url: `/ai/model/simple-list`,
      params: {
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/api/model/mpk/mpk.ts
@@ -50,10 +50,6 @@
  return request.get({ url: '/model/mpk/file/list',  params})
}
export const aiList = (params) => {
  return request.get({ url: '/ai/question-template/modelList',  params})
}
export const publish = (params) => {
  return request.post({ url: '/model/mpk/file/publish', data: 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>
    <slot></slot>
    <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/chat/index/components/conversation/ConversationUpdateForm.vue
@@ -110,7 +110,7 @@
    }
  }
  // 获得下拉数据
  models.value = await ModelApi.getModelSimpleList(AiModelTypeEnum.CHAT)
  models.value = await ModelApi.getModelSimpleList(AiModelTypeEnum.CHAT + "," + AiModelTypeEnum.LLM)
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
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/conversation/CommonConversationList.vue
@@ -128,7 +128,9 @@
    type: Boolean || null,
    required: true
  },
  defaultMessage: {}
  defaultMessage: {
    type: Object as PropType<ChatMessageVO>
  }
})
// 定义钩子
src/views/ai/dashboard/components/conversation/CommonConversationUpdateForm.vue
@@ -102,7 +102,7 @@
    }
  }
  // 获得下拉数据
  models.value = await ModelApi.getModelSimpleList(AiModelTypeEnum.CHAT)
  models.value = await ModelApi.getModelSimpleList(AiModelTypeEnum.LLM)
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
src/views/ai/dashboard/components/conversation/ConversationUpdateForm.vue
@@ -110,7 +110,7 @@
    }
  }
  // 获得下拉数据
  models.value = await ModelApi.getModelSimpleList(AiModelTypeEnum.CHAT)
  models.value = await ModelApi.getModelSimpleList(AiModelTypeEnum.LLM)
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
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/components/suggest/ScheduleSuggestDialog.vue
对比新文件
@@ -0,0 +1,417 @@
<template>
  <DialogSuggest title="" v-model="dialogVisible" width="1300">
    <!-- 搜索工作栏 -->
    <el-form
      class="-mb-15px query-area"
      :model="queryParams"
      ref="queryFormRef"
      :inline="true"
      label-width="68px"
    >
      <el-form-item label="采纳状态" prop="status">
        <el-select
          v-model="queryParams.status"
          placeholder="请选择"
          size="large"
          class="!w-200px"
          clearable
          @change="getList"
        >
          <el-option
            v-for="item in suggestStatus"
            :key="item.key"
            :label="item.name"
            :value="item.key"
          />
        </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="datetimerange"
          start-placeholder="开始日期"
          end-placeholder="结束日期"
          :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
          class="!w-360px transparent-date-picker-popper"
        />
      </el-form-item>
      <el-form-item>
        <el-button @click="handleQuery">
          <Icon icon="ep:search" class="mr-5px"/>
          搜索
        </el-button>
        <el-button @click="resetQuery">
          <Icon icon="ep:refresh" class="mr-5px"/>
          重置
        </el-button>
      </el-form-item>
    </el-form>
    <!-- 对话详情 -->
    <el-container class="detail-container">
      <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
        <el-table-column
          label="调度时间"
          align="center"
          prop="createTime"
          :formatter="dateFormatter"
          width="180px"
        />
        <el-table-column label="调度建议" align="center" prop="content" width="750"/>
        <el-table-column label="采纳状态" align="center" prop="status" width="90">
          <template #default="scope">
            <template v-if="scope.row.status === 0">
              未处理
            </template>
            <template v-else-if="scope.row.status === 1">
              <span style="color: var(--el-color-success)">已采纳</span>
            </template>
            <template v-else>
              <span style="color: var(--el-color-danger)">已忽略</span>
            </template>
          </template>
        </el-table-column>
        <el-table-column label="操作" align="center">
          <template #default="scope">
            <template v-if="scope.row.status === 0">
              <el-button
                link
                type="success"
                @click="operateSuggest(scope.row.id, 1)"
              >
                采纳建议
              </el-button>
              <el-button
                link
                type="primary"
                @click="operateSuggest(scope.row.id, 2)"
              >
                忽略建议
              </el-button>
            </template>
            <template v-else-if="scope.row.status === 1">
              <el-button
                link
                type="primary"
                @click="operateSuggest(scope.row.id, 2)"
              >
                取消采纳
              </el-button>
            </template>
            <template v-else>
              <el-button
                link
                type="primary"
              >
                已忽略
              </el-button>
            </template>
            <el-button
              link
              type="danger"
              @click="handleDelete(scope.row.id)"
              v-hasPermi="['ai:schedule-suggest:delete']"
            >
              删除
            </el-button>
          </template>
        </el-table-column>
      </el-table>
    </el-container>
    <!-- 分页 -->
    <Pagination
      :total="total"
      v-model:page="queryParams.pageNo"
      v-model:limit="queryParams.pageSize"
      @pagination="handleQuery"
    />
  </DialogSuggest>
</template>
<script setup lang="ts">
import {ChatMessageVO} from '@/api/ai/chat/message'
import {ref} from "vue";
import {dateFormatter} from "@/utils/formatTime";
import {ScheduleSuggestApi, ScheduleSuggestVO} from "@/api/ai/schedulesuggest";
import {OtherPlatformEnum} from "@/views/ai/utils/constants";
/** AI 聊天对话 列表 */
defineOptions({ name: 'HistoryMessageDialog' })
// 接收父组件传递的方法
// const props = defineProps({
//   parentMethod: Function,
//   gotoManualMethod: Function
// });
// 定义发射事件
// const emit = defineEmits(['gotoManualMethod'])
const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化
const dialogVisible = ref(false) // 弹窗的是否展示
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 operateData = ref({
  id: undefined,
  status: undefined,
})
const queryFormRef = ref() // 搜索的表单
const suggestStatus = ref([
  {
    key: 0,
    name: '未处理'
  },
  {
    key: 1,
    name: '已采纳'
  },
  {
    key: 2,
    name: '已忽略'
  }
])
/** 打开弹窗 */
const open = async () => {
  dialogVisible.value = true
  await nextTick() // 等待弹窗DOM挂载
  await getList()
}
defineExpose({ open }) // 提供方法给 parent 调用
/** 查询列表 */
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 operateSuggest = async (id: number, status: number) => {
  const data = operateData.value as unknown as ScheduleSuggestVO
  data.id = id
  data.status = status
  await ScheduleSuggestApi.operateScheduleSuggest(data)
  message.success(t('common.updateSuccess'))
  // 刷新列表
  await getList()
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
  try {
    // 删除的二次确认
    await message.delConfirm()
    // 发起删除
    await ScheduleSuggestApi.deleteScheduleSuggest(id)
    message.success(t('common.delSuccess'))
    // 刷新列表
    await getList()
  } catch {}
}
/** 搜索按钮操作 */
const handleQuery = () => {
  getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
  queryFormRef.value.resetFields()
  queryParams.status = undefined
  handleQuery()
}
// const gotoManual = async (item: ChatMessageVO) => {
//   emit('gotoManualMethod', item) // 发送数据给父组件
// }
/** 初始化 **/
onMounted(async () => {})
</script>
<style lang="scss" scoped>
.query-area {
  margin-top: 2px;
  margin-bottom: 2px;
  float: left;
  :deep(.el-select__wrapper) {
    background: rgba(255,255,255,0.1) !important; /* 保留浅色背景 */
    min-height: 30px;
    box-shadow: none !important;
  }
  :deep(.el-select__placeholder) {
    color: #DBEEFF;
  }
  :deep(.el-form-item__label) {
    color: #73C4FF;
  }
  :deep(.el-date-editor .el-icon) {
    color: #DBEEFF;
  }
  :deep(.el-date-editor .el-range-input) {
    color: rgba(219, 238, 255, 0.5);
  }
  /* 移除所有输入框边框 */
  :deep(.el-form-item .el-input__wrapper) {
    border: none !important;
    box-shadow: none !important;
    background: rgba(255,255,255,0.1) !important; /* 保留浅色背景 */
  }
  /* 所有状态通用透明背景 */
  :deep(.el-button) {
    background: transparent !important;
    border-color: currentColor; /* 保持与文字同色 */
    color: #409EFF; /* 蓝色文字 */
  }
  /* 悬停状态 */
  :deep(.el-button:hover) {
    background: rgba(0, 0, 0, 0.5) !important; /* 轻微悬停反馈 */
  }
  /* 点击状态 */
  :deep(.el-button:active) {
    background: rgba(0, 0, 0, 0.8) !important;
  }
}
// 头部
.detail-container {
  display: flex;
  flex-direction: column;
  width: 100%;
  height: 42vh;
  background-color: rgba(0, 0, 0, 0); /* 透明背景 */
  /* 表格透明背景 */
  :deep(.el-table) {
    background-color: transparent !important;
    border: 1px solid rgba(255, 255, 255, 0.3) !important;
    border-radius: 4px;
  }
  :deep(.el-table tr) {
    color: #00b4ff; /* 蓝色文字 */
    background: transparent !important;
  }
  /* 表头单元格边框 */
  :deep(.el-table th.el-table__cell) {
    border-bottom: 1px solid rgba(115,196,255,0.14) !important;
    border-right: 1px solid rgba(115,196,255,0.14) !important;
  }
  /* 表格内容单元格边框 */
  :deep(.el-table td.el-table__cell) {
    border-bottom: 1px solid rgba(115,196,255,0.14) !important;
    border-right: 1px solid rgba(115,196,255,0.14) !important;
  }
  /* 表头 */
  :deep(.el-table__header thead tr th) {
    background-color: rgba(0, 194, 255, 0.2);
    border-bottom: none;
  }
  /* 行头样式 */
  :deep(.el-table .el-table__body td:first-child) {
    color: #8FD6FE;
    font-weight: 500;
  }
  :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.1);
  }
  /* 移除表格内部边框线 */
  :deep(.el-table td, .el-table th.is-leaf) {
    border-bottom: none;
  }
  :deep(.el-table .el-table__inner-wrapper:before) {
    background-color: 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); }
    }
  }
}
.el-pagination {
  //--el-pagination-button-bg-color: transparent;
  opacity: 0.6;
  :deep(.el-pagination__total) {
    color: white;
  }
  :deep(.el-pager) {
    color: rgba(3,27,21);
    font-weight: bold;
  }
  :deep(.el-pagination__jump) {
    color: white;
  }
  :deep(.el-select__popper) {
    background-color: transparent;
  }
  :deep(.el-scrollbar) {
    --el-scrollbar-opacity: 0.8;
    --el-scrollbar-bg-color: transparent;
  }
}
</style>
src/views/ai/dashboard/components/suggest/ScheduleSuggestList.vue
对比新文件
@@ -0,0 +1,307 @@
<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 question" @click="gotoManual(item)">
            <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 userAvatarDefaultImg from '@/assets/ai/zhuanlu/user.png'
import roleAvatarDefaultImg from '@/assets/ai/zhuanlu/assistant.png'
const message = useMessage() // 消息弹窗
const { copy } = useClipboard() // 初始化 copy 到粘贴板
// 判断“消息列表”滚动的位置(用于判断是否需要滚动到消息最下方)
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
  },
  gotoManualMethod: Function
})
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 gotoManual = async (item: ChatMessageVO) => {
  if(props.gotoManualMethod) {
    props.gotoManualMethod(item)
  }
}
/** 复制 */
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;
    .question:hover {
      cursor: pointer;
      background: rgba(40, 139, 255, 0.3);
    }
    .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,255,255,0.1);
    border: solid 1px rgba(255,215,0,0.6);
    color: rgba(255,215,0,0.5);
  }
  .el-button:hover {
    cursor: pointer;
    background-color: rgba(255,255,255,0.4);
    border: solid 2px rgba(255,215,0);
    color: rgba(255,215,0);
  }
}
</style>
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,31 @@
          <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>
@@ -262,13 +293,16 @@
import MessageLoading from '../components/message/MessageLoading.vue'
import ConversationList from "../components/conversation/ConversationList.vue";
import HistoryMessageDialog from "../components/message/HistoryMessageDialog.vue"
import ScheduleSuggestDialog from "../components/suggest/ScheduleSuggestDialog.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([
  {
@@ -278,7 +312,7 @@
  },
  {
    name: '转炉煤气 O 含量',
    value: 618,
    value: 10,
    unit: '%'
  },
  {
@@ -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,36 +501,24 @@
const mqhsjhxxList = ref([
  {
    name: '转炉总炉数\n' +
      '日计划',
    value: 567,
    name: '转炉总炉数日计划',
    value: 123,
    unit: '炉'
  },
  {
    name: '转炉入炉铁水量\n' +
      '日计划',
    value: 200,
    unit: '吨'
  },
  {
    name: '转炉检修计划',
    value: '未进行',
    value: '0',
    unit: ''
  },
  {
    name: '钢产量日计划',
    value: 300,
    unit: '吨'
  },
  {
    name: '转炉加入废钢总量',
    value: 500,
    unit: '吨'
    value: 20000,
    unit: 't'
  },
  {
    name: '转炉实绩钢产量',
    value: 100,
    unit: '吨'
    value: 20929,
    unit: 't'
  }
])
@@ -495,20 +544,20 @@
  {
    id: 1,
    name: '1#转炉',
    current: 20,
    total: 30
    current: 4,
    total: 29
  },
  {
    id: 2,
    name: '2#转炉',
    current: 25,
    total: 100
    current: 5,
    total: 42
  },
  {
    id: 3,
    name: '3#转炉',
    current: 4,
    total: 29
    current: 6,
    total: 42
  }
])
@@ -536,6 +585,11 @@
  }
])
const topSuggests = ref<ScheduleSuggestVO[]>([])
const contentRef = ref([]);
const isOverflow = ref([]);
const ddtlResult = ref('')
@@ -725,10 +779,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 +794,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 +845,14 @@
  }
  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);
  const result = match ? match[2].trim() : '';
  return result
}
const extractRecoveryDetails = (text, consume, gui, totalMinutes = 60) => {
@@ -945,6 +1025,10 @@
    conversationId: activeConversationId.value,
    content: content
  } as ChatMessageVO)
  // 保存调度建议
  setTimeout(async () => {
    await createSuggest()
  }, 1000)
}
/** 真正执行【发送】消息操作 */
@@ -1108,6 +1192,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 +1257,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 +1341,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 +1402,7 @@
          focus: 'series'
        },
        lineStyle: {
          width: 1,
          color: '#FF7686' // 粉色
        }
      },
@@ -1306,6 +1416,7 @@
          focus: 'series'
        },
        lineStyle: {
          width: 1,
          color: '#49FFD3' // 绿色
        }
      },
@@ -1320,6 +1431,7 @@
          focus: 'series'
        },
        lineStyle: {
          width: 1,
          color: '#FFAE81' // 橙色
        },
      },
@@ -1333,6 +1445,7 @@
          focus: 'series'
        },
        lineStyle: {
          width: 1,
          color: 'white'
        },
      }
@@ -1383,7 +1496,7 @@
      left: 0,
      right: 0,
      bottom: 10,
      top: 20,
      top: 10,
      containLabel: true
    },
    tooltip: {
@@ -1434,7 +1547,10 @@
        data: realData,
        smooth: true,
        symbol: 'none',
        lineStyle: { color: '#95E6FF' },
        lineStyle: {
          color: '#95E6FF',
          width: 1
        },
        markLine: {
          symbol: ['none', 'none'],
          label: {
@@ -1459,6 +1575,7 @@
        lineStyle: {
          type: 'dashed',
          color: '#E76666',
          width: 1,
          dashOffset: 5
        }
      },
@@ -1591,6 +1708,11 @@
  // 初始状态检测
  updateFullscreenStatus();
}
/** 查询列表 */
const getTopSuggest = async () => {
  const data = await ScheduleSuggestApi.getTopScheduleSuggests(5)
  topSuggests.value = data
}
/** 初始化 **/
onMounted(async () => {
@@ -1605,11 +1727,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 +1804,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,10 +2380,10 @@
        }
      }
      .data2-item {
        height: 2.8rem;
        width: 45%;
        height: 1.4rem;
        width: 46%;
        display: inline-block;
        margin: 6px 8px;
        margin: 8px 8px;
        background: url("@/assets/ai/zhuanlu/data_bg3.png") no-repeat;
      }
      .content {
@@ -2241,15 +2392,13 @@
        margin-left: 10px;
        .name {
          width: 95px;
          height: 18px;
          width: 130px;
          font-weight: 400;
          font-size: 14px;
          color: #C7E7FF;
        }
        .value {
          margin-top: 10px;
          margin-left: auto;
          margin-right: 5px;
          span:nth-child(1) {
@@ -2279,13 +2428,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 +2504,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/model/template/index.vue
@@ -113,6 +113,7 @@
  import * as AiQuestionTemplateApi from '@/api/ai/questiontemplate'
  import TemplateForm from './templateForm.vue'
  import * as AiModelApi from "@/api/ai/model/model";
  import {AiModelTypeEnum} from "@/views/ai/utils/constants";
  defineOptions({name: 'AiTemplate'})
@@ -179,7 +180,7 @@
  /** 初始化 **/
  onMounted(async () => {
    aiModelList.value = await AiModelApi.ModelApi.getModelSimpleList(1)
    aiModelList.value = await AiModelApi.ModelApi.getModelSimpleList(AiModelTypeEnum.CHAT + "," + AiModelTypeEnum.LLM)
    await getList()
  })
</script>
src/views/ai/model/template/templateForm.vue
@@ -148,6 +148,7 @@
  import {CommonStatusEnum} from '@/utils/constants'
  import {ElMessage} from 'element-plus'
  import * as AiModelApi from "@/api/ai/model/model";
  import {AiModelTypeEnum} from "@/views/ai/utils/constants";
  const aiModelList = ref([] as AiModelApi.ModelVO[])
  defineOptions({name: 'AiTemplateForm'})
@@ -203,7 +204,7 @@
    formType.value = type
    resetForm()
    // 加载调度模型列表
    aiModelList.value = await AiModelApi.ModelApi.getModelSimpleList(1)
    aiModelList.value = await AiModelApi.ModelApi.getModelSimpleList(AiModelTypeEnum.CHAT + "," + AiModelTypeEnum.LLM)
    if (id) {
      formLoading.value = true
      try {
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/constants.ts
@@ -25,12 +25,12 @@
}
export const AiModelTypeEnum = {
  CHAT: 1, // 聊天
  IMAGE: 2, // 图像
  VOICE: 3, // 音频
  VIDEO: 4, // 视频
  EMBEDDING: 5, // 向量
  RERANK: 6 // 重排
  CHAT: '1', // 聊天
  IMAGE: '2', // 图像
  VOICE: '3', // 音频
  VIDEO: '4', // 视频
  EMBEDDING: '5', // 向量
  LLM: '6' // 重排
}
export const OtherPlatformEnum: ImageModelVO[] = [
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}`;
    }
  );
}
src/views/model/sche/model/ScheduleModelForm.vue
@@ -11,7 +11,7 @@
      <el-row>
        <el-col :span="12">
          <el-form-item label="模型编号" prop="modelCode">
            <el-input v-model="formData.modelCode" placeholder="请输入模型编号"/>
            <el-input v-model="formData.modelCode" placeholder="请输入模型编号" />
          </el-form-item>
        </el-col>
      </el-row>
@@ -43,17 +43,13 @@
      </el-row>
      <el-divider content-position="left">模型信息</el-divider>
      <div style="width: 120px;text-align: right;margin-bottom: 8px">
        <el-popover placement="right" :width="300" trigger="click" ref="modelPopover"
                    @before-enter="model = undefined">
        <el-popover placement="right" :width="300" trigger="click" ref="modelPopover" @before-enter="model = undefined">
          <template #reference>
            <span style="color: #409eff;cursor: pointer">关联模型信息</span>
          </template>
          <template #default>
            <div style="display:flex;flex-direction: row;align-items: center;">
              <el-cascader style="width: 100%" v-model="model" placeholder="选择模型"
                           :teleported="false" @change="changeModel" :options="scheduleModelList" v-if="formData.invocation != '4'"/>
              <el-cascader style="width: 100%" v-model="model" placeholder="选择模型"
                           :teleported="false" @change="changeAiModel" :options="aiModelList" v-if="formData.invocation === '4'"/>
              <el-cascader style="width: 100%" v-model="model" placeholder="选择模型" :teleported="false" @change="changeModel" :options="scheduleModelList"/>
            </div>
          </template>
        </el-popover>
@@ -66,34 +62,33 @@
        </el-col>
        <el-col :span="12">
          <el-form-item label="类名" prop="className">
            <el-input v-model="formData.className" placeholder="请输入类名" :disabled="true"/>
            <el-input v-model="formData.className" placeholder="请输入类名" :disabled="true" />
          </el-form-item>
        </el-col>
      </el-row>
      <el-row>
        <el-col :span="12">
          <el-form-item label="方法名" prop="methodName">
            <el-input v-model="formData.methodName" placeholder="请输入方法名" :disabled="true"/>
            <el-input v-model="formData.methodName" placeholder="请输入方法名" :disabled="true" />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="参数数量" prop="portLength">
            <el-input-number v-model="formData.portLength" :min="0" controls-position="right"
                             :disabled="true"/>
            <el-input-number v-model="formData.portLength" :min="0" controls-position="right" :disabled="true" />
          </el-form-item>
        </el-col>
      </el-row>
      <el-row>
        <el-col :span="24">
          <el-form-item label="参数构造" prop="paramStructure">
            <el-input v-model="formData.paramStructure" placeholder="请输入参数构造" :disabled="true"/>
            <el-input v-model="formData.paramStructure" placeholder="请输入参数构造" :disabled="true" />
          </el-form-item>
        </el-col>
      </el-row>
      <el-row>
        <el-col :span="24">
          <el-form-item label="模型路径" prop="modelPath">
            <el-input v-model="formData.modelPath" placeholder="模型路径" :disabled="true"/>
            <el-input v-model="formData.modelPath" placeholder="模型路径" :disabled="true" />
          </el-form-item>
        </el-col>
      </el-row>
@@ -128,8 +123,7 @@
          width="150"
          align="center">
          <template #default="scope">
            <el-select v-model="scope.row.modelparamtype" placeholder="请选择"
                       @change="changeModelparamtype(scope.row)">
            <el-select v-model="scope.row.modelparamtype" placeholder="请选择" @change="changeModelparamtype(scope.row)">
              <el-option
                v-for="dict in getStrDictOptions(DICT_TYPE.MODEL_PARAM_TYPE)"
                :key="dict.value"
@@ -145,22 +139,22 @@
          align="center">
          <template #default="scope">
            <el-select-v2 v-if="scope.row.modelparamtype === 'NormalItem'"
                          v-model="scope.row.modelparamid"
                          :options="modelparamListMap['NormalItem'] || []"
                          placeholder="请选择"
                          :props="{value:'value',label:'label',options:'children'}"
                          clearable
                          filterable
                          :fit-input-width="false"
              v-model="scope.row.modelparamid"
              :options="modelparamListMap['NormalItem'] || []"
              placeholder="请选择"
              :props="{value:'value',label:'label',options:'children'}"
              clearable
              filterable
              :fit-input-width="false"
            />
            <el-select-v2 v-else
                          v-model="scope.row.modelparamid"
                          :options="modelparamListMap[scope.row.modelparamtype] || []"
                          placeholder="请选择"
                          :props="{value:'id',label:'name'}"
                          clearable
                          filterable
                          :fit-input-width="false"
              v-model="scope.row.modelparamid"
              :options="modelparamListMap[scope.row.modelparamtype] || []"
              placeholder="请选择"
              :props="{value:'id',label:'name'}"
              clearable
              filterable
              :fit-input-width="false"
            />
          </template>
        </el-table-column>
@@ -170,8 +164,7 @@
          width="160"
          align="center">
          <template #default="scope">
            <el-input-number v-model="scope.row.datalength" :min="0" clearable
                             controls-position="right"
            <el-input-number v-model="scope.row.datalength" :min="0" clearable controls-position="right"
                             style="width:100%;hight:100%"/>
          </template>
        </el-table-column>
@@ -255,7 +248,7 @@
      <el-divider content-position="left">模型下发配置</el-divider>
      <el-row :gutter="20">
        <el-col :span="4">
          <el-button type="primary" size="small" @click="addRowOut()">新增</el-button>
          <el-button type="primary" size="small" @click="addRowOut()" >新增</el-button>
        </el-col>
      </el-row>
      <el-table
@@ -281,14 +274,12 @@
        </el-table-column>
        <el-table-column prop="resultPort" label="角标1" align="center" width="100">
          <template #default="scope">
            <el-input-number :min="0" clearable controls-position="right" size="small"
                             v-model="scope.row.resultPort" style="width:100%;height:100%"/>
            <el-input-number :min="0" clearable controls-position="right" size="small" v-model="scope.row.resultPort" style="width:100%;height:100%"/>
          </template>
        </el-table-column>
        <el-table-column prop="resultIndex" label="角标2" align="center" width="100">
          <template #default="scope">
            <el-input-number :min="0" clearable controls-position="right" size="small"
                             v-model="scope.row.resultIndex" style="width:100%;height:100%"/>
            <el-input-number :min="0" clearable controls-position="right" size="small" v-model="scope.row.resultIndex" style="width:100%;height:100%"/>
          </template>
        </el-table-column>
        <el-table-column prop="isWrite" label="是否下发" align="center" width="100">
@@ -313,7 +304,7 @@
            />
          </template>
        </el-table-column>
        <el-table-column prop="disturbancePointNo" label="无扰切换点位" align="center" min-width="200">
        <el-table-column prop="disturbancePointNo’" label="无扰切换点位" align="center" min-width="200">
          <template #default="scope">
            <el-select-v2
              v-model="scope.row.disturbancePointNo"
@@ -346,17 +337,17 @@
  </Dialog>
</template>
<script lang="ts" setup>
  import {DICT_TYPE, getStrDictOptions} from '@/utils/dict'
  import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
  import * as ScheduleModelApi from '@/api/model/sche/model'
  import {CommonStatusEnum} from '@/utils/constants'
  import { CommonStatusEnum } from '@/utils/constants'
  import * as MpkApi from "@/api/model/mpk/mpk";
  import {generateUUID} from "@/utils";
  import {ElMessage, ElMessageBox} from 'element-plus'
  import {Refresh} from '@element-plus/icons-vue'
  import { ElMessage,ElMessageBox } from 'element-plus'
  import { Refresh } from '@element-plus/icons-vue'
  defineOptions({name: 'ScheduleModelForm'})
  defineOptions({ name: 'ScheduleModelForm' })
  const {t} = useI18n() // 国际化
  const { t } = useI18n() // 国际化
  const message = useMessage() // 消息弹窗
  const dialogVisible = ref(false) // 弹窗的是否展示
  const dialogTitle = ref('') // 弹窗的标题
@@ -386,16 +377,14 @@
    modelOut: []
  })
  const formRules = reactive({
    modelCode: [{required: true, message: '模型编号不能为空', trigger: 'blur'}],
    modelName: [{required: true, message: '模型名称不能为空', trigger: 'blur'}],
    modelType: [{required: true, message: '模型类型不能为空', trigger: 'blur'}]
    modelCode: [{ required: true, message: '模型编号不能为空', trigger: 'blur' }],
    modelName: [{ required: true, message: '模型名称不能为空', trigger: 'blur' }],
    modelType: [{ required: true, message: '模型类型不能为空', trigger: 'blur' }]
  })
  const formRef = ref() // 表单 Ref
  const modelparamListMap = ref({})
  // 调度模型列表
  const scheduleModelList = ref([])
  const aiModelList = ref([])
  const aiModel = ref([])
  const model = ref()
  const modelPopover = ref()
@@ -446,10 +435,8 @@
    modelparamListMap.value = await ScheduleModelApi.getModelParamList(id)
    // 加载调度模型列表
    getScheduleModelList()
    // 加载ai模型列表
    getAiModelList()
  }
  defineExpose({open}) // 提供 open 方法,用于打开弹窗
  defineExpose({ open }) // 提供 open 方法,用于打开弹窗
  /** 提交表单 */
  const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
@@ -538,24 +525,6 @@
    }
  }
  const getAiModelList = async () => {
    aiModel.value = await MpkApi.aiList(null)
    if (aiModel.value && aiModel.value.length > 0) {
      aiModelList.value = aiModel.value.map(e => {
        return {
          value: e.id,
          label: e.name,
          children: e.children.map(template => {
            return {
              value: template.id,
              label: template.questionName
            }
          })
        }
      })
    }
  };
  // 选择调度模型
  const changeModel = async () => {
    // 校验
@@ -563,86 +532,16 @@
      ElMessageBox.confirm(
        '是否更新输入参数?',
        '提示',
        {
          confirmButtonText: '是',
          cancelButtonText: '否',
          type: 'success',
          icon: markRaw(Refresh),
          closeOnClickModal: false,
          closeOnPressEscape: false
        }
        {confirmButtonText: '是', cancelButtonText: '否', type: 'success',icon: markRaw(Refresh),closeOnClickModal:false,closeOnPressEscape:false}
      ).then(() => {
        relevanceModel(true)
      }).catch(() => {
        relevanceModel(false)
      })
    } else {
    }else {
      message.error("请先选择模型")
    }
  }
  // 选择ai模型
  const changeAiModel = () => {
    if (!model.value && model.value.length > 0) return
    // 查找选中的模型组
    const modelGroup = aiModel.value.find(
      group => group.id === model.value[0]
    )
    // 查找选中的模板
    const template = modelGroup?.children.find(
      t => t.id === model.value[1]
    )
    if (!modelGroup || !template) return
    // 弹窗确认
    ElMessageBox.confirm(
      '是否更新输入参数?',
      '提示',
      {
        confirmButtonText: '是',
        cancelButtonText: '否',
        type: 'success',
        icon: markRaw(Refresh),
        closeOnClickModal: false,
        closeOnPressEscape: false
      }
    ).then(() => {
      // 更新表单数据
      updateAiModelForm(modelGroup, template, true)
    }).catch(() => {
      updateAiModelForm(modelGroup, template, false)
    })
  }
  // 更新AI模型表单数据
  const updateAiModelForm = (modelGroup, template, updateParams) => {
    // 设置基本信息
    formData.value.modelName = modelGroup.name
    formData.value.methodName = template.questionName
    formData.value.portLength = template.dataLength
    // 设置参数列表转换
    formData.value.settingList = template.settingList.map(setting => {
      return {
        key: setting.settingKey,
        name: setting.settingName,
        value: setting.settingValue,
        valuetype: 'string'
      }
    })
      // 输入参数
      let paramList = []
      for (let i = 0; i < formData.value.portLength; i++) {
        paramList.push({
          modelparamportorder: i + 1 + '',
          modelparamorder: '1',
          modelparamtype: '',
          modelparamid: '',
          datalength: 0
        })
      }
      formData.value.paramList = paramList
    }
  function relevanceModel(refreshParam) {
    const modelInfo = model.value[0]
@@ -667,7 +566,7 @@
      let paramList = []
      for (let i = 0; i < methodInfo.dataLength; i++) {
        paramList.push({
          modelparamportorder: i + 1 + '',
          modelparamportorder: i+1 + '',
          modelparamorder: '1',
          modelparamtype: '',
          modelparamid: '',
@@ -693,9 +592,8 @@
  function changeModelparamtype(row) {
    row.modelparamid = ''
  }
  const addRowOut = function () {
    if (formData.value.modelOut === undefined) {
  const addRowOut= function () {
    if(formData.value.modelOut===undefined) {
      formData.value.modelOut = []
    }
    formData.value.modelOut.push({
@@ -705,9 +603,9 @@
      port: 0,
      index: 0,
      isWrite: 1,
      pointNo: undefined,
      sort: undefined,
      disturbancePointNo: undefined,
      pointNo:undefined,
      sort:undefined,
      disturbancePointNo:undefined,
    })
  }
  const deleteModelOutRow = function (index) {